2006-11-08 Michael Dominic Kostrzewa <michael.kostrzewa@nokia.com>
[hildon] / hildon-widgets / hildon-grid.c
1 /*
2  * This file is part of hildon-libs
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.
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-grid
27  * @short_description: Being used where ever a number of single tap
28  * activatable items need to be presented (e.g. Control Panel applets)
29  * @see_also: #HildonGridItem
30  *
31  * HildonGrid is a set of application-defineable items that are presented in a
32  * table. There are two modes for the form of the table; large icon mode
33  * and small icon mode.
34  *
35  * In large icon mode, the Grid View items are presented with a large
36  * icon and a label under it. In small icon mode, the items are
37  * presented with a small icon and a label on the right side of the
38  * icon.
39  *
40  * The label has a solid background as wide as the maximum text width.
41  * This allows the text to have focus as well as be legible when
42  * displayed upon a black or dark background image. Long names are
43  * truncated with an ellipsis ("...") appended.
44  */
45
46 /*
47  * TODO
48  * - there must be a predefined place for the "no items" -label...
49  * - performance :-)
50  * - dimmed items & scrolling by scrollbar
51  */
52
53
54 #ifdef HAVE_CONFIG_H
55 #include <config.h>
56 #endif
57
58 #include <stdio.h>
59 #include <stdlib.h>
60 #include <string.h>
61
62 #include <gtk/gtklabel.h>
63 #include <gtk/gtkrange.h>
64 #include <gtk/gtkvscrollbar.h>
65 #include <gtk/gtkmain.h>
66 #include <gtk/gtkwidget.h>
67 #include <gtk/gtkenums.h>
68 #include <gdk/gdkkeysyms.h>
69
70 #include "hildon-grid-item-private.h"
71 #include "hildon-marshalers.h"
72 #include "hildon-app.h"
73 #include <hildon-widgets/hildon-grid.h>
74 #include <hildon-widgets/hildon-grid-item.h>
75
76 #include <libintl.h>
77 #define _(String) dgettext(PACKAGE, String)
78
79 #define HILDON_GRID_GET_PRIVATE(obj) \
80         (G_TYPE_INSTANCE_GET_PRIVATE ((obj), HILDON_TYPE_GRID, \
81                                       HildonGridPrivate))
82
83
84 #define DEFAULT_STYLE   "largeicons-home"
85
86 #define DEFAULT_N_COLUMNS       3
87 #define GRID_LABEL_POS_PAD     16
88
89 #define DRAG_SENSITIVITY        6
90
91
92 enum {
93     ACTIVATE_CHILD,
94     POPUP_CONTEXT,
95     LAST_SIGNAL
96 };
97
98 enum {
99     PROP_0,
100     PROP_EMPTY_LABEL,
101     PROP_STYLE,
102     PROP_SCROLLBAR_POS
103 };
104
105
106 typedef struct _HildonGridChild HildonGridChild;
107 typedef struct _HildonGridPrivate HildonGridPrivate;
108
109
110 struct _HildonGridChild {
111     GtkWidget *widget;
112 };
113
114
115 struct _HildonGridPrivate {
116     GList *children;
117     GtkWidget *scrollbar;
118     gint old_sb_pos;
119     GdkWindow *event_window;
120
121     gchar *style;
122     gint emblem_size;
123     GtkWidget *empty_label;
124
125     gint item_width;
126     gint item_height;
127     gint h_margin;
128     gint v_margin;
129     gint focus_margin;
130     gint icon_label_margin;
131     gint icon_width;
132     gint num_columns;
133     HildonGridPositionType label_pos;
134     gint label_height;
135
136     gint focus_index;
137     guint click_x;
138     guint click_y;
139
140     /* Handy variables outsize _allocate. */
141     gint area_height;
142     gint area_rows;
143     gint scrollbar_width;
144
145     gint first_index;
146     GdkEventType last_button_event;
147     gint old_item_height;
148 };
149
150
151
152 /* Prototypes. */
153 static void hildon_grid_class_init(HildonGridClass * klass);
154 static void hildon_grid_init(HildonGrid * grid);
155 static void hildon_grid_realize(GtkWidget * widget);
156 static void hildon_grid_unrealize(GtkWidget * widget);
157 static void hildon_grid_map(GtkWidget * widget);
158 static void hildon_grid_unmap(GtkWidget * widget);
159 static gboolean hildon_grid_expose(GtkWidget * widget,
160                                    GdkEventExpose * event);
161 static void hildon_grid_size_request(GtkWidget * widget,
162                                      GtkRequisition * requisition);
163 static void hildon_grid_size_allocate(GtkWidget * widget,
164                                       GtkAllocation * allocation);
165 static void hildon_grid_add(GtkContainer * container, GtkWidget * widget);
166 static void hildon_grid_remove(GtkContainer * container,
167                                GtkWidget * widget);
168 static void hildon_grid_set_focus_child(GtkContainer * container,
169                                         GtkWidget * widget);
170 static void hildon_grid_forall(GtkContainer * container,
171                                gboolean include_internals,
172                                GtkCallback callback,
173                                gpointer callback_data);
174 static void hildon_grid_tap_and_hold_setup(GtkWidget * widget,
175                                            GtkWidget * menu,
176                                            GtkCallback func,
177                                            GtkWidgetTapAndHoldFlags flags);
178
179 static GType hildon_grid_child_type(GtkContainer * container);
180
181
182 static void hildon_grid_set_property(GObject * object,
183                                      guint prop_id,
184                                      const GValue * value,
185                                      GParamSpec * pspec);
186 static void hildon_grid_get_property(GObject * object,
187                                      guint prop_id,
188                                      GValue * value, GParamSpec * pspec);
189
190 static void hildon_grid_set_empty_label(HildonGrid *grid,
191                                         const gchar *empty_label);
192 static const gchar *hildon_grid_get_empty_label(HildonGrid * grid);
193 static void hildon_grid_set_num_columns(HildonGrid *grid, gint num_cols);
194 static void hildon_grid_set_label_pos(HildonGrid *grid,
195                                       HildonGridPositionType label_pos);
196 static void hildon_grid_set_focus_margin(HildonGrid *grid,
197                                          gint focus_margin);
198 static void hildon_grid_set_icon_label_margin(HildonGrid *grid,
199                                               gint icon_label_margin);
200 static void hildon_grid_set_icon_width(HildonGrid *grid, gint icon_width);
201 static void hildon_grid_set_emblem_size(HildonGrid *grid, gint emblem_size);
202 static void hildon_grid_set_label_height(HildonGrid *grid,
203                                          gint label_height);
204 static void hildon_grid_destroy(GtkObject * self);
205 static void hildon_grid_finalize(GObject * object);
206
207 /* Signal handlers. */
208 static gboolean hildon_grid_button_pressed(GtkWidget * widget,
209                                            GdkEventButton * event);
210 static gboolean hildon_grid_button_released(GtkWidget * widget,
211                                             GdkEventButton * event);
212 static gboolean hildon_grid_key_pressed(GtkWidget * widget,
213                                         GdkEventKey * event);
214 static gboolean hildon_grid_scrollbar_moved(GtkWidget * widget,
215                                             gpointer data);
216 static gboolean hildon_grid_state_changed(GtkWidget * widget,
217                                           GtkStateType state,
218                                           gpointer data);
219
220 /* Other internal functions. */
221 static void get_style_properties(HildonGrid * grid);
222 static gint get_child_index(HildonGridPrivate * priv, GtkWidget * child);
223 static gint get_child_index_by_coord(HildonGridPrivate * priv,
224                                      gint x, gint y);
225 static GtkWidget *get_child_by_index(HildonGridPrivate * priv, gint index);
226
227 static gboolean jump_scrollbar_to_focused(HildonGrid * grid);
228 static gboolean adjust_scrollbar_height(HildonGrid * grid);
229 static gboolean update_contents(HildonGrid * grid);
230 static void set_focus(HildonGrid * grid,
231                       GtkWidget * widget, gboolean refresh_view);
232
233 static GtkContainerClass *parent_class = NULL;
234 static guint grid_signals[LAST_SIGNAL] = { 0 };
235
236
237 GType hildon_grid_get_type(void)
238 {
239     static GType grid_type = 0;
240
241     if (!grid_type) {
242         static const GTypeInfo grid_info = {
243             sizeof(HildonGridClass),
244             NULL,       /* base_init */
245             NULL,       /* base_finalize */
246             (GClassInitFunc) hildon_grid_class_init,
247             NULL,       /* class_finalize */
248             NULL,       /* class_data */
249             sizeof(HildonGrid),
250             0,  /* n_preallocs */
251             (GInstanceInitFunc) hildon_grid_init,
252         };
253         grid_type = g_type_register_static(GTK_TYPE_CONTAINER,
254                                            "HildonGrid", &grid_info, 0);
255     }
256
257     return grid_type;
258 }
259
260
261
262 static void hildon_grid_class_init(HildonGridClass * klass)
263 {
264     GObjectClass *gobject_class;
265     GtkWidgetClass *widget_class;
266     GtkContainerClass *container_class;
267
268     widget_class = GTK_WIDGET_CLASS(klass);
269     container_class = GTK_CONTAINER_CLASS(klass);
270     gobject_class = G_OBJECT_CLASS(klass);
271
272     parent_class = g_type_class_peek_parent(klass);
273
274     g_type_class_add_private(klass, sizeof(HildonGridPrivate));
275
276     GTK_OBJECT_CLASS(klass)->destroy = hildon_grid_destroy;
277     gobject_class->finalize = hildon_grid_finalize;
278     gobject_class->set_property = hildon_grid_set_property;
279     gobject_class->get_property = hildon_grid_get_property;
280
281     widget_class->realize = hildon_grid_realize;
282     widget_class->unrealize = hildon_grid_unrealize;
283     widget_class->map = hildon_grid_map;
284     widget_class->unmap = hildon_grid_unmap;
285     widget_class->expose_event = hildon_grid_expose;
286     widget_class->size_request = hildon_grid_size_request;
287     widget_class->size_allocate = hildon_grid_size_allocate;
288     widget_class->tap_and_hold_setup = hildon_grid_tap_and_hold_setup;
289     widget_class->key_press_event = hildon_grid_key_pressed;
290     widget_class->button_press_event = hildon_grid_button_pressed;
291     widget_class->button_release_event = hildon_grid_button_released;
292
293     container_class->add = hildon_grid_add;
294     container_class->remove = hildon_grid_remove;
295     container_class->forall = hildon_grid_forall;
296     container_class->child_type = hildon_grid_child_type;
297     container_class->set_focus_child = hildon_grid_set_focus_child;
298
299     /* Install properties to the class */
300     g_object_class_install_property(gobject_class, PROP_EMPTY_LABEL,
301         g_param_spec_string("empty_label",
302                             "Empty label",
303                             "Label to show when grid has no items",
304                             _("ckct_wi_grid_no_items"), G_PARAM_READWRITE));
305
306     g_object_class_install_property(gobject_class, PROP_STYLE,
307         g_param_spec_string("style",
308                             "Style",
309                             "Widget's Style. Setting style sets widget size, "
310                             "spacing, label position, number of columns, "
311                             "and icon sizeLabel to show when grid has no items",
312                             DEFAULT_STYLE, G_PARAM_READWRITE));
313
314     g_object_class_install_property(gobject_class, PROP_SCROLLBAR_POS,
315         g_param_spec_int("scrollbar-position",
316                          "Scrollbar Position",
317                          "View (scrollbar) position.",
318                          G_MININT, G_MAXINT, 0, G_PARAM_READWRITE));
319
320     gtk_widget_class_install_style_property(widget_class,
321         g_param_spec_uint("item_width",
322                           "Item width",
323                           "Total width of an item (obsolete)",
324                           1, G_MAXINT, 212, G_PARAM_READABLE));
325
326     gtk_widget_class_install_style_property(widget_class,
327         g_param_spec_uint("item_height",
328                           "Item height",
329                           "Total height of an item",
330                           1, G_MAXINT, 96, G_PARAM_READABLE));
331
332     gtk_widget_class_install_style_property(widget_class,
333         g_param_spec_uint("item_hspacing",
334                           "Item horizontal spacing",
335                           "Margin between two columns and labels",
336                           0, G_MAXINT, 12, G_PARAM_READABLE));
337
338     gtk_widget_class_install_style_property(widget_class,
339         g_param_spec_uint("item_vspacing",
340                           "Item vertical spacing",
341                           "Icon on right: Margin between rows / Icon at bottom: Vertical margin betweeb label and icon",
342                           0, G_MAXINT, 6, G_PARAM_READABLE));
343
344     gtk_widget_class_install_style_property(widget_class,
345         g_param_spec_uint("label_hspacing",
346                           "Focus margin",
347                           "Margin between focus edge and item edge",
348                           0, G_MAXINT, 6, G_PARAM_READABLE));
349
350     gtk_widget_class_install_style_property(widget_class,
351         g_param_spec_uint("label_vspacing",
352                           "Vertical label spacing",
353                           "Vertical margin between item and label",
354                           0, G_MAXINT, 6, G_PARAM_READABLE));
355
356     gtk_widget_class_install_style_property(widget_class,
357         g_param_spec_uint("label_height",
358                           "Label height",
359                           "Height of icon label",
360                           1, G_MAXINT, 30, G_PARAM_READABLE));
361
362     gtk_widget_class_install_style_property(widget_class,
363         g_param_spec_uint("n_columns",
364                           "Columns",
365                           "Number of columns",
366                           0, G_MAXINT, DEFAULT_N_COLUMNS, G_PARAM_READABLE));
367
368     gtk_widget_class_install_style_property(widget_class,
369         g_param_spec_uint("label_pos",
370                           "Label position",
371                           "Position of label related to the icon",
372                           1, 2, 1, G_PARAM_READABLE));
373
374     gtk_widget_class_install_style_property(widget_class,
375         g_param_spec_uint("icon_size",
376                           "Icon size",
377                           "Size of the icon in pixels (width)",
378                           1, G_MAXINT, 64, G_PARAM_READABLE));
379
380     gtk_widget_class_install_style_property(widget_class,
381         g_param_spec_uint("emblem_size",
382                           "Emblem size",
383                           "Size of the emblem in pixels",
384                           1, G_MAXINT, 25, G_PARAM_READABLE));
385
386     /**
387      * HildonGrid::activate-child:
388      *
389      * Emitted when a child (@HildonGridItem) is activated either by
390      * tapping on it or by pressing enter.
391      */
392     grid_signals[ACTIVATE_CHILD] =
393         g_signal_new("activate-child",
394                      G_OBJECT_CLASS_TYPE(gobject_class),
395                      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
396                      G_STRUCT_OFFSET(HildonGridClass, activate_child),
397                      NULL, NULL,
398                      g_cclosure_marshal_VOID__OBJECT,
399                      G_TYPE_NONE, 1, HILDON_TYPE_GRID_ITEM);
400
401     /**
402      * HildonGrid::popup-context-menu:
403      *
404      * Emitted when popup-menu is supposed to open. Used for tap-and-hold.
405      */
406     grid_signals[POPUP_CONTEXT] =
407         g_signal_new("popup-context-menu",
408                      G_OBJECT_CLASS_TYPE(gobject_class),
409                      G_SIGNAL_RUN_LAST,
410                      G_STRUCT_OFFSET(HildonGridClass, popup_context_menu),
411                      NULL, NULL,
412                      _hildon_marshal_VOID__OBJECT,
413                      G_TYPE_NONE, 1, HILDON_TYPE_GRID_ITEM);
414 }
415
416
417
418 /*
419  * hildon_grid_set_empty_label:
420  * @grid:           #HildonGrid
421  * @empty_label:    New label
422  *
423  * Sets empty label.
424  */
425 static void
426 hildon_grid_set_empty_label(HildonGrid * grid, const gchar * empty_label)
427 {
428     /* No need to worry about update -- label receives a signal for it. */
429     gtk_label_set_label(GTK_LABEL(HILDON_GRID_GET_PRIVATE
430                                   (grid)->empty_label),
431                         empty_label == NULL ? "" : empty_label);
432 }
433
434 /*
435  * _hildon_grid_get_empty_label:
436  * @grid:   #HildonGrid
437  *
438  * Returns: empty label. Label must not be modified nor freed.
439  */
440 static const gchar *
441 hildon_grid_get_empty_label(HildonGrid * grid)
442 {
443     return gtk_label_get_label(GTK_LABEL(HILDON_GRID_GET_PRIVATE
444                                          (grid)->empty_label));
445 }
446
447 /*
448  * hildon_grid_set_num_columns:
449  * @grid:       #HildonGrid
450  * @columns:    Number of columns
451  *
452  * Sets number of columns.
453  */
454 static void
455 hildon_grid_set_num_columns(HildonGrid * grid, gint columns)
456 {
457     HildonGridPrivate *priv;
458
459     g_return_if_fail(HILDON_IS_GRID(grid));
460     priv = HILDON_GRID_GET_PRIVATE(grid);
461
462     if (priv->num_columns == columns) {
463         return;
464     }
465
466     if (columns != 0)
467         priv->num_columns = columns;
468     else
469         priv->num_columns = DEFAULT_N_COLUMNS;
470     
471     /* Update estimated row-count for jump_scrollbar... */
472     priv->area_rows = MAX(priv->area_height / priv->num_columns, 1);
473
474     /* Size could have changed. Scroll view so there's something to show. */
475     adjust_scrollbar_height(grid);
476     jump_scrollbar_to_focused(grid);
477     gtk_widget_queue_resize(GTK_WIDGET(grid));
478 }
479
480 /*
481  * hildon_grid_set_label_pos:
482  * @grid:       #HildonGrid
483  * @label_pos:  Label position
484  *
485  * Sets icon label position.
486  */
487 static void
488 hildon_grid_set_label_pos(HildonGrid * grid,
489                           HildonGridPositionType label_pos)
490 {
491     HildonGridPrivate *priv;
492     GList *list;
493     GtkWidget *child;
494
495     priv = HILDON_GRID_GET_PRIVATE(grid);
496
497     if (label_pos == priv->label_pos)
498         return;
499
500     /* gtknotebook doesn't check if we use valid values -- why should
501        we?-) */
502
503     priv->label_pos = label_pos;
504
505     /* Set label position to each HildonGridItem */
506     for (list = priv->children; list != NULL; list = list->next) {
507         child = ((HildonGridChild *) list->data)->widget;
508
509         _hildon_grid_item_set_label_pos(HILDON_GRID_ITEM(child),
510                                         label_pos);
511     }
512 }
513
514 /*
515  * hildon_grid_set_focus_margin:
516  * @grid:         #HildonGrid
517  * @focus_margin: Focus margin
518  *
519  * Sets margin between icon edge and label edge
520  */
521 static void
522 hildon_grid_set_focus_margin(HildonGrid *grid, gint focus_margin)
523 {
524     HildonGridPrivate *priv;
525     GList *list;
526     GtkWidget *child;
527
528     priv = HILDON_GRID_GET_PRIVATE(grid);
529     if (focus_margin == priv->focus_margin)
530         return;
531
532     priv->focus_margin = focus_margin;
533
534     /* Update children. */
535     for (list = priv->children; list != NULL; list = list->next) {
536         child = ((HildonGridChild *) list->data)->widget;
537
538         _hildon_grid_item_set_focus_margin(HILDON_GRID_ITEM(child),
539                                            priv->focus_margin);
540     }
541 }
542
543
544 /*
545  * hildon_grid_set_icon_label_margin:
546  * @grid:       #HildonGrid
547  * @hspacing:   Vertical spacing
548  *
549  * Sets vertical spacing for label.
550  * XXX
551  */
552 static void
553 hildon_grid_set_icon_label_margin(HildonGrid *grid, gint icon_label_margin)
554 {
555     HildonGridPrivate *priv;
556
557     priv = HILDON_GRID_GET_PRIVATE(grid);
558     if (icon_label_margin == priv->icon_label_margin)
559         return;
560
561     priv->icon_label_margin = icon_label_margin;
562 }
563
564
565 /*
566  * hildon_grid_set_icon_width:
567  * @grid:       #HildonGrid
568  * @icon_size:  Icon size (width)
569  *
570  * Sets icon size (in pixels).
571  */
572 static void
573 hildon_grid_set_icon_width(HildonGrid * grid, gint icon_width)
574 {
575     HildonGridPrivate *priv;
576     GList *list;
577     GtkWidget *child;
578
579     priv = HILDON_GRID_GET_PRIVATE(grid);
580
581     if (icon_width == priv->icon_width)
582         return;
583
584     priv->icon_width = icon_width;
585
586     for (list = priv->children; list != NULL; list = list->next) {
587         child = ((HildonGridChild *) list->data)->widget;
588
589         _hildon_grid_item_set_icon_width(HILDON_GRID_ITEM(child),
590                                          icon_width);
591     }
592 }
593
594
595 /*
596  * hildon_grid_set_emblem_size:
597  * @grid:           #HildonGrid
598  * @emblem_size:    Emblem size
599  *
600  * Sets emblem size (in pixels).
601  */
602 static void
603 hildon_grid_set_emblem_size(HildonGrid *grid, gint emblem_size)
604 {
605     HildonGridPrivate *priv;
606     GList *list;
607     GtkWidget *child;
608
609     priv = HILDON_GRID_GET_PRIVATE(grid);
610
611     if (emblem_size == priv->emblem_size)
612         return;
613
614     priv->emblem_size = emblem_size;
615
616     for (list = priv->children; list != NULL; list = list->next) {
617         child = ((HildonGridChild *) list->data)->widget;
618
619         _hildon_grid_item_set_emblem_size(HILDON_GRID_ITEM(child),
620                                           emblem_size);
621     }
622 }
623
624
625 static void
626 hildon_grid_set_label_height(HildonGrid *grid,
627                              gint label_height)
628 {
629     HildonGridPrivate *priv;
630     GList *list;
631     GtkWidget *child;
632
633     priv = HILDON_GRID_GET_PRIVATE(grid);
634
635     if (label_height == priv->label_height)
636         return;
637
638     priv->label_height = label_height;
639
640     for (list = priv->children; list != NULL; list = list->next) {
641         child = ((HildonGridChild *) list->data)->widget;
642
643         _hildon_grid_item_set_label_height(HILDON_GRID_ITEM(child),
644                                            label_height);
645     }
646 }
647
648
649 static GType hildon_grid_child_type(GtkContainer * container)
650 {
651     return GTK_TYPE_WIDGET;
652 }
653
654 static void hildon_grid_init(HildonGrid * grid)
655 {
656     HildonGridPrivate *priv;
657
658     priv = HILDON_GRID_GET_PRIVATE(grid);
659
660     GTK_CONTAINER(grid)->focus_child = NULL;
661     priv->focus_index = -1;
662
663     priv->scrollbar = gtk_vscrollbar_new(NULL);
664     priv->empty_label = gtk_label_new(_("ckct_wi_grid_no_items"));
665     priv->style = NULL;
666
667     priv->area_height = 1;
668     priv->area_rows = 1;
669     priv->children = NULL;
670
671     priv->first_index = 0;
672     priv->click_x = 0;
673     priv->click_y = 0;
674
675     priv->item_height = 96;
676     priv->h_margin = 12;
677     priv->v_margin = 6;
678     priv->focus_margin = 6;
679     priv->icon_label_margin = 6;
680     priv->icon_width = 64;
681     priv->label_pos = HILDON_GRID_ITEM_LABEL_POS_BOTTOM;
682
683     priv->old_sb_pos = -1;
684     priv->old_item_height = -1;
685
686     gtk_widget_set_parent(priv->scrollbar, GTK_WIDGET(grid));
687     gtk_widget_set_parent(priv->empty_label, GTK_WIDGET(grid));
688
689     priv->last_button_event = GDK_NOTHING;
690
691     GTK_WIDGET_SET_FLAGS(grid, GTK_NO_WINDOW);
692
693     /* Signal for scrollbar. */
694     g_signal_connect(G_OBJECT(priv->scrollbar), "value-changed",
695                      G_CALLBACK(hildon_grid_scrollbar_moved), grid);
696
697     /* Signal for key press. */
698     GTK_WIDGET_SET_FLAGS(GTK_WIDGET(grid), GTK_CAN_FOCUS);
699     gtk_widget_set_events(GTK_WIDGET(grid), GDK_KEY_PRESS_MASK);
700
701     GTK_WIDGET_UNSET_FLAGS(priv->scrollbar, GTK_CAN_FOCUS);
702     hildon_grid_set_style(grid, DEFAULT_STYLE);
703 }
704
705 /**
706  * hildon_grid_new:
707  *
708  * Creates a new #HildonGrid.
709  *
710  * Returns: a new #HildonGrid
711  */
712 GtkWidget *hildon_grid_new(void)
713 {
714
715     HildonGrid *grid;
716
717     grid = g_object_new(HILDON_TYPE_GRID, NULL);
718
719     return GTK_WIDGET(grid);
720 }
721
722
723 static void hildon_grid_realize(GtkWidget * widget)
724 {
725     HildonGrid *grid;
726     HildonGridPrivate *priv;
727     GdkWindowAttr attr;
728     gint attr_mask;
729
730
731     GTK_WIDGET_SET_FLAGS(widget, GTK_REALIZED);
732
733     grid = HILDON_GRID(widget);
734     priv = HILDON_GRID_GET_PRIVATE(grid);
735
736     /* Create GdkWindow for catching events. */
737     attr.x = widget->allocation.x;
738     attr.y = widget->allocation.y;
739     attr.width = widget->allocation.width - priv->scrollbar_width;
740     attr.height = widget->allocation.height;
741     attr.window_type = GDK_WINDOW_CHILD;
742     attr.event_mask = gtk_widget_get_events(widget)
743         | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK;
744
745     widget->window = gtk_widget_get_parent_window(widget);
746     g_object_ref(widget->window);
747
748     attr.wclass = GDK_INPUT_ONLY;
749     attr_mask = GDK_WA_X | GDK_WA_Y;
750
751     priv->event_window = gdk_window_new(widget->window, &attr, attr_mask);
752     gdk_window_set_user_data(priv->event_window, widget);
753
754     widget->style = gtk_style_attach(widget->style, widget->window);
755
756     gtk_style_set_background(widget->style,
757                              widget->window, GTK_STATE_NORMAL);
758 }
759
760
761 static void hildon_grid_unrealize(GtkWidget * widget)
762 {
763     HildonGridPrivate *priv;
764
765     priv = HILDON_GRID_GET_PRIVATE(HILDON_GRID(widget));
766
767     if (priv->event_window != NULL) {
768         gdk_window_set_user_data(priv->event_window, NULL);
769         gdk_window_destroy(priv->event_window);
770         priv->event_window = NULL;
771     }
772
773     if (GTK_WIDGET_CLASS(parent_class)->unrealize) {
774         (*GTK_WIDGET_CLASS(parent_class)->unrealize) (widget);
775     }
776 }
777
778
779
780 static void hildon_grid_map(GtkWidget * widget)
781 {
782     HildonGrid *grid;
783     HildonGridPrivate *priv;
784     GList *list;
785     GtkWidget *child;
786
787     g_return_if_fail(HILDON_IS_GRID(widget));
788
789     if (!GTK_WIDGET_VISIBLE(widget))
790         return;
791
792     grid = HILDON_GRID(widget);
793     priv = HILDON_GRID_GET_PRIVATE(grid);
794
795     (*GTK_WIDGET_CLASS(parent_class)->map) (widget);
796
797     /* We shouldn't really need the following...*/
798     if (priv->scrollbar != NULL && GTK_WIDGET_VISIBLE(priv->scrollbar)) {
799         if (!GTK_WIDGET_MAPPED(priv->scrollbar)) {
800             gtk_widget_map(priv->scrollbar);
801         }
802     }
803
804     if (priv->empty_label != NULL &&
805         GTK_WIDGET_VISIBLE(priv->empty_label)) {
806         if (!GTK_WIDGET_MAPPED(priv->empty_label)) {
807             gtk_widget_map(priv->empty_label);
808         }
809     }
810
811     for (list = priv->children; list != NULL; list = list->next) {
812         child = ((HildonGridChild *) list->data)->widget;
813
814         if (GTK_WIDGET_VISIBLE(child)) {
815             if (!GTK_WIDGET_MAPPED(child)) {
816                 gtk_widget_map(child);
817             }
818         }
819     }
820     /* END OF don't really need */
821
822     /* Also make event window visible. */
823     gdk_window_show(priv->event_window);
824 }
825
826
827
828 static void hildon_grid_unmap(GtkWidget * widget)
829 {
830     HildonGridPrivate *priv;
831
832     priv = HILDON_GRID_GET_PRIVATE(HILDON_GRID(widget));
833
834     if (priv->event_window != NULL) {
835         gdk_window_hide(priv->event_window);
836     }
837
838     (*GTK_WIDGET_CLASS(parent_class)->unmap) (widget);
839 }
840
841
842
843 static gboolean
844 hildon_grid_expose(GtkWidget * widget, GdkEventExpose * event)
845 {
846     HildonGrid *grid;
847     HildonGridPrivate *priv;
848     GtkContainer *container;
849     GList *list;
850     gint child_no;
851
852     g_return_val_if_fail(widget, FALSE);
853     g_return_val_if_fail(HILDON_IS_GRID(widget), FALSE);
854     g_return_val_if_fail(event, FALSE);
855
856     grid = HILDON_GRID(widget);
857     priv = HILDON_GRID_GET_PRIVATE(grid);
858     container = GTK_CONTAINER(grid);
859
860     /* If grid has no children,
861      * propagate the expose event to the label is one exists */ 
862     if (priv->children == NULL || g_list_length(priv->children) == 0) {
863         if (priv->empty_label != NULL) {
864             gtk_container_propagate_expose(container,
865                                            priv->empty_label, event);
866         }
867         return FALSE;
868     }
869
870     /* Only expose visible children. */
871
872     /* Jump over invisible. */
873     for (list = priv->children, child_no = 0;
874          list != NULL && child_no < priv->first_index;
875          list = list->next, child_no++) {
876         ;       /* Nothing here. */
877     }
878
879     for (; list != NULL && child_no < priv->first_index +
880          priv->num_columns * priv->area_rows; list = list->next) {
881         gtk_container_propagate_expose(container,
882                                        ((HildonGridChild *) list->data)
883                                        ->widget, event);
884     }
885
886     /* Keep focused item focused. */
887     if (container->focus_child != NULL
888         && !GTK_WIDGET_HAS_FOCUS(container->focus_child)) {
889         set_focus(grid, container->focus_child, FALSE);
890     }
891     if (priv->scrollbar_width > 0 && priv->scrollbar != NULL) {
892         gtk_container_propagate_expose(container, priv->scrollbar, event);
893     }
894
895     return FALSE;
896 }
897
898
899 static void
900 hildon_grid_size_request(GtkWidget * widget, GtkRequisition * requisition)
901 {
902     HildonGrid *grid;
903     HildonGridPrivate *priv;
904     GList *list;
905     GtkWidget *child;
906     GtkRequisition req;
907
908     g_return_if_fail(widget);
909     g_return_if_fail(requisition);
910
911     grid = HILDON_GRID(widget);
912     priv = HILDON_GRID_GET_PRIVATE(grid);
913
914     /* Want as big as possible. */
915     requisition->width = 0x7fff;        /* Largest possible gint16 */
916     requisition->height = 0x7fff;       /* Largest possible gint16 */
917
918     if (priv->children == NULL) {
919         if (priv->empty_label != NULL &&
920             GTK_WIDGET_VISIBLE(priv->empty_label)) {
921             gtk_widget_size_request(priv->empty_label, &req);
922         }
923     }
924
925     if (priv->scrollbar != NULL && GTK_WIDGET_VISIBLE(priv->scrollbar)) {
926         gtk_widget_size_request(priv->scrollbar, &req);
927     }
928
929     for (list = priv->children; list != NULL; list = list->next) {
930         child = ((HildonGridChild *) list->data)->widget;
931
932         gtk_widget_size_request(child, &req);
933     }
934 }
935
936 /*
937  * hildon_grid_size_allocate:
938  *
939  * Supposingly called when size of grid changes and after view have moved so
940  * that items need to be relocated.
941  */
942 static void
943 hildon_grid_size_allocate(GtkWidget * widget, GtkAllocation * allocation)
944 {
945     HildonGrid *grid;
946     HildonGridPrivate *priv;
947     GList *list;
948     GtkWidget *child;
949     gint child_no;
950     gint y_offset;
951     gint row_margin;
952
953     GtkAllocation alloc;
954     GtkRequisition req;
955
956     g_return_if_fail(widget);
957     g_return_if_fail(allocation);
958
959     grid = HILDON_GRID(widget);
960     priv = HILDON_GRID_GET_PRIVATE(grid);
961     widget->allocation = *allocation;
962
963     get_style_properties(grid);
964
965     /* First of all, make sure GdkWindow is over our widget. */
966     if (priv->event_window != NULL) {
967         gdk_window_move_resize(priv->event_window,
968                                widget->allocation.x,
969                                widget->allocation.y,
970                                widget->allocation.width -
971                                    priv->scrollbar_width,
972                                widget->allocation.height);
973     }
974     /* Show the label if there are no items. */
975     if (priv->children == NULL) {
976         /* 
977          * We probably don't need this as scrollbar should be hidden when
978          * removing items, but one can never be too sure...
979          */
980         if (priv->scrollbar != NULL &&
981             GTK_WIDGET_VISIBLE(priv->scrollbar)) {
982             priv->scrollbar_width = 0;
983             gtk_widget_hide(priv->scrollbar);
984         }
985
986         /* Show label if creating one actually worked. */
987         if (priv->empty_label != NULL) {
988             gtk_widget_get_child_requisition(priv->empty_label, &req);
989
990             /* ...for sure we must have a position for the label here... */
991             alloc.x = allocation->x + GRID_LABEL_POS_PAD;
992             alloc.y = allocation->y + GRID_LABEL_POS_PAD;
993             alloc.width = MIN(req.width, allocation->width -
994                               GRID_LABEL_POS_PAD);
995             alloc.height = MIN(req.height, allocation->height -
996                                GRID_LABEL_POS_PAD);
997
998             /* Make sure we don't use negative values. */
999             if (alloc.width < 0) {
1000                 alloc.width = 0;
1001             }
1002             if (alloc.height < 0) {
1003                 alloc.height = 0;
1004             }
1005
1006             gtk_widget_size_allocate(priv->empty_label, &alloc);
1007
1008             if (!GTK_WIDGET_VISIBLE(priv->empty_label)) {
1009                 gtk_widget_show(priv->empty_label);
1010             }
1011         }
1012
1013         return;
1014     }
1015
1016     /* As we have some items, hide label if it was visible. */
1017     if (priv->empty_label != NULL &&
1018         GTK_WIDGET_VISIBLE(priv->empty_label)) {
1019         gtk_widget_hide(priv->empty_label);
1020     }
1021
1022     priv->area_height = allocation->height;
1023     priv->area_rows = allocation->height / priv->item_height;
1024
1025     /* Adjust/show/hide scrollbar. */
1026     adjust_scrollbar_height(grid);
1027     if (priv->old_item_height != priv->item_height) {
1028         priv->old_item_height = priv->item_height;
1029         jump_scrollbar_to_focused(grid);
1030     }
1031
1032     /* Update item width. */
1033     if (priv->num_columns == 1) {
1034         priv->item_width = allocation->width - priv->scrollbar_width -
1035             priv->h_margin - priv->scrollbar_width;
1036     } else {
1037         priv->item_width = (allocation->width - priv->scrollbar_width) /
1038             priv->num_columns;
1039     }
1040
1041     priv->first_index =
1042         (int) gtk_range_get_value(GTK_RANGE(priv->scrollbar)) /
1043         priv->item_height * priv->num_columns;
1044
1045     /* Hide items before visible ones. */
1046     for (list = priv->children, child_no = 0;
1047          list != NULL && child_no < priv->first_index;
1048          list = list->next, child_no++) {
1049         child = ((HildonGridChild *) list->data)->widget;
1050
1051         if (GTK_WIDGET_VISIBLE(child)) {
1052             gtk_widget_hide(child);
1053         }
1054     }
1055
1056     /* Allocate visible items. */
1057     alloc.width = priv->item_width - priv->h_margin;
1058     switch (priv->label_pos) {
1059     case HILDON_GRID_ITEM_LABEL_POS_BOTTOM:
1060         row_margin = priv->icon_label_margin;
1061         break;
1062     case HILDON_GRID_ITEM_LABEL_POS_RIGHT:
1063         row_margin = priv->v_margin;
1064         break;
1065     default:
1066         row_margin = 0;
1067         break;
1068     }
1069     alloc.height = priv->item_height - row_margin;
1070
1071     for (y_offset = priv->first_index / priv->num_columns * priv->item_height;
1072          list != NULL && child_no < priv->first_index +
1073          priv->area_rows * priv->num_columns;
1074          list = list->next, child_no++) {
1075         child = ((HildonGridChild *) list->data)->widget;
1076
1077         if (!GTK_WIDGET_VISIBLE(child)) {
1078             gtk_widget_show(child);
1079         }
1080
1081         /* Don't update icons which are not visible... */
1082         alloc.y = (child_no / priv->num_columns) * priv->item_height +
1083                   allocation->y - y_offset + row_margin;
1084         alloc.x = (child_no % priv->num_columns) * priv->item_width +
1085                   allocation->x;
1086
1087         _hildon_grid_item_done_updating_settings(HILDON_GRID_ITEM(child));
1088         gtk_widget_size_allocate(child, &alloc);
1089     }
1090
1091     /* Hide items after visible items. */
1092     for (; list != NULL; list = list->next) {
1093         child = ((HildonGridChild *) list->data)->widget;
1094
1095         if (GTK_WIDGET_VISIBLE(child)) {
1096             gtk_widget_hide(child);
1097         }
1098     }
1099 }
1100
1101
1102
1103 /**
1104  * hildon_grid_add:
1105  * @container:  container (#HildonGrid) to add HildonGridItem into
1106  * @widget:     #GtkWidget (#HildonGridItem) to add
1107  *
1108  * Adds a new HildonGridItem into HildonGrid.
1109  */
1110 static void hildon_grid_add(GtkContainer * container, GtkWidget * widget)
1111 {
1112     HildonGrid *grid;
1113     HildonGridPrivate *priv;
1114     HildonGridChild *child;
1115
1116
1117     g_return_if_fail(HILDON_IS_GRID(container));
1118     g_return_if_fail(HILDON_IS_GRID_ITEM(widget));
1119
1120     grid = HILDON_GRID(container);
1121     priv = HILDON_GRID_GET_PRIVATE(HILDON_GRID(grid));
1122     GTK_WIDGET_SET_FLAGS(widget, GTK_NO_WINDOW);
1123
1124     child = g_new(HildonGridChild, 1);
1125     if (child == NULL) {
1126         g_critical("no memory for child - not adding");
1127         return;
1128     }
1129     child->widget = widget;
1130
1131     _hildon_grid_item_set_label_pos   (HILDON_GRID_ITEM(widget), priv->label_pos);
1132     _hildon_grid_item_set_focus_margin(HILDON_GRID_ITEM(widget), priv->focus_margin);
1133     _hildon_grid_item_set_icon_width  (HILDON_GRID_ITEM(widget), priv->icon_width);
1134     _hildon_grid_item_set_emblem_size (HILDON_GRID_ITEM(widget), priv->emblem_size);
1135
1136     /* Add the new item to the grid */
1137     priv->children = g_list_append(priv->children, child);
1138     gtk_widget_set_parent(widget, GTK_WIDGET(grid));
1139
1140     /* Property changes (child's set_sensitive) */
1141     g_signal_connect_after(G_OBJECT(widget), "state-changed",
1142                            G_CALLBACK(hildon_grid_state_changed), grid);
1143
1144     /* Matches both empty grid and all-dimmed grid. */
1145     if (GTK_CONTAINER(grid)->focus_child == NULL)
1146         set_focus(grid, widget, TRUE);
1147
1148     /* 
1149      * If item was added in visible area, relocate items. Otherwise update
1150      * scrollbar and see if items need relocating.
1151      */
1152     if (g_list_length(priv->children) < priv->first_index +
1153         priv->area_rows * priv->num_columns) {
1154         gtk_widget_queue_resize(GTK_WIDGET(grid));
1155     } else {
1156         gboolean updated;
1157
1158         updated = adjust_scrollbar_height(grid);
1159         /* Basically this other test is useless -- shouldn't need to jump. 
1160          */
1161         updated |= jump_scrollbar_to_focused(grid);
1162
1163         if (updated) {
1164             gtk_widget_queue_resize(GTK_WIDGET(grid));
1165         }
1166     }
1167 }
1168
1169 /**
1170  * hildon_grid_remove:
1171  * @container:  container (#HildonGrid) to remove #HildonGridItem from
1172  * @widget:     widget (#HildonGridItem) to be removed
1173  *
1174  * Removes HildonGridItem from HildonGrid.
1175  */
1176 static void
1177 hildon_grid_remove(GtkContainer * container, GtkWidget * widget)
1178 {
1179     HildonGrid *grid;
1180     HildonGridPrivate *priv;
1181     HildonGridChild *child;
1182     GtkWidget *child_widget;
1183     GList *list;
1184     gint index, old_index;
1185     gboolean deleted;
1186     gboolean updated;
1187
1188     g_return_if_fail(HILDON_IS_GRID(container));
1189     g_return_if_fail(HILDON_IS_GRID_ITEM(widget));
1190
1191     grid = HILDON_GRID(container);
1192     priv = HILDON_GRID_GET_PRIVATE(container);
1193
1194     old_index = priv->focus_index;
1195     updated = GTK_WIDGET_VISIBLE(widget);
1196
1197     for (list = priv->children, index = 0, deleted = FALSE;
1198          list != NULL; list = list->next, index++) {
1199         child = (HildonGridChild *) list->data;
1200         child_widget = child->widget;
1201
1202         /* Remove the Item if it is found in the grid */
1203         if (child_widget == widget) {
1204             gtk_widget_unparent(child_widget);
1205             priv->children = g_list_remove_link(priv->children, list);
1206             g_list_free(list);
1207             g_free(child);
1208
1209             deleted = TRUE;
1210
1211             break;
1212         }
1213     }
1214
1215     /* Emit warning if the item is not found */
1216     if (!deleted) {
1217         g_warning("tried to remove unexisting item");
1218         return;
1219     }
1220
1221     /* Move focus somewhere. */
1222     if (old_index == index) {
1223         if (old_index == g_list_length(priv->children)) {
1224             if (index == 0) {
1225                 set_focus(grid, NULL, TRUE);
1226             } else {
1227                 set_focus(grid,
1228                           get_child_by_index(priv, old_index - 1), TRUE);
1229             }
1230         } else {
1231             set_focus(grid, get_child_by_index(priv, old_index), TRUE);
1232         }
1233     } else {
1234         set_focus(grid, GTK_CONTAINER(grid)->focus_child, TRUE);
1235     }
1236
1237     updated |= adjust_scrollbar_height(grid);
1238     updated |= jump_scrollbar_to_focused(grid);
1239
1240     if (updated) {
1241         gtk_widget_queue_resize(GTK_WIDGET(grid));
1242     }
1243 }
1244
1245 /**
1246  * hildon_grid_set_focus_child:
1247  * @container:  HildonGrid
1248  * @widget:     HildonGridItem
1249  *
1250  * Sets focus.
1251  */
1252 static void
1253 hildon_grid_set_focus_child(GtkContainer * container, GtkWidget * widget)
1254 {
1255     HildonGrid *grid;
1256     HildonGridPrivate *priv;
1257
1258     g_return_if_fail(HILDON_IS_GRID(container));
1259     g_return_if_fail(HILDON_IS_GRID_ITEM(widget) || widget == NULL);
1260
1261     grid = HILDON_GRID(container);
1262     priv = HILDON_GRID_GET_PRIVATE(grid);
1263
1264     if (GTK_CONTAINER(grid)->focus_child == widget || widget == NULL)
1265         return;
1266
1267     set_focus(grid, widget, TRUE);
1268 }
1269
1270
1271
1272 static void
1273 set_focus(HildonGrid * grid, GtkWidget * widget, gboolean refresh_view)
1274 {
1275     HildonGridPrivate *priv;
1276     GtkContainer *container;
1277     gboolean view_updated;
1278
1279
1280     priv = HILDON_GRID_GET_PRIVATE(grid);
1281     container = GTK_CONTAINER(grid);
1282
1283     /* If widget is NULL -> unfocus */ 
1284     if (widget == NULL && container->focus_child != NULL)
1285         GTK_WIDGET_UNSET_FLAGS(container->focus_child, GTK_HAS_FOCUS);
1286
1287     GTK_CONTAINER(grid)->focus_child = widget;
1288     if (widget == NULL) {
1289         priv->focus_index = -1;
1290         return;
1291     }
1292
1293     /* Get the child index which the user wanted to focus */
1294     priv->focus_index = get_child_index(priv, widget);
1295
1296     gtk_widget_grab_focus(widget);
1297
1298     if (refresh_view) {
1299         view_updated = jump_scrollbar_to_focused(grid);
1300     } else {
1301         view_updated = FALSE;
1302     }
1303
1304     if (view_updated) {
1305         hildon_grid_size_allocate(GTK_WIDGET(grid),
1306                                   &GTK_WIDGET(grid)->allocation);
1307     }
1308 }
1309
1310 static void
1311 hildon_grid_forall(GtkContainer * container,
1312                    gboolean include_internals,
1313                    GtkCallback callback, gpointer callback_data)
1314 {
1315     HildonGrid *grid;
1316     HildonGridPrivate *priv;
1317     GList *list;
1318
1319     g_return_if_fail(container);
1320     g_return_if_fail(callback);
1321
1322     grid = HILDON_GRID(container);
1323     priv = HILDON_GRID_GET_PRIVATE(grid);
1324
1325     /* Connect callback functions */
1326     if (include_internals) {
1327         if (priv->scrollbar != NULL) {
1328             (*callback) (priv->scrollbar, callback_data);
1329         }
1330         if (priv->empty_label != NULL) {
1331             (*callback) (priv->empty_label, callback_data);
1332         }
1333     }
1334
1335     for (list = priv->children; list != NULL; list = list->next) {
1336         (*callback) (((HildonGridChild *) list->data)->widget,
1337                      callback_data);
1338     }
1339 }
1340
1341 static void hildon_grid_destroy(GtkObject * self)
1342 {
1343     HildonGridPrivate *priv;
1344
1345     g_return_if_fail(self != NULL);
1346     g_return_if_fail(HILDON_IS_GRID(self));
1347
1348     priv = HILDON_GRID_GET_PRIVATE(self);
1349
1350     if (GTK_WIDGET(self)->window != NULL) {
1351         g_object_unref(G_OBJECT(GTK_WIDGET(self)->window));
1352     }
1353
1354     gtk_container_forall(GTK_CONTAINER(self),
1355                          (GtkCallback) gtk_object_ref, NULL);
1356     gtk_container_forall(GTK_CONTAINER(self),
1357                          (GtkCallback) gtk_widget_unparent, NULL);
1358
1359     GTK_OBJECT_CLASS(parent_class)->destroy(self);
1360 }
1361
1362 static void hildon_grid_finalize(GObject * object)
1363 {
1364     HildonGrid *grid;
1365     HildonGridPrivate *priv;
1366
1367     grid = HILDON_GRID(object);
1368     priv = HILDON_GRID_GET_PRIVATE(grid);
1369
1370     gtk_container_forall(GTK_CONTAINER(object),
1371                          (GtkCallback) gtk_object_unref, NULL);
1372
1373     if (priv->style != NULL) {
1374         g_free(priv->style);
1375     }
1376     if (G_OBJECT_CLASS(parent_class)->finalize) {
1377         G_OBJECT_CLASS(parent_class)->finalize(object);
1378     }
1379 }
1380
1381 /*
1382  * hildon_grid_key_pressed:
1383  * @widget: Widget where we get the signal from
1384  * @event:  EventKey
1385  * @data:   #HildonGrid
1386  *
1387  * Handle user key press (keyboard navigation).
1388  *
1389  * And here's how it works if some items are dimmed (moving to right):
1390  * . . . .      . . # .     . 2 # .     . # . .
1391  * . 1 # 2      . 1 # #     1 # # #     1 # # #
1392  * . . .        . . 2       . . .       . 2 .
1393  *
1394  * '.' = item,
1395  * '#' = dimmed item, 
1396  * '1' = starting position,
1397  * '2' = final position
1398  *
1399  * ...although only the first example is implemented right now.
1400  *
1401  * Return value: Signal handled
1402  */
1403 static gboolean
1404 hildon_grid_key_pressed(GtkWidget * widget,
1405                         GdkEventKey * event)
1406 {
1407     GtkAdjustment *adjustment;
1408     GtkContainer *container;
1409     GtkWidget *new_focus;
1410     HildonGrid *grid;
1411     HildonGridPrivate *priv;
1412     gboolean shift;
1413     gint keyval;
1414     gint x, y;
1415     gint focus_index;
1416     gint child_count, child_rows;
1417     gint t;
1418     gint addition, max_add;
1419
1420     g_return_val_if_fail(widget, FALSE);
1421
1422     grid = HILDON_GRID(widget);
1423     priv = HILDON_GRID_GET_PRIVATE(grid);
1424
1425     /* 
1426      * If focus was never lost, we could just see if an item is focused - 
1427      * if not, there's nothing else to focus...
1428      */
1429
1430     /* No items? */
1431     if (priv->children == NULL || g_list_length(priv->children) == 0)
1432         return GTK_WIDGET_CLASS (parent_class)->key_press_event (widget, event);
1433
1434     /* Focused item is dimmed? */
1435     /* If we have no focus, allow non-existing focus to move... */
1436     container = GTK_CONTAINER(grid);
1437     if (container->focus_child != NULL
1438         && !GTK_WIDGET_IS_SENSITIVE(container->focus_child)) {
1439         return GTK_WIDGET_CLASS (parent_class)->key_press_event (widget, event);
1440     }
1441     /* At the moment we don't want to do anything here if alt or control
1442        or MODX is pressed, so return now. Shift + TAB are accepted (from
1443        hildon-table-grid) And right now modifiers do not make any
1444        difference... */
1445
1446     /* Said somewhere that "foo = a == b" is not desirable. */
1447     if (event->state & GDK_SHIFT_MASK) {
1448         shift = TRUE;
1449     } else {
1450         shift = FALSE;
1451     }
1452
1453     keyval = event->keyval;
1454     if (gtk_widget_get_default_direction() == GTK_TEXT_DIR_RTL) {
1455         switch (event->keyval) {
1456         case GDK_Left:
1457             keyval = GDK_Right;
1458             break;
1459         case GDK_KP_Left:
1460             keyval = GDK_KP_Right;
1461             break;
1462         case GDK_Right:
1463             keyval = GDK_Left;
1464             break;
1465         case GDK_KP_Right:
1466             keyval = GDK_KP_Left;
1467             break;
1468         }
1469     }
1470
1471     child_count = g_list_length(priv->children);
1472     child_rows = (child_count - 1) / priv->num_columns + 1;
1473
1474     if (priv->focus_index != -1) {
1475         x = priv->focus_index % priv->num_columns;
1476         y = priv->focus_index / priv->num_columns;
1477     } else {
1478         x = y = 0;
1479     }
1480
1481     switch (keyval) {
1482     case GDK_KP_Page_Up:
1483     case GDK_Page_Up:
1484         if (priv->first_index == 0) {
1485             if (priv->focus_index == 0) {
1486                 return TRUE;
1487             }
1488             set_focus(grid, get_child_by_index(priv, 0), TRUE);
1489             return TRUE;
1490         }
1491
1492         t = MAX(priv->first_index / priv->num_columns - priv->area_rows, 0);
1493         adjustment = gtk_range_get_adjustment(GTK_RANGE(priv->scrollbar));
1494         adjustment->value = (gdouble) (t * priv->item_height);
1495         gtk_range_set_adjustment(GTK_RANGE(priv->scrollbar), adjustment);
1496         gtk_widget_queue_draw(priv->scrollbar);
1497         update_contents(grid);
1498
1499         /* Want to update now. */
1500         hildon_grid_size_allocate(GTK_WIDGET(grid),
1501                                   &GTK_WIDGET(grid)->allocation);
1502
1503         return TRUE;
1504         break;
1505
1506     case GDK_KP_Page_Down:
1507     case GDK_Page_Down:
1508         if (priv->first_index / priv->num_columns ==
1509             child_rows - priv->area_rows) {
1510             if (priv->focus_index == child_count - 1) {
1511                 return TRUE;
1512             }
1513             set_focus(grid, get_child_by_index(priv, child_count - 1),
1514                       TRUE);
1515             return TRUE;
1516         }
1517
1518         t = MIN(priv->first_index / priv->num_columns +
1519                 priv->area_rows, child_rows - priv->area_rows);
1520         adjustment = gtk_range_get_adjustment(GTK_RANGE(priv->scrollbar));
1521         adjustment->value = (gdouble) (t * priv->item_height);
1522         gtk_range_set_adjustment(GTK_RANGE(priv->scrollbar), adjustment);
1523         gtk_widget_queue_draw(priv->scrollbar);
1524         update_contents(grid);
1525
1526         /* Want to update now. */
1527         hildon_grid_size_allocate(GTK_WIDGET(grid),
1528                                   &GTK_WIDGET(grid)->allocation);
1529
1530         return TRUE;
1531         break;
1532
1533     case GDK_KP_Up:
1534     case GDK_Up:
1535         if (y <= 0) {
1536             return TRUE;
1537         }
1538         addition = -priv->num_columns;
1539         max_add = y;
1540         y--;
1541         break;
1542
1543     case GDK_KP_Down:
1544     case GDK_Down:
1545         if (y >= (child_count - 1) / priv->num_columns) {
1546             return TRUE;
1547         }
1548         t = child_count % priv->num_columns;
1549         if (t == 0) {
1550             t = priv->num_columns;
1551         }
1552         if (y == (child_count - 1) / priv->num_columns - 1 && x >= t) {
1553             x = t - 1;
1554         }
1555         y++;
1556         addition = priv->num_columns;
1557         max_add = child_rows - y;
1558         break;
1559
1560     case GDK_KP_Left:
1561     case GDK_Left:
1562         if (x <= 0) {
1563             return TRUE;
1564         }
1565         addition = -1;
1566         max_add = x;
1567         x--;
1568         break;
1569
1570     case GDK_KP_Right:
1571     case GDK_Right:
1572         if (x >= priv->num_columns - 1) {
1573             return TRUE;
1574         }
1575         if (y == 0 && x >= child_count - 1) {
1576             return TRUE;
1577         }
1578         x++;
1579         addition = 1;
1580         max_add = priv->num_columns - x;
1581         if (y * priv->num_columns + x == child_count) {
1582             y--;
1583         }
1584         break;
1585     case GDK_KP_Enter:
1586     case GDK_Return:
1587         hildon_grid_activate_child(grid,
1588                                    HILDON_GRID_ITEM
1589                                    (GTK_CONTAINER(grid)->focus_child));
1590         return TRUE;
1591         break;
1592     default:
1593         return GTK_WIDGET_CLASS (parent_class)->key_press_event (widget, event);
1594         break;
1595     }
1596
1597     focus_index = y * priv->num_columns + x;
1598     new_focus = get_child_by_index(priv, focus_index);
1599
1600     while (new_focus != NULL &&
1601            focus_index < child_count && !GTK_WIDGET_SENSITIVE(new_focus)) {
1602         max_add--;
1603
1604         if (max_add == 0) {
1605             return TRUE;
1606         }
1607         focus_index += addition;
1608         new_focus = get_child_by_index(priv, focus_index);
1609     }
1610
1611     if (new_focus != NULL) {
1612         set_focus(grid, new_focus, TRUE);
1613     }
1614     return TRUE;
1615 }
1616
1617
1618 /*
1619  * hildon_grid_button_pressed:
1620  * @widget: Widget where signal is coming from
1621  * @event:  #EventButton
1622  * @data:   #HildonGrid
1623  *
1624  * Handle mouse button press.
1625  *
1626  * Return value: Signal handled
1627  */
1628 static gboolean
1629 hildon_grid_button_pressed(GtkWidget * widget,
1630                            GdkEventButton * event)
1631 {
1632     HildonGrid *grid;
1633     HildonGridPrivate *priv;
1634     GtkWidget *child;
1635     int child_no;
1636
1637     grid = HILDON_GRID(widget);
1638     priv = HILDON_GRID_GET_PRIVATE(grid);
1639
1640 /* Watch out for double/triple click press events */
1641
1642     if (event->type == GDK_2BUTTON_PRESS ||
1643         event->type == GDK_3BUTTON_PRESS) {
1644         priv->last_button_event = event->type;
1645         return FALSE;
1646     }
1647
1648     priv->last_button_event = event->type;
1649
1650     if (event->type != GDK_BUTTON_PRESS)
1651         return FALSE;
1652
1653
1654     child_no = get_child_index_by_coord(priv, event->x, event->y);
1655
1656     if (child_no == -1 || child_no >= g_list_length(priv->children))
1657         return FALSE;
1658
1659     child = get_child_by_index(priv, child_no);
1660     if (!GTK_WIDGET_IS_SENSITIVE(child))
1661         return FALSE;
1662
1663     priv->click_x = event->x;
1664     priv->click_y = event->y;
1665
1666     return FALSE;
1667 }
1668
1669 /*
1670  * hildon_grid_button_released:
1671  * @widget: Widget the signal is coming from
1672  * @event:  #EventButton
1673  * @data:   #HildonGrid
1674  *
1675  * Handle mouse button release.
1676  *
1677  * Return value: Signal handled
1678  */
1679 static gboolean
1680 hildon_grid_button_released(GtkWidget * widget,
1681                             GdkEventButton * event)
1682 {
1683     HildonGrid *grid;
1684     HildonGridPrivate *priv;
1685     GtkWidget *child;
1686     int child_no;
1687     gboolean already_selected;
1688
1689     grid = HILDON_GRID(widget);
1690     priv = HILDON_GRID_GET_PRIVATE(grid);
1691
1692     /* In case of double/triple click, silently ignore the release event */
1693
1694     if (priv->last_button_event == GDK_2BUTTON_PRESS ||
1695         priv->last_button_event == GDK_3BUTTON_PRESS) {
1696         priv->last_button_event = event->type;
1697         return FALSE;
1698     }
1699
1700     child_no = get_child_index_by_coord(priv, event->x, event->y);
1701
1702     if (child_no == -1 || child_no >= g_list_length(priv->children)) {
1703         return FALSE;
1704     }
1705     child = get_child_by_index(priv, child_no);
1706     if (!GTK_WIDGET_IS_SENSITIVE(child)) {
1707         return FALSE;
1708     }
1709     if (abs(priv->click_x - event->x) >= DRAG_SENSITIVITY
1710         && abs(priv->click_y - event->y) >= DRAG_SENSITIVITY) {
1711         return FALSE;
1712     }
1713
1714     /* Check if this element was already selected */
1715     already_selected = (priv->focus_index == child_no);
1716
1717     set_focus(grid, child, TRUE);
1718     priv->last_button_event = event->type;
1719
1720     /* If this is not the first click in this element, activate it */
1721     if (already_selected)
1722       hildon_grid_activate_child(grid, HILDON_GRID_ITEM(child));
1723
1724     return FALSE;
1725 }
1726
1727 /*
1728  * hildon_grid_scrollbar_moved:
1729  * @widget: Widget which sent the signal
1730  * @data:   #HildonGrid
1731  *
1732  * Update HildonGrid contents when scrollbar is moved.
1733  *
1734  * Return value: Signal handeld
1735  */
1736 static gboolean
1737 hildon_grid_scrollbar_moved(GtkWidget * widget, gpointer data)
1738 {
1739     HildonGrid *grid;
1740     HildonGridPrivate *priv;
1741     gboolean updated = FALSE;
1742
1743     grid = HILDON_GRID(data);
1744     priv = HILDON_GRID_GET_PRIVATE(grid);
1745     updated = update_contents(grid);
1746
1747     /* 
1748      * If grid changes focus while dragging scrollbar and pointer leaves
1749      * scrollbar, focus is moved to prev_focus... This prevents that.
1750      */
1751     gtk_window_set_prev_focus_widget(GTK_WINDOW
1752                                      (gtk_widget_get_toplevel(widget)),
1753                                      GTK_CONTAINER(grid)->focus_child);
1754
1755     if (updated)
1756         /* Don't just queue it, let's do it now! */
1757         hildon_grid_size_allocate(GTK_WIDGET(grid),
1758                                   &GTK_WIDGET(grid)->allocation);
1759
1760     return TRUE;
1761 }
1762
1763
1764 /*
1765  * update_contents:
1766  * @grid:   #HildonGrid
1767  *
1768  * Update the view if scrollbar has moved so that first visible row
1769  * should've changed. Returns true if location actually changed.
1770  *
1771  * Return value: Content changed
1772  */
1773 static gboolean update_contents(HildonGrid * grid)
1774 {
1775     HildonGridPrivate *priv;
1776     gint new_row;
1777
1778     priv = HILDON_GRID_GET_PRIVATE(grid);
1779     new_row = (int) gtk_range_get_value(GTK_RANGE(priv->scrollbar))
1780         / priv->item_height;
1781
1782     if (new_row != priv->old_sb_pos) {
1783         priv->old_sb_pos = new_row;
1784         priv->first_index = new_row * priv->num_columns;
1785
1786         return TRUE;
1787     }
1788     return FALSE;
1789 }
1790
1791 /*
1792  * jump_scrollbar_to_focused:
1793  * @grid:   #HildonGrid
1794  *
1795  * Moves scrollbar position so that focused item will be shown 
1796  * in visible area.
1797  * Returns TRUE if visible position of widgets have changed.
1798  *
1799  * Return value: Content changed
1800  */
1801 static gboolean jump_scrollbar_to_focused(HildonGrid * grid)
1802 {
1803     HildonGridPrivate *priv;
1804     GtkAdjustment *adjustment;
1805     gint child_count;
1806     gint empty_grids;
1807     gint new_row;
1808
1809     priv = HILDON_GRID_GET_PRIVATE(grid);
1810     /* If we don't have scrollbar, let the focus be broken, too. */
1811     g_return_val_if_fail(priv->scrollbar != NULL, FALSE);
1812
1813     /* Make sure "first widget" is something sensible. */
1814     priv->first_index = priv->first_index -
1815         priv->first_index % priv->num_columns;
1816
1817     child_count = g_list_length(priv->children);
1818     empty_grids = priv->num_columns * priv->area_rows - child_count +
1819         priv->first_index;
1820
1821     /* Determine the position of the new row */ 
1822     if (priv->focus_index < priv->first_index) {
1823         new_row = priv->focus_index / priv->num_columns;
1824     } else if (priv->focus_index >= priv->first_index +
1825                priv->area_rows * priv->num_columns) {
1826         gint last_top_row;
1827         new_row = priv->focus_index / priv->num_columns -
1828             priv->area_rows + 1;
1829         last_top_row = child_count / priv->num_columns - priv->area_rows + 1;
1830         if (child_count % priv->num_columns != 0) {
1831             last_top_row++;
1832         }
1833         if (new_row > last_top_row) {
1834             new_row = last_top_row;
1835         }
1836     } else if (empty_grids >= priv->num_columns) {
1837         new_row = ((child_count - 1) / priv->num_columns + 1)
1838             - priv->area_rows;
1839         if (new_row < 0) {
1840             new_row = 0;
1841         }
1842     } else {
1843         return FALSE;
1844     }
1845
1846     /* Move scrollbar accordingly. */
1847     adjustment = gtk_range_get_adjustment(GTK_RANGE(priv->scrollbar));
1848     adjustment->value = (gdouble) (new_row * priv->item_height);
1849     gtk_range_set_adjustment(GTK_RANGE(priv->scrollbar), adjustment);
1850     priv->first_index = new_row * priv->num_columns;
1851     priv->old_sb_pos = new_row;
1852
1853     gtk_widget_queue_draw(priv->scrollbar);
1854
1855     return TRUE;
1856 }
1857
1858
1859 /*
1860  * adjust_scrollbar_height:
1861  * @grid:   HildonGridPrivate
1862  *
1863  * Return value: View should change
1864  *
1865  * Adjust scrollbar according the #HildonGrid contents. 
1866  * Show/hide scrollbar if
1867  * appropriate. Also sets priv->first_index.
1868  */
1869 static gboolean adjust_scrollbar_height(HildonGrid * grid)
1870 {
1871     HildonGridPrivate *priv;
1872     GtkRequisition req;
1873     GtkAdjustment *adj;
1874     GtkAllocation alloc;
1875     GtkAllocation *gridalloc;
1876     gint old_upper;
1877     gint need_rows;
1878     gint need_pixels;
1879     gboolean updated;
1880
1881     priv = HILDON_GRID_GET_PRIVATE(grid);
1882     g_return_val_if_fail(priv->scrollbar != NULL, FALSE);
1883
1884     updated = FALSE;
1885     gridalloc = &GTK_WIDGET(grid)->allocation;
1886
1887     /* See if we need scrollbar at all. */
1888     if (priv->num_columns == 0) {
1889         priv->num_columns = DEFAULT_N_COLUMNS;
1890     } else {
1891         priv->num_columns = MAX(1, priv->num_columns);
1892     }
1893
1894     if (g_list_length(priv->children) != 0) {
1895         need_rows = (g_list_length(priv->children) - 1) /
1896             priv->num_columns + 1;
1897     } else {
1898         need_rows = 0;
1899     }
1900
1901     if (need_rows <= priv->area_rows) {
1902         updated = priv->first_index != 0;
1903         priv->scrollbar_width = 0;
1904
1905         priv->first_index = 0;
1906         if (GTK_WIDGET_VISIBLE(priv->scrollbar)) {
1907             GtkWidget *parent = gtk_widget_get_toplevel (GTK_WIDGET (grid));
1908             if (HILDON_IS_APP (parent))
1909                 g_object_set (parent, "scroll-control", FALSE, NULL);
1910             gtk_widget_hide(priv->scrollbar);
1911             updated = TRUE;
1912         }
1913
1914         return updated;
1915     }
1916
1917     /* All right then, we need scrollbar. Place scrollbar on the screen. */
1918     gtk_widget_get_child_requisition(priv->scrollbar, &req);
1919     priv->scrollbar_width = req.width;
1920
1921     alloc.width = req.width;
1922     alloc.height = gridalloc->height;
1923     alloc.x = gridalloc->width - req.width + gridalloc->x;
1924     alloc.y = gridalloc->y;
1925     gtk_widget_size_allocate(priv->scrollbar, &alloc);
1926
1927     if (!GTK_WIDGET_VISIBLE(priv->scrollbar)) {
1928         GtkWidget *parent = gtk_widget_get_toplevel (GTK_WIDGET (grid));
1929         if (HILDON_IS_APP (parent))
1930             g_object_set (parent, "scroll-control", TRUE, NULL);
1931         gtk_widget_show(priv->scrollbar);
1932         updated = TRUE;
1933     }
1934
1935
1936     need_pixels = need_rows * priv->item_height;
1937
1938     /* Once we know how much space we need, update the scrollbar. */
1939     adj = gtk_range_get_adjustment(GTK_RANGE(priv->scrollbar));
1940     old_upper = (int) adj->upper;
1941     adj->lower = 0.0;
1942     adj->upper = (gdouble) need_pixels;
1943     adj->step_increment = (gdouble) priv->item_height;
1944     adj->page_increment = (gdouble) (priv->area_rows * priv->item_height);
1945     adj->page_size =
1946         (gdouble) (priv->area_height - priv->area_height % priv->item_height);
1947
1948     /* Also update position if needed to show focused item. */
1949
1950     gtk_range_set_adjustment(GTK_RANGE(priv->scrollbar), adj);
1951
1952     /* Then set first_index. */
1953     priv->first_index = (int) adj->value / priv->item_height *
1954                               priv->num_columns;
1955
1956     /* Finally, ask Gtk to redraw the scrollbar. */
1957     if (old_upper != (int) adj->upper) {
1958         gtk_widget_queue_draw(priv->scrollbar);
1959     }
1960     return updated;
1961 }
1962
1963 /*
1964  * get_child_index_by_coord:
1965  * @priv:   HildonGridPrivate
1966  * @x:      X-coordinate
1967  * @y:      Y-coordinate
1968  *
1969  * Returns index of child at given coordinates, -1 if no child.
1970  *
1971  * Return value: Index
1972  */
1973 static gint
1974 get_child_index_by_coord(HildonGridPrivate * priv, gint x, gint y)
1975 {
1976     int xgap, ygap;
1977     int t;
1978
1979     if (priv->item_width==0 || priv->item_height==0) {
1980       return -1;
1981     }
1982
1983     xgap = x % priv->item_width;
1984     ygap = y % priv->item_height;
1985
1986     if (xgap > priv->item_width - priv->h_margin) { /*FIXME*/
1987         return -1;
1988     }
1989     
1990     /* Event may come from outside of the grid. Skipping those events */
1991     if (x >= priv->item_width * priv->num_columns)
1992         return -1;
1993
1994     t = y / priv->item_height * priv->num_columns +
1995         x / priv->item_width + priv->first_index;
1996
1997     if (t >= priv->first_index + priv->area_rows * priv->num_columns ||
1998         t >= g_list_length(priv->children) || t < 0) {
1999         return -1;
2000     }
2001     return t;
2002 }
2003
2004 /*
2005  * get_child_by_index:
2006  * @priv:   HildonGridPrivate
2007  * @index:  Index of child
2008  *
2009  * Returns child that is #th in HildonGrid or NULL if child was not found
2010  * among the children.
2011  *
2012  * Return value: GtkWidget
2013  */
2014 static GtkWidget *get_child_by_index(HildonGridPrivate * priv, gint index)
2015 {
2016     GList *list;
2017     int i = 0;
2018
2019     if (index >= g_list_length(priv->children) || index < 0) {
2020         return NULL;
2021     }
2022     for (list = priv->children, i = 0; list != NULL;
2023          list = list->next, i++) {
2024         if (index == i) {
2025             return ((HildonGridChild *) list->data)->widget;
2026         }
2027     }
2028
2029     g_warning("no such child");
2030     return NULL;
2031 }
2032
2033 /*
2034  * get_child_index:
2035  * @priv:   HildonGridPrivate
2036  * @child:  #GtkWidget to look for
2037  *
2038  * Returns index of a child or -1 if child was not found among the
2039  * children.
2040  *
2041  * Return value: Index
2042  */
2043 static gint get_child_index(HildonGridPrivate * priv, GtkWidget * child)
2044 {
2045     GList *list;
2046     gint index;
2047
2048     if (child == NULL)
2049         return -1;
2050
2051     for (list = priv->children, index = 0;
2052          list != NULL; list = list->next, index++) {
2053         if (((HildonGridChild *) list->data)->widget == child) {
2054             return index;
2055         }
2056     }
2057
2058     g_warning("no such child");
2059     return -1;
2060 }
2061
2062
2063 /**
2064  * hildon_grid_activate_child:
2065  * @grid:   #HildonGrid
2066  * @item:   #HildonGridItem
2067  *
2068  * Sends a signal to indicate that this HildonGridItem is activated.
2069  */
2070 void hildon_grid_activate_child(HildonGrid * grid, HildonGridItem * item)
2071 {
2072     g_return_if_fail(HILDON_IS_GRID(grid));
2073
2074     g_signal_emit(grid, grid_signals[ACTIVATE_CHILD], 0, item);
2075 }
2076
2077
2078
2079 /**
2080  * hildon_grid_set_style:
2081  * @grid:       #HildonGrid
2082  * @style_name: style name
2083  *
2084  * Sets style. Setting style sets widget size, spacing, label position,
2085  * number of columns, and icon size.
2086  */
2087 void hildon_grid_set_style(HildonGrid * grid, const gchar * style_name)
2088 {
2089     HildonGridPrivate *priv;
2090
2091     g_return_if_fail(HILDON_IS_GRID(grid));
2092
2093
2094     priv = HILDON_GRID_GET_PRIVATE(grid);
2095     if (priv->style != NULL) {
2096         g_free((gpointer) priv->style);
2097     }
2098     if (style_name != NULL) {
2099         priv->style = g_strdup(style_name);
2100     } else {
2101         priv->style = NULL;
2102     }
2103
2104     gtk_widget_set_name(GTK_WIDGET(grid), style_name);
2105     get_style_properties(grid);
2106
2107     gtk_widget_queue_resize(GTK_WIDGET(grid));
2108 }
2109
2110 /**
2111  * hildon_grid_get_style:
2112  * @grid:   #HildonGrid
2113  *
2114  * Returns the name of style currently used in HildonGrid.
2115  *
2116  * Returns: style name
2117  */
2118 const gchar *hildon_grid_get_style(HildonGrid * grid)
2119 {
2120     g_return_val_if_fail(HILDON_IS_GRID(grid), NULL);
2121
2122     return gtk_widget_get_name(GTK_WIDGET(grid));
2123 }
2124
2125 /*
2126  * get_style_properties:
2127  * @grid:   #HildonGrid
2128  *
2129  * Gets widget size and other properties from gtkrc. If some properties
2130  * have changed, notify children of this, too.
2131  */
2132 static void get_style_properties(HildonGrid * grid)
2133 {
2134     GList *iter;
2135     gint num_columns;
2136     HildonGridPositionType label_pos;
2137     gint emblem_size;
2138
2139     gint h_margin, v_margin;
2140     gint item_height;
2141     gint icon_width;
2142     gint focus_margin, icon_label_margin;
2143     gint label_height;
2144
2145     HildonGridPrivate *priv;
2146     g_return_if_fail(HILDON_IS_GRID(grid));
2147     priv = HILDON_GRID_GET_PRIVATE(grid);
2148
2149     gtk_widget_style_get(GTK_WIDGET(grid),
2150                          "item_hspacing", &h_margin,
2151                          "item_vspacing", &v_margin,
2152                          "item_height", &item_height,
2153                          "icon_size", &icon_width,
2154                          "n_columns", &num_columns,
2155                          "label_pos", &label_pos,
2156                          "label_hspacing", &focus_margin,
2157                          "label_vspacing", &icon_label_margin,
2158                          "emblem_size", &emblem_size,
2159                          "label_height", &label_height,
2160                          NULL);
2161
2162     hildon_grid_set_icon_width(grid, icon_width);
2163     hildon_grid_set_num_columns(grid, num_columns);
2164     hildon_grid_set_label_pos(grid, label_pos);
2165     hildon_grid_set_focus_margin(grid, focus_margin);
2166     hildon_grid_set_icon_label_margin(grid, icon_label_margin);
2167     hildon_grid_set_emblem_size(grid, emblem_size);
2168     hildon_grid_set_label_height(grid, label_height);
2169
2170     priv->h_margin = h_margin;
2171     priv->v_margin = v_margin;
2172     priv->item_height = item_height;
2173
2174     iter = NULL;
2175     /*
2176     for (iter = priv->children; iter != NULL; iter = iter->next) {
2177         HildonGridItem *child;
2178         child = HILDON_GRID_ITEM(((HildonGridChild *) iter->data)->widget);
2179         _hildon_grid_item_done_updating_settings(child);
2180     }
2181     */
2182 }
2183
2184
2185
2186 /**
2187  * hildon_grid_set_scrollbar_pos:
2188  * @grid:           #HildonGrid
2189  * @scrollbar_pos:  new position (in pixels)
2190  *
2191  * Sets view (scrollbar) to specified position.
2192  */
2193 void hildon_grid_set_scrollbar_pos(HildonGrid * grid, gint scrollbar_pos)
2194 {
2195     HildonGridPrivate *priv;
2196     GtkAdjustment *adjustment;
2197
2198     g_return_if_fail(HILDON_IS_GRID(grid));
2199
2200     priv = HILDON_GRID_GET_PRIVATE(grid);
2201     adjustment = gtk_range_get_adjustment(GTK_RANGE(priv->scrollbar));
2202     adjustment->value = (gdouble) scrollbar_pos;
2203
2204     gtk_range_set_adjustment(GTK_RANGE(priv->scrollbar), adjustment);
2205
2206     g_object_notify (G_OBJECT (grid), "scrollbar-position");
2207
2208     /* If grid isn't drawable, updating anything could mess up focus. */
2209     if (!GTK_WIDGET_DRAWABLE(GTK_WIDGET(grid)))
2210         return;
2211
2212     update_contents(grid);
2213 }
2214
2215 /**
2216  * hildon_grid_get_scrollbar_pos:
2217  * @grid:   #HildonGrid
2218  *
2219  * Returns: position of scrollbar (in pixels).
2220  */
2221 gint hildon_grid_get_scrollbar_pos(HildonGrid * grid)
2222 {
2223     GtkAdjustment *adjustment;
2224
2225     g_return_val_if_fail(HILDON_IS_GRID(grid), -1);
2226
2227     adjustment = gtk_range_get_adjustment(GTK_RANGE
2228                                           (HILDON_GRID_GET_PRIVATE
2229                                            (grid)->scrollbar));
2230     return (int) adjustment->value;
2231 }
2232
2233 static void
2234 hildon_grid_set_property(GObject * object,
2235                          guint prop_id,
2236                          const GValue * value, GParamSpec * pspec)
2237 {
2238     HildonGrid *grid;
2239
2240     grid = HILDON_GRID(object);
2241
2242     switch (prop_id) {
2243     case PROP_EMPTY_LABEL:
2244         hildon_grid_set_empty_label(grid, g_value_get_string(value));
2245         break;
2246
2247     case PROP_STYLE:
2248         hildon_grid_set_style(grid, g_value_get_string(value));
2249         break;
2250
2251     case PROP_SCROLLBAR_POS:
2252         hildon_grid_set_scrollbar_pos(grid, g_value_get_int(value));
2253         break;
2254
2255     default:
2256         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
2257         break;
2258     }
2259 }
2260
2261 static void
2262 hildon_grid_get_property(GObject * object,
2263                          guint prop_id, GValue * value, GParamSpec * pspec)
2264 {
2265     HildonGrid *grid;
2266
2267     grid = HILDON_GRID(object);
2268
2269     switch (prop_id) {
2270     case PROP_EMPTY_LABEL:
2271         g_value_set_string(value, hildon_grid_get_empty_label(grid));
2272         break;
2273
2274     case PROP_STYLE:
2275         g_value_set_string(value, hildon_grid_get_style(grid));
2276         break;
2277
2278     case PROP_SCROLLBAR_POS:
2279         g_value_set_int(value, hildon_grid_get_scrollbar_pos(grid));
2280         break;
2281
2282     default:
2283         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
2284         break;
2285     }
2286 }
2287
2288 static gboolean
2289 hildon_grid_state_changed(GtkWidget * widget,
2290                           GtkStateType state, gpointer data)
2291 {
2292     HildonGrid *grid;
2293     HildonGridPrivate *priv;
2294     GList *list;
2295     GtkWidget *current;
2296     GtkWidget *prev_focusable, *next_focusable;
2297     gboolean found_old;
2298
2299     g_return_val_if_fail(HILDON_IS_GRID(data), FALSE);
2300     g_return_val_if_fail(HILDON_IS_GRID_ITEM(widget), FALSE);
2301
2302     grid = HILDON_GRID(data);
2303     priv = HILDON_GRID_GET_PRIVATE(grid);
2304
2305
2306     if (GTK_WIDGET_IS_SENSITIVE(widget))
2307         return FALSE;
2308
2309     prev_focusable = next_focusable = NULL;
2310     found_old = FALSE;
2311
2312     for (list = priv->children; list != NULL; list = list->next) {
2313         current = ((HildonGridChild *) list->data)->widget;
2314
2315         if (GTK_WIDGET_IS_SENSITIVE(current)) {
2316             if (found_old) {
2317                 next_focusable = current;
2318                 break;
2319             } else {
2320                 prev_focusable = current;
2321             }
2322         } else if (current == widget) {
2323             found_old = TRUE;
2324         }
2325     }
2326
2327     if (next_focusable == NULL) {
2328         next_focusable = prev_focusable;
2329     }
2330
2331     gtk_container_set_focus_child(GTK_CONTAINER(grid), next_focusable);
2332
2333     return FALSE;
2334 }
2335
2336
2337
2338 static void
2339 hildon_grid_tap_and_hold_setup(GtkWidget * widget,
2340                                GtkWidget * menu,
2341                                GtkCallback func,
2342                                GtkWidgetTapAndHoldFlags flags)
2343 {
2344     g_return_if_fail(HILDON_IS_GRID(widget) && GTK_IS_MENU(menu));
2345
2346     parent_class->parent_class.tap_and_hold_setup
2347         (widget, menu, func, flags | GTK_TAP_AND_HOLD_NO_INTERNALS);
2348 }