2 * This file is part of hildon-libs
4 * Copyright (C) 2005 Nokia Corporation.
6 * Contact: Luc Pionchon <luc.pionchon@nokia.com>
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.
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.
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
28 * This file contains the implementation of HildonGrid. HildonGrid is used
29 * in views like Home and Control Panel which have single-tap activated
35 * - there must be a predefined place for the "no items" -label...
37 * - dimmed items & scrolling by scrollbar
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>
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>
64 #define _(String) dgettext(PACKAGE, String)
66 #define HILDON_GRID_GET_PRIVATE(obj) \
67 (G_TYPE_INSTANCE_GET_PRIVATE ((obj), HILDON_TYPE_GRID, \
71 #define DEFAULT_STYLE "largeicons-home"
73 #define DEFAULT_N_COLUMNS 3
74 #define GRID_LABEL_POS_PAD 16
76 #define DRAG_SENSITIVITY 6
93 typedef struct _HildonGridChild HildonGridChild;
94 typedef struct _HildonGridPrivate HildonGridPrivate;
97 struct _HildonGridChild {
102 struct _HildonGridPrivate {
104 GtkWidget *scrollbar;
106 GdkWindow *event_window;
110 GtkWidget *empty_label;
117 gint icon_label_margin;
120 HildonGridPositionType label_pos;
127 /* Handy variables outsize _allocate. */
130 gint scrollbar_width;
133 GdkEventType last_button_event;
134 gint old_item_height;
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,
155 static void hildon_grid_set_focus_child(GtkContainer * container,
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,
164 GtkWidgetTapAndHoldFlags flags);
166 static GType hildon_grid_child_type(GtkContainer * container);
169 static void hildon_grid_set_property(GObject * object,
171 const GValue * value,
173 static void hildon_grid_get_property(GObject * object,
175 GValue * value, GParamSpec * pspec);
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,
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,
191 static void hildon_grid_destroy(GtkObject * self);
192 static void hildon_grid_finalize(GObject * object);
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,
203 static gboolean hildon_grid_state_changed(GtkWidget * widget,
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,
212 static GtkWidget *get_child_by_index(HildonGridPrivate * priv, gint index);
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);
220 static GtkContainerClass *parent_class = NULL;
221 static guint grid_signals[LAST_SIGNAL] = { 0 };
224 GType hildon_grid_get_type(void)
226 static GType grid_type = 0;
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 */
238 (GInstanceInitFunc) hildon_grid_init,
240 grid_type = g_type_register_static(GTK_TYPE_CONTAINER,
241 "HildonGrid", &grid_info, 0);
249 static void hildon_grid_class_init(HildonGridClass * klass)
251 GObjectClass *gobject_class;
252 GtkWidgetClass *widget_class;
253 GtkContainerClass *container_class;
255 widget_class = GTK_WIDGET_CLASS(klass);
256 container_class = GTK_CONTAINER_CLASS(klass);
257 gobject_class = G_OBJECT_CLASS(klass);
259 parent_class = g_type_class_peek_parent(klass);
261 g_type_class_add_private(klass, sizeof(HildonGridPrivate));
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;
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;
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;
286 /* Install properties to the class */
287 g_object_class_install_property(gobject_class, PROP_EMPTY_LABEL,
288 g_param_spec_string("empty_label",
290 "Label to show when grid has no items",
291 _("Ckct_wi_grid_no_items"), G_PARAM_READWRITE));
293 g_object_class_install_property(gobject_class, PROP_STYLE,
294 g_param_spec_string("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));
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));
307 gtk_widget_class_install_style_property(widget_class,
308 g_param_spec_uint("item_width",
310 "Total width of an item (obsolete)",
311 1, G_MAXINT, 212, G_PARAM_READABLE));
313 gtk_widget_class_install_style_property(widget_class,
314 g_param_spec_uint("item_height",
316 "Total height of an item",
317 1, G_MAXINT, 96, G_PARAM_READABLE));
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));
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));
331 gtk_widget_class_install_style_property(widget_class,
332 g_param_spec_uint("label_hspacing",
334 "Margin between focus edge and item edge",
335 0, G_MAXINT, 6, G_PARAM_READABLE));
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));
343 gtk_widget_class_install_style_property(widget_class,
344 g_param_spec_uint("label_height",
346 "Height of icon label",
347 1, G_MAXINT, 30, G_PARAM_READABLE));
349 gtk_widget_class_install_style_property(widget_class,
350 g_param_spec_uint("n_columns",
353 0, G_MAXINT, DEFAULT_N_COLUMNS, G_PARAM_READABLE));
355 gtk_widget_class_install_style_property(widget_class,
356 g_param_spec_uint("label_pos",
358 "Position of label related to the icon",
359 1, 2, 1, G_PARAM_READABLE));
361 gtk_widget_class_install_style_property(widget_class,
362 g_param_spec_uint("icon_size",
364 "Size of the icon in pixels (width)",
365 1, G_MAXINT, 64, G_PARAM_READABLE));
367 gtk_widget_class_install_style_property(widget_class,
368 g_param_spec_uint("emblem_size",
370 "Size of the emblem in pixels",
371 1, G_MAXINT, 25, G_PARAM_READABLE));
374 * HildonGrid::activate-child:
376 * Emitted when a child (@HildonGridItem) is activated either by
377 * tapping on it or by pressing enter.
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),
385 g_cclosure_marshal_VOID__OBJECT,
386 G_TYPE_NONE, 1, HILDON_TYPE_GRID_ITEM);
389 * HildonGrid::popup-context-menu:
391 * Emitted when popup-menu is supposed to open. Used for tap-and-hold.
393 grid_signals[POPUP_CONTEXT] =
394 g_signal_new("popup-context-menu",
395 G_OBJECT_CLASS_TYPE(gobject_class),
397 G_STRUCT_OFFSET(HildonGridClass, popup_context_menu),
398 g_signal_accumulator_true_handled, NULL,
399 _hildon_marshal_BOOLEAN__INT_INT_INT,
401 G_TYPE_INT, G_TYPE_INT, G_TYPE_INT);
407 * hildon_grid_set_empty_label:
409 * @empty_label: New label
414 hildon_grid_set_empty_label(HildonGrid * grid, const gchar * empty_label)
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);
423 * _hildon_grid_get_empty_label:
426 * Returns empty label. Label must not be modified nor freed.
428 * Return value: Label
431 hildon_grid_get_empty_label(HildonGrid * grid)
433 return gtk_label_get_label(GTK_LABEL(HILDON_GRID_GET_PRIVATE
434 (grid)->empty_label));
438 * hildon_grid_set_num_columns:
440 * @columsn: Number of columns
442 * Sets number of columns.
445 hildon_grid_set_num_columns(HildonGrid * grid, gint columns)
447 HildonGridPrivate *priv;
449 g_return_if_fail(HILDON_IS_GRID(grid));
450 priv = HILDON_GRID_GET_PRIVATE(grid);
452 if (priv->num_columns == columns) {
457 priv->num_columns = columns;
459 priv->num_columns = DEFAULT_N_COLUMNS;
461 /* Update estimated row-count for jump_scrollbar... */
462 priv->area_rows = MAX(priv->area_height / priv->num_columns, 1);
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));
471 * hildon_grid_set_label_pos:
473 * @label_pos: Label position
475 * Sets icon label position.
478 hildon_grid_set_label_pos(HildonGrid * grid,
479 HildonGridPositionType label_pos)
481 HildonGridPrivate *priv;
485 priv = HILDON_GRID_GET_PRIVATE(grid);
487 if (label_pos == priv->label_pos)
490 /* gtknotebook doesn't check if we use valid values -- why should
493 priv->label_pos = label_pos;
495 /* Set label position to each HildonGridItem */
496 for (list = priv->children; list != NULL; list = list->next) {
497 child = ((HildonGridChild *) list->data)->widget;
499 _hildon_grid_item_set_label_pos(HILDON_GRID_ITEM(child),
505 * hildon_grid_set_focus_margin:
507 * @focus_margin: Focus margin
509 * Sets margin between icon edge and label edge
512 hildon_grid_set_focus_margin(HildonGrid *grid, gint focus_margin)
514 HildonGridPrivate *priv;
518 priv = HILDON_GRID_GET_PRIVATE(grid);
519 if (focus_margin == priv->focus_margin)
522 priv->focus_margin = focus_margin;
524 /* Update children. */
525 for (list = priv->children; list != NULL; list = list->next) {
526 child = ((HildonGridChild *) list->data)->widget;
528 _hildon_grid_item_set_focus_margin(HILDON_GRID_ITEM(child),
535 * hildon_grid_set_icon_label_margin:
537 * @hspacing: Vertical spacing
539 * Sets vertical spacing for label.
543 hildon_grid_set_icon_label_margin(HildonGrid *grid, gint icon_label_margin)
545 HildonGridPrivate *priv;
547 priv = HILDON_GRID_GET_PRIVATE(grid);
548 if (icon_label_margin == priv->icon_label_margin)
551 priv->icon_label_margin = icon_label_margin;
556 * hildon_grid_set_icon_width:
558 * @icon_size: Icon size (width)
560 * Sets icon size (in pixels).
563 hildon_grid_set_icon_width(HildonGrid * grid, gint icon_width)
565 HildonGridPrivate *priv;
569 priv = HILDON_GRID_GET_PRIVATE(grid);
571 if (icon_width == priv->icon_width)
574 priv->icon_width = icon_width;
576 for (list = priv->children; list != NULL; list = list->next) {
577 child = ((HildonGridChild *) list->data)->widget;
579 _hildon_grid_item_set_icon_width(HILDON_GRID_ITEM(child),
586 * hildon_grid_set_emblem_size:
588 * @emblem_size: Emblem size
590 * Sets emblem size (in pixels).
593 hildon_grid_set_emblem_size(HildonGrid *grid, gint emblem_size)
595 HildonGridPrivate *priv;
599 priv = HILDON_GRID_GET_PRIVATE(grid);
601 if (emblem_size == priv->emblem_size)
604 priv->emblem_size = emblem_size;
606 for (list = priv->children; list != NULL; list = list->next) {
607 child = ((HildonGridChild *) list->data)->widget;
609 _hildon_grid_item_set_emblem_size(HILDON_GRID_ITEM(child),
616 hildon_grid_set_label_height(HildonGrid *grid,
619 HildonGridPrivate *priv;
623 priv = HILDON_GRID_GET_PRIVATE(grid);
625 if (label_height == priv->label_height)
628 priv->label_height = label_height;
630 for (list = priv->children; list != NULL; list = list->next) {
631 child = ((HildonGridChild *) list->data)->widget;
633 _hildon_grid_item_set_label_height(HILDON_GRID_ITEM(child),
639 static GType hildon_grid_child_type(GtkContainer * container)
641 return GTK_TYPE_WIDGET;
644 static void hildon_grid_init(HildonGrid * grid)
646 HildonGridPrivate *priv;
648 priv = HILDON_GRID_GET_PRIVATE(grid);
650 GTK_CONTAINER(grid)->focus_child = NULL;
651 priv->focus_index = -1;
653 priv->scrollbar = gtk_vscrollbar_new(NULL);
654 priv->empty_label = gtk_label_new(_("Ckct_wi_grid_no_items"));
657 priv->area_height = 1;
659 priv->children = NULL;
661 priv->first_index = 0;
665 priv->item_height = 96;
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;
673 priv->old_sb_pos = -1;
674 priv->old_item_height = -1;
676 gtk_widget_set_parent(priv->scrollbar, GTK_WIDGET(grid));
677 gtk_widget_set_parent(priv->empty_label, GTK_WIDGET(grid));
679 priv->last_button_event = GDK_NOTHING;
681 GTK_WIDGET_SET_FLAGS(grid, GTK_NO_WINDOW);
683 /* Signal for scrollbar. */
684 g_signal_connect(G_OBJECT(priv->scrollbar), "value-changed",
685 G_CALLBACK(hildon_grid_scrollbar_moved), grid);
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);
691 GTK_WIDGET_UNSET_FLAGS(priv->scrollbar, GTK_CAN_FOCUS);
692 hildon_grid_set_style(grid, DEFAULT_STYLE);
698 * Creates a new #HildonGrid.
700 * Return value: A new #HildonGrid
702 GtkWidget *hildon_grid_new(void)
707 grid = g_object_new(HILDON_TYPE_GRID, NULL);
709 return GTK_WIDGET(grid);
713 static void hildon_grid_realize(GtkWidget * widget)
716 HildonGridPrivate *priv;
721 GTK_WIDGET_SET_FLAGS(widget, GTK_REALIZED);
723 grid = HILDON_GRID(widget);
724 priv = HILDON_GRID_GET_PRIVATE(grid);
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;
735 widget->window = gtk_widget_get_parent_window(widget);
736 g_object_ref(widget->window);
738 attr.wclass = GDK_INPUT_ONLY;
739 attr_mask = GDK_WA_X | GDK_WA_Y;
741 priv->event_window = gdk_window_new(widget->window, &attr, attr_mask);
742 gdk_window_set_user_data(priv->event_window, widget);
744 widget->style = gtk_style_attach(widget->style, widget->window);
746 gtk_style_set_background(widget->style,
747 widget->window, GTK_STATE_NORMAL);
751 static void hildon_grid_unrealize(GtkWidget * widget)
753 HildonGridPrivate *priv;
755 priv = HILDON_GRID_GET_PRIVATE(HILDON_GRID(widget));
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;
763 if (GTK_WIDGET_CLASS(parent_class)->unrealize) {
764 (*GTK_WIDGET_CLASS(parent_class)->unrealize) (widget);
770 static void hildon_grid_map(GtkWidget * widget)
773 HildonGridPrivate *priv;
777 g_return_if_fail(HILDON_IS_GRID(widget));
779 if (!GTK_WIDGET_VISIBLE(widget))
782 grid = HILDON_GRID(widget);
783 priv = HILDON_GRID_GET_PRIVATE(grid);
785 (*GTK_WIDGET_CLASS(parent_class)->map) (widget);
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);
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);
801 for (list = priv->children; list != NULL; list = list->next) {
802 child = ((HildonGridChild *) list->data)->widget;
804 if (GTK_WIDGET_VISIBLE(child)) {
805 if (!GTK_WIDGET_MAPPED(child)) {
806 gtk_widget_map(child);
810 /* END OF don't really need */
812 /* Also make event window visible. */
813 gdk_window_show(priv->event_window);
818 static void hildon_grid_unmap(GtkWidget * widget)
820 HildonGridPrivate *priv;
822 priv = HILDON_GRID_GET_PRIVATE(HILDON_GRID(widget));
824 if (priv->event_window != NULL) {
825 gdk_window_hide(priv->event_window);
828 (*GTK_WIDGET_CLASS(parent_class)->unmap) (widget);
834 hildon_grid_expose(GtkWidget * widget, GdkEventExpose * event)
837 HildonGridPrivate *priv;
838 GtkContainer *container;
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);
846 grid = HILDON_GRID(widget);
847 priv = HILDON_GRID_GET_PRIVATE(grid);
848 container = GTK_CONTAINER(grid);
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);
860 /* Only expose visible children. */
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. */
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)
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);
881 if (priv->scrollbar_width > 0 && priv->scrollbar != NULL) {
882 gtk_container_propagate_expose(container, priv->scrollbar, event);
890 hildon_grid_size_request(GtkWidget * widget, GtkRequisition * requisition)
893 HildonGridPrivate *priv;
898 g_return_if_fail(widget);
899 g_return_if_fail(requisition);
901 grid = HILDON_GRID(widget);
902 priv = HILDON_GRID_GET_PRIVATE(grid);
904 /* Want as big as possible. */
905 requisition->width = 0x7fff; /* Largest possible gint16 */
906 requisition->height = 0x7fff; /* Largest possible gint16 */
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);
915 if (priv->scrollbar != NULL && GTK_WIDGET_VISIBLE(priv->scrollbar)) {
916 gtk_widget_size_request(priv->scrollbar, &req);
919 for (list = priv->children; list != NULL; list = list->next) {
920 child = ((HildonGridChild *) list->data)->widget;
922 gtk_widget_size_request(child, &req);
927 * hildon_grid_size_allocate:
929 * Supposingly called when size of grid changes and after view have moved so
930 * that items need to be relocated.
933 hildon_grid_size_allocate(GtkWidget * widget, GtkAllocation * allocation)
936 HildonGridPrivate *priv;
946 g_return_if_fail(widget);
947 g_return_if_fail(allocation);
949 grid = HILDON_GRID(widget);
950 priv = HILDON_GRID_GET_PRIVATE(grid);
951 widget->allocation = *allocation;
953 get_style_properties(grid);
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);
964 /* Show the label if there are no items. */
965 if (priv->children == NULL) {
967 * We probably don't need this as scrollbar should be hidden when
968 * removing items, but one can never be too sure...
970 if (priv->scrollbar != NULL &&
971 GTK_WIDGET_VISIBLE(priv->scrollbar)) {
972 priv->scrollbar_width = 0;
973 gtk_widget_hide(priv->scrollbar);
976 /* Show label if creating one actually worked. */
977 if (priv->empty_label != NULL) {
978 gtk_widget_get_child_requisition(priv->empty_label, &req);
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 -
985 alloc.height = MIN(req.height, allocation->height -
988 /* Make sure we don't use negative values. */
989 if (alloc.width < 0) {
992 if (alloc.height < 0) {
996 gtk_widget_size_allocate(priv->empty_label, &alloc);
998 if (!GTK_WIDGET_VISIBLE(priv->empty_label)) {
999 gtk_widget_show(priv->empty_label);
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);
1012 priv->area_height = allocation->height;
1013 priv->area_rows = allocation->height / priv->item_height;
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);
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;
1027 priv->item_width = (allocation->width - priv->scrollbar_width) /
1032 (int) gtk_range_get_value(GTK_RANGE(priv->scrollbar)) /
1033 priv->item_height * priv->num_columns;
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;
1041 if (GTK_WIDGET_VISIBLE(child)) {
1042 gtk_widget_hide(child);
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;
1052 case HILDON_GRID_ITEM_LABEL_POS_RIGHT:
1053 row_margin = priv->v_margin;
1059 alloc.height = priv->item_height - row_margin;
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;
1067 if (!GTK_WIDGET_VISIBLE(child)) {
1068 gtk_widget_show(child);
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 +
1077 _hildon_grid_item_done_updating_settings(HILDON_GRID_ITEM(child));
1078 gtk_widget_size_allocate(child, &alloc);
1081 /* Hide items after visible items. */
1082 for (; list != NULL; list = list->next) {
1083 child = ((HildonGridChild *) list->data)->widget;
1085 if (GTK_WIDGET_VISIBLE(child)) {
1086 gtk_widget_hide(child);
1095 * @container: Container (#HildonGrid) to add HildonGridItem into.
1096 * @widget: #GtkWidget (#HildonGridItem) to add.
1098 * Adds a new HildonGridItem into HildonGrid.
1100 static void hildon_grid_add(GtkContainer * container, GtkWidget * widget)
1103 HildonGridPrivate *priv;
1104 HildonGridChild *child;
1107 g_return_if_fail(HILDON_IS_GRID(container));
1108 g_return_if_fail(HILDON_IS_GRID_ITEM(widget));
1110 grid = HILDON_GRID(container);
1111 priv = HILDON_GRID_GET_PRIVATE(HILDON_GRID(grid));
1112 GTK_WIDGET_SET_FLAGS(widget, GTK_NO_WINDOW);
1114 child = g_new(HildonGridChild, 1);
1115 if (child == NULL) {
1116 g_critical("no memory for child - not adding");
1119 child->widget = widget;
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);
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));
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);
1134 /* Matches both empty grid and all-dimmed grid. */
1135 if (GTK_CONTAINER(grid)->focus_child == NULL)
1136 set_focus(grid, widget, TRUE);
1139 * If item was added in visible area, relocate items. Otherwise update
1140 * scrollbar and see if items need relocating.
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));
1148 updated = adjust_scrollbar_height(grid);
1149 /* Basically this other test is useless -- shouldn't need to jump.
1151 updated |= jump_scrollbar_to_focused(grid);
1154 gtk_widget_queue_resize(GTK_WIDGET(grid));
1160 * hildon_grid_remove:
1161 * @container: Container (#HildonGrid) to remove #HildonGridItem from.
1162 * @widget: Widget (#HildonGridItem) to be removed.
1164 * Removes HildonGridItem from HildonGrid.
1167 hildon_grid_remove(GtkContainer * container, GtkWidget * widget)
1170 HildonGridPrivate *priv;
1171 HildonGridChild *child;
1172 GtkWidget *child_widget;
1174 gint index, old_index;
1178 g_return_if_fail(HILDON_IS_GRID(container));
1179 g_return_if_fail(HILDON_IS_GRID_ITEM(widget));
1181 grid = HILDON_GRID(container);
1182 priv = HILDON_GRID_GET_PRIVATE(container);
1184 old_index = priv->focus_index;
1185 updated = GTK_WIDGET_VISIBLE(widget);
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;
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);
1205 /* Emit warning if the item is not found */
1207 g_warning("tried to remove unexisting item");
1211 /* Move focus somewhere. */
1212 if (old_index == index) {
1213 if (old_index == g_list_length(priv->children)) {
1215 set_focus(grid, NULL, TRUE);
1218 get_child_by_index(priv, old_index - 1), TRUE);
1221 set_focus(grid, get_child_by_index(priv, old_index), TRUE);
1224 set_focus(grid, GTK_CONTAINER(grid)->focus_child, TRUE);
1227 updated |= adjust_scrollbar_height(grid);
1228 updated |= jump_scrollbar_to_focused(grid);
1231 gtk_widget_queue_resize(GTK_WIDGET(grid));
1236 * hildon_grid_set_focus_child:
1237 * @container: HildonGrid
1238 * @widget: HildonGridItem
1243 hildon_grid_set_focus_child(GtkContainer * container, GtkWidget * widget)
1246 HildonGridPrivate *priv;
1248 g_return_if_fail(HILDON_IS_GRID(container));
1249 g_return_if_fail(HILDON_IS_GRID_ITEM(widget) || widget == NULL);
1251 grid = HILDON_GRID(container);
1252 priv = HILDON_GRID_GET_PRIVATE(grid);
1254 if (GTK_CONTAINER(grid)->focus_child == widget || widget == NULL)
1257 set_focus(grid, widget, TRUE);
1263 set_focus(HildonGrid * grid, GtkWidget * widget, gboolean refresh_view)
1265 HildonGridPrivate *priv;
1266 GtkContainer *container;
1267 gboolean view_updated;
1270 priv = HILDON_GRID_GET_PRIVATE(grid);
1271 container = GTK_CONTAINER(grid);
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);
1277 GTK_CONTAINER(grid)->focus_child = widget;
1278 if (widget == NULL) {
1279 priv->focus_index = -1;
1283 /* Get the child index which the user wanted to focus */
1284 priv->focus_index = get_child_index(priv, widget);
1286 gtk_widget_grab_focus(widget);
1289 view_updated = jump_scrollbar_to_focused(grid);
1291 view_updated = FALSE;
1295 hildon_grid_size_allocate(GTK_WIDGET(grid),
1296 >K_WIDGET(grid)->allocation);
1301 hildon_grid_forall(GtkContainer * container,
1302 gboolean include_internals,
1303 GtkCallback callback, gpointer callback_data)
1306 HildonGridPrivate *priv;
1309 g_return_if_fail(container);
1310 g_return_if_fail(callback);
1312 grid = HILDON_GRID(container);
1313 priv = HILDON_GRID_GET_PRIVATE(grid);
1315 /* Connect callback functions */
1316 if (include_internals) {
1317 if (priv->scrollbar != NULL) {
1318 (*callback) (priv->scrollbar, callback_data);
1320 if (priv->empty_label != NULL) {
1321 (*callback) (priv->empty_label, callback_data);
1325 for (list = priv->children; list != NULL; list = list->next) {
1326 (*callback) (((HildonGridChild *) list->data)->widget,
1331 static void hildon_grid_destroy(GtkObject * self)
1333 HildonGridPrivate *priv;
1335 g_return_if_fail(self != NULL);
1336 g_return_if_fail(HILDON_IS_GRID(self));
1338 priv = HILDON_GRID_GET_PRIVATE(self);
1340 if (GTK_WIDGET(self)->window != NULL) {
1341 g_object_unref(G_OBJECT(GTK_WIDGET(self)->window));
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);
1349 GTK_OBJECT_CLASS(parent_class)->destroy(self);
1352 static void hildon_grid_finalize(GObject * object)
1355 HildonGridPrivate *priv;
1357 grid = HILDON_GRID(object);
1358 priv = HILDON_GRID_GET_PRIVATE(grid);
1360 gtk_container_forall(GTK_CONTAINER(object),
1361 (GtkCallback) gtk_object_unref, NULL);
1363 if (priv->style != NULL) {
1364 g_free(priv->style);
1366 if (G_OBJECT_CLASS(parent_class)->finalize) {
1367 G_OBJECT_CLASS(parent_class)->finalize(object);
1372 * hildon_grid_key_pressed:
1373 * @widget: Widget where we get the signal from
1375 * @data: #HildonGrid
1377 * Handle user key press (keyboard navigation).
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 .
1385 * '#' = dimmed item,
1386 * '1' = starting position,
1387 * '2' = final position
1389 * ...although only the first example is implemented right now.
1391 * Return value: Signal handled
1394 hildon_grid_key_pressed(GtkWidget * widget,
1395 GdkEventKey * event)
1397 GtkAdjustment *adjustment;
1398 GtkContainer *container;
1399 GtkWidget *new_focus;
1401 HildonGridPrivate *priv;
1406 gint child_count, child_rows;
1408 gint addition, max_add;
1410 g_return_val_if_fail(widget, FALSE);
1412 grid = HILDON_GRID(widget);
1413 priv = HILDON_GRID_GET_PRIVATE(grid);
1416 * If focus was never lost, we could just see if an item is focused -
1417 * if not, there's nothing else to focus...
1421 if (priv->children == NULL || g_list_length(priv->children) == 0)
1422 return GTK_WIDGET_CLASS (parent_class)->key_press_event (widget, event);
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);
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
1436 /* Said somewhere that "foo = a == b" is not desirable. */
1437 if (event->state & GDK_SHIFT_MASK) {
1443 keyval = event->keyval;
1444 if (gtk_widget_get_default_direction() == GTK_TEXT_DIR_RTL) {
1445 switch (event->keyval) {
1450 keyval = GDK_KP_Right;
1456 keyval = GDK_KP_Left;
1461 child_count = g_list_length(priv->children);
1462 child_rows = (child_count - 1) / priv->num_columns + 1;
1464 if (priv->focus_index != -1) {
1465 x = priv->focus_index % priv->num_columns;
1466 y = priv->focus_index / priv->num_columns;
1472 case GDK_KP_Page_Up:
1474 if (priv->first_index == 0) {
1475 if (priv->focus_index == 0) {
1478 set_focus(grid, get_child_by_index(priv, 0), TRUE);
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);
1489 /* Want to update now. */
1490 hildon_grid_size_allocate(GTK_WIDGET(grid),
1491 >K_WIDGET(grid)->allocation);
1496 case GDK_KP_Page_Down:
1498 if (priv->first_index / priv->num_columns ==
1499 child_rows - priv->area_rows) {
1500 if (priv->focus_index == child_count - 1) {
1503 set_focus(grid, get_child_by_index(priv, child_count - 1),
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);
1516 /* Want to update now. */
1517 hildon_grid_size_allocate(GTK_WIDGET(grid),
1518 >K_WIDGET(grid)->allocation);
1528 addition = -priv->num_columns;
1535 if (y >= (child_count - 1) / priv->num_columns) {
1538 t = child_count % priv->num_columns;
1540 t = priv->num_columns;
1542 if (y == (child_count - 1) / priv->num_columns - 1 && x >= t) {
1546 addition = priv->num_columns;
1547 max_add = child_rows - y;
1562 if (x >= priv->num_columns - 1) {
1565 if (y == 0 && x >= child_count - 1) {
1570 max_add = priv->num_columns - x;
1571 if (y * priv->num_columns + x == child_count) {
1577 hildon_grid_activate_child(grid,
1579 (GTK_CONTAINER(grid)->focus_child));
1583 return GTK_WIDGET_CLASS (parent_class)->key_press_event (widget, event);
1587 focus_index = y * priv->num_columns + x;
1588 new_focus = get_child_by_index(priv, focus_index);
1590 while (new_focus != NULL &&
1591 focus_index < child_count && !GTK_WIDGET_SENSITIVE(new_focus)) {
1597 focus_index += addition;
1598 new_focus = get_child_by_index(priv, focus_index);
1601 if (new_focus != NULL) {
1602 set_focus(grid, new_focus, TRUE);
1609 * hildon_grid_button_pressed:
1610 * @widget: Widget where signal is coming from
1611 * @event: #EventButton
1612 * @data: #HildonGrid
1614 * Handle mouse button press.
1616 * Return value: Signal handled
1619 hildon_grid_button_pressed(GtkWidget * widget,
1620 GdkEventButton * event)
1623 HildonGridPrivate *priv;
1627 grid = HILDON_GRID(widget);
1628 priv = HILDON_GRID_GET_PRIVATE(grid);
1630 /* Watch out for double/triple click press events */
1632 if (event->type == GDK_2BUTTON_PRESS ||
1633 event->type == GDK_3BUTTON_PRESS) {
1634 priv->last_button_event = event->type;
1638 priv->last_button_event = event->type;
1640 if (event->type != GDK_BUTTON_PRESS)
1644 child_no = get_child_index_by_coord(priv, event->x, event->y);
1646 if (child_no == -1 || child_no >= g_list_length(priv->children))
1649 child = get_child_by_index(priv, child_no);
1650 if (!GTK_WIDGET_IS_SENSITIVE(child))
1653 set_focus(grid, child, TRUE);
1655 priv->click_x = event->x;
1656 priv->click_y = event->y;
1662 * hildon_grid_button_released:
1663 * @widget: Widget the signal is coming from
1664 * @event: #EventButton
1665 * @data: #HildonGrid
1667 * Handle mouse button release.
1669 * Return value: Signal handled
1672 hildon_grid_button_released(GtkWidget * widget,
1673 GdkEventButton * event)
1676 HildonGridPrivate *priv;
1680 grid = HILDON_GRID(widget);
1681 priv = HILDON_GRID_GET_PRIVATE(grid);
1683 /* In case of double/triple click, silently ignore the release event */
1685 if (priv->last_button_event == GDK_2BUTTON_PRESS ||
1686 priv->last_button_event == GDK_3BUTTON_PRESS) {
1687 priv->last_button_event = event->type;
1691 child_no = get_child_index_by_coord(priv, event->x, event->y);
1693 if (child_no == -1 || child_no >= g_list_length(priv->children)) {
1696 child = get_child_by_index(priv, child_no);
1697 if (!GTK_WIDGET_IS_SENSITIVE(child)) {
1700 if (abs(priv->click_x - event->x) >= DRAG_SENSITIVITY
1701 && abs(priv->click_y - event->y) >= DRAG_SENSITIVITY) {
1704 set_focus(grid, child, TRUE);
1705 priv->last_button_event = event->type;
1706 hildon_grid_activate_child(grid, HILDON_GRID_ITEM(child));
1712 * hildon_grid_scrollbar_moved:
1713 * @widget: Widget which sent the signal
1714 * @data: #HildonGrid
1716 * Update HildonGrid contents when scrollbar is moved.
1718 * Return value: Signal handeld
1721 hildon_grid_scrollbar_moved(GtkWidget * widget, gpointer data)
1724 HildonGridPrivate *priv;
1725 gboolean updated = FALSE;
1727 grid = HILDON_GRID(data);
1728 priv = HILDON_GRID_GET_PRIVATE(grid);
1729 updated = update_contents(grid);
1732 * If grid changes focus while dragging scrollbar and pointer leaves
1733 * scrollbar, focus is moved to prev_focus... This prevents that.
1735 gtk_window_set_prev_focus_widget(GTK_WINDOW
1736 (gtk_widget_get_toplevel(widget)),
1737 GTK_CONTAINER(grid)->focus_child);
1740 /* Don't just queue it, let's do it now! */
1741 hildon_grid_size_allocate(GTK_WIDGET(grid),
1742 >K_WIDGET(grid)->allocation);
1750 * @grid: #HildonGrid
1752 * Update the view if scrollbar has moved so that first visible row
1753 * should've changed. Returns true if location actually changed.
1755 * Return value: Content changed
1757 static gboolean update_contents(HildonGrid * grid)
1759 HildonGridPrivate *priv;
1762 priv = HILDON_GRID_GET_PRIVATE(grid);
1763 new_row = (int) gtk_range_get_value(GTK_RANGE(priv->scrollbar))
1764 / priv->item_height;
1766 if (new_row != priv->old_sb_pos) {
1767 priv->old_sb_pos = new_row;
1768 priv->first_index = new_row * priv->num_columns;
1776 * jump_scrollbar_to_focused:
1777 * @grid: #HildonGrid
1779 * Moves scrollbar position so that focused item will be shown
1781 * Returns TRUE if visible position of widgets have changed.
1783 * Return value: Content changed
1785 static gboolean jump_scrollbar_to_focused(HildonGrid * grid)
1787 HildonGridPrivate *priv;
1788 GtkAdjustment *adjustment;
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);
1797 /* Make sure "first widget" is something sensible. */
1798 priv->first_index = priv->first_index -
1799 priv->first_index % priv->num_columns;
1801 child_count = g_list_length(priv->children);
1802 empty_grids = priv->num_columns * priv->area_rows - child_count +
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) {
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) {
1817 if (new_row > last_top_row) {
1818 new_row = last_top_row;
1820 } else if (empty_grids >= priv->num_columns) {
1821 new_row = ((child_count - 1) / priv->num_columns + 1)
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;
1837 gtk_widget_queue_draw(priv->scrollbar);
1844 * adjust_scrollbar_height:
1845 * @grid: HildonGridPrivate
1847 * Return value: View should change
1849 * Adjust scrollbar according the #HildonGrid contents.
1850 * Show/hide scrollbar if
1851 * appropriate. Also sets priv->first_index.
1853 static gboolean adjust_scrollbar_height(HildonGrid * grid)
1855 HildonGridPrivate *priv;
1858 GtkAllocation alloc;
1859 GtkAllocation *gridalloc;
1865 priv = HILDON_GRID_GET_PRIVATE(grid);
1866 g_return_val_if_fail(priv->scrollbar != NULL, FALSE);
1869 gridalloc = >K_WIDGET(grid)->allocation;
1871 /* See if we need scrollbar at all. */
1872 if (priv->num_columns == 0) {
1873 priv->num_columns = DEFAULT_N_COLUMNS;
1875 priv->num_columns = MAX(1, priv->num_columns);
1878 if (g_list_length(priv->children) != 0) {
1879 need_rows = (g_list_length(priv->children) - 1) /
1880 priv->num_columns + 1;
1885 if (need_rows <= priv->area_rows) {
1886 updated = priv->first_index != 0;
1887 priv->scrollbar_width = 0;
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);
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;
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);
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);
1920 need_pixels = need_rows * priv->item_height;
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;
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);
1930 (gdouble) (priv->area_height - priv->area_height % priv->item_height);
1932 /* Also update position if needed to show focused item. */
1934 gtk_range_set_adjustment(GTK_RANGE(priv->scrollbar), adj);
1936 /* Then set first_index. */
1937 priv->first_index = (int) adj->value / priv->item_height *
1940 /* Finally, ask Gtk to redraw the scrollbar. */
1941 if (old_upper != (int) adj->upper) {
1942 gtk_widget_queue_draw(priv->scrollbar);
1948 * get_child_index_by_coord:
1949 * @priv: HildonGridPrivate
1953 * Returns index of child at given coordinates, -1 if no child.
1955 * Return value: Index
1958 get_child_index_by_coord(HildonGridPrivate * priv, gint x, gint y)
1963 xgap = x % priv->item_width;
1964 ygap = y % priv->item_height;
1966 if (xgap > priv->item_width - priv->h_margin) { /*FIXME*/
1970 /* Event may come from outside of the grid. Skipping those events */
1971 if (x >= priv->item_width * priv->num_columns)
1974 t = y / priv->item_height * priv->num_columns +
1975 x / priv->item_width + priv->first_index;
1977 if (t >= priv->first_index + priv->area_rows * priv->num_columns ||
1978 t >= g_list_length(priv->children) || t < 0) {
1985 * get_child_by_index:
1986 * @priv: HildonGridPrivate
1987 * @index: Index of child
1989 * Returns child that is #th in HildonGrid or NULL if child was not found
1990 * among the children.
1992 * Return value: GtkWidget
1994 static GtkWidget *get_child_by_index(HildonGridPrivate * priv, gint index)
1999 if (index >= g_list_length(priv->children) || index < 0) {
2002 for (list = priv->children, i = 0; list != NULL;
2003 list = list->next, i++) {
2005 return ((HildonGridChild *) list->data)->widget;
2009 g_warning("no such child");
2015 * @priv: HildonGridPrivate
2016 * @child: #GtkWidget to look for
2018 * Returns index of a child or -1 if child was not found among the
2021 * Return value: Index
2023 static gint get_child_index(HildonGridPrivate * priv, GtkWidget * child)
2031 for (list = priv->children, index = 0;
2032 list != NULL; list = list->next, index++) {
2033 if (((HildonGridChild *) list->data)->widget == child) {
2038 g_warning("no such child");
2044 * hildon_grid_activate_child:
2045 * @grid: #HildonGrid
2046 * @item: #HildonGridItem
2048 * Emits a signal to tell HildonGridItem was actiavated.
2050 void hildon_grid_activate_child(HildonGrid * grid, HildonGridItem * item)
2052 g_return_if_fail(HILDON_IS_GRID(grid));
2054 g_signal_emit(grid, grid_signals[ACTIVATE_CHILD], 0, item);
2060 * hildon_grid_set_style:
2061 * @grid: #HildonGrid
2062 * @style_name: Style name
2064 * Sets style. Setting style sets widget size, spacing, label position,
2065 * number of columns, and icon size.
2067 void hildon_grid_set_style(HildonGrid * grid, const gchar * style_name)
2069 HildonGridPrivate *priv;
2071 g_return_if_fail(HILDON_IS_GRID(grid));
2074 priv = HILDON_GRID_GET_PRIVATE(grid);
2075 if (priv->style != NULL) {
2076 g_free((gpointer) priv->style);
2078 if (style_name != NULL) {
2079 priv->style = g_strdup(style_name);
2084 gtk_widget_set_name(GTK_WIDGET(grid), style_name);
2085 get_style_properties(grid);
2087 gtk_widget_queue_resize(GTK_WIDGET(grid));
2091 * hildon_grid_get_style:
2092 * @grid: #HildonGrid
2094 * Returns the name of style currently used in HildonGrid.
2096 * Return value: Style name
2098 const gchar *hildon_grid_get_style(HildonGrid * grid)
2100 g_return_val_if_fail(HILDON_IS_GRID(grid), NULL);
2102 return gtk_widget_get_name(GTK_WIDGET(grid));
2106 * get_style_properties:
2107 * @grid: #HildonGrid
2109 * Gets widget size and other stuff from gtkrc. If some stuff changed, let
2110 * children know this, too.
2112 static void get_style_properties(HildonGrid * grid)
2116 HildonGridPositionType label_pos;
2119 gint h_margin, v_margin;
2122 gint focus_margin, icon_label_margin;
2125 HildonGridPrivate *priv;
2126 g_return_if_fail(HILDON_IS_GRID(grid));
2127 priv = HILDON_GRID_GET_PRIVATE(grid);
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,
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);
2150 priv->h_margin = h_margin;
2151 priv->v_margin = v_margin;
2152 priv->item_height = item_height;
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);
2167 * hildon_grid_set_scrollbar_pos:
2168 * @grid: #HildonGrid
2169 * @scrollbar_pos: new position (in pixels)
2171 * Sets view (scrollbar) to specified position.
2173 void hildon_grid_set_scrollbar_pos(HildonGrid * grid, gint scrollbar_pos)
2175 HildonGridPrivate *priv;
2176 GtkAdjustment *adjustment;
2178 g_return_if_fail(HILDON_IS_GRID(grid));
2180 priv = HILDON_GRID_GET_PRIVATE(grid);
2181 adjustment = gtk_range_get_adjustment(GTK_RANGE(priv->scrollbar));
2182 adjustment->value = (gdouble) scrollbar_pos;
2184 gtk_range_set_adjustment(GTK_RANGE(priv->scrollbar), adjustment);
2186 g_object_notify (G_OBJECT (grid), "scrollbar-position");
2188 /* If grid isn't drawable, updating anything could mess up focus. */
2189 if (!GTK_WIDGET_DRAWABLE(GTK_WIDGET(grid)))
2192 update_contents(grid);
2196 * hildon_grid_get_scrollbar_pos:
2197 * @grid: #HildonGrid
2199 * Returns position of scrollbar (in pixels).
2201 * Return value: Scrollbar position
2203 gint hildon_grid_get_scrollbar_pos(HildonGrid * grid)
2205 GtkAdjustment *adjustment;
2207 g_return_val_if_fail(HILDON_IS_GRID(grid), -1);
2209 adjustment = gtk_range_get_adjustment(GTK_RANGE
2210 (HILDON_GRID_GET_PRIVATE
2211 (grid)->scrollbar));
2212 return (int) adjustment->value;
2216 hildon_grid_set_property(GObject * object,
2218 const GValue * value, GParamSpec * pspec)
2222 grid = HILDON_GRID(object);
2225 case PROP_EMPTY_LABEL:
2226 hildon_grid_set_empty_label(grid, g_value_get_string(value));
2230 hildon_grid_set_style(grid, g_value_get_string(value));
2233 case PROP_SCROLLBAR_POS:
2234 hildon_grid_set_scrollbar_pos(grid, g_value_get_int(value));
2238 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
2244 hildon_grid_get_property(GObject * object,
2245 guint prop_id, GValue * value, GParamSpec * pspec)
2249 grid = HILDON_GRID(object);
2252 case PROP_EMPTY_LABEL:
2253 g_value_set_string(value, hildon_grid_get_empty_label(grid));
2257 g_value_set_string(value, hildon_grid_get_style(grid));
2260 case PROP_SCROLLBAR_POS:
2261 g_value_set_int(value, hildon_grid_get_scrollbar_pos(grid));
2265 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
2271 hildon_grid_state_changed(GtkWidget * widget,
2272 GtkStateType state, gpointer data)
2275 HildonGridPrivate *priv;
2278 GtkWidget *prev_focusable, *next_focusable;
2281 g_return_val_if_fail(HILDON_IS_GRID(data), FALSE);
2282 g_return_val_if_fail(HILDON_IS_GRID_ITEM(widget), FALSE);
2284 grid = HILDON_GRID(data);
2285 priv = HILDON_GRID_GET_PRIVATE(grid);
2288 if (GTK_WIDGET_IS_SENSITIVE(widget))
2291 prev_focusable = next_focusable = NULL;
2294 for (list = priv->children; list != NULL; list = list->next) {
2295 current = ((HildonGridChild *) list->data)->widget;
2297 if (GTK_WIDGET_IS_SENSITIVE(current)) {
2299 next_focusable = current;
2302 prev_focusable = current;
2304 } else if (current == widget) {
2309 if (next_focusable == NULL) {
2310 next_focusable = prev_focusable;
2313 gtk_container_set_focus_child(GTK_CONTAINER(grid), next_focusable);
2321 hildon_grid_tap_and_hold_setup(GtkWidget * widget,
2324 GtkWidgetTapAndHoldFlags flags)
2326 g_return_if_fail(HILDON_IS_GRID(widget) && GTK_IS_MENU(menu));
2328 parent_class->parent_class.tap_and_hold_setup
2329 (widget, menu, func, flags | GTK_TAP_AND_HOLD_NO_INTERNALS);