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>
63 #define _(String) dgettext(PACKAGE, String)
65 #define HILDON_GRID_GET_PRIVATE(obj) \
66 (G_TYPE_INSTANCE_GET_PRIVATE ((obj), HILDON_TYPE_GRID, \
70 #define DEFAULT_STYLE "largeicons-home"
72 #define DEFAULT_N_COLUMNS 3
73 #define GRID_LABEL_POS_PAD 16
75 #define DRAG_SENSITIVITY 6
92 typedef struct _HildonGridChild HildonGridChild;
93 typedef struct _HildonGridPrivate HildonGridPrivate;
96 struct _HildonGridChild {
101 struct _HildonGridPrivate {
103 GtkWidget *scrollbar;
105 GdkWindow *event_window;
109 GtkWidget *empty_label;
116 gint icon_label_margin;
119 HildonGridPositionType label_pos;
126 /* Handy variables outsize _allocate. */
129 gint scrollbar_width;
132 GdkEventType last_button_event;
133 gint old_item_height;
139 static void hildon_grid_class_init(HildonGridClass * klass);
140 static void hildon_grid_init(HildonGrid * grid);
141 static void hildon_grid_realize(GtkWidget * widget);
142 static void hildon_grid_unrealize(GtkWidget * widget);
143 static void hildon_grid_map(GtkWidget * widget);
144 static void hildon_grid_unmap(GtkWidget * widget);
145 static gboolean hildon_grid_expose(GtkWidget * widget,
146 GdkEventExpose * event);
147 static void hildon_grid_size_request(GtkWidget * widget,
148 GtkRequisition * requisition);
149 static void hildon_grid_size_allocate(GtkWidget * widget,
150 GtkAllocation * allocation);
151 static void hildon_grid_add(GtkContainer * container, GtkWidget * widget);
152 static void hildon_grid_remove(GtkContainer * container,
154 static void hildon_grid_set_focus_child(GtkContainer * container,
156 static void hildon_grid_forall(GtkContainer * container,
157 gboolean include_internals,
158 GtkCallback callback,
159 gpointer callback_data);
160 static void hildon_grid_tap_and_hold_setup(GtkWidget * widget,
163 GtkWidgetTapAndHoldFlags flags);
165 static GType hildon_grid_child_type(GtkContainer * container);
168 static void hildon_grid_set_property(GObject * object,
170 const GValue * value,
172 static void hildon_grid_get_property(GObject * object,
174 GValue * value, GParamSpec * pspec);
176 static void hildon_grid_set_empty_label(HildonGrid *grid,
177 const gchar *empty_label);
178 static const gchar *hildon_grid_get_empty_label(HildonGrid * grid);
179 static void hildon_grid_set_num_columns(HildonGrid *grid, gint num_cols);
180 static void hildon_grid_set_label_pos(HildonGrid *grid,
181 HildonGridPositionType label_pos);
182 static void hildon_grid_set_focus_margin(HildonGrid *grid,
184 static void hildon_grid_set_icon_label_margin(HildonGrid *grid,
185 gint icon_label_margin);
186 static void hildon_grid_set_icon_width(HildonGrid *grid, gint icon_width);
187 static void hildon_grid_set_emblem_size(HildonGrid *grid, gint emblem_size);
188 static void hildon_grid_set_label_height(HildonGrid *grid,
190 static void hildon_grid_destroy(GtkObject * self);
191 static void hildon_grid_finalize(GObject * object);
193 /* Signal handlers. */
194 static gboolean hildon_grid_button_pressed(GtkWidget * widget,
195 GdkEventButton * event);
196 static gboolean hildon_grid_button_released(GtkWidget * widget,
197 GdkEventButton * event);
198 static gboolean hildon_grid_key_pressed(GtkWidget * widget,
199 GdkEventKey * event);
200 static gboolean hildon_grid_scrollbar_moved(GtkWidget * widget,
202 static gboolean hildon_grid_state_changed(GtkWidget * widget,
206 /* Other internal functions. */
207 static void get_style_properties(HildonGrid * grid);
208 static gint get_child_index(HildonGridPrivate * priv, GtkWidget * child);
209 static gint get_child_index_by_coord(HildonGridPrivate * priv,
211 static GtkWidget *get_child_by_index(HildonGridPrivate * priv, gint index);
213 static gboolean jump_scrollbar_to_focused(HildonGrid * grid);
214 static gboolean adjust_scrollbar_height(HildonGrid * grid);
215 static gboolean update_contents(HildonGrid * grid);
216 static void set_focus(HildonGrid * grid,
217 GtkWidget * widget, gboolean refresh_view);
219 static GtkContainerClass *parent_class = NULL;
220 static guint grid_signals[LAST_SIGNAL] = { 0 };
223 GType hildon_grid_get_type(void)
225 static GType grid_type = 0;
228 static const GTypeInfo grid_info = {
229 sizeof(HildonGridClass),
230 NULL, /* base_init */
231 NULL, /* base_finalize */
232 (GClassInitFunc) hildon_grid_class_init,
233 NULL, /* class_finalize */
234 NULL, /* class_data */
237 (GInstanceInitFunc) hildon_grid_init,
239 grid_type = g_type_register_static(GTK_TYPE_CONTAINER,
240 "HildonGrid", &grid_info, 0);
248 static void hildon_grid_class_init(HildonGridClass * klass)
250 GObjectClass *gobject_class;
251 GtkWidgetClass *widget_class;
252 GtkContainerClass *container_class;
254 widget_class = GTK_WIDGET_CLASS(klass);
255 container_class = GTK_CONTAINER_CLASS(klass);
256 gobject_class = G_OBJECT_CLASS(klass);
258 parent_class = g_type_class_peek_parent(klass);
260 g_type_class_add_private(klass, sizeof(HildonGridPrivate));
262 GTK_OBJECT_CLASS(klass)->destroy = hildon_grid_destroy;
263 gobject_class->finalize = hildon_grid_finalize;
264 gobject_class->set_property = hildon_grid_set_property;
265 gobject_class->get_property = hildon_grid_get_property;
267 widget_class->realize = hildon_grid_realize;
268 widget_class->unrealize = hildon_grid_unrealize;
269 widget_class->map = hildon_grid_map;
270 widget_class->unmap = hildon_grid_unmap;
271 widget_class->expose_event = hildon_grid_expose;
272 widget_class->size_request = hildon_grid_size_request;
273 widget_class->size_allocate = hildon_grid_size_allocate;
274 widget_class->tap_and_hold_setup = hildon_grid_tap_and_hold_setup;
275 widget_class->key_press_event = hildon_grid_key_pressed;
276 widget_class->button_press_event = hildon_grid_button_pressed;
277 widget_class->button_release_event = hildon_grid_button_released;
279 container_class->add = hildon_grid_add;
280 container_class->remove = hildon_grid_remove;
281 container_class->forall = hildon_grid_forall;
282 container_class->child_type = hildon_grid_child_type;
283 container_class->set_focus_child = hildon_grid_set_focus_child;
285 /* Install properties to the class */
286 g_object_class_install_property(gobject_class, PROP_EMPTY_LABEL,
287 g_param_spec_string("empty_label",
289 "Label to show when grid has no items",
290 _("Ckct_wi_grid_no_items"), G_PARAM_READWRITE));
292 g_object_class_install_property(gobject_class, PROP_STYLE,
293 g_param_spec_string("style",
295 "Widget's Style. Setting style sets widget size, "
296 "spacing, label position, number of columns, "
297 "and icon sizeLabel to show when grid has no items",
298 DEFAULT_STYLE, G_PARAM_READWRITE));
300 g_object_class_install_property(gobject_class, PROP_SCROLLBAR_POS,
301 g_param_spec_int("scrollbar-position",
302 "Scrollbar Position",
303 "View (scrollbar) position.",
304 G_MININT, G_MAXINT, 0, G_PARAM_READWRITE));
306 gtk_widget_class_install_style_property(widget_class,
307 g_param_spec_uint("item_width",
309 "Total width of an item (obsolete)",
310 1, G_MAXINT, 212, G_PARAM_READABLE));
312 gtk_widget_class_install_style_property(widget_class,
313 g_param_spec_uint("item_height",
315 "Total height of an item",
316 1, G_MAXINT, 96, G_PARAM_READABLE));
318 gtk_widget_class_install_style_property(widget_class,
319 g_param_spec_uint("item_hspacing",
320 "Item horizontal spacing",
321 "Margin between two columns and labels",
322 0, G_MAXINT, 12, G_PARAM_READABLE));
324 gtk_widget_class_install_style_property(widget_class,
325 g_param_spec_uint("item_vspacing",
326 "Item vertical spacing",
327 "Icon on right: Margin between rows / Icon at bottom: Vertical margin betweeb label and icon",
328 0, G_MAXINT, 6, G_PARAM_READABLE));
330 gtk_widget_class_install_style_property(widget_class,
331 g_param_spec_uint("label_hspacing",
333 "Margin between focus edge and item edge",
334 0, G_MAXINT, 6, G_PARAM_READABLE));
336 gtk_widget_class_install_style_property(widget_class,
337 g_param_spec_uint("label_vspacing",
338 "Vertical label spacing",
339 "Vertical margin between item and label",
340 0, G_MAXINT, 6, G_PARAM_READABLE));
342 gtk_widget_class_install_style_property(widget_class,
343 g_param_spec_uint("label_height",
345 "Height of icon label",
346 1, G_MAXINT, 30, G_PARAM_READABLE));
348 gtk_widget_class_install_style_property(widget_class,
349 g_param_spec_uint("n_columns",
352 0, G_MAXINT, DEFAULT_N_COLUMNS, G_PARAM_READABLE));
354 gtk_widget_class_install_style_property(widget_class,
355 g_param_spec_uint("label_pos",
357 "Position of label related to the icon",
358 1, 2, 1, G_PARAM_READABLE));
360 gtk_widget_class_install_style_property(widget_class,
361 g_param_spec_uint("icon_size",
363 "Size of the icon in pixels (width)",
364 1, G_MAXINT, 64, G_PARAM_READABLE));
366 gtk_widget_class_install_style_property(widget_class,
367 g_param_spec_uint("emblem_size",
369 "Size of the emblem in pixels",
370 1, G_MAXINT, 25, G_PARAM_READABLE));
373 * HildonGrid::activate-child:
375 * Emitted when a child (@HildonGridItem) is activated either by
376 * tapping on it or by pressing enter.
378 grid_signals[ACTIVATE_CHILD] =
379 g_signal_new("activate-child",
380 G_OBJECT_CLASS_TYPE(gobject_class),
381 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
382 G_STRUCT_OFFSET(HildonGridClass, activate_child),
384 g_cclosure_marshal_VOID__OBJECT,
385 G_TYPE_NONE, 1, HILDON_TYPE_GRID_ITEM);
388 * HildonGrid::popup-context-menu:
390 * Emitted when popup-menu is supposed to open. Used for tap-and-hold.
392 grid_signals[POPUP_CONTEXT] =
393 g_signal_new("popup-context-menu",
394 G_OBJECT_CLASS_TYPE(gobject_class),
396 G_STRUCT_OFFSET(HildonGridClass, popup_context_menu),
397 g_signal_accumulator_true_handled, NULL,
398 _hildon_marshal_BOOLEAN__INT_INT_INT,
400 G_TYPE_INT, G_TYPE_INT, G_TYPE_INT);
406 * hildon_grid_set_empty_label:
408 * @empty_label: New label
413 hildon_grid_set_empty_label(HildonGrid * grid, const gchar * empty_label)
415 /* No need to worry about update -- label receives a signal for it. */
416 gtk_label_set_label(GTK_LABEL(HILDON_GRID_GET_PRIVATE
417 (grid)->empty_label),
418 empty_label == NULL ? "" : empty_label);
422 * _hildon_grid_get_empty_label:
425 * Returns empty label. Label must not be modified nor freed.
427 * Return value: Label
430 hildon_grid_get_empty_label(HildonGrid * grid)
432 return gtk_label_get_label(GTK_LABEL(HILDON_GRID_GET_PRIVATE
433 (grid)->empty_label));
437 * hildon_grid_set_num_columns:
439 * @columsn: Number of columns
441 * Sets number of columns.
444 hildon_grid_set_num_columns(HildonGrid * grid, gint columns)
446 HildonGridPrivate *priv;
448 g_return_if_fail(HILDON_IS_GRID(grid));
449 priv = HILDON_GRID_GET_PRIVATE(grid);
451 if (priv->num_columns == columns) {
456 priv->num_columns = columns;
458 priv->num_columns = DEFAULT_N_COLUMNS;
460 /* Update estimated row-count for jump_scrollbar... */
461 priv->area_rows = MAX(priv->area_height / priv->num_columns, 1);
463 /* Size could have changed. Scroll view so there's something to show. */
464 adjust_scrollbar_height(grid);
465 jump_scrollbar_to_focused(grid);
466 gtk_widget_queue_resize(GTK_WIDGET(grid));
470 * hildon_grid_set_label_pos:
472 * @label_pos: Label position
474 * Sets icon label position.
477 hildon_grid_set_label_pos(HildonGrid * grid,
478 HildonGridPositionType label_pos)
480 HildonGridPrivate *priv;
484 priv = HILDON_GRID_GET_PRIVATE(grid);
486 if (label_pos == priv->label_pos)
489 /* gtknotebook doesn't check if we use valid values -- why should
492 priv->label_pos = label_pos;
494 /* Set label position to each HildonGridItem */
495 for (list = priv->children; list != NULL; list = list->next) {
496 child = ((HildonGridChild *) list->data)->widget;
498 _hildon_grid_item_set_label_pos(HILDON_GRID_ITEM(child),
504 * hildon_grid_set_focus_margin:
506 * @focus_margin: Focus margin
508 * Sets margin between icon edge and label edge
511 hildon_grid_set_focus_margin(HildonGrid *grid, gint focus_margin)
513 HildonGridPrivate *priv;
517 priv = HILDON_GRID_GET_PRIVATE(grid);
518 if (focus_margin == priv->focus_margin)
521 priv->focus_margin = focus_margin;
523 /* Update children. */
524 for (list = priv->children; list != NULL; list = list->next) {
525 child = ((HildonGridChild *) list->data)->widget;
527 _hildon_grid_item_set_focus_margin(HILDON_GRID_ITEM(child),
534 * hildon_grid_set_icon_label_margin:
536 * @hspacing: Vertical spacing
538 * Sets vertical spacing for label.
542 hildon_grid_set_icon_label_margin(HildonGrid *grid, gint icon_label_margin)
544 HildonGridPrivate *priv;
546 priv = HILDON_GRID_GET_PRIVATE(grid);
547 if (icon_label_margin == priv->icon_label_margin)
550 priv->icon_label_margin = icon_label_margin;
555 * hildon_grid_set_icon_width:
557 * @icon_size: Icon size (width)
559 * Sets icon size (in pixels).
562 hildon_grid_set_icon_width(HildonGrid * grid, gint icon_width)
564 HildonGridPrivate *priv;
568 priv = HILDON_GRID_GET_PRIVATE(grid);
570 if (icon_width == priv->icon_width)
573 priv->icon_width = icon_width;
575 for (list = priv->children; list != NULL; list = list->next) {
576 child = ((HildonGridChild *) list->data)->widget;
578 _hildon_grid_item_set_icon_width(HILDON_GRID_ITEM(child),
585 * hildon_grid_set_emblem_size:
587 * @emblem_size: Emblem size
589 * Sets emblem size (in pixels).
592 hildon_grid_set_emblem_size(HildonGrid *grid, gint emblem_size)
594 HildonGridPrivate *priv;
598 priv = HILDON_GRID_GET_PRIVATE(grid);
600 if (emblem_size == priv->emblem_size)
603 priv->emblem_size = emblem_size;
605 for (list = priv->children; list != NULL; list = list->next) {
606 child = ((HildonGridChild *) list->data)->widget;
608 _hildon_grid_item_set_emblem_size(HILDON_GRID_ITEM(child),
615 hildon_grid_set_label_height(HildonGrid *grid,
618 HildonGridPrivate *priv;
622 priv = HILDON_GRID_GET_PRIVATE(grid);
624 if (label_height == priv->label_height)
627 priv->label_height = label_height;
629 for (list = priv->children; list != NULL; list = list->next) {
630 child = ((HildonGridChild *) list->data)->widget;
632 _hildon_grid_item_set_label_height(HILDON_GRID_ITEM(child),
638 static GType hildon_grid_child_type(GtkContainer * container)
640 return GTK_TYPE_WIDGET;
643 static void hildon_grid_init(HildonGrid * grid)
645 HildonGridPrivate *priv;
647 priv = HILDON_GRID_GET_PRIVATE(grid);
649 GTK_CONTAINER(grid)->focus_child = NULL;
650 priv->focus_index = -1;
652 priv->scrollbar = gtk_vscrollbar_new(NULL);
653 priv->empty_label = gtk_label_new(_("Ckct_wi_grid_no_items"));
656 priv->area_height = 1;
658 priv->children = NULL;
660 priv->first_index = 0;
664 priv->item_height = 96;
667 priv->focus_margin = 6;
668 priv->icon_label_margin = 6;
669 priv->icon_width = 64;
670 priv->label_pos = HILDON_GRID_ITEM_LABEL_POS_BOTTOM;
672 priv->old_sb_pos = -1;
673 priv->old_item_height = -1;
675 gtk_widget_set_parent(priv->scrollbar, GTK_WIDGET(grid));
676 gtk_widget_set_parent(priv->empty_label, GTK_WIDGET(grid));
678 priv->last_button_event = GDK_NOTHING;
680 GTK_WIDGET_SET_FLAGS(grid, GTK_NO_WINDOW);
682 /* Signal for scrollbar. */
683 g_signal_connect(G_OBJECT(priv->scrollbar), "value-changed",
684 G_CALLBACK(hildon_grid_scrollbar_moved), grid);
686 /* Signal for key press. */
687 GTK_WIDGET_SET_FLAGS(GTK_WIDGET(grid), GTK_CAN_FOCUS);
688 gtk_widget_set_events(GTK_WIDGET(grid), GDK_KEY_PRESS_MASK);
690 GTK_WIDGET_UNSET_FLAGS(priv->scrollbar, GTK_CAN_FOCUS);
691 hildon_grid_set_style(grid, DEFAULT_STYLE);
697 * Creates a new #HildonGrid.
699 * Return value: A new #HildonGrid
701 GtkWidget *hildon_grid_new(void)
706 grid = g_object_new(HILDON_TYPE_GRID, NULL);
708 return GTK_WIDGET(grid);
712 static void hildon_grid_realize(GtkWidget * widget)
715 HildonGridPrivate *priv;
720 GTK_WIDGET_SET_FLAGS(widget, GTK_REALIZED);
722 grid = HILDON_GRID(widget);
723 priv = HILDON_GRID_GET_PRIVATE(grid);
725 /* Create GdkWindow for catching events. */
726 attr.x = widget->allocation.x;
727 attr.y = widget->allocation.y;
728 attr.width = widget->allocation.width - priv->scrollbar_width;
729 attr.height = widget->allocation.height;
730 attr.window_type = GDK_WINDOW_CHILD;
731 attr.event_mask = gtk_widget_get_events(widget)
732 | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK;
734 widget->window = gtk_widget_get_parent_window(widget);
735 g_object_ref(widget->window);
737 attr.wclass = GDK_INPUT_ONLY;
738 attr_mask = GDK_WA_X | GDK_WA_Y;
740 priv->event_window = gdk_window_new(widget->window, &attr, attr_mask);
741 gdk_window_set_user_data(priv->event_window, widget);
743 widget->style = gtk_style_attach(widget->style, widget->window);
745 gtk_style_set_background(widget->style,
746 widget->window, GTK_STATE_NORMAL);
750 static void hildon_grid_unrealize(GtkWidget * widget)
752 HildonGridPrivate *priv;
754 priv = HILDON_GRID_GET_PRIVATE(HILDON_GRID(widget));
756 if (priv->event_window != NULL) {
757 gdk_window_set_user_data(priv->event_window, NULL);
758 gdk_window_destroy(priv->event_window);
759 priv->event_window = NULL;
762 if (GTK_WIDGET_CLASS(parent_class)->unrealize) {
763 (*GTK_WIDGET_CLASS(parent_class)->unrealize) (widget);
769 static void hildon_grid_map(GtkWidget * widget)
772 HildonGridPrivate *priv;
776 g_return_if_fail(HILDON_IS_GRID(widget));
778 if (!GTK_WIDGET_VISIBLE(widget))
781 grid = HILDON_GRID(widget);
782 priv = HILDON_GRID_GET_PRIVATE(grid);
784 (*GTK_WIDGET_CLASS(parent_class)->map) (widget);
786 /* We shouldn't really need the following...*/
787 if (priv->scrollbar != NULL && GTK_WIDGET_VISIBLE(priv->scrollbar)) {
788 if (!GTK_WIDGET_MAPPED(priv->scrollbar)) {
789 gtk_widget_map(priv->scrollbar);
793 if (priv->empty_label != NULL &&
794 GTK_WIDGET_VISIBLE(priv->empty_label)) {
795 if (!GTK_WIDGET_MAPPED(priv->empty_label)) {
796 gtk_widget_map(priv->empty_label);
800 for (list = priv->children; list != NULL; list = list->next) {
801 child = ((HildonGridChild *) list->data)->widget;
803 if (GTK_WIDGET_VISIBLE(child)) {
804 if (!GTK_WIDGET_MAPPED(child)) {
805 gtk_widget_map(child);
809 /* END OF don't really need */
811 /* Also make event window visible. */
812 gdk_window_show(priv->event_window);
817 static void hildon_grid_unmap(GtkWidget * widget)
819 HildonGridPrivate *priv;
821 priv = HILDON_GRID_GET_PRIVATE(HILDON_GRID(widget));
823 if (priv->event_window != NULL) {
824 gdk_window_hide(priv->event_window);
827 (*GTK_WIDGET_CLASS(parent_class)->unmap) (widget);
833 hildon_grid_expose(GtkWidget * widget, GdkEventExpose * event)
836 HildonGridPrivate *priv;
837 GtkContainer *container;
841 g_return_val_if_fail(widget, FALSE);
842 g_return_val_if_fail(HILDON_IS_GRID(widget), FALSE);
843 g_return_val_if_fail(event, FALSE);
845 grid = HILDON_GRID(widget);
846 priv = HILDON_GRID_GET_PRIVATE(grid);
847 container = GTK_CONTAINER(grid);
849 /* If grid has no children,
850 * propagate the expose event to the label is one exists */
851 if (priv->children == NULL || g_list_length(priv->children) == 0) {
852 if (priv->empty_label != NULL) {
853 gtk_container_propagate_expose(container,
854 priv->empty_label, event);
859 /* Only expose visible children. */
861 /* Jump over invisible. */
862 for (list = priv->children, child_no = 0;
863 list != NULL && child_no < priv->first_index;
864 list = list->next, child_no++) {
865 ; /* Nothing here. */
868 for (; list != NULL && child_no < priv->first_index +
869 priv->num_columns * priv->area_rows; list = list->next) {
870 gtk_container_propagate_expose(container,
871 ((HildonGridChild *) list->data)
875 /* Keep focused item focused. */
876 if (container->focus_child != NULL
877 && !GTK_WIDGET_HAS_FOCUS(container->focus_child)) {
878 set_focus(grid, container->focus_child, FALSE);
880 if (priv->scrollbar_width > 0 && priv->scrollbar != NULL) {
881 gtk_container_propagate_expose(container, priv->scrollbar, event);
889 hildon_grid_size_request(GtkWidget * widget, GtkRequisition * requisition)
892 HildonGridPrivate *priv;
897 g_return_if_fail(widget);
898 g_return_if_fail(requisition);
900 grid = HILDON_GRID(widget);
901 priv = HILDON_GRID_GET_PRIVATE(grid);
903 /* Want as big as possible. */
904 requisition->width = 0x7fff; /* Largest possible gint16 */
905 requisition->height = 0x7fff; /* Largest possible gint16 */
907 if (priv->children == NULL) {
908 if (priv->empty_label != NULL &&
909 GTK_WIDGET_VISIBLE(priv->empty_label)) {
910 gtk_widget_size_request(priv->empty_label, &req);
914 if (priv->scrollbar != NULL && GTK_WIDGET_VISIBLE(priv->scrollbar)) {
915 gtk_widget_size_request(priv->scrollbar, &req);
918 for (list = priv->children; list != NULL; list = list->next) {
919 child = ((HildonGridChild *) list->data)->widget;
921 gtk_widget_size_request(child, &req);
926 * hildon_grid_size_allocate:
928 * Supposingly called when size of grid changes and after view have moved so
929 * that items need to be relocated.
932 hildon_grid_size_allocate(GtkWidget * widget, GtkAllocation * allocation)
935 HildonGridPrivate *priv;
945 g_return_if_fail(widget);
946 g_return_if_fail(allocation);
948 grid = HILDON_GRID(widget);
949 priv = HILDON_GRID_GET_PRIVATE(grid);
950 widget->allocation = *allocation;
952 get_style_properties(grid);
954 /* First of all, make sure GdkWindow is over our widget. */
955 if (priv->event_window != NULL) {
956 gdk_window_move_resize(priv->event_window,
957 widget->allocation.x,
958 widget->allocation.y,
959 widget->allocation.width -
960 priv->scrollbar_width,
961 widget->allocation.height);
963 /* Show the label if there are no items. */
964 if (priv->children == NULL) {
966 * We probably don't need this as scrollbar should be hidden when
967 * removing items, but one can never be too sure...
969 if (priv->scrollbar != NULL &&
970 GTK_WIDGET_VISIBLE(priv->scrollbar)) {
971 priv->scrollbar_width = 0;
972 gtk_widget_hide(priv->scrollbar);
975 /* Show label if creating one actually worked. */
976 if (priv->empty_label != NULL) {
977 gtk_widget_get_child_requisition(priv->empty_label, &req);
979 /* ...for sure we must have a position for the label here... */
980 alloc.x = allocation->x + GRID_LABEL_POS_PAD;
981 alloc.y = allocation->y + GRID_LABEL_POS_PAD;
982 alloc.width = MIN(req.width, allocation->width -
984 alloc.height = MIN(req.height, allocation->height -
987 /* Make sure we don't use negative values. */
988 if (alloc.width < 0) {
991 if (alloc.height < 0) {
995 gtk_widget_size_allocate(priv->empty_label, &alloc);
997 if (!GTK_WIDGET_VISIBLE(priv->empty_label)) {
998 gtk_widget_show(priv->empty_label);
1005 /* As we have some items, hide label if it was visible. */
1006 if (priv->empty_label != NULL &&
1007 GTK_WIDGET_VISIBLE(priv->empty_label)) {
1008 gtk_widget_hide(priv->empty_label);
1011 priv->area_height = allocation->height;
1012 priv->area_rows = allocation->height / priv->item_height;
1014 /* Adjust/show/hide scrollbar. */
1015 adjust_scrollbar_height(grid);
1016 if (priv->old_item_height != priv->item_height) {
1017 priv->old_item_height = priv->item_height;
1018 jump_scrollbar_to_focused(grid);
1021 /* Update item width. */
1022 if (priv->num_columns == 1) {
1023 priv->item_width = allocation->width - priv->scrollbar_width -
1024 priv->h_margin - priv->scrollbar_width;
1026 priv->item_width = (allocation->width - priv->scrollbar_width) /
1031 (int) gtk_range_get_value(GTK_RANGE(priv->scrollbar)) /
1032 priv->item_height * priv->num_columns;
1034 /* Hide items before visible ones. */
1035 for (list = priv->children, child_no = 0;
1036 list != NULL && child_no < priv->first_index;
1037 list = list->next, child_no++) {
1038 child = ((HildonGridChild *) list->data)->widget;
1040 if (GTK_WIDGET_VISIBLE(child)) {
1041 gtk_widget_hide(child);
1045 /* Allocate visible items. */
1046 alloc.width = priv->item_width - priv->h_margin;
1047 switch (priv->label_pos) {
1048 case HILDON_GRID_ITEM_LABEL_POS_BOTTOM:
1049 row_margin = priv->icon_label_margin;
1051 case HILDON_GRID_ITEM_LABEL_POS_RIGHT:
1052 row_margin = priv->v_margin;
1058 alloc.height = priv->item_height - row_margin;
1060 for (y_offset = priv->first_index / priv->num_columns * priv->item_height;
1061 list != NULL && child_no < priv->first_index +
1062 priv->area_rows * priv->num_columns;
1063 list = list->next, child_no++) {
1064 child = ((HildonGridChild *) list->data)->widget;
1066 if (!GTK_WIDGET_VISIBLE(child)) {
1067 gtk_widget_show(child);
1070 /* Don't update icons which are not visible... */
1071 alloc.y = (child_no / priv->num_columns) * priv->item_height +
1072 allocation->y - y_offset + row_margin;
1073 alloc.x = (child_no % priv->num_columns) * priv->item_width +
1076 _hildon_grid_item_done_updating_settings(HILDON_GRID_ITEM(child));
1077 gtk_widget_size_allocate(child, &alloc);
1080 /* Hide items after visible items. */
1081 for (; list != NULL; list = list->next) {
1082 child = ((HildonGridChild *) list->data)->widget;
1084 if (GTK_WIDGET_VISIBLE(child)) {
1085 gtk_widget_hide(child);
1094 * @container: Container (#HildonGrid) to add HildonGridItem into.
1095 * @widget: #GtkWidget (#HildonGridItem) to add.
1097 * Adds a new HildonGridItem into HildonGrid.
1099 static void hildon_grid_add(GtkContainer * container, GtkWidget * widget)
1102 HildonGridPrivate *priv;
1103 HildonGridChild *child;
1106 g_return_if_fail(HILDON_IS_GRID(container));
1107 g_return_if_fail(HILDON_IS_GRID_ITEM(widget));
1109 grid = HILDON_GRID(container);
1110 priv = HILDON_GRID_GET_PRIVATE(HILDON_GRID(grid));
1111 GTK_WIDGET_SET_FLAGS(widget, GTK_NO_WINDOW);
1113 child = g_new(HildonGridChild, 1);
1114 if (child == NULL) {
1115 g_critical("no memory for child - not adding");
1118 child->widget = widget;
1120 _hildon_grid_item_set_label_pos (HILDON_GRID_ITEM(widget), priv->label_pos);
1121 _hildon_grid_item_set_focus_margin(HILDON_GRID_ITEM(widget), priv->focus_margin);
1122 _hildon_grid_item_set_icon_width (HILDON_GRID_ITEM(widget), priv->icon_width);
1123 _hildon_grid_item_set_emblem_size (HILDON_GRID_ITEM(widget), priv->emblem_size);
1125 /* Add the new item to the grid */
1126 priv->children = g_list_append(priv->children, child);
1127 gtk_widget_set_parent(widget, GTK_WIDGET(grid));
1129 /* Property changes (child's set_sensitive) */
1130 g_signal_connect_after(G_OBJECT(widget), "state-changed",
1131 G_CALLBACK(hildon_grid_state_changed), grid);
1133 /* Matches both empty grid and all-dimmed grid. */
1134 if (GTK_CONTAINER(grid)->focus_child == NULL)
1135 set_focus(grid, widget, TRUE);
1138 * If item was added in visible area, relocate items. Otherwise update
1139 * scrollbar and see if items need relocating.
1141 if (g_list_length(priv->children) < priv->first_index +
1142 priv->area_rows * priv->num_columns) {
1143 gtk_widget_queue_resize(GTK_WIDGET(grid));
1147 updated = adjust_scrollbar_height(grid);
1148 /* Basically this other test is useless -- shouldn't need to jump.
1150 updated |= jump_scrollbar_to_focused(grid);
1153 gtk_widget_queue_resize(GTK_WIDGET(grid));
1159 * hildon_grid_remove:
1160 * @container: Container (#HildonGrid) to remove #HildonGridItem from.
1161 * @widget: Widget (#HildonGridItem) to be removed.
1163 * Removes HildonGridItem from HildonGrid.
1166 hildon_grid_remove(GtkContainer * container, GtkWidget * widget)
1169 HildonGridPrivate *priv;
1170 HildonGridChild *child;
1171 GtkWidget *child_widget;
1173 gint index, old_index;
1177 g_return_if_fail(HILDON_IS_GRID(container));
1178 g_return_if_fail(HILDON_IS_GRID_ITEM(widget));
1180 grid = HILDON_GRID(container);
1181 priv = HILDON_GRID_GET_PRIVATE(container);
1183 old_index = priv->focus_index;
1184 updated = GTK_WIDGET_VISIBLE(widget);
1186 for (list = priv->children, index = 0, deleted = FALSE;
1187 list != NULL; list = list->next, index++) {
1188 child = (HildonGridChild *) list->data;
1189 child_widget = child->widget;
1191 /* Remove the Item if it is found in the grid */
1192 if (child_widget == widget) {
1193 gtk_widget_unparent(child_widget);
1194 priv->children = g_list_remove_link(priv->children, list);
1204 /* Emit warning if the item is not found */
1206 g_warning("tried to remove unexisting item");
1210 /* Move focus somewhere. */
1211 if (old_index == index) {
1212 if (old_index == g_list_length(priv->children)) {
1214 set_focus(grid, NULL, TRUE);
1217 get_child_by_index(priv, old_index - 1), TRUE);
1220 set_focus(grid, get_child_by_index(priv, old_index), TRUE);
1223 set_focus(grid, GTK_CONTAINER(grid)->focus_child, TRUE);
1226 updated |= adjust_scrollbar_height(grid);
1227 updated |= jump_scrollbar_to_focused(grid);
1230 gtk_widget_queue_resize(GTK_WIDGET(grid));
1235 * hildon_grid_set_focus_child:
1236 * @container: HildonGrid
1237 * @widget: HildonGridItem
1242 hildon_grid_set_focus_child(GtkContainer * container, GtkWidget * widget)
1245 HildonGridPrivate *priv;
1247 g_return_if_fail(HILDON_IS_GRID(container));
1248 g_return_if_fail(HILDON_IS_GRID_ITEM(widget) || widget == NULL);
1250 grid = HILDON_GRID(container);
1251 priv = HILDON_GRID_GET_PRIVATE(grid);
1253 if (GTK_CONTAINER(grid)->focus_child == widget || widget == NULL)
1256 set_focus(grid, widget, TRUE);
1262 set_focus(HildonGrid * grid, GtkWidget * widget, gboolean refresh_view)
1264 HildonGridPrivate *priv;
1265 GtkContainer *container;
1266 gboolean view_updated;
1269 priv = HILDON_GRID_GET_PRIVATE(grid);
1270 container = GTK_CONTAINER(grid);
1272 /* If widget is NULL -> unfocus */
1273 if (widget == NULL && container->focus_child != NULL)
1274 GTK_WIDGET_UNSET_FLAGS(container->focus_child, GTK_HAS_FOCUS);
1276 GTK_CONTAINER(grid)->focus_child = widget;
1277 if (widget == NULL) {
1278 priv->focus_index = -1;
1282 /* Get the child index which the user wanted to focus */
1283 priv->focus_index = get_child_index(priv, widget);
1285 gtk_widget_grab_focus(widget);
1288 view_updated = jump_scrollbar_to_focused(grid);
1290 view_updated = FALSE;
1294 hildon_grid_size_allocate(GTK_WIDGET(grid),
1295 >K_WIDGET(grid)->allocation);
1300 hildon_grid_forall(GtkContainer * container,
1301 gboolean include_internals,
1302 GtkCallback callback, gpointer callback_data)
1305 HildonGridPrivate *priv;
1308 g_return_if_fail(container);
1309 g_return_if_fail(callback);
1311 grid = HILDON_GRID(container);
1312 priv = HILDON_GRID_GET_PRIVATE(grid);
1314 /* Connect callback functions */
1315 if (include_internals) {
1316 if (priv->scrollbar != NULL) {
1317 (*callback) (priv->scrollbar, callback_data);
1319 if (priv->empty_label != NULL) {
1320 (*callback) (priv->empty_label, callback_data);
1324 for (list = priv->children; list != NULL; list = list->next) {
1325 (*callback) (((HildonGridChild *) list->data)->widget,
1330 static void hildon_grid_destroy(GtkObject * self)
1332 HildonGridPrivate *priv;
1334 g_return_if_fail(self != NULL);
1335 g_return_if_fail(HILDON_IS_GRID(self));
1337 priv = HILDON_GRID_GET_PRIVATE(self);
1339 if (GTK_WIDGET(self)->window != NULL) {
1340 g_object_unref(G_OBJECT(GTK_WIDGET(self)->window));
1343 gtk_container_forall(GTK_CONTAINER(self),
1344 (GtkCallback) gtk_object_ref, NULL);
1345 gtk_container_forall(GTK_CONTAINER(self),
1346 (GtkCallback) gtk_widget_unparent, NULL);
1348 GTK_OBJECT_CLASS(parent_class)->destroy(self);
1351 static void hildon_grid_finalize(GObject * object)
1354 HildonGridPrivate *priv;
1356 grid = HILDON_GRID(object);
1357 priv = HILDON_GRID_GET_PRIVATE(grid);
1359 gtk_container_forall(GTK_CONTAINER(object),
1360 (GtkCallback) gtk_object_unref, NULL);
1362 if (priv->style != NULL) {
1363 g_free(priv->style);
1365 if (G_OBJECT_CLASS(parent_class)->finalize) {
1366 G_OBJECT_CLASS(parent_class)->finalize(object);
1371 * hildon_grid_key_pressed:
1372 * @widget: Widget where we get the signal from
1374 * @data: #HildonGrid
1376 * Handle user key press (keyboard navigation).
1378 * And here's how it works if some items are dimmed (moving to right):
1379 * . . . . . . # . . 2 # . . # . .
1380 * . 1 # 2 . 1 # # 1 # # # 1 # # #
1381 * . . . . . 2 . . . . 2 .
1384 * '#' = dimmed item,
1385 * '1' = starting position,
1386 * '2' = final position
1388 * ...although only the first example is implemented right now.
1390 * Return value: Signal handled
1393 hildon_grid_key_pressed(GtkWidget * widget,
1394 GdkEventKey * event)
1396 GtkAdjustment *adjustment;
1397 GtkContainer *container;
1398 GtkWidget *new_focus;
1400 HildonGridPrivate *priv;
1405 gint child_count, child_rows;
1407 gint addition, max_add;
1409 g_return_val_if_fail(widget, FALSE);
1411 grid = HILDON_GRID(widget);
1412 priv = HILDON_GRID_GET_PRIVATE(grid);
1415 * If focus was never lost, we could just see if an item is focused -
1416 * if not, there's nothing else to focus...
1420 if (priv->children == NULL || g_list_length(priv->children) == 0)
1421 return GTK_WIDGET_CLASS (parent_class)->key_press_event (widget, event);
1423 /* Focused item is dimmed? */
1424 /* If we have no focus, allow non-existing focus to move... */
1425 container = GTK_CONTAINER(grid);
1426 if (container->focus_child != NULL
1427 && !GTK_WIDGET_IS_SENSITIVE(container->focus_child)) {
1428 return GTK_WIDGET_CLASS (parent_class)->key_press_event (widget, event);
1430 /* At the moment we don't want to do anything here if alt or control
1431 or MODX is pressed, so return now. Shift + TAB are accepted (from
1432 hildon-table-grid) And right now modifiers do not make any
1435 /* Said somewhere that "foo = a == b" is not desirable. */
1436 if (event->state & GDK_SHIFT_MASK) {
1442 keyval = event->keyval;
1443 if (gtk_widget_get_default_direction() == GTK_TEXT_DIR_RTL) {
1444 switch (event->keyval) {
1449 keyval = GDK_KP_Right;
1455 keyval = GDK_KP_Left;
1460 child_count = g_list_length(priv->children);
1461 child_rows = (child_count - 1) / priv->num_columns + 1;
1463 if (priv->focus_index != -1) {
1464 x = priv->focus_index % priv->num_columns;
1465 y = priv->focus_index / priv->num_columns;
1471 case GDK_KP_Page_Up:
1473 if (priv->first_index == 0) {
1474 if (priv->focus_index == 0) {
1477 set_focus(grid, get_child_by_index(priv, 0), TRUE);
1481 t = MAX(priv->first_index / priv->num_columns - priv->area_rows, 0);
1482 adjustment = gtk_range_get_adjustment(GTK_RANGE(priv->scrollbar));
1483 adjustment->value = (gdouble) (t * priv->item_height);
1484 gtk_range_set_adjustment(GTK_RANGE(priv->scrollbar), adjustment);
1485 gtk_widget_queue_draw(priv->scrollbar);
1486 update_contents(grid);
1488 /* Want to update now. */
1489 hildon_grid_size_allocate(GTK_WIDGET(grid),
1490 >K_WIDGET(grid)->allocation);
1495 case GDK_KP_Page_Down:
1497 if (priv->first_index / priv->num_columns ==
1498 child_rows - priv->area_rows) {
1499 if (priv->focus_index == child_count - 1) {
1502 set_focus(grid, get_child_by_index(priv, child_count - 1),
1507 t = MIN(priv->first_index / priv->num_columns +
1508 priv->area_rows, child_rows - priv->area_rows);
1509 adjustment = gtk_range_get_adjustment(GTK_RANGE(priv->scrollbar));
1510 adjustment->value = (gdouble) (t * priv->item_height);
1511 gtk_range_set_adjustment(GTK_RANGE(priv->scrollbar), adjustment);
1512 gtk_widget_queue_draw(priv->scrollbar);
1513 update_contents(grid);
1515 /* Want to update now. */
1516 hildon_grid_size_allocate(GTK_WIDGET(grid),
1517 >K_WIDGET(grid)->allocation);
1527 addition = -priv->num_columns;
1534 if (y >= (child_count - 1) / priv->num_columns) {
1537 t = child_count % priv->num_columns;
1539 t = priv->num_columns;
1541 if (y == (child_count - 1) / priv->num_columns - 1 && x >= t) {
1545 addition = priv->num_columns;
1546 max_add = child_rows - y;
1561 if (x >= priv->num_columns - 1) {
1564 if (y == 0 && x >= child_count - 1) {
1569 max_add = priv->num_columns - x;
1570 if (y * priv->num_columns + x == child_count) {
1576 hildon_grid_activate_child(grid,
1578 (GTK_CONTAINER(grid)->focus_child));
1582 return GTK_WIDGET_CLASS (parent_class)->key_press_event (widget, event);
1586 focus_index = y * priv->num_columns + x;
1587 new_focus = get_child_by_index(priv, focus_index);
1589 while (new_focus != NULL &&
1590 focus_index < child_count && !GTK_WIDGET_SENSITIVE(new_focus)) {
1596 focus_index += addition;
1597 new_focus = get_child_by_index(priv, focus_index);
1600 if (new_focus != NULL) {
1601 set_focus(grid, new_focus, TRUE);
1608 * hildon_grid_button_pressed:
1609 * @widget: Widget where signal is coming from
1610 * @event: #EventButton
1611 * @data: #HildonGrid
1613 * Handle mouse button press.
1615 * Return value: Signal handled
1618 hildon_grid_button_pressed(GtkWidget * widget,
1619 GdkEventButton * event)
1622 HildonGridPrivate *priv;
1626 grid = HILDON_GRID(widget);
1627 priv = HILDON_GRID_GET_PRIVATE(grid);
1629 /* Watch out for double/triple click press events */
1631 if (event->type == GDK_2BUTTON_PRESS ||
1632 event->type == GDK_3BUTTON_PRESS) {
1633 priv->last_button_event = event->type;
1637 priv->last_button_event = event->type;
1639 if (event->type != GDK_BUTTON_PRESS)
1643 child_no = get_child_index_by_coord(priv, event->x, event->y);
1645 if (child_no == -1 || child_no >= g_list_length(priv->children))
1648 child = get_child_by_index(priv, child_no);
1649 if (!GTK_WIDGET_IS_SENSITIVE(child))
1652 set_focus(grid, child, TRUE);
1654 priv->click_x = event->x;
1655 priv->click_y = event->y;
1661 * hildon_grid_button_released:
1662 * @widget: Widget the signal is coming from
1663 * @event: #EventButton
1664 * @data: #HildonGrid
1666 * Handle mouse button release.
1668 * Return value: Signal handled
1671 hildon_grid_button_released(GtkWidget * widget,
1672 GdkEventButton * event)
1675 HildonGridPrivate *priv;
1679 grid = HILDON_GRID(widget);
1680 priv = HILDON_GRID_GET_PRIVATE(grid);
1682 /* In case of double/triple click, silently ignore the release event */
1684 if (priv->last_button_event == GDK_2BUTTON_PRESS ||
1685 priv->last_button_event == GDK_3BUTTON_PRESS) {
1686 priv->last_button_event = event->type;
1690 child_no = get_child_index_by_coord(priv, event->x, event->y);
1692 if (child_no == -1 || child_no >= g_list_length(priv->children)) {
1695 child = get_child_by_index(priv, child_no);
1696 if (!GTK_WIDGET_IS_SENSITIVE(child)) {
1699 if (abs(priv->click_x - event->x) >= DRAG_SENSITIVITY
1700 && abs(priv->click_y - event->y) >= DRAG_SENSITIVITY) {
1703 set_focus(grid, child, TRUE);
1704 priv->last_button_event = event->type;
1705 hildon_grid_activate_child(grid, HILDON_GRID_ITEM(child));
1711 * hildon_grid_scrollbar_moved:
1712 * @widget: Widget which sent the signal
1713 * @data: #HildonGrid
1715 * Update HildonGrid contents when scrollbar is moved.
1717 * Return value: Signal handeld
1720 hildon_grid_scrollbar_moved(GtkWidget * widget, gpointer data)
1723 HildonGridPrivate *priv;
1724 gboolean updated = FALSE;
1726 grid = HILDON_GRID(data);
1727 priv = HILDON_GRID_GET_PRIVATE(grid);
1728 updated = update_contents(grid);
1731 * If grid changes focus while dragging scrollbar and pointer leaves
1732 * scrollbar, focus is moved to prev_focus... This prevents that.
1734 gtk_window_set_prev_focus_widget(GTK_WINDOW
1735 (gtk_widget_get_toplevel(widget)),
1736 GTK_CONTAINER(grid)->focus_child);
1739 /* Don't just queue it, let's do it now! */
1740 hildon_grid_size_allocate(GTK_WIDGET(grid),
1741 >K_WIDGET(grid)->allocation);
1749 * @grid: #HildonGrid
1751 * Update the view if scrollbar has moved so that first visible row
1752 * should've changed. Returns true if location actually changed.
1754 * Return value: Content changed
1756 static gboolean update_contents(HildonGrid * grid)
1758 HildonGridPrivate *priv;
1761 priv = HILDON_GRID_GET_PRIVATE(grid);
1762 new_row = (int) gtk_range_get_value(GTK_RANGE(priv->scrollbar))
1763 / priv->item_height;
1765 if (new_row != priv->old_sb_pos) {
1766 priv->old_sb_pos = new_row;
1767 priv->first_index = new_row * priv->num_columns;
1775 * jump_scrollbar_to_focused:
1776 * @grid: #HildonGrid
1778 * Moves scrollbar position so that focused item will be shown
1780 * Returns TRUE if visible position of widgets have changed.
1782 * Return value: Content changed
1784 static gboolean jump_scrollbar_to_focused(HildonGrid * grid)
1786 HildonGridPrivate *priv;
1787 GtkAdjustment *adjustment;
1792 priv = HILDON_GRID_GET_PRIVATE(grid);
1793 /* If we don't have scrollbar, let the focus be broken, too. */
1794 g_return_val_if_fail(priv->scrollbar != NULL, FALSE);
1796 /* Make sure "first widget" is something sensible. */
1797 priv->first_index = priv->first_index -
1798 priv->first_index % priv->num_columns;
1800 child_count = g_list_length(priv->children);
1801 empty_grids = priv->num_columns * priv->area_rows - child_count +
1804 /* Determine the position of the new row */
1805 if (priv->focus_index < priv->first_index) {
1806 new_row = priv->focus_index / priv->num_columns;
1807 } else if (priv->focus_index >= priv->first_index +
1808 priv->area_rows * priv->num_columns) {
1810 new_row = priv->focus_index / priv->num_columns -
1811 priv->area_rows + 1;
1812 last_top_row = child_count / priv->num_columns - priv->area_rows + 1;
1813 if (child_count % priv->num_columns != 0) {
1816 if (new_row > last_top_row) {
1817 new_row = last_top_row;
1819 } else if (empty_grids >= priv->num_columns) {
1820 new_row = ((child_count - 1) / priv->num_columns + 1)
1829 /* Move scrollbar accordingly. */
1830 adjustment = gtk_range_get_adjustment(GTK_RANGE(priv->scrollbar));
1831 adjustment->value = (gdouble) (new_row * priv->item_height);
1832 gtk_range_set_adjustment(GTK_RANGE(priv->scrollbar), adjustment);
1833 priv->first_index = new_row * priv->num_columns;
1834 priv->old_sb_pos = new_row;
1836 gtk_widget_queue_draw(priv->scrollbar);
1843 * adjust_scrollbar_height:
1844 * @grid: HildonGridPrivate
1846 * Return value: View should change
1848 * Adjust scrollbar according the #HildonGrid contents.
1849 * Show/hide scrollbar if
1850 * appropriate. Also sets priv->first_index.
1852 static gboolean adjust_scrollbar_height(HildonGrid * grid)
1854 HildonGridPrivate *priv;
1857 GtkAllocation alloc;
1858 GtkAllocation *gridalloc;
1864 priv = HILDON_GRID_GET_PRIVATE(grid);
1865 g_return_val_if_fail(priv->scrollbar != NULL, FALSE);
1868 gridalloc = >K_WIDGET(grid)->allocation;
1870 /* See if we need scrollbar at all. */
1871 if (priv->num_columns == 0) {
1872 priv->num_columns = DEFAULT_N_COLUMNS;
1874 priv->num_columns = MAX(1, priv->num_columns);
1877 if (g_list_length(priv->children) != 0) {
1878 need_rows = (g_list_length(priv->children) - 1) /
1879 priv->num_columns + 1;
1884 if (need_rows <= priv->area_rows) {
1885 updated = priv->first_index != 0;
1886 priv->scrollbar_width = 0;
1888 priv->first_index = 0;
1889 if (GTK_WIDGET_VISIBLE(priv->scrollbar)) {
1890 gtk_widget_hide(priv->scrollbar);
1897 /* All right then, we need scrollbar. Place scrollbar on the screen. */
1898 gtk_widget_get_child_requisition(priv->scrollbar, &req);
1899 priv->scrollbar_width = req.width;
1901 alloc.width = req.width;
1902 alloc.height = gridalloc->height;
1903 alloc.x = gridalloc->width - req.width + gridalloc->x;
1904 alloc.y = gridalloc->y;
1905 gtk_widget_size_allocate(priv->scrollbar, &alloc);
1907 if (!GTK_WIDGET_VISIBLE(priv->scrollbar)) {
1908 gtk_widget_show(priv->scrollbar);
1913 need_pixels = need_rows * priv->item_height;
1915 /* Once we know how much space we need, update the scrollbar. */
1916 adj = gtk_range_get_adjustment(GTK_RANGE(priv->scrollbar));
1917 old_upper = (int) adj->upper;
1919 adj->upper = (gdouble) need_pixels;
1920 adj->step_increment = (gdouble) priv->item_height;
1921 adj->page_increment = (gdouble) (priv->area_rows * priv->item_height);
1923 (gdouble) (priv->area_height - priv->area_height % priv->item_height);
1925 /* Also update position if needed to show focused item. */
1927 gtk_range_set_adjustment(GTK_RANGE(priv->scrollbar), adj);
1929 /* Then set first_index. */
1930 priv->first_index = (int) adj->value / priv->item_height *
1933 /* Finally, ask Gtk to redraw the scrollbar. */
1934 if (old_upper != (int) adj->upper) {
1935 gtk_widget_queue_draw(priv->scrollbar);
1941 * get_child_index_by_coord:
1942 * @priv: HildonGridPrivate
1946 * Returns index of child at given coordinates, -1 if no child.
1948 * Return value: Index
1951 get_child_index_by_coord(HildonGridPrivate * priv, gint x, gint y)
1956 xgap = x % priv->item_width;
1957 ygap = y % priv->item_height;
1959 if (xgap > priv->item_width - priv->h_margin) { /*FIXME*/
1963 t = y / priv->item_height * priv->num_columns +
1964 x / priv->item_width + priv->first_index;
1966 if (t >= priv->first_index + priv->area_rows * priv->num_columns ||
1967 t >= g_list_length(priv->children) || t < 0) {
1974 * get_child_by_index:
1975 * @priv: HildonGridPrivate
1976 * @index: Index of child
1978 * Returns child that is #th in HildonGrid or NULL if child was not found
1979 * among the children.
1981 * Return value: GtkWidget
1983 static GtkWidget *get_child_by_index(HildonGridPrivate * priv, gint index)
1988 if (index >= g_list_length(priv->children) || index < 0) {
1991 for (list = priv->children, i = 0; list != NULL;
1992 list = list->next, i++) {
1994 return ((HildonGridChild *) list->data)->widget;
1998 g_warning("no such child");
2004 * @priv: HildonGridPrivate
2005 * @child: #GtkWidget to look for
2007 * Returns index of a child or -1 if child was not found among the
2010 * Return value: Index
2012 static gint get_child_index(HildonGridPrivate * priv, GtkWidget * child)
2020 for (list = priv->children, index = 0;
2021 list != NULL; list = list->next, index++) {
2022 if (((HildonGridChild *) list->data)->widget == child) {
2027 g_warning("no such child");
2033 * hildon_grid_activate_child:
2034 * @grid: #HildonGrid
2035 * @item: #HildonGridItem
2037 * Emits a signal to tell HildonGridItem was actiavated.
2039 void hildon_grid_activate_child(HildonGrid * grid, HildonGridItem * item)
2041 g_return_if_fail(HILDON_IS_GRID(grid));
2043 g_signal_emit(grid, grid_signals[ACTIVATE_CHILD], 0, item);
2049 * hildon_grid_set_style:
2050 * @grid: #HildonGrid
2051 * @style_name: Style name
2053 * Sets style. Setting style sets widget size, spacing, label position,
2054 * number of columns, and icon size.
2056 void hildon_grid_set_style(HildonGrid * grid, const gchar * style_name)
2058 HildonGridPrivate *priv;
2060 g_return_if_fail(HILDON_IS_GRID(grid));
2063 priv = HILDON_GRID_GET_PRIVATE(grid);
2064 if (priv->style != NULL) {
2065 g_free((gpointer) priv->style);
2067 if (style_name != NULL) {
2068 priv->style = g_strdup(style_name);
2073 gtk_widget_set_name(GTK_WIDGET(grid), style_name);
2074 get_style_properties(grid);
2076 gtk_widget_queue_resize(GTK_WIDGET(grid));
2080 * hildon_grid_get_style:
2081 * @grid: #HildonGrid
2083 * Returns the name of style currently used in HildonGrid.
2085 * Return value: Style name
2087 const gchar *hildon_grid_get_style(HildonGrid * grid)
2089 g_return_val_if_fail(HILDON_IS_GRID(grid), NULL);
2091 return gtk_widget_get_name(GTK_WIDGET(grid));
2095 * get_style_properties:
2096 * @grid: #HildonGrid
2098 * Gets widget size and other stuff from gtkrc. If some stuff changed, let
2099 * children know this, too.
2101 static void get_style_properties(HildonGrid * grid)
2105 HildonGridPositionType label_pos;
2108 gint h_margin, v_margin;
2111 gint focus_margin, icon_label_margin;
2114 HildonGridPrivate *priv;
2115 g_return_if_fail(HILDON_IS_GRID(grid));
2116 priv = HILDON_GRID_GET_PRIVATE(grid);
2118 gtk_widget_style_get(GTK_WIDGET(grid),
2119 "item_hspacing", &h_margin,
2120 "item_vspacing", &v_margin,
2121 "item_height", &item_height,
2122 "icon_size", &icon_width,
2123 "n_columns", &num_columns,
2124 "label_pos", &label_pos,
2125 "label_hspacing", &focus_margin,
2126 "label_vspacing", &icon_label_margin,
2127 "emblem_size", &emblem_size,
2128 "label_height", &label_height,
2131 hildon_grid_set_icon_width(grid, icon_width);
2132 hildon_grid_set_num_columns(grid, num_columns);
2133 hildon_grid_set_label_pos(grid, label_pos);
2134 hildon_grid_set_focus_margin(grid, focus_margin);
2135 hildon_grid_set_icon_label_margin(grid, icon_label_margin);
2136 hildon_grid_set_emblem_size(grid, emblem_size);
2137 hildon_grid_set_label_height(grid, label_height);
2139 priv->h_margin = h_margin;
2140 priv->v_margin = v_margin;
2141 priv->item_height = item_height;
2145 for (iter = priv->children; iter != NULL; iter = iter->next) {
2146 HildonGridItem *child;
2147 child = HILDON_GRID_ITEM(((HildonGridChild *) iter->data)->widget);
2148 _hildon_grid_item_done_updating_settings(child);
2156 * hildon_grid_set_scrollbar_pos:
2157 * @grid: #HildonGrid
2158 * @scrollbar_pos: new position (in pixels)
2160 * Sets view (scrollbar) to specified position.
2162 void hildon_grid_set_scrollbar_pos(HildonGrid * grid, gint scrollbar_pos)
2164 HildonGridPrivate *priv;
2165 GtkAdjustment *adjustment;
2167 g_return_if_fail(HILDON_IS_GRID(grid));
2169 priv = HILDON_GRID_GET_PRIVATE(grid);
2170 adjustment = gtk_range_get_adjustment(GTK_RANGE(priv->scrollbar));
2171 adjustment->value = (gdouble) scrollbar_pos;
2173 gtk_range_set_adjustment(GTK_RANGE(priv->scrollbar), adjustment);
2175 g_object_notify (G_OBJECT (grid), "scrollbar-position");
2177 /* If grid isn't drawable, updating anything could mess up focus. */
2178 if (!GTK_WIDGET_DRAWABLE(GTK_WIDGET(grid)))
2181 update_contents(grid);
2185 * hildon_grid_get_scrollbar_pos:
2186 * @grid: #HildonGrid
2188 * Returns position of scrollbar (in pixels).
2190 * Return value: Scrollbar position
2192 gint hildon_grid_get_scrollbar_pos(HildonGrid * grid)
2194 GtkAdjustment *adjustment;
2196 g_return_val_if_fail(HILDON_IS_GRID(grid), -1);
2198 adjustment = gtk_range_get_adjustment(GTK_RANGE
2199 (HILDON_GRID_GET_PRIVATE
2200 (grid)->scrollbar));
2201 return (int) adjustment->value;
2205 hildon_grid_set_property(GObject * object,
2207 const GValue * value, GParamSpec * pspec)
2211 grid = HILDON_GRID(object);
2214 case PROP_EMPTY_LABEL:
2215 hildon_grid_set_empty_label(grid, g_value_get_string(value));
2219 hildon_grid_set_style(grid, g_value_get_string(value));
2222 case PROP_SCROLLBAR_POS:
2223 hildon_grid_set_scrollbar_pos(grid, g_value_get_int(value));
2227 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
2233 hildon_grid_get_property(GObject * object,
2234 guint prop_id, GValue * value, GParamSpec * pspec)
2238 grid = HILDON_GRID(object);
2241 case PROP_EMPTY_LABEL:
2242 g_value_set_string(value, hildon_grid_get_empty_label(grid));
2246 g_value_set_string(value, hildon_grid_get_style(grid));
2249 case PROP_SCROLLBAR_POS:
2250 g_value_set_int(value, hildon_grid_get_scrollbar_pos(grid));
2254 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
2260 hildon_grid_state_changed(GtkWidget * widget,
2261 GtkStateType state, gpointer data)
2264 HildonGridPrivate *priv;
2267 GtkWidget *prev_focusable, *next_focusable;
2270 g_return_val_if_fail(HILDON_IS_GRID(data), FALSE);
2271 g_return_val_if_fail(HILDON_IS_GRID_ITEM(widget), FALSE);
2273 grid = HILDON_GRID(data);
2274 priv = HILDON_GRID_GET_PRIVATE(grid);
2277 if (GTK_WIDGET_IS_SENSITIVE(widget))
2280 prev_focusable = next_focusable = NULL;
2283 for (list = priv->children; list != NULL; list = list->next) {
2284 current = ((HildonGridChild *) list->data)->widget;
2286 if (GTK_WIDGET_IS_SENSITIVE(current)) {
2288 next_focusable = current;
2291 prev_focusable = current;
2293 } else if (current == widget) {
2298 if (next_focusable == NULL) {
2299 next_focusable = prev_focusable;
2302 gtk_container_set_focus_child(GTK_CONTAINER(grid), next_focusable);
2310 hildon_grid_tap_and_hold_setup(GtkWidget * widget,
2313 GtkWidgetTapAndHoldFlags flags)
2315 g_return_if_fail(HILDON_IS_GRID(widget) && GTK_IS_MENU(menu));
2317 parent_class->parent_class.tap_and_hold_setup
2318 (widget, menu, func, flags | GTK_TAP_AND_HOLD_NO_INTERNALS);