[gtk+/combo-refactor: 15/45] Connected to GtkTreeModel signals in GtkTreeMenu



commit 116a08d96773362f5237c585ff9c7b60558c7cd0
Author: Tristan Van Berkom <tristan van berkom gmail com>
Date:   Mon Nov 22 15:53:19 2010 +0900

    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.

 gtk/gtktreemenu.c |  465 ++++++++++++++++++++++++++++++++---------------------
 1 files changed, 280 insertions(+), 185 deletions(-)
---
diff --git a/gtk/gtktreemenu.c b/gtk/gtktreemenu.c
index d6586b2..6974912 100644
--- a/gtk/gtktreemenu.c
+++ b/gtk/gtktreemenu.c
@@ -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);
 
@@ -110,12 +114,12 @@ struct _GtkTreeMenuPrivate
   /* CellArea and context for this menu */
   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;
-
-  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 
+  /* 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.
    */
-  gtk_cell_area_context_flush_preferred_width (priv->context);
+  g_signal_handler_block (priv->context, priv->size_changed_id);
 
   sync_reserve_submenu_size (menu);
+  gtk_cell_area_context_flush_preferred_width (priv->context);
 
-  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 
-   */
-  gtk_cell_area_context_flush_preferred_height (priv->context);
-
   sync_reserve_submenu_size (menu);
+  gtk_cell_area_context_flush_preferred_height (priv->context);
 
-  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,28 +771,73 @@ 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);
 
-  gtk_tree_path_free (path);
+  if (is_separator)
+    {
+      item = gtk_separator_menu_item_new ();
 
-  gtk_widget_show (view);
-  gtk_container_add (GTK_CONTAINER (item), view);
+      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);
 
-  g_signal_connect (item, "activate", G_CALLBACK (item_activated_cb), menu);
+	  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);
 
   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);
 	}
     }
 }



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]