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
27 * @short_description: Being used where ever a number of single tap
28 * activatable items need to be presented (e.g. Control Panel applets)
29 * @see_also: #HildonGridItem
31 * HildonGrid is a set of application-defineable items that are presented in a
32 * table. There are two modes for the form of the table; large icon mode
33 * and small icon mode.
35 * In large icon mode, the Grid View items are presented with a large
36 * icon and a label under it. In small icon mode, the items are
37 * presented with a small icon and a label on the right side of the
40 * The label has a solid background as wide as the maximum text width.
41 * This allows the text to have focus as well as be legible when
42 * displayed upon a black or dark background image. Long names are
43 * truncated with an ellipsis ("...") appended.
48 * - there must be a predefined place for the "no items" -label...
50 * - dimmed items & scrolling by scrollbar
62 #include <gtk/gtklabel.h>
63 #include <gtk/gtkrange.h>
64 #include <gtk/gtkvscrollbar.h>
65 #include <gtk/gtkmain.h>
66 #include <gtk/gtkwidget.h>
67 #include <gtk/gtkenums.h>
68 #include <gdk/gdkkeysyms.h>
70 #include "hildon-grid-item-private.h"
71 #include "hildon-marshalers.h"
72 #include <hildon-widgets/hildon-grid.h>
73 #include <hildon-widgets/hildon-grid-item.h>
76 #define _(String) dgettext(PACKAGE, String)
78 #define HILDON_GRID_GET_PRIVATE(obj) \
79 (G_TYPE_INSTANCE_GET_PRIVATE ((obj), HILDON_TYPE_GRID, \
83 #define DEFAULT_STYLE "largeicons-home"
85 #define DEFAULT_N_COLUMNS 3
86 #define GRID_LABEL_POS_PAD 16
88 #define DRAG_SENSITIVITY 6
105 typedef struct _HildonGridChild HildonGridChild;
106 typedef struct _HildonGridPrivate HildonGridPrivate;
109 struct _HildonGridChild {
114 struct _HildonGridPrivate {
116 GtkWidget *scrollbar;
118 GdkWindow *event_window;
122 GtkWidget *empty_label;
129 gint icon_label_margin;
132 HildonGridPositionType label_pos;
139 /* Handy variables outsize _allocate. */
142 gint scrollbar_width;
145 GdkEventType last_button_event;
146 gint old_item_height;
152 static void hildon_grid_class_init(HildonGridClass * klass);
153 static void hildon_grid_init(HildonGrid * grid);
154 static void hildon_grid_realize(GtkWidget * widget);
155 static void hildon_grid_unrealize(GtkWidget * widget);
156 static void hildon_grid_map(GtkWidget * widget);
157 static void hildon_grid_unmap(GtkWidget * widget);
158 static gboolean hildon_grid_expose(GtkWidget * widget,
159 GdkEventExpose * event);
160 static void hildon_grid_size_request(GtkWidget * widget,
161 GtkRequisition * requisition);
162 static void hildon_grid_size_allocate(GtkWidget * widget,
163 GtkAllocation * allocation);
164 static void hildon_grid_add(GtkContainer * container, GtkWidget * widget);
165 static void hildon_grid_remove(GtkContainer * container,
167 static void hildon_grid_set_focus_child(GtkContainer * container,
169 static void hildon_grid_forall(GtkContainer * container,
170 gboolean include_internals,
171 GtkCallback callback,
172 gpointer callback_data);
173 static void hildon_grid_tap_and_hold_setup(GtkWidget * widget,
176 GtkWidgetTapAndHoldFlags flags);
178 static GType hildon_grid_child_type(GtkContainer * container);
181 static void hildon_grid_set_property(GObject * object,
183 const GValue * value,
185 static void hildon_grid_get_property(GObject * object,
187 GValue * value, GParamSpec * pspec);
189 static void hildon_grid_set_empty_label(HildonGrid *grid,
190 const gchar *empty_label);
191 static const gchar *hildon_grid_get_empty_label(HildonGrid * grid);
192 static void hildon_grid_set_num_columns(HildonGrid *grid, gint num_cols);
193 static void hildon_grid_set_label_pos(HildonGrid *grid,
194 HildonGridPositionType label_pos);
195 static void hildon_grid_set_focus_margin(HildonGrid *grid,
197 static void hildon_grid_set_icon_label_margin(HildonGrid *grid,
198 gint icon_label_margin);
199 static void hildon_grid_set_icon_width(HildonGrid *grid, gint icon_width);
200 static void hildon_grid_set_emblem_size(HildonGrid *grid, gint emblem_size);
201 static void hildon_grid_set_label_height(HildonGrid *grid,
203 static void hildon_grid_destroy(GtkObject * self);
204 static void hildon_grid_finalize(GObject * object);
206 /* Signal handlers. */
207 static gboolean hildon_grid_button_pressed(GtkWidget * widget,
208 GdkEventButton * event);
209 static gboolean hildon_grid_button_released(GtkWidget * widget,
210 GdkEventButton * event);
211 static gboolean hildon_grid_key_pressed(GtkWidget * widget,
212 GdkEventKey * event);
213 static gboolean hildon_grid_scrollbar_moved(GtkWidget * widget,
215 static gboolean hildon_grid_state_changed(GtkWidget * widget,
219 /* Other internal functions. */
220 static void get_style_properties(HildonGrid * grid);
221 static gint get_child_index(HildonGridPrivate * priv, GtkWidget * child);
222 static gint get_child_index_by_coord(HildonGridPrivate * priv,
224 static GtkWidget *get_child_by_index(HildonGridPrivate * priv, gint index);
226 static gboolean jump_scrollbar_to_focused(HildonGrid * grid);
227 static gboolean adjust_scrollbar_height(HildonGrid * grid);
228 static gboolean update_contents(HildonGrid * grid);
229 static void set_focus(HildonGrid * grid,
230 GtkWidget * widget, gboolean refresh_view);
232 static GtkContainerClass *parent_class = NULL;
233 static guint grid_signals[LAST_SIGNAL] = { 0 };
236 GType hildon_grid_get_type(void)
238 static GType grid_type = 0;
241 static const GTypeInfo grid_info = {
242 sizeof(HildonGridClass),
243 NULL, /* base_init */
244 NULL, /* base_finalize */
245 (GClassInitFunc) hildon_grid_class_init,
246 NULL, /* class_finalize */
247 NULL, /* class_data */
250 (GInstanceInitFunc) hildon_grid_init,
252 grid_type = g_type_register_static(GTK_TYPE_CONTAINER,
253 "HildonGrid", &grid_info, 0);
261 static void hildon_grid_class_init(HildonGridClass * klass)
263 GObjectClass *gobject_class;
264 GtkWidgetClass *widget_class;
265 GtkContainerClass *container_class;
267 widget_class = GTK_WIDGET_CLASS(klass);
268 container_class = GTK_CONTAINER_CLASS(klass);
269 gobject_class = G_OBJECT_CLASS(klass);
271 parent_class = g_type_class_peek_parent(klass);
273 g_type_class_add_private(klass, sizeof(HildonGridPrivate));
275 GTK_OBJECT_CLASS(klass)->destroy = hildon_grid_destroy;
276 gobject_class->finalize = hildon_grid_finalize;
277 gobject_class->set_property = hildon_grid_set_property;
278 gobject_class->get_property = hildon_grid_get_property;
280 widget_class->realize = hildon_grid_realize;
281 widget_class->unrealize = hildon_grid_unrealize;
282 widget_class->map = hildon_grid_map;
283 widget_class->unmap = hildon_grid_unmap;
284 widget_class->expose_event = hildon_grid_expose;
285 widget_class->size_request = hildon_grid_size_request;
286 widget_class->size_allocate = hildon_grid_size_allocate;
287 widget_class->tap_and_hold_setup = hildon_grid_tap_and_hold_setup;
288 widget_class->key_press_event = hildon_grid_key_pressed;
289 widget_class->button_press_event = hildon_grid_button_pressed;
290 widget_class->button_release_event = hildon_grid_button_released;
292 container_class->add = hildon_grid_add;
293 container_class->remove = hildon_grid_remove;
294 container_class->forall = hildon_grid_forall;
295 container_class->child_type = hildon_grid_child_type;
296 container_class->set_focus_child = hildon_grid_set_focus_child;
298 /* Install properties to the class */
299 g_object_class_install_property(gobject_class, PROP_EMPTY_LABEL,
300 g_param_spec_string("empty_label",
302 "Label to show when grid has no items",
303 _("Ckct_wi_grid_no_items"), G_PARAM_READWRITE));
305 g_object_class_install_property(gobject_class, PROP_STYLE,
306 g_param_spec_string("style",
308 "Widget's Style. Setting style sets widget size, "
309 "spacing, label position, number of columns, "
310 "and icon sizeLabel to show when grid has no items",
311 DEFAULT_STYLE, G_PARAM_READWRITE));
313 g_object_class_install_property(gobject_class, PROP_SCROLLBAR_POS,
314 g_param_spec_int("scrollbar-position",
315 "Scrollbar Position",
316 "View (scrollbar) position.",
317 G_MININT, G_MAXINT, 0, G_PARAM_READWRITE));
319 gtk_widget_class_install_style_property(widget_class,
320 g_param_spec_uint("item_width",
322 "Total width of an item (obsolete)",
323 1, G_MAXINT, 212, G_PARAM_READABLE));
325 gtk_widget_class_install_style_property(widget_class,
326 g_param_spec_uint("item_height",
328 "Total height of an item",
329 1, G_MAXINT, 96, G_PARAM_READABLE));
331 gtk_widget_class_install_style_property(widget_class,
332 g_param_spec_uint("item_hspacing",
333 "Item horizontal spacing",
334 "Margin between two columns and labels",
335 0, G_MAXINT, 12, G_PARAM_READABLE));
337 gtk_widget_class_install_style_property(widget_class,
338 g_param_spec_uint("item_vspacing",
339 "Item vertical spacing",
340 "Icon on right: Margin between rows / Icon at bottom: Vertical margin betweeb label and icon",
341 0, G_MAXINT, 6, G_PARAM_READABLE));
343 gtk_widget_class_install_style_property(widget_class,
344 g_param_spec_uint("label_hspacing",
346 "Margin between focus edge and item edge",
347 0, G_MAXINT, 6, G_PARAM_READABLE));
349 gtk_widget_class_install_style_property(widget_class,
350 g_param_spec_uint("label_vspacing",
351 "Vertical label spacing",
352 "Vertical margin between item and label",
353 0, G_MAXINT, 6, G_PARAM_READABLE));
355 gtk_widget_class_install_style_property(widget_class,
356 g_param_spec_uint("label_height",
358 "Height of icon label",
359 1, G_MAXINT, 30, G_PARAM_READABLE));
361 gtk_widget_class_install_style_property(widget_class,
362 g_param_spec_uint("n_columns",
365 0, G_MAXINT, DEFAULT_N_COLUMNS, G_PARAM_READABLE));
367 gtk_widget_class_install_style_property(widget_class,
368 g_param_spec_uint("label_pos",
370 "Position of label related to the icon",
371 1, 2, 1, G_PARAM_READABLE));
373 gtk_widget_class_install_style_property(widget_class,
374 g_param_spec_uint("icon_size",
376 "Size of the icon in pixels (width)",
377 1, G_MAXINT, 64, G_PARAM_READABLE));
379 gtk_widget_class_install_style_property(widget_class,
380 g_param_spec_uint("emblem_size",
382 "Size of the emblem in pixels",
383 1, G_MAXINT, 25, G_PARAM_READABLE));
386 * HildonGrid::activate-child:
388 * Emitted when a child (@HildonGridItem) is activated either by
389 * tapping on it or by pressing enter.
391 grid_signals[ACTIVATE_CHILD] =
392 g_signal_new("activate-child",
393 G_OBJECT_CLASS_TYPE(gobject_class),
394 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
395 G_STRUCT_OFFSET(HildonGridClass, activate_child),
397 g_cclosure_marshal_VOID__OBJECT,
398 G_TYPE_NONE, 1, HILDON_TYPE_GRID_ITEM);
401 * HildonGrid::popup-context-menu:
403 * Emitted when popup-menu is supposed to open. Used for tap-and-hold.
405 grid_signals[POPUP_CONTEXT] =
406 g_signal_new("popup-context-menu",
407 G_OBJECT_CLASS_TYPE(gobject_class),
409 G_STRUCT_OFFSET(HildonGridClass, popup_context_menu),
410 g_signal_accumulator_true_handled, NULL,
411 _hildon_marshal_BOOLEAN__INT_INT_INT,
413 G_TYPE_INT, G_TYPE_INT, G_TYPE_INT);
419 * hildon_grid_set_empty_label:
421 * @empty_label: New label
426 hildon_grid_set_empty_label(HildonGrid * grid, const gchar * empty_label)
428 /* No need to worry about update -- label receives a signal for it. */
429 gtk_label_set_label(GTK_LABEL(HILDON_GRID_GET_PRIVATE
430 (grid)->empty_label),
431 empty_label == NULL ? "" : empty_label);
435 * _hildon_grid_get_empty_label:
438 * Returns: empty label. Label must not be modified nor freed.
441 hildon_grid_get_empty_label(HildonGrid * grid)
443 return gtk_label_get_label(GTK_LABEL(HILDON_GRID_GET_PRIVATE
444 (grid)->empty_label));
448 * hildon_grid_set_num_columns:
450 * @columns: Number of columns
452 * Sets number of columns.
455 hildon_grid_set_num_columns(HildonGrid * grid, gint columns)
457 HildonGridPrivate *priv;
459 g_return_if_fail(HILDON_IS_GRID(grid));
460 priv = HILDON_GRID_GET_PRIVATE(grid);
462 if (priv->num_columns == columns) {
467 priv->num_columns = columns;
469 priv->num_columns = DEFAULT_N_COLUMNS;
471 /* Update estimated row-count for jump_scrollbar... */
472 priv->area_rows = MAX(priv->area_height / priv->num_columns, 1);
474 /* Size could have changed. Scroll view so there's something to show. */
475 adjust_scrollbar_height(grid);
476 jump_scrollbar_to_focused(grid);
477 gtk_widget_queue_resize(GTK_WIDGET(grid));
481 * hildon_grid_set_label_pos:
483 * @label_pos: Label position
485 * Sets icon label position.
488 hildon_grid_set_label_pos(HildonGrid * grid,
489 HildonGridPositionType label_pos)
491 HildonGridPrivate *priv;
495 priv = HILDON_GRID_GET_PRIVATE(grid);
497 if (label_pos == priv->label_pos)
500 /* gtknotebook doesn't check if we use valid values -- why should
503 priv->label_pos = label_pos;
505 /* Set label position to each HildonGridItem */
506 for (list = priv->children; list != NULL; list = list->next) {
507 child = ((HildonGridChild *) list->data)->widget;
509 _hildon_grid_item_set_label_pos(HILDON_GRID_ITEM(child),
515 * hildon_grid_set_focus_margin:
517 * @focus_margin: Focus margin
519 * Sets margin between icon edge and label edge
522 hildon_grid_set_focus_margin(HildonGrid *grid, gint focus_margin)
524 HildonGridPrivate *priv;
528 priv = HILDON_GRID_GET_PRIVATE(grid);
529 if (focus_margin == priv->focus_margin)
532 priv->focus_margin = focus_margin;
534 /* Update children. */
535 for (list = priv->children; list != NULL; list = list->next) {
536 child = ((HildonGridChild *) list->data)->widget;
538 _hildon_grid_item_set_focus_margin(HILDON_GRID_ITEM(child),
545 * hildon_grid_set_icon_label_margin:
547 * @hspacing: Vertical spacing
549 * Sets vertical spacing for label.
553 hildon_grid_set_icon_label_margin(HildonGrid *grid, gint icon_label_margin)
555 HildonGridPrivate *priv;
557 priv = HILDON_GRID_GET_PRIVATE(grid);
558 if (icon_label_margin == priv->icon_label_margin)
561 priv->icon_label_margin = icon_label_margin;
566 * hildon_grid_set_icon_width:
568 * @icon_size: Icon size (width)
570 * Sets icon size (in pixels).
573 hildon_grid_set_icon_width(HildonGrid * grid, gint icon_width)
575 HildonGridPrivate *priv;
579 priv = HILDON_GRID_GET_PRIVATE(grid);
581 if (icon_width == priv->icon_width)
584 priv->icon_width = icon_width;
586 for (list = priv->children; list != NULL; list = list->next) {
587 child = ((HildonGridChild *) list->data)->widget;
589 _hildon_grid_item_set_icon_width(HILDON_GRID_ITEM(child),
596 * hildon_grid_set_emblem_size:
598 * @emblem_size: Emblem size
600 * Sets emblem size (in pixels).
603 hildon_grid_set_emblem_size(HildonGrid *grid, gint emblem_size)
605 HildonGridPrivate *priv;
609 priv = HILDON_GRID_GET_PRIVATE(grid);
611 if (emblem_size == priv->emblem_size)
614 priv->emblem_size = emblem_size;
616 for (list = priv->children; list != NULL; list = list->next) {
617 child = ((HildonGridChild *) list->data)->widget;
619 _hildon_grid_item_set_emblem_size(HILDON_GRID_ITEM(child),
626 hildon_grid_set_label_height(HildonGrid *grid,
629 HildonGridPrivate *priv;
633 priv = HILDON_GRID_GET_PRIVATE(grid);
635 if (label_height == priv->label_height)
638 priv->label_height = label_height;
640 for (list = priv->children; list != NULL; list = list->next) {
641 child = ((HildonGridChild *) list->data)->widget;
643 _hildon_grid_item_set_label_height(HILDON_GRID_ITEM(child),
649 static GType hildon_grid_child_type(GtkContainer * container)
651 return GTK_TYPE_WIDGET;
654 static void hildon_grid_init(HildonGrid * grid)
656 HildonGridPrivate *priv;
658 priv = HILDON_GRID_GET_PRIVATE(grid);
660 GTK_CONTAINER(grid)->focus_child = NULL;
661 priv->focus_index = -1;
663 priv->scrollbar = gtk_vscrollbar_new(NULL);
664 priv->empty_label = gtk_label_new(_("Ckct_wi_grid_no_items"));
667 priv->area_height = 1;
669 priv->children = NULL;
671 priv->first_index = 0;
675 priv->item_height = 96;
678 priv->focus_margin = 6;
679 priv->icon_label_margin = 6;
680 priv->icon_width = 64;
681 priv->label_pos = HILDON_GRID_ITEM_LABEL_POS_BOTTOM;
683 priv->old_sb_pos = -1;
684 priv->old_item_height = -1;
686 gtk_widget_set_parent(priv->scrollbar, GTK_WIDGET(grid));
687 gtk_widget_set_parent(priv->empty_label, GTK_WIDGET(grid));
689 priv->last_button_event = GDK_NOTHING;
691 GTK_WIDGET_SET_FLAGS(grid, GTK_NO_WINDOW);
693 /* Signal for scrollbar. */
694 g_signal_connect(G_OBJECT(priv->scrollbar), "value-changed",
695 G_CALLBACK(hildon_grid_scrollbar_moved), grid);
697 /* Signal for key press. */
698 GTK_WIDGET_SET_FLAGS(GTK_WIDGET(grid), GTK_CAN_FOCUS);
699 gtk_widget_set_events(GTK_WIDGET(grid), GDK_KEY_PRESS_MASK);
701 GTK_WIDGET_UNSET_FLAGS(priv->scrollbar, GTK_CAN_FOCUS);
702 hildon_grid_set_style(grid, DEFAULT_STYLE);
708 * Creates a new #HildonGrid.
710 * Returns: a new #HildonGrid
712 GtkWidget *hildon_grid_new(void)
717 grid = g_object_new(HILDON_TYPE_GRID, NULL);
719 return GTK_WIDGET(grid);
723 static void hildon_grid_realize(GtkWidget * widget)
726 HildonGridPrivate *priv;
731 GTK_WIDGET_SET_FLAGS(widget, GTK_REALIZED);
733 grid = HILDON_GRID(widget);
734 priv = HILDON_GRID_GET_PRIVATE(grid);
736 /* Create GdkWindow for catching events. */
737 attr.x = widget->allocation.x;
738 attr.y = widget->allocation.y;
739 attr.width = widget->allocation.width - priv->scrollbar_width;
740 attr.height = widget->allocation.height;
741 attr.window_type = GDK_WINDOW_CHILD;
742 attr.event_mask = gtk_widget_get_events(widget)
743 | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK;
745 widget->window = gtk_widget_get_parent_window(widget);
746 g_object_ref(widget->window);
748 attr.wclass = GDK_INPUT_ONLY;
749 attr_mask = GDK_WA_X | GDK_WA_Y;
751 priv->event_window = gdk_window_new(widget->window, &attr, attr_mask);
752 gdk_window_set_user_data(priv->event_window, widget);
754 widget->style = gtk_style_attach(widget->style, widget->window);
756 gtk_style_set_background(widget->style,
757 widget->window, GTK_STATE_NORMAL);
761 static void hildon_grid_unrealize(GtkWidget * widget)
763 HildonGridPrivate *priv;
765 priv = HILDON_GRID_GET_PRIVATE(HILDON_GRID(widget));
767 if (priv->event_window != NULL) {
768 gdk_window_set_user_data(priv->event_window, NULL);
769 gdk_window_destroy(priv->event_window);
770 priv->event_window = NULL;
773 if (GTK_WIDGET_CLASS(parent_class)->unrealize) {
774 (*GTK_WIDGET_CLASS(parent_class)->unrealize) (widget);
780 static void hildon_grid_map(GtkWidget * widget)
783 HildonGridPrivate *priv;
787 g_return_if_fail(HILDON_IS_GRID(widget));
789 if (!GTK_WIDGET_VISIBLE(widget))
792 grid = HILDON_GRID(widget);
793 priv = HILDON_GRID_GET_PRIVATE(grid);
795 (*GTK_WIDGET_CLASS(parent_class)->map) (widget);
797 /* We shouldn't really need the following...*/
798 if (priv->scrollbar != NULL && GTK_WIDGET_VISIBLE(priv->scrollbar)) {
799 if (!GTK_WIDGET_MAPPED(priv->scrollbar)) {
800 gtk_widget_map(priv->scrollbar);
804 if (priv->empty_label != NULL &&
805 GTK_WIDGET_VISIBLE(priv->empty_label)) {
806 if (!GTK_WIDGET_MAPPED(priv->empty_label)) {
807 gtk_widget_map(priv->empty_label);
811 for (list = priv->children; list != NULL; list = list->next) {
812 child = ((HildonGridChild *) list->data)->widget;
814 if (GTK_WIDGET_VISIBLE(child)) {
815 if (!GTK_WIDGET_MAPPED(child)) {
816 gtk_widget_map(child);
820 /* END OF don't really need */
822 /* Also make event window visible. */
823 gdk_window_show(priv->event_window);
828 static void hildon_grid_unmap(GtkWidget * widget)
830 HildonGridPrivate *priv;
832 priv = HILDON_GRID_GET_PRIVATE(HILDON_GRID(widget));
834 if (priv->event_window != NULL) {
835 gdk_window_hide(priv->event_window);
838 (*GTK_WIDGET_CLASS(parent_class)->unmap) (widget);
844 hildon_grid_expose(GtkWidget * widget, GdkEventExpose * event)
847 HildonGridPrivate *priv;
848 GtkContainer *container;
852 g_return_val_if_fail(widget, FALSE);
853 g_return_val_if_fail(HILDON_IS_GRID(widget), FALSE);
854 g_return_val_if_fail(event, FALSE);
856 grid = HILDON_GRID(widget);
857 priv = HILDON_GRID_GET_PRIVATE(grid);
858 container = GTK_CONTAINER(grid);
860 /* If grid has no children,
861 * propagate the expose event to the label is one exists */
862 if (priv->children == NULL || g_list_length(priv->children) == 0) {
863 if (priv->empty_label != NULL) {
864 gtk_container_propagate_expose(container,
865 priv->empty_label, event);
870 /* Only expose visible children. */
872 /* Jump over invisible. */
873 for (list = priv->children, child_no = 0;
874 list != NULL && child_no < priv->first_index;
875 list = list->next, child_no++) {
876 ; /* Nothing here. */
879 for (; list != NULL && child_no < priv->first_index +
880 priv->num_columns * priv->area_rows; list = list->next) {
881 gtk_container_propagate_expose(container,
882 ((HildonGridChild *) list->data)
886 /* Keep focused item focused. */
887 if (container->focus_child != NULL
888 && !GTK_WIDGET_HAS_FOCUS(container->focus_child)) {
889 set_focus(grid, container->focus_child, FALSE);
891 if (priv->scrollbar_width > 0 && priv->scrollbar != NULL) {
892 gtk_container_propagate_expose(container, priv->scrollbar, event);
900 hildon_grid_size_request(GtkWidget * widget, GtkRequisition * requisition)
903 HildonGridPrivate *priv;
908 g_return_if_fail(widget);
909 g_return_if_fail(requisition);
911 grid = HILDON_GRID(widget);
912 priv = HILDON_GRID_GET_PRIVATE(grid);
914 /* Want as big as possible. */
915 requisition->width = 0x7fff; /* Largest possible gint16 */
916 requisition->height = 0x7fff; /* Largest possible gint16 */
918 if (priv->children == NULL) {
919 if (priv->empty_label != NULL &&
920 GTK_WIDGET_VISIBLE(priv->empty_label)) {
921 gtk_widget_size_request(priv->empty_label, &req);
925 if (priv->scrollbar != NULL && GTK_WIDGET_VISIBLE(priv->scrollbar)) {
926 gtk_widget_size_request(priv->scrollbar, &req);
929 for (list = priv->children; list != NULL; list = list->next) {
930 child = ((HildonGridChild *) list->data)->widget;
932 gtk_widget_size_request(child, &req);
937 * hildon_grid_size_allocate:
939 * Supposingly called when size of grid changes and after view have moved so
940 * that items need to be relocated.
943 hildon_grid_size_allocate(GtkWidget * widget, GtkAllocation * allocation)
946 HildonGridPrivate *priv;
956 g_return_if_fail(widget);
957 g_return_if_fail(allocation);
959 grid = HILDON_GRID(widget);
960 priv = HILDON_GRID_GET_PRIVATE(grid);
961 widget->allocation = *allocation;
963 get_style_properties(grid);
965 /* First of all, make sure GdkWindow is over our widget. */
966 if (priv->event_window != NULL) {
967 gdk_window_move_resize(priv->event_window,
968 widget->allocation.x,
969 widget->allocation.y,
970 widget->allocation.width -
971 priv->scrollbar_width,
972 widget->allocation.height);
974 /* Show the label if there are no items. */
975 if (priv->children == NULL) {
977 * We probably don't need this as scrollbar should be hidden when
978 * removing items, but one can never be too sure...
980 if (priv->scrollbar != NULL &&
981 GTK_WIDGET_VISIBLE(priv->scrollbar)) {
982 priv->scrollbar_width = 0;
983 gtk_widget_hide(priv->scrollbar);
986 /* Show label if creating one actually worked. */
987 if (priv->empty_label != NULL) {
988 gtk_widget_get_child_requisition(priv->empty_label, &req);
990 /* ...for sure we must have a position for the label here... */
991 alloc.x = allocation->x + GRID_LABEL_POS_PAD;
992 alloc.y = allocation->y + GRID_LABEL_POS_PAD;
993 alloc.width = MIN(req.width, allocation->width -
995 alloc.height = MIN(req.height, allocation->height -
998 /* Make sure we don't use negative values. */
999 if (alloc.width < 0) {
1002 if (alloc.height < 0) {
1006 gtk_widget_size_allocate(priv->empty_label, &alloc);
1008 if (!GTK_WIDGET_VISIBLE(priv->empty_label)) {
1009 gtk_widget_show(priv->empty_label);
1016 /* As we have some items, hide label if it was visible. */
1017 if (priv->empty_label != NULL &&
1018 GTK_WIDGET_VISIBLE(priv->empty_label)) {
1019 gtk_widget_hide(priv->empty_label);
1022 priv->area_height = allocation->height;
1023 priv->area_rows = allocation->height / priv->item_height;
1025 /* Adjust/show/hide scrollbar. */
1026 adjust_scrollbar_height(grid);
1027 if (priv->old_item_height != priv->item_height) {
1028 priv->old_item_height = priv->item_height;
1029 jump_scrollbar_to_focused(grid);
1032 /* Update item width. */
1033 if (priv->num_columns == 1) {
1034 priv->item_width = allocation->width - priv->scrollbar_width -
1035 priv->h_margin - priv->scrollbar_width;
1037 priv->item_width = (allocation->width - priv->scrollbar_width) /
1042 (int) gtk_range_get_value(GTK_RANGE(priv->scrollbar)) /
1043 priv->item_height * priv->num_columns;
1045 /* Hide items before visible ones. */
1046 for (list = priv->children, child_no = 0;
1047 list != NULL && child_no < priv->first_index;
1048 list = list->next, child_no++) {
1049 child = ((HildonGridChild *) list->data)->widget;
1051 if (GTK_WIDGET_VISIBLE(child)) {
1052 gtk_widget_hide(child);
1056 /* Allocate visible items. */
1057 alloc.width = priv->item_width - priv->h_margin;
1058 switch (priv->label_pos) {
1059 case HILDON_GRID_ITEM_LABEL_POS_BOTTOM:
1060 row_margin = priv->icon_label_margin;
1062 case HILDON_GRID_ITEM_LABEL_POS_RIGHT:
1063 row_margin = priv->v_margin;
1069 alloc.height = priv->item_height - row_margin;
1071 for (y_offset = priv->first_index / priv->num_columns * priv->item_height;
1072 list != NULL && child_no < priv->first_index +
1073 priv->area_rows * priv->num_columns;
1074 list = list->next, child_no++) {
1075 child = ((HildonGridChild *) list->data)->widget;
1077 if (!GTK_WIDGET_VISIBLE(child)) {
1078 gtk_widget_show(child);
1081 /* Don't update icons which are not visible... */
1082 alloc.y = (child_no / priv->num_columns) * priv->item_height +
1083 allocation->y - y_offset + row_margin;
1084 alloc.x = (child_no % priv->num_columns) * priv->item_width +
1087 _hildon_grid_item_done_updating_settings(HILDON_GRID_ITEM(child));
1088 gtk_widget_size_allocate(child, &alloc);
1091 /* Hide items after visible items. */
1092 for (; list != NULL; list = list->next) {
1093 child = ((HildonGridChild *) list->data)->widget;
1095 if (GTK_WIDGET_VISIBLE(child)) {
1096 gtk_widget_hide(child);
1105 * @container: container (#HildonGrid) to add HildonGridItem into
1106 * @widget: #GtkWidget (#HildonGridItem) to add
1108 * Adds a new HildonGridItem into HildonGrid.
1110 static void hildon_grid_add(GtkContainer * container, GtkWidget * widget)
1113 HildonGridPrivate *priv;
1114 HildonGridChild *child;
1117 g_return_if_fail(HILDON_IS_GRID(container));
1118 g_return_if_fail(HILDON_IS_GRID_ITEM(widget));
1120 grid = HILDON_GRID(container);
1121 priv = HILDON_GRID_GET_PRIVATE(HILDON_GRID(grid));
1122 GTK_WIDGET_SET_FLAGS(widget, GTK_NO_WINDOW);
1124 child = g_new(HildonGridChild, 1);
1125 if (child == NULL) {
1126 g_critical("no memory for child - not adding");
1129 child->widget = widget;
1131 _hildon_grid_item_set_label_pos (HILDON_GRID_ITEM(widget), priv->label_pos);
1132 _hildon_grid_item_set_focus_margin(HILDON_GRID_ITEM(widget), priv->focus_margin);
1133 _hildon_grid_item_set_icon_width (HILDON_GRID_ITEM(widget), priv->icon_width);
1134 _hildon_grid_item_set_emblem_size (HILDON_GRID_ITEM(widget), priv->emblem_size);
1136 /* Add the new item to the grid */
1137 priv->children = g_list_append(priv->children, child);
1138 gtk_widget_set_parent(widget, GTK_WIDGET(grid));
1140 /* Property changes (child's set_sensitive) */
1141 g_signal_connect_after(G_OBJECT(widget), "state-changed",
1142 G_CALLBACK(hildon_grid_state_changed), grid);
1144 /* Matches both empty grid and all-dimmed grid. */
1145 if (GTK_CONTAINER(grid)->focus_child == NULL)
1146 set_focus(grid, widget, TRUE);
1149 * If item was added in visible area, relocate items. Otherwise update
1150 * scrollbar and see if items need relocating.
1152 if (g_list_length(priv->children) < priv->first_index +
1153 priv->area_rows * priv->num_columns) {
1154 gtk_widget_queue_resize(GTK_WIDGET(grid));
1158 updated = adjust_scrollbar_height(grid);
1159 /* Basically this other test is useless -- shouldn't need to jump.
1161 updated |= jump_scrollbar_to_focused(grid);
1164 gtk_widget_queue_resize(GTK_WIDGET(grid));
1170 * hildon_grid_remove:
1171 * @container: container (#HildonGrid) to remove #HildonGridItem from
1172 * @widget: widget (#HildonGridItem) to be removed
1174 * Removes HildonGridItem from HildonGrid.
1177 hildon_grid_remove(GtkContainer * container, GtkWidget * widget)
1180 HildonGridPrivate *priv;
1181 HildonGridChild *child;
1182 GtkWidget *child_widget;
1184 gint index, old_index;
1188 g_return_if_fail(HILDON_IS_GRID(container));
1189 g_return_if_fail(HILDON_IS_GRID_ITEM(widget));
1191 grid = HILDON_GRID(container);
1192 priv = HILDON_GRID_GET_PRIVATE(container);
1194 old_index = priv->focus_index;
1195 updated = GTK_WIDGET_VISIBLE(widget);
1197 for (list = priv->children, index = 0, deleted = FALSE;
1198 list != NULL; list = list->next, index++) {
1199 child = (HildonGridChild *) list->data;
1200 child_widget = child->widget;
1202 /* Remove the Item if it is found in the grid */
1203 if (child_widget == widget) {
1204 gtk_widget_unparent(child_widget);
1205 priv->children = g_list_remove_link(priv->children, list);
1215 /* Emit warning if the item is not found */
1217 g_warning("tried to remove unexisting item");
1221 /* Move focus somewhere. */
1222 if (old_index == index) {
1223 if (old_index == g_list_length(priv->children)) {
1225 set_focus(grid, NULL, TRUE);
1228 get_child_by_index(priv, old_index - 1), TRUE);
1231 set_focus(grid, get_child_by_index(priv, old_index), TRUE);
1234 set_focus(grid, GTK_CONTAINER(grid)->focus_child, TRUE);
1237 updated |= adjust_scrollbar_height(grid);
1238 updated |= jump_scrollbar_to_focused(grid);
1241 gtk_widget_queue_resize(GTK_WIDGET(grid));
1246 * hildon_grid_set_focus_child:
1247 * @container: HildonGrid
1248 * @widget: HildonGridItem
1253 hildon_grid_set_focus_child(GtkContainer * container, GtkWidget * widget)
1256 HildonGridPrivate *priv;
1258 g_return_if_fail(HILDON_IS_GRID(container));
1259 g_return_if_fail(HILDON_IS_GRID_ITEM(widget) || widget == NULL);
1261 grid = HILDON_GRID(container);
1262 priv = HILDON_GRID_GET_PRIVATE(grid);
1264 if (GTK_CONTAINER(grid)->focus_child == widget || widget == NULL)
1267 set_focus(grid, widget, TRUE);
1273 set_focus(HildonGrid * grid, GtkWidget * widget, gboolean refresh_view)
1275 HildonGridPrivate *priv;
1276 GtkContainer *container;
1277 gboolean view_updated;
1280 priv = HILDON_GRID_GET_PRIVATE(grid);
1281 container = GTK_CONTAINER(grid);
1283 /* If widget is NULL -> unfocus */
1284 if (widget == NULL && container->focus_child != NULL)
1285 GTK_WIDGET_UNSET_FLAGS(container->focus_child, GTK_HAS_FOCUS);
1287 GTK_CONTAINER(grid)->focus_child = widget;
1288 if (widget == NULL) {
1289 priv->focus_index = -1;
1293 /* Get the child index which the user wanted to focus */
1294 priv->focus_index = get_child_index(priv, widget);
1296 gtk_widget_grab_focus(widget);
1299 view_updated = jump_scrollbar_to_focused(grid);
1301 view_updated = FALSE;
1305 hildon_grid_size_allocate(GTK_WIDGET(grid),
1306 >K_WIDGET(grid)->allocation);
1311 hildon_grid_forall(GtkContainer * container,
1312 gboolean include_internals,
1313 GtkCallback callback, gpointer callback_data)
1316 HildonGridPrivate *priv;
1319 g_return_if_fail(container);
1320 g_return_if_fail(callback);
1322 grid = HILDON_GRID(container);
1323 priv = HILDON_GRID_GET_PRIVATE(grid);
1325 /* Connect callback functions */
1326 if (include_internals) {
1327 if (priv->scrollbar != NULL) {
1328 (*callback) (priv->scrollbar, callback_data);
1330 if (priv->empty_label != NULL) {
1331 (*callback) (priv->empty_label, callback_data);
1335 for (list = priv->children; list != NULL; list = list->next) {
1336 (*callback) (((HildonGridChild *) list->data)->widget,
1341 static void hildon_grid_destroy(GtkObject * self)
1343 HildonGridPrivate *priv;
1345 g_return_if_fail(self != NULL);
1346 g_return_if_fail(HILDON_IS_GRID(self));
1348 priv = HILDON_GRID_GET_PRIVATE(self);
1350 if (GTK_WIDGET(self)->window != NULL) {
1351 g_object_unref(G_OBJECT(GTK_WIDGET(self)->window));
1354 gtk_container_forall(GTK_CONTAINER(self),
1355 (GtkCallback) gtk_object_ref, NULL);
1356 gtk_container_forall(GTK_CONTAINER(self),
1357 (GtkCallback) gtk_widget_unparent, NULL);
1359 GTK_OBJECT_CLASS(parent_class)->destroy(self);
1362 static void hildon_grid_finalize(GObject * object)
1365 HildonGridPrivate *priv;
1367 grid = HILDON_GRID(object);
1368 priv = HILDON_GRID_GET_PRIVATE(grid);
1370 gtk_container_forall(GTK_CONTAINER(object),
1371 (GtkCallback) gtk_object_unref, NULL);
1373 if (priv->style != NULL) {
1374 g_free(priv->style);
1376 if (G_OBJECT_CLASS(parent_class)->finalize) {
1377 G_OBJECT_CLASS(parent_class)->finalize(object);
1382 * hildon_grid_key_pressed:
1383 * @widget: Widget where we get the signal from
1385 * @data: #HildonGrid
1387 * Handle user key press (keyboard navigation).
1389 * And here's how it works if some items are dimmed (moving to right):
1390 * . . . . . . # . . 2 # . . # . .
1391 * . 1 # 2 . 1 # # 1 # # # 1 # # #
1392 * . . . . . 2 . . . . 2 .
1395 * '#' = dimmed item,
1396 * '1' = starting position,
1397 * '2' = final position
1399 * ...although only the first example is implemented right now.
1401 * Return value: Signal handled
1404 hildon_grid_key_pressed(GtkWidget * widget,
1405 GdkEventKey * event)
1407 GtkAdjustment *adjustment;
1408 GtkContainer *container;
1409 GtkWidget *new_focus;
1411 HildonGridPrivate *priv;
1416 gint child_count, child_rows;
1418 gint addition, max_add;
1420 g_return_val_if_fail(widget, FALSE);
1422 grid = HILDON_GRID(widget);
1423 priv = HILDON_GRID_GET_PRIVATE(grid);
1426 * If focus was never lost, we could just see if an item is focused -
1427 * if not, there's nothing else to focus...
1431 if (priv->children == NULL || g_list_length(priv->children) == 0)
1432 return GTK_WIDGET_CLASS (parent_class)->key_press_event (widget, event);
1434 /* Focused item is dimmed? */
1435 /* If we have no focus, allow non-existing focus to move... */
1436 container = GTK_CONTAINER(grid);
1437 if (container->focus_child != NULL
1438 && !GTK_WIDGET_IS_SENSITIVE(container->focus_child)) {
1439 return GTK_WIDGET_CLASS (parent_class)->key_press_event (widget, event);
1441 /* At the moment we don't want to do anything here if alt or control
1442 or MODX is pressed, so return now. Shift + TAB are accepted (from
1443 hildon-table-grid) And right now modifiers do not make any
1446 /* Said somewhere that "foo = a == b" is not desirable. */
1447 if (event->state & GDK_SHIFT_MASK) {
1453 keyval = event->keyval;
1454 if (gtk_widget_get_default_direction() == GTK_TEXT_DIR_RTL) {
1455 switch (event->keyval) {
1460 keyval = GDK_KP_Right;
1466 keyval = GDK_KP_Left;
1471 child_count = g_list_length(priv->children);
1472 child_rows = (child_count - 1) / priv->num_columns + 1;
1474 if (priv->focus_index != -1) {
1475 x = priv->focus_index % priv->num_columns;
1476 y = priv->focus_index / priv->num_columns;
1482 case GDK_KP_Page_Up:
1484 if (priv->first_index == 0) {
1485 if (priv->focus_index == 0) {
1488 set_focus(grid, get_child_by_index(priv, 0), TRUE);
1492 t = MAX(priv->first_index / priv->num_columns - priv->area_rows, 0);
1493 adjustment = gtk_range_get_adjustment(GTK_RANGE(priv->scrollbar));
1494 adjustment->value = (gdouble) (t * priv->item_height);
1495 gtk_range_set_adjustment(GTK_RANGE(priv->scrollbar), adjustment);
1496 gtk_widget_queue_draw(priv->scrollbar);
1497 update_contents(grid);
1499 /* Want to update now. */
1500 hildon_grid_size_allocate(GTK_WIDGET(grid),
1501 >K_WIDGET(grid)->allocation);
1506 case GDK_KP_Page_Down:
1508 if (priv->first_index / priv->num_columns ==
1509 child_rows - priv->area_rows) {
1510 if (priv->focus_index == child_count - 1) {
1513 set_focus(grid, get_child_by_index(priv, child_count - 1),
1518 t = MIN(priv->first_index / priv->num_columns +
1519 priv->area_rows, child_rows - priv->area_rows);
1520 adjustment = gtk_range_get_adjustment(GTK_RANGE(priv->scrollbar));
1521 adjustment->value = (gdouble) (t * priv->item_height);
1522 gtk_range_set_adjustment(GTK_RANGE(priv->scrollbar), adjustment);
1523 gtk_widget_queue_draw(priv->scrollbar);
1524 update_contents(grid);
1526 /* Want to update now. */
1527 hildon_grid_size_allocate(GTK_WIDGET(grid),
1528 >K_WIDGET(grid)->allocation);
1538 addition = -priv->num_columns;
1545 if (y >= (child_count - 1) / priv->num_columns) {
1548 t = child_count % priv->num_columns;
1550 t = priv->num_columns;
1552 if (y == (child_count - 1) / priv->num_columns - 1 && x >= t) {
1556 addition = priv->num_columns;
1557 max_add = child_rows - y;
1572 if (x >= priv->num_columns - 1) {
1575 if (y == 0 && x >= child_count - 1) {
1580 max_add = priv->num_columns - x;
1581 if (y * priv->num_columns + x == child_count) {
1587 hildon_grid_activate_child(grid,
1589 (GTK_CONTAINER(grid)->focus_child));
1593 return GTK_WIDGET_CLASS (parent_class)->key_press_event (widget, event);
1597 focus_index = y * priv->num_columns + x;
1598 new_focus = get_child_by_index(priv, focus_index);
1600 while (new_focus != NULL &&
1601 focus_index < child_count && !GTK_WIDGET_SENSITIVE(new_focus)) {
1607 focus_index += addition;
1608 new_focus = get_child_by_index(priv, focus_index);
1611 if (new_focus != NULL) {
1612 set_focus(grid, new_focus, TRUE);
1619 * hildon_grid_button_pressed:
1620 * @widget: Widget where signal is coming from
1621 * @event: #EventButton
1622 * @data: #HildonGrid
1624 * Handle mouse button press.
1626 * Return value: Signal handled
1629 hildon_grid_button_pressed(GtkWidget * widget,
1630 GdkEventButton * event)
1633 HildonGridPrivate *priv;
1637 grid = HILDON_GRID(widget);
1638 priv = HILDON_GRID_GET_PRIVATE(grid);
1640 /* Watch out for double/triple click press events */
1642 if (event->type == GDK_2BUTTON_PRESS ||
1643 event->type == GDK_3BUTTON_PRESS) {
1644 priv->last_button_event = event->type;
1648 priv->last_button_event = event->type;
1650 if (event->type != GDK_BUTTON_PRESS)
1654 child_no = get_child_index_by_coord(priv, event->x, event->y);
1656 if (child_no == -1 || child_no >= g_list_length(priv->children))
1659 child = get_child_by_index(priv, child_no);
1660 if (!GTK_WIDGET_IS_SENSITIVE(child))
1663 set_focus(grid, child, TRUE);
1665 priv->click_x = event->x;
1666 priv->click_y = event->y;
1672 * hildon_grid_button_released:
1673 * @widget: Widget the signal is coming from
1674 * @event: #EventButton
1675 * @data: #HildonGrid
1677 * Handle mouse button release.
1679 * Return value: Signal handled
1682 hildon_grid_button_released(GtkWidget * widget,
1683 GdkEventButton * event)
1686 HildonGridPrivate *priv;
1690 grid = HILDON_GRID(widget);
1691 priv = HILDON_GRID_GET_PRIVATE(grid);
1693 /* In case of double/triple click, silently ignore the release event */
1695 if (priv->last_button_event == GDK_2BUTTON_PRESS ||
1696 priv->last_button_event == GDK_3BUTTON_PRESS) {
1697 priv->last_button_event = event->type;
1701 child_no = get_child_index_by_coord(priv, event->x, event->y);
1703 if (child_no == -1 || child_no >= g_list_length(priv->children)) {
1706 child = get_child_by_index(priv, child_no);
1707 if (!GTK_WIDGET_IS_SENSITIVE(child)) {
1710 if (abs(priv->click_x - event->x) >= DRAG_SENSITIVITY
1711 && abs(priv->click_y - event->y) >= DRAG_SENSITIVITY) {
1714 set_focus(grid, child, TRUE);
1715 priv->last_button_event = event->type;
1716 hildon_grid_activate_child(grid, HILDON_GRID_ITEM(child));
1722 * hildon_grid_scrollbar_moved:
1723 * @widget: Widget which sent the signal
1724 * @data: #HildonGrid
1726 * Update HildonGrid contents when scrollbar is moved.
1728 * Return value: Signal handeld
1731 hildon_grid_scrollbar_moved(GtkWidget * widget, gpointer data)
1734 HildonGridPrivate *priv;
1735 gboolean updated = FALSE;
1737 grid = HILDON_GRID(data);
1738 priv = HILDON_GRID_GET_PRIVATE(grid);
1739 updated = update_contents(grid);
1742 * If grid changes focus while dragging scrollbar and pointer leaves
1743 * scrollbar, focus is moved to prev_focus... This prevents that.
1745 gtk_window_set_prev_focus_widget(GTK_WINDOW
1746 (gtk_widget_get_toplevel(widget)),
1747 GTK_CONTAINER(grid)->focus_child);
1750 /* Don't just queue it, let's do it now! */
1751 hildon_grid_size_allocate(GTK_WIDGET(grid),
1752 >K_WIDGET(grid)->allocation);
1760 * @grid: #HildonGrid
1762 * Update the view if scrollbar has moved so that first visible row
1763 * should've changed. Returns true if location actually changed.
1765 * Return value: Content changed
1767 static gboolean update_contents(HildonGrid * grid)
1769 HildonGridPrivate *priv;
1772 priv = HILDON_GRID_GET_PRIVATE(grid);
1773 new_row = (int) gtk_range_get_value(GTK_RANGE(priv->scrollbar))
1774 / priv->item_height;
1776 if (new_row != priv->old_sb_pos) {
1777 priv->old_sb_pos = new_row;
1778 priv->first_index = new_row * priv->num_columns;
1786 * jump_scrollbar_to_focused:
1787 * @grid: #HildonGrid
1789 * Moves scrollbar position so that focused item will be shown
1791 * Returns TRUE if visible position of widgets have changed.
1793 * Return value: Content changed
1795 static gboolean jump_scrollbar_to_focused(HildonGrid * grid)
1797 HildonGridPrivate *priv;
1798 GtkAdjustment *adjustment;
1803 priv = HILDON_GRID_GET_PRIVATE(grid);
1804 /* If we don't have scrollbar, let the focus be broken, too. */
1805 g_return_val_if_fail(priv->scrollbar != NULL, FALSE);
1807 /* Make sure "first widget" is something sensible. */
1808 priv->first_index = priv->first_index -
1809 priv->first_index % priv->num_columns;
1811 child_count = g_list_length(priv->children);
1812 empty_grids = priv->num_columns * priv->area_rows - child_count +
1815 /* Determine the position of the new row */
1816 if (priv->focus_index < priv->first_index) {
1817 new_row = priv->focus_index / priv->num_columns;
1818 } else if (priv->focus_index >= priv->first_index +
1819 priv->area_rows * priv->num_columns) {
1821 new_row = priv->focus_index / priv->num_columns -
1822 priv->area_rows + 1;
1823 last_top_row = child_count / priv->num_columns - priv->area_rows + 1;
1824 if (child_count % priv->num_columns != 0) {
1827 if (new_row > last_top_row) {
1828 new_row = last_top_row;
1830 } else if (empty_grids >= priv->num_columns) {
1831 new_row = ((child_count - 1) / priv->num_columns + 1)
1840 /* Move scrollbar accordingly. */
1841 adjustment = gtk_range_get_adjustment(GTK_RANGE(priv->scrollbar));
1842 adjustment->value = (gdouble) (new_row * priv->item_height);
1843 gtk_range_set_adjustment(GTK_RANGE(priv->scrollbar), adjustment);
1844 priv->first_index = new_row * priv->num_columns;
1845 priv->old_sb_pos = new_row;
1847 gtk_widget_queue_draw(priv->scrollbar);
1854 * adjust_scrollbar_height:
1855 * @grid: HildonGridPrivate
1857 * Return value: View should change
1859 * Adjust scrollbar according the #HildonGrid contents.
1860 * Show/hide scrollbar if
1861 * appropriate. Also sets priv->first_index.
1863 static gboolean adjust_scrollbar_height(HildonGrid * grid)
1865 HildonGridPrivate *priv;
1868 GtkAllocation alloc;
1869 GtkAllocation *gridalloc;
1875 priv = HILDON_GRID_GET_PRIVATE(grid);
1876 g_return_val_if_fail(priv->scrollbar != NULL, FALSE);
1879 gridalloc = >K_WIDGET(grid)->allocation;
1881 /* See if we need scrollbar at all. */
1882 if (priv->num_columns == 0) {
1883 priv->num_columns = DEFAULT_N_COLUMNS;
1885 priv->num_columns = MAX(1, priv->num_columns);
1888 if (g_list_length(priv->children) != 0) {
1889 need_rows = (g_list_length(priv->children) - 1) /
1890 priv->num_columns + 1;
1895 if (need_rows <= priv->area_rows) {
1896 updated = priv->first_index != 0;
1897 priv->scrollbar_width = 0;
1899 priv->first_index = 0;
1900 if (GTK_WIDGET_VISIBLE(priv->scrollbar)) {
1901 GtkWidget *parent = gtk_widget_get_toplevel (GTK_WIDGET (grid));
1902 if (HILDON_IS_APP (parent))
1903 g_object_set (parent, "scroll-control", FALSE, NULL);
1904 gtk_widget_hide(priv->scrollbar);
1911 /* All right then, we need scrollbar. Place scrollbar on the screen. */
1912 gtk_widget_get_child_requisition(priv->scrollbar, &req);
1913 priv->scrollbar_width = req.width;
1915 alloc.width = req.width;
1916 alloc.height = gridalloc->height;
1917 alloc.x = gridalloc->width - req.width + gridalloc->x;
1918 alloc.y = gridalloc->y;
1919 gtk_widget_size_allocate(priv->scrollbar, &alloc);
1921 if (!GTK_WIDGET_VISIBLE(priv->scrollbar)) {
1922 GtkWidget *parent = gtk_widget_get_toplevel (GTK_WIDGET (grid));
1923 if (HILDON_IS_APP (parent))
1924 g_object_set (parent, "scroll-control", TRUE, NULL);
1925 gtk_widget_show(priv->scrollbar);
1930 need_pixels = need_rows * priv->item_height;
1932 /* Once we know how much space we need, update the scrollbar. */
1933 adj = gtk_range_get_adjustment(GTK_RANGE(priv->scrollbar));
1934 old_upper = (int) adj->upper;
1936 adj->upper = (gdouble) need_pixels;
1937 adj->step_increment = (gdouble) priv->item_height;
1938 adj->page_increment = (gdouble) (priv->area_rows * priv->item_height);
1940 (gdouble) (priv->area_height - priv->area_height % priv->item_height);
1942 /* Also update position if needed to show focused item. */
1944 gtk_range_set_adjustment(GTK_RANGE(priv->scrollbar), adj);
1946 /* Then set first_index. */
1947 priv->first_index = (int) adj->value / priv->item_height *
1950 /* Finally, ask Gtk to redraw the scrollbar. */
1951 if (old_upper != (int) adj->upper) {
1952 gtk_widget_queue_draw(priv->scrollbar);
1958 * get_child_index_by_coord:
1959 * @priv: HildonGridPrivate
1963 * Returns index of child at given coordinates, -1 if no child.
1965 * Return value: Index
1968 get_child_index_by_coord(HildonGridPrivate * priv, gint x, gint y)
1973 xgap = x % priv->item_width;
1974 ygap = y % priv->item_height;
1976 if (xgap > priv->item_width - priv->h_margin) { /*FIXME*/
1980 /* Event may come from outside of the grid. Skipping those events */
1981 if (x >= priv->item_width * priv->num_columns)
1984 t = y / priv->item_height * priv->num_columns +
1985 x / priv->item_width + priv->first_index;
1987 if (t >= priv->first_index + priv->area_rows * priv->num_columns ||
1988 t >= g_list_length(priv->children) || t < 0) {
1995 * get_child_by_index:
1996 * @priv: HildonGridPrivate
1997 * @index: Index of child
1999 * Returns child that is #th in HildonGrid or NULL if child was not found
2000 * among the children.
2002 * Return value: GtkWidget
2004 static GtkWidget *get_child_by_index(HildonGridPrivate * priv, gint index)
2009 if (index >= g_list_length(priv->children) || index < 0) {
2012 for (list = priv->children, i = 0; list != NULL;
2013 list = list->next, i++) {
2015 return ((HildonGridChild *) list->data)->widget;
2019 g_warning("no such child");
2025 * @priv: HildonGridPrivate
2026 * @child: #GtkWidget to look for
2028 * Returns index of a child or -1 if child was not found among the
2031 * Return value: Index
2033 static gint get_child_index(HildonGridPrivate * priv, GtkWidget * child)
2041 for (list = priv->children, index = 0;
2042 list != NULL; list = list->next, index++) {
2043 if (((HildonGridChild *) list->data)->widget == child) {
2048 g_warning("no such child");
2054 * hildon_grid_activate_child:
2055 * @grid: #HildonGrid
2056 * @item: #HildonGridItem
2058 * Sends a signal to indicate that this HildonGridItem is activated.
2060 void hildon_grid_activate_child(HildonGrid * grid, HildonGridItem * item)
2062 g_return_if_fail(HILDON_IS_GRID(grid));
2064 g_signal_emit(grid, grid_signals[ACTIVATE_CHILD], 0, item);
2070 * hildon_grid_set_style:
2071 * @grid: #HildonGrid
2072 * @style_name: style name
2074 * Sets style. Setting style sets widget size, spacing, label position,
2075 * number of columns, and icon size.
2077 void hildon_grid_set_style(HildonGrid * grid, const gchar * style_name)
2079 HildonGridPrivate *priv;
2081 g_return_if_fail(HILDON_IS_GRID(grid));
2084 priv = HILDON_GRID_GET_PRIVATE(grid);
2085 if (priv->style != NULL) {
2086 g_free((gpointer) priv->style);
2088 if (style_name != NULL) {
2089 priv->style = g_strdup(style_name);
2094 gtk_widget_set_name(GTK_WIDGET(grid), style_name);
2095 get_style_properties(grid);
2097 gtk_widget_queue_resize(GTK_WIDGET(grid));
2101 * hildon_grid_get_style:
2102 * @grid: #HildonGrid
2104 * Returns the name of style currently used in HildonGrid.
2106 * Returns: style name
2108 const gchar *hildon_grid_get_style(HildonGrid * grid)
2110 g_return_val_if_fail(HILDON_IS_GRID(grid), NULL);
2112 return gtk_widget_get_name(GTK_WIDGET(grid));
2116 * get_style_properties:
2117 * @grid: #HildonGrid
2119 * Gets widget size and other properties from gtkrc. If some properties
2120 * have changed, notify children of this, too.
2122 static void get_style_properties(HildonGrid * grid)
2126 HildonGridPositionType label_pos;
2129 gint h_margin, v_margin;
2132 gint focus_margin, icon_label_margin;
2135 HildonGridPrivate *priv;
2136 g_return_if_fail(HILDON_IS_GRID(grid));
2137 priv = HILDON_GRID_GET_PRIVATE(grid);
2139 gtk_widget_style_get(GTK_WIDGET(grid),
2140 "item_hspacing", &h_margin,
2141 "item_vspacing", &v_margin,
2142 "item_height", &item_height,
2143 "icon_size", &icon_width,
2144 "n_columns", &num_columns,
2145 "label_pos", &label_pos,
2146 "label_hspacing", &focus_margin,
2147 "label_vspacing", &icon_label_margin,
2148 "emblem_size", &emblem_size,
2149 "label_height", &label_height,
2152 hildon_grid_set_icon_width(grid, icon_width);
2153 hildon_grid_set_num_columns(grid, num_columns);
2154 hildon_grid_set_label_pos(grid, label_pos);
2155 hildon_grid_set_focus_margin(grid, focus_margin);
2156 hildon_grid_set_icon_label_margin(grid, icon_label_margin);
2157 hildon_grid_set_emblem_size(grid, emblem_size);
2158 hildon_grid_set_label_height(grid, label_height);
2160 priv->h_margin = h_margin;
2161 priv->v_margin = v_margin;
2162 priv->item_height = item_height;
2166 for (iter = priv->children; iter != NULL; iter = iter->next) {
2167 HildonGridItem *child;
2168 child = HILDON_GRID_ITEM(((HildonGridChild *) iter->data)->widget);
2169 _hildon_grid_item_done_updating_settings(child);
2177 * hildon_grid_set_scrollbar_pos:
2178 * @grid: #HildonGrid
2179 * @scrollbar_pos: new position (in pixels)
2181 * Sets view (scrollbar) to specified position.
2183 void hildon_grid_set_scrollbar_pos(HildonGrid * grid, gint scrollbar_pos)
2185 HildonGridPrivate *priv;
2186 GtkAdjustment *adjustment;
2188 g_return_if_fail(HILDON_IS_GRID(grid));
2190 priv = HILDON_GRID_GET_PRIVATE(grid);
2191 adjustment = gtk_range_get_adjustment(GTK_RANGE(priv->scrollbar));
2192 adjustment->value = (gdouble) scrollbar_pos;
2194 gtk_range_set_adjustment(GTK_RANGE(priv->scrollbar), adjustment);
2196 g_object_notify (G_OBJECT (grid), "scrollbar-position");
2198 /* If grid isn't drawable, updating anything could mess up focus. */
2199 if (!GTK_WIDGET_DRAWABLE(GTK_WIDGET(grid)))
2202 update_contents(grid);
2206 * hildon_grid_get_scrollbar_pos:
2207 * @grid: #HildonGrid
2209 * Returns: position of scrollbar (in pixels).
2211 gint hildon_grid_get_scrollbar_pos(HildonGrid * grid)
2213 GtkAdjustment *adjustment;
2215 g_return_val_if_fail(HILDON_IS_GRID(grid), -1);
2217 adjustment = gtk_range_get_adjustment(GTK_RANGE
2218 (HILDON_GRID_GET_PRIVATE
2219 (grid)->scrollbar));
2220 return (int) adjustment->value;
2224 hildon_grid_set_property(GObject * object,
2226 const GValue * value, GParamSpec * pspec)
2230 grid = HILDON_GRID(object);
2233 case PROP_EMPTY_LABEL:
2234 hildon_grid_set_empty_label(grid, g_value_get_string(value));
2238 hildon_grid_set_style(grid, g_value_get_string(value));
2241 case PROP_SCROLLBAR_POS:
2242 hildon_grid_set_scrollbar_pos(grid, g_value_get_int(value));
2246 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
2252 hildon_grid_get_property(GObject * object,
2253 guint prop_id, GValue * value, GParamSpec * pspec)
2257 grid = HILDON_GRID(object);
2260 case PROP_EMPTY_LABEL:
2261 g_value_set_string(value, hildon_grid_get_empty_label(grid));
2265 g_value_set_string(value, hildon_grid_get_style(grid));
2268 case PROP_SCROLLBAR_POS:
2269 g_value_set_int(value, hildon_grid_get_scrollbar_pos(grid));
2273 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
2279 hildon_grid_state_changed(GtkWidget * widget,
2280 GtkStateType state, gpointer data)
2283 HildonGridPrivate *priv;
2286 GtkWidget *prev_focusable, *next_focusable;
2289 g_return_val_if_fail(HILDON_IS_GRID(data), FALSE);
2290 g_return_val_if_fail(HILDON_IS_GRID_ITEM(widget), FALSE);
2292 grid = HILDON_GRID(data);
2293 priv = HILDON_GRID_GET_PRIVATE(grid);
2296 if (GTK_WIDGET_IS_SENSITIVE(widget))
2299 prev_focusable = next_focusable = NULL;
2302 for (list = priv->children; list != NULL; list = list->next) {
2303 current = ((HildonGridChild *) list->data)->widget;
2305 if (GTK_WIDGET_IS_SENSITIVE(current)) {
2307 next_focusable = current;
2310 prev_focusable = current;
2312 } else if (current == widget) {
2317 if (next_focusable == NULL) {
2318 next_focusable = prev_focusable;
2321 gtk_container_set_focus_child(GTK_CONTAINER(grid), next_focusable);
2329 hildon_grid_tap_and_hold_setup(GtkWidget * widget,
2332 GtkWidgetTapAndHoldFlags flags)
2334 g_return_if_fail(HILDON_IS_GRID(widget) && GTK_IS_MENU(menu));
2336 parent_class->parent_class.tap_and_hold_setup
2337 (widget, menu, func, flags | GTK_TAP_AND_HOLD_NO_INTERNALS);