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