diff options
author | Tristan Van Berkom <tristan.van.berkom@gmail.com> | 2010-11-23 15:48:14 +0900 |
---|---|---|
committer | Tristan Van Berkom <tristan.van.berkom@gmail.com> | 2011-01-04 23:37:07 +0900 |
commit | 84a726c3ce9df0ed3bd79d997c93511bcd190548 (patch) | |
tree | 713e8b509a001a01a3d6db399aaacbb201cc4190 /gtk/gtktreemenu.c | |
parent | e6283453948c01abc8644ba20011ec420faffff0 (diff) | |
download | gtk+-84a726c3ce9df0ed3bd79d997c93511bcd190548.tar.gz |
Support grid mode in GtkTreeMenu
Added properties "wrap-width", "row-span-column" and "column-span-column"
to allow grid style menus from treemodels. Handling row data changes
appropriately.
Diffstat (limited to 'gtk/gtktreemenu.c')
-rw-r--r-- | gtk/gtktreemenu.c | 627 |
1 files changed, 525 insertions, 102 deletions
diff --git a/gtk/gtktreemenu.c b/gtk/gtktreemenu.c index 278bdc4f78..959803243e 100644 --- a/gtk/gtktreemenu.c +++ b/gtk/gtktreemenu.c @@ -64,6 +64,16 @@ static GtkCellArea *gtk_tree_menu_cell_layout_get_area (GtkCellLayout /* TreeModel/DrawingArea callbacks and building menus/submenus */ +static inline void rebuild_menu (GtkTreeMenu *menu); +static gboolean menu_occupied (GtkTreeMenu *menu, + guint left_attach, + guint right_attach, + guint top_attach, + guint bottom_attach); +static void relayout_item (GtkTreeMenu *menu, + GtkWidget *item, + GtkTreeIter *iter, + GtkWidget *prev); static void gtk_tree_menu_populate (GtkTreeMenu *menu); static GtkWidget *gtk_tree_menu_create_item (GtkTreeMenu *menu, GtkTreeIter *iter, @@ -72,6 +82,22 @@ static void gtk_tree_menu_set_area (GtkTreeMenu GtkCellArea *area); static GtkWidget *gtk_tree_menu_get_path_item (GtkTreeMenu *menu, GtkTreePath *path); +static void row_inserted_cb (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + GtkTreeMenu *menu); +static void row_deleted_cb (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeMenu *menu); +static void row_reordered_cb (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gint *new_order, + GtkTreeMenu *menu); +static void row_changed_cb (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + GtkTreeMenu *menu); static void context_size_changed_cb (GtkCellAreaContext *context, GParamSpec *pspec, @@ -82,6 +108,8 @@ static void submenu_activated_cb (GtkTreeMenu const gchar *path, GtkTreeMenu *menu); + + struct _GtkTreeMenuPrivate { /* TreeModel and parent for this menu */ @@ -97,6 +125,16 @@ struct _GtkTreeMenuPrivate gulong row_inserted_id; gulong row_deleted_id; gulong row_reordered_id; + gulong row_changed_id; + + /* Grid menu mode */ + gint wrap_width; + gint row_span_col; + gint col_span_col; + + /* Flags */ + guint32 menu_with_header : 1; + guint32 tearoff : 1; /* Row separators */ GtkTreeViewRowSeparatorFunc row_separator_func; @@ -107,9 +145,6 @@ struct _GtkTreeMenuPrivate GtkTreeMenuHeaderFunc header_func; gpointer header_data; GDestroyNotify header_destroy; - - guint32 menu_with_header : 1; - guint32 tearoff : 1; }; enum { @@ -117,7 +152,10 @@ enum { PROP_MODEL, PROP_ROOT, PROP_CELL_AREA, - PROP_TEAROFF + PROP_TEAROFF, + PROP_WRAP_WIDTH, + PROP_ROW_SPAN_COL, + PROP_COL_SPAN_COL }; enum { @@ -142,6 +180,32 @@ gtk_tree_menu_init (GtkTreeMenu *menu) GtkTreeMenuPrivate); priv = menu->priv; + priv->model = NULL; + priv->root = NULL; + priv->area = NULL; + priv->context = NULL; + + priv->size_changed_id = 0; + priv->row_inserted_id = 0; + priv->row_deleted_id = 0; + priv->row_reordered_id = 0; + priv->row_changed_id = 0; + + priv->wrap_width = 0; + priv->row_span_col = -1; + priv->col_span_col = -1; + + priv->menu_with_header = FALSE; + priv->tearoff = FALSE; + + priv->row_separator_func = NULL; + priv->row_separator_data = NULL; + priv->row_separator_destroy = NULL; + + priv->header_func = NULL; + priv->header_data = NULL; + priv->header_destroy = NULL; + gtk_menu_set_reserve_toggle_size (GTK_MENU (menu), FALSE); } @@ -188,24 +252,85 @@ gtk_tree_menu_class_init (GtkTreeMenuClass *class) GTK_TYPE_TREE_PATH, GTK_PARAM_READWRITE)); - g_object_class_install_property (object_class, - PROP_CELL_AREA, - g_param_spec_object ("cell-area", - P_("Cell Area"), - P_("The GtkCellArea used to layout cells"), - GTK_TYPE_CELL_AREA, - GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); - - g_object_class_install_property (object_class, - PROP_TEAROFF, - g_param_spec_boolean ("tearoff", - P_("Tearoff"), - P_("Whether the menu has a tearoff item"), - FALSE, - GTK_PARAM_READWRITE)); - + g_object_class_install_property (object_class, + PROP_CELL_AREA, + g_param_spec_object ("cell-area", + P_("Cell Area"), + P_("The GtkCellArea used to layout cells"), + GTK_TYPE_CELL_AREA, + GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); - g_type_class_add_private (object_class, sizeof (GtkTreeMenuPrivate)); + g_object_class_install_property (object_class, + PROP_TEAROFF, + g_param_spec_boolean ("tearoff", + P_("Tearoff"), + P_("Whether the menu has a tearoff item"), + FALSE, + GTK_PARAM_READWRITE)); + + /** + * GtkTreeMenu:wrap-width: + * + * If wrap-width is set to a positive value, the list will be + * displayed in multiple columns, the number of columns is + * determined by wrap-width. + * + * Since: 3.0 + */ + g_object_class_install_property (object_class, + PROP_WRAP_WIDTH, + g_param_spec_int ("wrap-width", + P_("Wrap Width"), + P_("Wrap width for laying out items in a grid"), + 0, + G_MAXINT, + 0, + GTK_PARAM_READWRITE)); + + /** + * GtkTreeMenu:row-span-column: + * + * If this is set to a non-negative value, it must be the index of a column + * of type %G_TYPE_INT in the model. + * + * The values of that column are used to determine how many rows a value in + * the list will span. Therefore, the values in the model column pointed to + * by this property must be greater than zero and not larger than wrap-width. + * + * Since: 3.0 + */ + g_object_class_install_property (object_class, + PROP_ROW_SPAN_COL, + g_param_spec_int ("row-span-column", + P_("Row span column"), + P_("TreeModel column containing the row span values"), + -1, + G_MAXINT, + -1, + GTK_PARAM_READWRITE)); + + /** + * GtkTreeMenu:column-span-column: + * + * If this is set to a non-negative value, it must be the index of a column + * of type %G_TYPE_INT in the model. + * + * The values of that column are used to determine how many columns a value + * in the list will span. + * + * Since: 3.0 + */ + g_object_class_install_property (object_class, + PROP_COL_SPAN_COL, + g_param_spec_int ("column-span-column", + P_("Column span column"), + P_("TreeModel column containing the column span values"), + -1, + G_MAXINT, + -1, + GTK_PARAM_READWRITE)); + + g_type_class_add_private (object_class, sizeof (GtkTreeMenuPrivate)); } /**************************************************************** @@ -312,6 +437,18 @@ gtk_tree_menu_set_property (GObject *object, gtk_tree_menu_set_tearoff (menu, g_value_get_boolean (value)); break; + case PROP_WRAP_WIDTH: + gtk_tree_menu_set_wrap_width (menu, g_value_get_int (value)); + break; + + case PROP_ROW_SPAN_COL: + gtk_tree_menu_set_row_span_column (menu, g_value_get_int (value)); + break; + + case PROP_COL_SPAN_COL: + gtk_tree_menu_set_column_span_column (menu, g_value_get_int (value)); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -510,7 +647,7 @@ row_inserted_cb (GtkTreeModel *model, parent_path = gtk_tree_path_copy (path); /* Check if the menu and the added iter are in root of the model */ - if (!gtk_tree_path_up (parent_path)) + if (gtk_tree_path_get_depth (parent_path) <= 1) { if (!priv->root) this_menu = TRUE; @@ -534,27 +671,32 @@ row_inserted_cb (GtkTreeModel *model, { GtkWidget *item; - /* Get the index of the path for this depth */ - indices = gtk_tree_path_get_indices (path); - depth = gtk_tree_path_get_depth (path); - index = indices[depth -1]; - - /* Menus with a header include a menuitem for it's root node - * and a separator menu item */ - if (priv->menu_with_header) - index += 2; - - /* Index after the tearoff item for the root menu if - * there is a tearoff item - */ - if (priv->root == NULL && priv->tearoff) - index += 1; - - item = gtk_tree_menu_create_item (menu, iter, FALSE); - gtk_menu_shell_insert (GTK_MENU_SHELL (menu), item, index); - - /* Resize everything */ - gtk_cell_area_context_flush (menu->priv->context); + if (priv->wrap_width > 0) + rebuild_menu (menu); + else + { + /* Get the index of the path for this depth */ + indices = gtk_tree_path_get_indices (path); + depth = gtk_tree_path_get_depth (path); + index = indices[depth -1]; + + /* Menus with a header include a menuitem for it's root node + * and a separator menu item */ + if (priv->menu_with_header) + index += 2; + + /* Index after the tearoff item for the root menu if + * there is a tearoff item + */ + if (priv->root == NULL && priv->tearoff) + index += 1; + + item = gtk_tree_menu_create_item (menu, iter, FALSE); + gtk_menu_shell_insert (GTK_MENU_SHELL (menu), item, index); + + /* Resize everything */ + gtk_cell_area_context_flush (menu->priv->context); + } } } @@ -580,14 +722,19 @@ row_deleted_cb (GtkTreeModel *model, } } - /* Get rid of the deleted item */ item = gtk_tree_menu_get_path_item (menu, path); if (item) { - gtk_widget_destroy (item); - - /* Resize everything */ - gtk_cell_area_context_flush (menu->priv->context); + if (priv->wrap_width > 0) + rebuild_menu (menu); + else + { + /* Get rid of the deleted item */ + gtk_widget_destroy (item); + + /* Resize everything */ + gtk_cell_area_context_flush (menu->priv->context); + } } } @@ -615,15 +762,102 @@ row_reordered_cb (GtkTreeModel *model, } if (this_menu) + rebuild_menu (menu); +} + +static gint +menu_item_position (GtkTreeMenu *menu, + GtkWidget *item) +{ + GList *children, *l; + gint position; + + children = gtk_container_get_children (GTK_CONTAINER (menu)); + for (position = 0, l = children; l; position++, l = l->next) + { + GtkWidget *iitem = l->data; + + if (item == iitem) + break; + } + + g_list_free (children); + + return position; +} + +static void +row_changed_cb (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + GtkTreeMenu *menu) +{ + GtkTreeMenuPrivate *priv = menu->priv; + gboolean is_separator = FALSE; + gboolean has_header = FALSE; + GtkWidget *item; + + item = gtk_tree_menu_get_path_item (menu, path); + + if (priv->root) { - /* Destroy and repopulate the menu at the level where the order changed */ - gtk_container_foreach (GTK_CONTAINER (menu), - (GtkCallback) gtk_widget_destroy, NULL); + GtkTreePath *root_path = + gtk_tree_row_reference_get_path (priv->root); + + if (gtk_tree_path_compare (root_path, path) == 0) + { + if (priv->header_func) + has_header = + priv->header_func (priv->model, iter, priv->header_data); + + if (has_header && !item) + { + item = gtk_separator_menu_item_new (); + gtk_widget_show (item); + gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item); + + item = gtk_tree_menu_create_item (menu, iter, TRUE); + gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item); + + priv->menu_with_header = TRUE; + } + else if (!has_header && item) + { + /* Destroy the header item and the following separator */ + gtk_widget_destroy (item); + gtk_widget_destroy (GTK_MENU_SHELL (menu)->children->data); + + priv->menu_with_header = FALSE; + } + } + + gtk_tree_path_free (root_path); + } + + if (item) + { + if (priv->wrap_width > 0) + /* Ugly, we need to rebuild the menu here if + * the row-span/row-column values change + */ + rebuild_menu (menu); + else + { + if (priv->row_separator_func) + is_separator = + priv->row_separator_func (model, iter, + priv->row_separator_data); - gtk_tree_menu_populate (menu); - /* Resize everything */ - gtk_cell_area_context_flush (menu->priv->context); + if (is_separator != GTK_IS_SEPARATOR_MENU_ITEM (item)) + { + gint position = menu_item_position (menu, item); + + gtk_widget_destroy (item); + item = gtk_tree_menu_create_item (menu, iter, FALSE); + gtk_menu_shell_insert (GTK_MENU_SHELL (menu), item, position); + } + } } } @@ -654,6 +888,93 @@ gtk_tree_menu_set_area (GtkTreeMenu *menu, g_object_ref_sink (priv->area); } +static gboolean +menu_occupied (GtkTreeMenu *menu, + guint left_attach, + guint right_attach, + guint top_attach, + guint bottom_attach) +{ + GList *i; + + for (i = GTK_MENU_SHELL (menu)->children; i; i = i->next) + { + guint l, r, b, t; + + gtk_container_child_get (GTK_CONTAINER (menu), + i->data, + "left-attach", &l, + "right-attach", &r, + "bottom-attach", &b, + "top-attach", &t, + NULL); + + /* look if this item intersects with the given coordinates */ + if (right_attach > l && left_attach < r && bottom_attach > t && top_attach < b) + return TRUE; + } + + return FALSE; +} + +static void +relayout_item (GtkTreeMenu *menu, + GtkWidget *item, + GtkTreeIter *iter, + GtkWidget *prev) +{ + GtkTreeMenuPrivate *priv = menu->priv; + gint current_col = 0, current_row = 0; + gint rows = 1, cols = 1; + + if (priv->col_span_col == -1 && + priv->row_span_col == -1 && + prev) + { + gtk_container_child_get (GTK_CONTAINER (menu), prev, + "right-attach", ¤t_col, + "top-attach", ¤t_row, + NULL); + if (current_col + cols > priv->wrap_width) + { + current_col = 0; + current_row++; + } + } + else + { + if (priv->col_span_col != -1) + gtk_tree_model_get (priv->model, iter, + priv->col_span_col, &cols, + -1); + if (priv->row_span_col != -1) + gtk_tree_model_get (priv->model, iter, + priv->row_span_col, &rows, + -1); + + while (1) + { + if (current_col + cols > priv->wrap_width) + { + current_col = 0; + current_row++; + } + + if (!menu_occupied (menu, + current_col, current_col + cols, + current_row, current_row + rows)) + break; + + current_col++; + } + } + + /* set attach props */ + gtk_menu_attach (GTK_MENU (menu), item, + current_col, current_col + cols, + current_row, current_row + rows); +} + static GtkWidget * gtk_tree_menu_create_item (GtkTreeMenu *menu, GtkTreeIter *iter, @@ -727,6 +1048,21 @@ gtk_tree_menu_create_item (GtkTreeMenu *menu, return item; } +static inline void +rebuild_menu (GtkTreeMenu *menu) +{ + GtkTreeMenuPrivate *priv = menu->priv; + + /* Destroy all the menu items */ + gtk_container_foreach (GTK_CONTAINER (menu), + (GtkCallback) gtk_widget_destroy, NULL); + + /* Populate */ + if (priv->model) + gtk_tree_menu_populate (menu); +} + + static void gtk_tree_menu_populate (GtkTreeMenu *menu) { @@ -735,7 +1071,7 @@ gtk_tree_menu_populate (GtkTreeMenu *menu) GtkTreeIter parent; GtkTreeIter iter; gboolean valid = FALSE; - GtkWidget *menu_item; + GtkWidget *menu_item, *prev = NULL; if (!priv->model) return; @@ -762,6 +1098,7 @@ gtk_tree_menu_populate (GtkTreeMenu *menu) gtk_widget_show (menu_item); gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item); + prev = menu_item; priv->menu_with_header = TRUE; } } @@ -775,11 +1112,13 @@ gtk_tree_menu_populate (GtkTreeMenu *menu) menu_item = gtk_tearoff_menu_item_new (); gtk_widget_show (menu_item); - /* Here if wrap_width > 0 then we need to attach the menu - * item to span the entire first row of the grid menu */ - gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item); - } + if (priv->wrap_width > 0) + gtk_menu_attach (GTK_MENU (menu), menu_item, 0, priv->wrap_width, 0, 1); + else + gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item); + prev = menu_item; + } valid = gtk_tree_model_iter_children (priv->model, &iter, NULL); } @@ -789,8 +1128,13 @@ gtk_tree_menu_populate (GtkTreeMenu *menu) while (valid) { menu_item = gtk_tree_menu_create_item (menu, &iter, FALSE); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item); + if (priv->wrap_width > 0) + relayout_item (menu, menu_item, &iter, prev); + + prev = menu_item; valid = gtk_tree_model_iter_next (priv->model, &iter); } } @@ -876,9 +1220,12 @@ gtk_tree_menu_set_model (GtkTreeMenu *menu, priv->row_deleted_id); g_signal_handler_disconnect (priv->model, priv->row_reordered_id); + g_signal_handler_disconnect (priv->model, + priv->row_changed_id); priv->row_inserted_id = 0; priv->row_deleted_id = 0; priv->row_reordered_id = 0; + priv->row_changed_id = 0; g_object_unref (priv->model); } @@ -896,6 +1243,8 @@ gtk_tree_menu_set_model (GtkTreeMenu *menu, G_CALLBACK (row_deleted_cb), menu); priv->row_reordered_id = g_signal_connect (priv->model, "rows-reordered", G_CALLBACK (row_reordered_cb), menu); + priv->row_changed_id = g_signal_connect (priv->model, "row-changed", + G_CALLBACK (row_changed_cb), menu); } } } @@ -931,13 +1280,7 @@ gtk_tree_menu_set_root (GtkTreeMenu *menu, else priv->root = NULL; - /* Destroy all the menu items for the previous root */ - gtk_container_foreach (GTK_CONTAINER (menu), - (GtkCallback) gtk_widget_destroy, NULL); - - /* Populate for the new root */ - if (priv->model) - gtk_tree_menu_populate (menu); + rebuild_menu (menu); } GtkTreePath * @@ -981,18 +1324,122 @@ gtk_tree_menu_set_tearoff (GtkTreeMenu *menu, { priv->tearoff = tearoff; - /* Destroy all the menu items */ - gtk_container_foreach (GTK_CONTAINER (menu), - (GtkCallback) gtk_widget_destroy, NULL); - - /* Populate again */ - if (priv->model) - gtk_tree_menu_populate (menu); + rebuild_menu (menu); g_object_notify (G_OBJECT (menu), "tearoff"); } } +gint +gtk_tree_menu_get_wrap_width (GtkTreeMenu *menu) +{ + GtkTreeMenuPrivate *priv; + + g_return_val_if_fail (GTK_IS_TREE_MENU (menu), FALSE); + + priv = menu->priv; + + return priv->wrap_width; +} + +void +gtk_tree_menu_set_wrap_width (GtkTreeMenu *menu, + gint width) +{ + GtkTreeMenuPrivate *priv; + + g_return_if_fail (GTK_IS_TREE_MENU (menu)); + g_return_if_fail (width >= 0); + + priv = menu->priv; + + if (priv->wrap_width != width) + { + priv->wrap_width = width; + + rebuild_menu (menu); + + g_object_notify (G_OBJECT (menu), "wrap-width"); + } +} + +gint +gtk_tree_menu_get_row_span_column (GtkTreeMenu *menu) +{ + GtkTreeMenuPrivate *priv; + + g_return_val_if_fail (GTK_IS_TREE_MENU (menu), FALSE); + + priv = menu->priv; + + return priv->row_span_col; +} + +void +gtk_tree_menu_set_row_span_column (GtkTreeMenu *menu, + gint row_span) +{ + GtkTreeMenuPrivate *priv; + + g_return_if_fail (GTK_IS_TREE_MENU (menu)); + + priv = menu->priv; + + if (priv->row_span_col != row_span) + { + priv->row_span_col = row_span; + + if (priv->wrap_width > 0) + rebuild_menu (menu); + + g_object_notify (G_OBJECT (menu), "row-span-column"); + } +} + +gint +gtk_tree_menu_get_column_span_column (GtkTreeMenu *menu) +{ + GtkTreeMenuPrivate *priv; + + g_return_val_if_fail (GTK_IS_TREE_MENU (menu), FALSE); + + priv = menu->priv; + + return priv->col_span_col; +} + +void +gtk_tree_menu_set_column_span_column (GtkTreeMenu *menu, + gint column_span) +{ + GtkTreeMenuPrivate *priv; + + g_return_if_fail (GTK_IS_TREE_MENU (menu)); + + priv = menu->priv; + + if (priv->col_span_col != column_span) + { + priv->col_span_col = column_span; + + if (priv->wrap_width > 0) + rebuild_menu (menu); + + g_object_notify (G_OBJECT (menu), "column-span-column"); + } +} + +GtkTreeViewRowSeparatorFunc +gtk_tree_menu_get_row_separator_func (GtkTreeMenu *menu) +{ + GtkTreeMenuPrivate *priv; + + g_return_val_if_fail (GTK_IS_TREE_MENU (menu), NULL); + + priv = menu->priv; + + return priv->row_separator_func; +} void gtk_tree_menu_set_row_separator_func (GtkTreeMenu *menu, @@ -1013,17 +1460,11 @@ gtk_tree_menu_set_row_separator_func (GtkTreeMenu *menu, priv->row_separator_data = data; priv->row_separator_destroy = destroy; - /* Destroy all the menu items */ - gtk_container_foreach (GTK_CONTAINER (menu), - (GtkCallback) gtk_widget_destroy, NULL); - - /* Populate again */ - if (priv->model) - gtk_tree_menu_populate (menu); + rebuild_menu (menu); } -GtkTreeViewRowSeparatorFunc -gtk_tree_menu_get_row_separator_func (GtkTreeMenu *menu) +GtkTreeMenuHeaderFunc +gtk_tree_menu_get_header_func (GtkTreeMenu *menu) { GtkTreeMenuPrivate *priv; @@ -1031,7 +1472,7 @@ gtk_tree_menu_get_row_separator_func (GtkTreeMenu *menu) priv = menu->priv; - return priv->row_separator_func; + return priv->header_func; } void @@ -1053,23 +1494,5 @@ gtk_tree_menu_set_header_func (GtkTreeMenu *menu, priv->header_data = data; priv->header_destroy = destroy; - /* Destroy all the menu items */ - gtk_container_foreach (GTK_CONTAINER (menu), - (GtkCallback) gtk_widget_destroy, NULL); - - /* Populate again */ - if (priv->model) - gtk_tree_menu_populate (menu); -} - -GtkTreeMenuHeaderFunc -gtk_tree_menu_get_header_func (GtkTreeMenu *menu) -{ - GtkTreeMenuPrivate *priv; - - g_return_val_if_fail (GTK_IS_TREE_MENU (menu), NULL); - - priv = menu->priv; - - return priv->header_func; + rebuild_menu (menu); } |