Connected to GtkTreeModel signals in GtkTreeMenu

Now the GtkTreeMenu properly updates its hierarchy when the underlying
model data changes (row inserted/deleted or reordered). Also some unneeded
hackery was removed, all size calculations are delegated to the cellviews.
This commit is contained in:
Tristan Van Berkom 2010-11-22 15:53:19 +09:00
parent 88ec6a62ef
commit 438b0f7c9b

View File

@ -87,17 +87,21 @@ static GtkCellArea *gtk_tree_menu_cell_layout_get_area (GtkCellLayout
/* TreeModel/DrawingArea callbacks and building menus/submenus */
static void gtk_tree_menu_populate (GtkTreeMenu *menu);
static void gtk_tree_menu_populate (GtkTreeMenu *menu);
static GtkWidget *gtk_tree_menu_create_item (GtkTreeMenu *menu,
GtkTreeIter *iter);
static void gtk_tree_menu_set_area (GtkTreeMenu *menu,
GtkTreeIter *iter,
gboolean header_item);
static void gtk_tree_menu_set_area (GtkTreeMenu *menu,
GtkCellArea *area);
static void context_size_changed_cb (GtkCellAreaContext *context,
static GtkWidget *gtk_tree_menu_get_path_item (GtkTreeMenu *menu,
GtkTreePath *path);
static void context_size_changed_cb (GtkCellAreaContext *context,
GParamSpec *pspec,
GtkWidget *menu);
static void item_activated_cb (GtkMenuItem *item,
static void item_activated_cb (GtkMenuItem *item,
GtkTreeMenu *menu);
static void submenu_activated_cb (GtkTreeMenu *submenu,
static void submenu_activated_cb (GtkTreeMenu *submenu,
const gchar *path,
GtkTreeMenu *menu);
@ -111,11 +115,11 @@ struct _GtkTreeMenuPrivate
GtkCellArea *area;
GtkCellAreaContext *context;
gint last_alloc_width;
gint last_alloc_height;
/* Signals */
gulong size_changed_id;
gulong row_inserted_id;
gulong row_deleted_id;
gulong row_reordered_id;
/* Row separators */
GtkTreeViewRowSeparatorFunc row_separator_func;
@ -126,6 +130,8 @@ struct _GtkTreeMenuPrivate
GtkTreeMenuHeaderFunc header_func;
gpointer header_data;
GDestroyNotify header_destroy;
guint32 menu_with_header : 1;
};
enum {
@ -140,7 +146,8 @@ enum {
N_SIGNALS
};
static guint tree_menu_signals[N_SIGNALS] = { 0 };
static guint tree_menu_signals[N_SIGNALS] = { 0 };
static GQuark tree_menu_path_quark = 0;
G_DEFINE_TYPE_WITH_CODE (GtkTreeMenu, gtk_tree_menu, GTK_TYPE_MENU,
G_IMPLEMENT_INTERFACE (GTK_TYPE_CELL_LAYOUT,
@ -165,6 +172,8 @@ gtk_tree_menu_class_init (GtkTreeMenuClass *class)
GObjectClass *object_class = G_OBJECT_CLASS (class);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
tree_menu_path_quark = g_quark_from_static_string ("gtk-tree-menu-path");
object_class->constructor = gtk_tree_menu_constructor;
object_class->dispose = gtk_tree_menu_dispose;
object_class->finalize = gtk_tree_menu_finalize;
@ -392,63 +401,22 @@ gtk_tree_menu_get_preferred_width (GtkWidget *widget,
{
GtkTreeMenu *menu = GTK_TREE_MENU (widget);
GtkTreeMenuPrivate *priv = menu->priv;
GtkTreePath *path = NULL;
GtkTreeIter iter;
gboolean valid = FALSE;
/* We leave the requesting work up to the cellviews which operate in the same
* context, reserving space for the submenu indicator if any of the items have
* submenus ensures that every cellview will receive the same allocated width.
*
* Since GtkMenu does hieght-for-width correctly, we know that the width of
* every cell will be requested before the height-for-widths are requested.
*/
g_signal_handler_block (priv->context, priv->size_changed_id);
/* Before chaining up to the parent class and requesting the
* menu item/cell view sizes, we need to request the size of
* each row for this menu and make sure all the cellviews
* request enough space
*/
sync_reserve_submenu_size (menu);
gtk_cell_area_context_flush_preferred_width (priv->context);
sync_reserve_submenu_size (menu);
if (priv->model)
{
if (priv->root)
path = gtk_tree_row_reference_get_path (priv->root);
if (path)
{
GtkTreeIter parent;
if (gtk_tree_model_get_iter (priv->model, &parent, path))
valid = gtk_tree_model_iter_children (priv->model, &iter, &parent);
gtk_tree_path_free (path);
}
else
valid = gtk_tree_model_iter_children (priv->model, &iter, NULL);
while (valid)
{
gboolean is_separator = FALSE;
if (priv->row_separator_func)
is_separator =
priv->row_separator_func (priv->model, &iter, priv->row_separator_data);
if (!is_separator)
{
gtk_cell_area_apply_attributes (priv->area, priv->model, &iter, FALSE, FALSE);
gtk_cell_area_get_preferred_width (priv->area, priv->context, widget, NULL, NULL);
}
valid = gtk_tree_model_iter_next (priv->model, &iter);
}
}
gtk_cell_area_context_sum_preferred_width (priv->context);
GTK_WIDGET_CLASS (gtk_tree_menu_parent_class)->get_preferred_width (widget, minimum_size, natural_size);
g_signal_handler_unblock (priv->context, priv->size_changed_id);
/* Now that we've requested all the row's and updated priv->context properly, we can go ahead
* and calculate the sizes by requesting the menu items and thier cell views */
GTK_WIDGET_CLASS (gtk_tree_menu_parent_class)->get_preferred_width (widget, minimum_size, natural_size);
}
static void
@ -458,63 +426,15 @@ gtk_tree_menu_get_preferred_height (GtkWidget *widget,
{
GtkTreeMenu *menu = GTK_TREE_MENU (widget);
GtkTreeMenuPrivate *priv = menu->priv;
GtkTreePath *path = NULL;
GtkTreeIter iter;
gboolean valid = FALSE;
g_signal_handler_block (priv->context, priv->size_changed_id);
/* Before chaining up to the parent class and requesting the
* menu item/cell view sizes, we need to request the size of
* each row for this menu and make sure all the cellviews
* request enough space
*/
sync_reserve_submenu_size (menu);
gtk_cell_area_context_flush_preferred_height (priv->context);
sync_reserve_submenu_size (menu);
if (priv->model)
{
if (priv->root)
path = gtk_tree_row_reference_get_path (priv->root);
if (path)
{
GtkTreeIter parent;
if (gtk_tree_model_get_iter (priv->model, &parent, path))
valid = gtk_tree_model_iter_children (priv->model, &iter, &parent);
gtk_tree_path_free (path);
}
else
valid = gtk_tree_model_iter_children (priv->model, &iter, NULL);
while (valid)
{
gboolean is_separator = FALSE;
if (priv->row_separator_func)
is_separator =
priv->row_separator_func (priv->model, &iter, priv->row_separator_data);
if (!is_separator)
{
gtk_cell_area_apply_attributes (priv->area, priv->model, &iter, FALSE, FALSE);
gtk_cell_area_get_preferred_height (priv->area, priv->context, widget, NULL, NULL);
}
valid = gtk_tree_model_iter_next (priv->model, &iter);
}
}
gtk_cell_area_context_sum_preferred_height (priv->context);
GTK_WIDGET_CLASS (gtk_tree_menu_parent_class)->get_preferred_height (widget, minimum_size, natural_size);
g_signal_handler_unblock (priv->context, priv->size_changed_id);
/* Now that we've requested all the row's and updated priv->context properly, we can go ahead
* and calculate the sizes by requesting the menu items and thier cell views */
GTK_WIDGET_CLASS (gtk_tree_menu_parent_class)->get_preferred_height (widget, minimum_size, natural_size);
}
static void
@ -523,29 +443,14 @@ gtk_tree_menu_size_allocate (GtkWidget *widget,
{
GtkTreeMenu *menu = GTK_TREE_MENU (widget);
GtkTreeMenuPrivate *priv = menu->priv;
gint new_width, new_height;
/* flush the context allocation */
gtk_cell_area_context_flush_allocation (priv->context);
/* Leave it to the first cell area to allocate the size of priv->context, since
* we configure the menu to allocate all children the same width this should work fine */
* we configure the menu to allocate all children the same width this works fine
*/
GTK_WIDGET_CLASS (gtk_tree_menu_parent_class)->size_allocate (widget, allocation);
/* In alot of cases the menu gets allocated while the children dont need
* any reallocation, in this case we need to restore the context allocation */
gtk_cell_area_context_get_allocation (priv->context, &new_width, &new_height);
if (new_width <= 0 && new_height <= 0)
{
gtk_cell_area_context_allocate_width (priv->context, priv->last_alloc_width);
gtk_cell_area_context_allocate_height (priv->context, priv->last_alloc_height);
}
/* Save the allocation for the next round */
gtk_cell_area_context_get_allocation (priv->context,
&priv->last_alloc_width,
&priv->last_alloc_height);
}
/****************************************************************
@ -667,6 +572,176 @@ gtk_tree_menu_cell_layout_get_area (GtkCellLayout *layout)
/****************************************************************
* TreeModel callbacks/populating menus *
****************************************************************/
static GtkWidget *
gtk_tree_menu_get_path_item (GtkTreeMenu *menu,
GtkTreePath *search)
{
GtkWidget *item = NULL;
GList *children, *l;
children = gtk_container_get_children (GTK_CONTAINER (menu));
for (l = children; item == NULL && l != NULL; l = l->next)
{
GtkWidget *child = l->data;
GtkTreePath *path = NULL;
if (GTK_IS_SEPARATOR_MENU_ITEM (child))
{
GtkTreeRowReference *row =
g_object_get_qdata (G_OBJECT (child), tree_menu_path_quark);
if (row && gtk_tree_row_reference_valid (row))
path = gtk_tree_row_reference_get_path (row);
}
else
{
GtkWidget *view = gtk_bin_get_child (GTK_BIN (child));
/* It's always a cellview */
if (GTK_IS_CELL_VIEW (view))
path = gtk_cell_view_get_displayed_row (GTK_CELL_VIEW (view));
}
if (path)
{
if (gtk_tree_path_compare (search, path) == 0)
item = child;
gtk_tree_path_free (path);
}
}
g_list_free (children);
return item;
}
static void
row_inserted_cb (GtkTreeModel *model,
GtkTreePath *path,
GtkTreeIter *iter,
GtkTreeMenu *menu)
{
GtkTreeMenuPrivate *priv = menu->priv;
GtkTreePath *parent_path;
gboolean this_menu = FALSE;
gint *indices, index, depth;
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 (!priv->root)
this_menu = TRUE;
}
/* If we are a submenu, compare the path */
else if (priv->root)
{
GtkTreePath *root_path =
gtk_tree_row_reference_get_path (priv->root);
if (gtk_tree_path_compare (root_path, parent_path) == 0)
this_menu = TRUE;
gtk_tree_path_free (root_path);
}
gtk_tree_path_free (parent_path);
/* If the iter should be in this menu then go ahead and insert it */
if (this_menu)
{
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;
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);
}
}
static void
row_deleted_cb (GtkTreeModel *model,
GtkTreePath *path,
GtkTreeMenu *menu)
{
GtkTreeMenuPrivate *priv = menu->priv;
GtkTreePath *root_path;
GtkWidget *item;
/* If it's the root node we leave it to the parent menu to remove us
* from its menu */
if (priv->root)
{
root_path = gtk_tree_row_reference_get_path (priv->root);
if (gtk_tree_path_compare (root_path, path) == 0)
{
gtk_tree_path_free (root_path);
return;
}
}
/* 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);
}
}
static void
row_reordered_cb (GtkTreeModel *model,
GtkTreePath *path,
GtkTreeIter *iter,
gint *new_order,
GtkTreeMenu *menu)
{
GtkTreeMenuPrivate *priv = menu->priv;
gboolean this_menu = FALSE;
if (iter == NULL && priv->root == NULL)
this_menu = TRUE;
else if (priv->root)
{
GtkTreePath *root_path =
gtk_tree_row_reference_get_path (priv->root);
if (gtk_tree_path_compare (root_path, path) == 0)
this_menu = TRUE;
gtk_tree_path_free (root_path);
}
if (this_menu)
{
/* Destroy and repopulate the menu at the level where the order changed */
gtk_container_foreach (GTK_CONTAINER (menu),
(GtkCallback) gtk_widget_destroy, NULL);
gtk_tree_menu_populate (menu);
/* Resize everything */
gtk_cell_area_context_flush (menu->priv->context);
}
}
static void
context_size_changed_cb (GtkCellAreaContext *context,
GParamSpec *pspec,
@ -696,29 +771,74 @@ gtk_tree_menu_set_area (GtkTreeMenu *menu,
static GtkWidget *
gtk_tree_menu_create_item (GtkTreeMenu *menu,
GtkTreeIter *iter)
GtkTreeIter *iter,
gboolean header_item)
{
GtkTreeMenuPrivate *priv = menu->priv;
GtkWidget *item, *view;
GtkTreePath *path;
view = gtk_cell_view_new_with_context (priv->area, priv->context);
item = gtk_menu_item_new ();
gtk_widget_show (view);
gtk_widget_show (item);
gboolean is_separator = FALSE;
path = gtk_tree_model_get_path (priv->model, iter);
gtk_cell_view_set_model (GTK_CELL_VIEW (view), priv->model);
gtk_cell_view_set_displayed_row (GTK_CELL_VIEW (view), path);
if (priv->row_separator_func)
is_separator =
priv->row_separator_func (priv->model, iter,
priv->row_separator_data);
if (is_separator)
{
item = gtk_separator_menu_item_new ();
g_object_set_qdata_full (G_OBJECT (item),
tree_menu_path_quark,
gtk_tree_row_reference_new (priv->model, path),
(GDestroyNotify)gtk_tree_row_reference_free);
}
else
{
view = gtk_cell_view_new_with_context (priv->area, priv->context);
item = gtk_menu_item_new ();
gtk_widget_show (view);
gtk_widget_show (item);
gtk_cell_view_set_model (GTK_CELL_VIEW (view), priv->model);
gtk_cell_view_set_displayed_row (GTK_CELL_VIEW (view), path);
gtk_widget_show (view);
gtk_container_add (GTK_CONTAINER (item), view);
g_signal_connect (item, "activate", G_CALLBACK (item_activated_cb), menu);
/* Add a GtkTreeMenu submenu to render the children of this row */
if (header_item == FALSE &&
gtk_tree_model_iter_has_child (priv->model, iter))
{
GtkWidget *submenu;
submenu = gtk_tree_menu_new_with_area (priv->area);
gtk_tree_menu_set_row_separator_func (GTK_TREE_MENU (submenu),
priv->row_separator_func,
priv->row_separator_data,
priv->row_separator_destroy);
gtk_tree_menu_set_header_func (GTK_TREE_MENU (submenu),
priv->header_func,
priv->header_data,
priv->header_destroy);
gtk_tree_menu_set_model (GTK_TREE_MENU (submenu), priv->model);
gtk_tree_menu_set_root (GTK_TREE_MENU (submenu), path);
gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu);
g_signal_connect (submenu, "menu-activate",
G_CALLBACK (submenu_activated_cb), menu);
}
}
gtk_tree_path_free (path);
gtk_widget_show (view);
gtk_container_add (GTK_CONTAINER (item), view);
g_signal_connect (item, "activate", G_CALLBACK (item_activated_cb), menu);
return item;
}
@ -750,12 +870,14 @@ gtk_tree_menu_populate (GtkTreeMenu *menu)
/* Add a submenu header for rows which desire one, used for
* combo boxes to allow all rows to be activatable/selectable
*/
menu_item = gtk_tree_menu_create_item (menu, &parent);
menu_item = gtk_tree_menu_create_item (menu, &parent, TRUE);
gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
menu_item = gtk_separator_menu_item_new ();
gtk_widget_show (menu_item);
gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
priv->menu_with_header = TRUE;
}
}
gtk_tree_path_free (path);
@ -767,49 +889,7 @@ gtk_tree_menu_populate (GtkTreeMenu *menu)
* submenu for iters/items that have children */
while (valid)
{
gboolean is_separator = FALSE;
if (priv->row_separator_func)
is_separator =
priv->row_separator_func (priv->model, &iter,
priv->row_separator_data);
if (is_separator)
menu_item = gtk_separator_menu_item_new ();
else
{
menu_item = gtk_tree_menu_create_item (menu, &iter);
/* Add a GtkTreeMenu submenu to render the children of this row */
if (gtk_tree_model_iter_has_child (priv->model, &iter))
{
GtkTreePath *row_path;
GtkWidget *submenu;
row_path = gtk_tree_model_get_path (priv->model, &iter);
submenu = gtk_tree_menu_new_with_area (priv->area);
gtk_tree_menu_set_row_separator_func (GTK_TREE_MENU (submenu),
priv->row_separator_func,
priv->row_separator_data,
priv->row_separator_destroy);
gtk_tree_menu_set_header_func (GTK_TREE_MENU (submenu),
priv->header_func,
priv->header_data,
priv->header_destroy);
gtk_tree_menu_set_model (GTK_TREE_MENU (submenu), priv->model);
gtk_tree_menu_set_root (GTK_TREE_MENU (submenu), row_path);
gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu_item), submenu);
gtk_tree_path_free (row_path);
g_signal_connect (submenu, "menu-activate",
G_CALLBACK (submenu_activated_cb), menu);
}
}
menu_item = gtk_tree_menu_create_item (menu, &iter, FALSE);
gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
valid = gtk_tree_model_iter_next (priv->model, &iter);
@ -891,6 +971,15 @@ gtk_tree_menu_set_model (GtkTreeMenu *menu,
if (priv->model)
{
/* Disconnect signals */
g_signal_handler_disconnect (priv->model,
priv->row_inserted_id);
g_signal_handler_disconnect (priv->model,
priv->row_deleted_id);
g_signal_handler_disconnect (priv->model,
priv->row_reordered_id);
priv->row_inserted_id = 0;
priv->row_deleted_id = 0;
priv->row_reordered_id = 0;
g_object_unref (priv->model);
}
@ -899,9 +988,15 @@ gtk_tree_menu_set_model (GtkTreeMenu *menu,
if (priv->model)
{
/* Connect signals */
g_object_ref (priv->model);
/* Connect signals */
priv->row_inserted_id = g_signal_connect (priv->model, "row-inserted",
G_CALLBACK (row_inserted_cb), menu);
priv->row_deleted_id = g_signal_connect (priv->model, "row-deleted",
G_CALLBACK (row_deleted_cb), menu);
priv->row_reordered_id = g_signal_connect (priv->model, "rows-reordered",
G_CALLBACK (row_reordered_cb), menu);
}
}
}