[gnome-menus] [libmenu] Sort inlined items unless inline_header is used



commit 8bddaf2076e094043189a20c36bf66b8c3813921
Author: Vincent Untz <vuntz gnome org>
Date:   Sat Jun 27 04:18:02 2009 +0200

    [libmenu] Sort inlined items unless inline_header is used
    
    We also strip duplicate entries in a menu when we do such a sort.
    
    This is actually not a trivial task since it means we have to correctly
    preprocess all inline data before using the layout -- else, we'd try to
    sort items while a layout has been used, which is too late (since you
    can specify that you want mixed entries and directories, eg; or even
    entries or directories in a specified order).
    
    We make sure to evaluate the inline data at the deepest first, so we can
    correctly propagate as much inline items as possible to the top.
    
    http://bugzilla.gnome.org/show_bug.cgi?id=490483

 libmenu/gmenu-tree.c |  512 +++++++++++++++++++++++++++++++++++++-------------
 1 files changed, 385 insertions(+), 127 deletions(-)
---
diff --git a/libmenu/gmenu-tree.c b/libmenu/gmenu-tree.c
index 7fd486a..dd4c417 100644
--- a/libmenu/gmenu-tree.c
+++ b/libmenu/gmenu-tree.c
@@ -102,6 +102,10 @@ struct GMenuTreeDirectory
   guint is_root : 1;
   guint is_nodisplay : 1;
   guint layout_pending_separator : 1;
+  guint preprocessed : 1;
+
+  /* 16 bits should be more than enough; G_MAXUINT16 means no inline header */
+  guint will_inline_header : 16;
 };
 
 typedef struct
@@ -1256,6 +1260,9 @@ gmenu_tree_directory_new (GMenuTreeDirectory *parent,
   retval->contents            = NULL;
   retval->only_unallocated    = FALSE;
   retval->is_nodisplay        = FALSE;
+  retval->layout_pending_separator = FALSE;
+  retval->preprocessed        = FALSE;
+  retval->will_inline_header  = G_MAXUINT16;
 
   retval->default_layout_values.mask          = MENU_LAYOUT_VALUES_NONE;
   retval->default_layout_values.show_empty    = FALSE;
@@ -1453,6 +1460,13 @@ gmenu_tree_entry_compare (GMenuTreeEntry *a,
                          desktop_entry_get_name (b->desktop_entry));
 }
 
+static int
+gmenu_tree_entry_compare_by_id (GMenuTreeEntry *a,
+				GMenuTreeEntry *b)
+{
+  return strcmp (a->desktop_file_id, b->desktop_file_id);
+}
+
 gpointer
 gmenu_tree_item_ref (gpointer itemp)
 {
@@ -3544,107 +3558,403 @@ process_only_unallocated (GMenuTree          *tree,
    }
 }
 
-static void process_layout_info (GMenuTree          *tree,
-				 GMenuTreeDirectory *directory);
+static void preprocess_layout_info (GMenuTree          *tree,
+                                    GMenuTreeDirectory *directory);
 
-static void
-check_pending_separator (GMenuTreeDirectory *directory)
+static GSList *
+get_layout_info (GMenuTreeDirectory *directory)
 {
-  if (directory->layout_pending_separator)
+  GMenuTreeDirectory *iter;
+
+  if (directory->layout_info != NULL)
     {
-      menu_verbose ("Adding pending separator in '%s'\n", directory->name);
+      return directory->layout_info;
+    }
 
-      directory->contents = g_slist_append (directory->contents,
-					    gmenu_tree_separator_new (directory));
-      directory->layout_pending_separator = FALSE;
+  iter = directory;
+  while (iter != NULL)
+    {
+      if (iter->default_layout_info != NULL)
+	{
+	  return iter->default_layout_info;
+	}
+
+      iter = GMENU_TREE_ITEM (iter)->parent;
     }
+
+  return NULL;
 }
 
 static void
-merge_subdir (GMenuTree          *tree,
-	      GMenuTreeDirectory *directory,
-	      GMenuTreeDirectory *subdir,
-	      MenuLayoutValues   *layout_values)
+get_values_with_defaults (MenuLayoutNode   *node,
+			  MenuLayoutValues *layout_values,
+			  MenuLayoutValues *default_layout_values)
 {
-  gboolean removed;
+  menu_layout_node_menuname_get_values (node, layout_values);
 
-  menu_verbose ("Merging subdir '%s' in directory '%s'\n",
-		subdir->name, directory->name);
+  if (!(layout_values->mask & MENU_LAYOUT_VALUES_SHOW_EMPTY))
+    layout_values->show_empty = default_layout_values->show_empty;
 
-  process_layout_info (tree, subdir);
+  if (!(layout_values->mask & MENU_LAYOUT_VALUES_INLINE_MENUS))
+    layout_values->inline_menus = default_layout_values->inline_menus;
 
-  removed = FALSE;
+  if (!(layout_values->mask & MENU_LAYOUT_VALUES_INLINE_LIMIT))
+    layout_values->inline_limit = default_layout_values->inline_limit;
 
-  if (subdir->contents == NULL)
+  if (!(layout_values->mask & MENU_LAYOUT_VALUES_INLINE_HEADER))
+    layout_values->inline_header = default_layout_values->inline_header;
+
+  if (!(layout_values->mask & MENU_LAYOUT_VALUES_INLINE_ALIAS))
+    layout_values->inline_alias = default_layout_values->inline_alias;
+}
+
+static guint
+get_real_subdirs_len (GMenuTreeDirectory *directory)
+{
+  guint   len;
+  GSList *tmp;
+
+  len = 0;
+
+  tmp = directory->subdirs;
+  while (tmp != NULL)
     {
-      if (!(tree->flags & GMENU_TREE_FLAGS_SHOW_EMPTY) && !layout_values->show_empty)
+      GMenuTreeDirectory *subdir = tmp->data;
+
+      tmp = tmp->next;
+
+      if (subdir->will_inline_header)
+        {
+          len = get_real_subdirs_len (subdir) + g_slist_length (subdir->entries) + 1;
+        }
+      else
+        len += 1;
+    }
+
+  return len;
+}
+
+static void
+preprocess_layout_info_subdir_helper (GMenuTree          *tree,
+                                      GMenuTreeDirectory *directory,
+                                      GMenuTreeDirectory *subdir,
+                                      MenuLayoutValues   *layout_values,
+                                      gboolean           *contents_added,
+                                      gboolean           *should_remove)
+{
+  preprocess_layout_info (tree, subdir);
+
+  *should_remove = FALSE;
+  *contents_added = FALSE;
+
+  if (subdir->subdirs == NULL && subdir->entries == NULL)
+    {
+      if (!(tree->flags & GMENU_TREE_FLAGS_SHOW_EMPTY) &&
+          !layout_values->show_empty)
 	{
 	  menu_verbose ("Not showing empty menu '%s'\n", subdir->name);
-	  removed = TRUE;
+	  *should_remove = TRUE;
 	}
     }
+
   else if (layout_values->inline_menus)
     {
-      check_pending_separator (directory);
+      guint real_subdirs_len;
 
-      if (layout_values->inline_alias && g_slist_length (subdir->contents) == 1)
-	{
-	  GMenuTreeAlias *alias;
-	  GMenuTreeItem  *item;
+      real_subdirs_len = get_real_subdirs_len (subdir);
+
+      if (layout_values->inline_alias &&
+          real_subdirs_len + g_slist_length (subdir->entries) == 1)
+        {
+          GMenuTreeAlias *alias;
+          GMenuTreeItem  *item;
+          GSList         *list;
+
+          if (subdir->subdirs != NULL)
+            list = subdir->subdirs;
+          else
+            list = subdir->entries;
+
+          item = GMENU_TREE_ITEM (list->data);
+
+          menu_verbose ("Inline aliasing '%s' to '%s'\n",
+                        subdir->name,
+                        item->type == GMENU_TREE_ITEM_ENTRY ?
+                        gmenu_tree_entry_get_name (GMENU_TREE_ENTRY (item)) :
+                        GMENU_TREE_DIRECTORY (directory)->name);
+
+          alias = gmenu_tree_alias_new (directory, subdir, item);
+
+          g_slist_foreach (list,
+                           (GFunc) gmenu_tree_item_unref_and_unset_parent,
+                           NULL);
+          g_slist_free (list);
+          subdir->subdirs = NULL;
+          subdir->entries = NULL;
+
+          if (item->type == GMENU_TREE_ITEM_DIRECTORY)
+            directory->subdirs = g_slist_append (directory->subdirs, alias);
+          else
+            directory->entries = g_slist_append (directory->entries, alias);
+
+          *contents_added = TRUE;
+          *should_remove = TRUE;
+        }
+
+      else if (layout_values->inline_limit == 0 ||
+               layout_values->inline_limit >= real_subdirs_len + g_slist_length (subdir->entries))
+        {
+          if (layout_values->inline_header)
+            {
+              menu_verbose ("Creating inline header with name '%s'\n", subdir->name);
+              /* we're limited to 16-bits to spare some memory; if the limit is
+               * higher than that (would be crazy), we just consider it's
+               * unlimited */
+              if (layout_values->inline_limit < G_MAXUINT16)
+                subdir->will_inline_header = layout_values->inline_limit;
+              else
+                subdir->will_inline_header = 0;
+            }
+          else
+            {
+              g_slist_foreach (subdir->subdirs,
+                               (GFunc) gmenu_tree_item_set_parent,
+                               directory);
+              directory->subdirs = g_slist_concat (directory->subdirs,
+                                                   subdir->subdirs);
+              subdir->subdirs = NULL;
+
+              g_slist_foreach (subdir->entries,
+                               (GFunc) gmenu_tree_item_set_parent,
+                               directory);
+              directory->entries = g_slist_concat (directory->entries,
+                                                   subdir->entries);
+              subdir->entries = NULL;
+
+              *contents_added = TRUE;
+              *should_remove = TRUE;
+            }
 
-	  item = GMENU_TREE_ITEM (subdir->contents->data);
+          menu_verbose ("Inlining directory contents of '%s' to '%s'\n",
+                        subdir->name, directory->name);
+        }
+    }
+}
 
-	  menu_verbose ("Inline aliasing '%s' to '%s'\n",
-			subdir->name,
-			item->type == GMENU_TREE_ITEM_ENTRY ?
-			gmenu_tree_entry_get_name (GMENU_TREE_ENTRY (item)) :
-			GMENU_TREE_DIRECTORY (directory)->name);
+static void
+preprocess_layout_info (GMenuTree          *tree,
+                        GMenuTreeDirectory *directory)
+{
+  GSList   *tmp;
+  GSList   *layout_info;
+  GSList   *last_subdir;
+  gboolean  strip_duplicates;
+  gboolean  contents_added;
+  gboolean  should_remove;
+  GSList   *subdirs_sentinel;
+
+  /* Note: we need to preprocess all menus, even if the layout mask for a menu
+   * is MENU_LAYOUT_VALUES_NONE: in this case, we need to remove empty menus;
+   * and the layout mask can be different for a submenu anyway */
+
+  menu_verbose ("Processing menu layout inline hints for %s\n", directory->name);
+  g_assert (!directory->preprocessed);
+
+  strip_duplicates = FALSE;
+  /* we use last_subdir to track the last non-inlined subdirectory */
+  last_subdir = g_slist_last (directory->subdirs);
+
+  /*
+   * First process subdirectories with explicit layout
+   */
+  layout_info = get_layout_info (directory);
+  tmp = layout_info;
+  /* see comment below about Menuname to undersand why we leave the loop if
+   * last_subdir is NULL */
+  while (tmp != NULL && last_subdir != NULL)
+    {
+      MenuLayoutNode     *node = tmp->data;
+      MenuLayoutValues    layout_values;
+      const char         *name;
+      GMenuTreeDirectory *subdir;
+      GSList             *subdir_l;
 
-	  alias = gmenu_tree_alias_new (directory, subdir, item);
+      tmp = tmp->next;
 
-	  g_slist_foreach (subdir->contents,
-			   (GFunc) gmenu_tree_item_unref_and_unset_parent,
-			   NULL);
-	  g_slist_free (subdir->contents);
-	  subdir->contents = NULL;
+      /* only Menuname nodes are relevant here */
+      if (menu_layout_node_get_type (node) != MENU_LAYOUT_NODE_MENUNAME)
+        continue;
 
-	  directory->contents = g_slist_append (directory->contents, alias); 
+      get_values_with_defaults (node,
+                                &layout_values,
+                                &directory->default_layout_values);
 
-	  removed = TRUE;
-	}
-      else if (layout_values->inline_limit == 0 ||
-	       layout_values->inline_limit >= g_slist_length (subdir->contents))
-	{
-	  if (layout_values->inline_header)
-	    {
-	      GMenuTreeHeader *header;
+      /* find the subdirectory that is affected by those attributes */
+      name = menu_layout_node_get_content (node);
+      subdir = NULL;
+      subdir_l = directory->subdirs;
+      while (subdir_l != NULL)
+        {
+          GMenuTreeDirectory *subdir = subdir_l->data;
 
-	      menu_verbose ("Creating inline header with name '%s'\n", subdir->name);
+          if (!strcmp (subdir->name, name))
+            break;
 
-	      header = gmenu_tree_header_new (directory, subdir);
-	      directory->contents = g_slist_append (directory->contents, header); 
-	    }
+          subdir = NULL;
+          subdir_l = subdir_l->next;
 
-	  menu_verbose ("Inlining directory contents of '%s' to '%s'\n",
-			subdir->name, directory->name);
+          /* we do not want to use Menuname on a menu that appeared via
+           * inlining: without inlining, the Menuname wouldn't have matched
+           * anything, and we want to keep the same behavior */
+          if (subdir_l == last_subdir)
+            {
+              subdir_l = NULL;
+              break;
+            }
+        }
 
-	  g_slist_foreach (subdir->contents,
-			   (GFunc) gmenu_tree_item_set_parent,
-			   directory);
-	  directory->contents = g_slist_concat (directory->contents, subdir->contents);
-	  subdir->contents = NULL;
+      if (subdir == NULL)
+        continue;
 
-	  removed = TRUE;
-	}
+      preprocess_layout_info_subdir_helper (tree, directory,
+                                            subdir, &layout_values,
+                                            &contents_added, &should_remove);
+      strip_duplicates = strip_duplicates || contents_added;
+      if (should_remove)
+        {
+          if (last_subdir == subdir_l)
+            {
+              /* we need to recompute last_subdir since we'll remove it from
+               * the list */
+              GSList *buf;
+
+              if (subdir_l == directory->subdirs)
+                last_subdir = NULL;
+              else
+                {
+                  buf = directory->subdirs;
+                  while (buf != NULL && buf->next != subdir_l)
+                    buf = buf->next;
+                  last_subdir = buf;
+                }
+            }
+
+          directory->subdirs = g_slist_remove (directory->subdirs, subdir);
+          gmenu_tree_item_unref_and_unset_parent (GMENU_TREE_ITEM (subdir));
+        }
+    }
+
+  /*
+   * Now process the subdirectories with no explicit layout
+   */
+  /* this is bogus data, but we just need the pointer anyway */
+  subdirs_sentinel = g_slist_prepend (directory->subdirs, PACKAGE);
+  directory->subdirs = subdirs_sentinel;
+
+  tmp = directory->subdirs;
+  while (tmp->next != NULL)
+    {
+      GMenuTreeDirectory *subdir = tmp->next->data;
+
+      if (subdir->preprocessed)
+        {
+          tmp = tmp->next;
+          continue;
+        }
+
+      preprocess_layout_info_subdir_helper (tree, directory,
+                                            subdir, &directory->default_layout_values,
+                                            &contents_added, &should_remove);
+      strip_duplicates = strip_duplicates || contents_added;
+      if (should_remove)
+        {
+          tmp = g_slist_delete_link (tmp, tmp->next);
+          gmenu_tree_item_unref_and_unset_parent (GMENU_TREE_ITEM (subdir));
+        }
+      else
+        tmp = tmp->next;
     }
 
-  if (removed)
+  /* remove the sentinel */
+  directory->subdirs = g_slist_delete_link (directory->subdirs,
+                                            directory->subdirs);
+
+  /*
+   * Finally, remove duplicates if needed
+   */
+  if (strip_duplicates)
     {
+      /* strip duplicate entries; there should be no duplicate directories */
+      directory->entries = g_slist_sort (directory->entries,
+                                         (GCompareFunc) gmenu_tree_entry_compare_by_id);
+      tmp = directory->entries;
+      while (tmp != NULL && tmp->next != NULL)
+        {
+          GMenuTreeEntry *a = tmp->data;
+          GMenuTreeEntry *b = tmp->next->data;
+
+          if (strcmp (a->desktop_file_id, b->desktop_file_id) == 0)
+            {
+              tmp = g_slist_delete_link (tmp, tmp->next);
+              gmenu_tree_item_unref (b);
+            }
+          else
+            tmp = tmp->next;
+        }
+    }
+
+  directory->preprocessed = TRUE;
+}
+
+static void process_layout_info (GMenuTree          *tree,
+				 GMenuTreeDirectory *directory);
+
+static void
+check_pending_separator (GMenuTreeDirectory *directory)
+{
+  if (directory->layout_pending_separator)
+    {
+      menu_verbose ("Adding pending separator in '%s'\n", directory->name);
+
+      directory->contents = g_slist_append (directory->contents,
+					    gmenu_tree_separator_new (directory));
+      directory->layout_pending_separator = FALSE;
+    }
+}
+
+static void
+merge_subdir (GMenuTree          *tree,
+	      GMenuTreeDirectory *directory,
+	      GMenuTreeDirectory *subdir)
+{
+  menu_verbose ("Merging subdir '%s' in directory '%s'\n",
+		subdir->name, directory->name);
+
+  process_layout_info (tree, subdir);
+
+  check_pending_separator (directory);
+
+  if (subdir->will_inline_header == 0 ||
+      (subdir->will_inline_header != G_MAXUINT16 &&
+       g_slist_length (subdir->contents) <= subdir->will_inline_header))
+    {
+      GMenuTreeHeader *header;
+
+      header = gmenu_tree_header_new (directory, subdir);
+      directory->contents = g_slist_append (directory->contents, header);
+
+      g_slist_foreach (subdir->contents,
+                       (GFunc) gmenu_tree_item_set_parent,
+                       directory);
+      directory->contents = g_slist_concat (directory->contents,
+                                            subdir->contents);
+      subdir->contents = NULL;
+      subdir->will_inline_header = G_MAXUINT16;
+
       gmenu_tree_item_set_parent (GMENU_TREE_ITEM (subdir), NULL);
     }
   else
     {
-      check_pending_separator (directory);
       directory->contents = g_slist_append (directory->contents,
 					    gmenu_tree_item_ref (subdir));
     }
@@ -3653,8 +3963,7 @@ merge_subdir (GMenuTree          *tree,
 static void
 merge_subdir_by_name (GMenuTree          *tree,
 		      GMenuTreeDirectory *directory,
-		      const char         *subdir_name,
-		      MenuLayoutValues   *values)
+		      const char         *subdir_name)
 {
   GSList *tmp;
 
@@ -3670,7 +3979,7 @@ merge_subdir_by_name (GMenuTree          *tree,
       if (!strcmp (subdir->name, subdir_name))
 	{
 	  directory->subdirs = g_slist_delete_link (directory->subdirs, tmp);
-	  merge_subdir (tree, directory, subdir, values);
+	  merge_subdir (tree, directory, subdir);
 	  gmenu_tree_item_unref (subdir);
 	}
       
@@ -3756,7 +4065,7 @@ merge_subdirs (GMenuTree          *tree,
 
       if (!find_name_in_list (subdir->name, except))
 	{
-	  merge_subdir (tree, directory, subdir, &directory->default_layout_values);
+	  merge_subdir (tree, directory, subdir);
 	  gmenu_tree_item_unref (subdir);
 	}
       else
@@ -3842,8 +4151,7 @@ merge_subdirs_and_entries (GMenuTree          *tree,
 	    {
 	      merge_subdir (tree,
 			    directory,
-			    GMENU_TREE_DIRECTORY (item),
-			    &directory->default_layout_values);
+			    GMENU_TREE_DIRECTORY (item));
 	      gmenu_tree_item_unref (item);
 	    }
 	  else
@@ -3876,53 +4184,6 @@ merge_subdirs_and_entries (GMenuTree          *tree,
   g_slist_free (except_entries);
 }
 
-static void
-get_values_with_defaults (MenuLayoutNode   *node,
-			  MenuLayoutValues *layout_values,
-			  MenuLayoutValues *default_layout_values)
-{
-  menu_layout_node_menuname_get_values (node, layout_values);
-
-  if (!(layout_values->mask & MENU_LAYOUT_VALUES_SHOW_EMPTY))
-    layout_values->show_empty = default_layout_values->show_empty;
-
-  if (!(layout_values->mask & MENU_LAYOUT_VALUES_INLINE_MENUS))
-    layout_values->inline_menus = default_layout_values->inline_menus;
-
-  if (!(layout_values->mask & MENU_LAYOUT_VALUES_INLINE_LIMIT))
-    layout_values->inline_limit = default_layout_values->inline_limit;
-
-  if (!(layout_values->mask & MENU_LAYOUT_VALUES_INLINE_HEADER))
-    layout_values->inline_header = default_layout_values->inline_header;
-
-  if (!(layout_values->mask & MENU_LAYOUT_VALUES_INLINE_ALIAS))
-    layout_values->inline_alias = default_layout_values->inline_alias;
-}
-
-static GSList *
-get_layout_info (GMenuTreeDirectory *directory)
-{
-  GMenuTreeDirectory *iter;
-
-  if (directory->layout_info != NULL)
-    {
-      return directory->layout_info;
-    }
-
-  iter = directory;
-  while (iter != NULL)
-    {
-      if (iter->default_layout_info != NULL)
-	{
-	  return iter->default_layout_info;
-	}
-
-      iter = GMENU_TREE_ITEM (iter)->parent;
-    }
-
-  return NULL;
-}
-
 static GSList *
 get_subdirs_from_layout_info (GSList *layout_info)
 {
@@ -4005,18 +4266,9 @@ process_layout_info (GMenuTree          *tree,
 	  switch (menu_layout_node_get_type (node))
 	    {
 	    case MENU_LAYOUT_NODE_MENUNAME:
-	      {
-		MenuLayoutValues layout_values;
-
-		get_values_with_defaults (node,
-					  &layout_values,
-					  &directory->default_layout_values);
-
-		merge_subdir_by_name (tree,
-				      directory,
-				      menu_layout_node_get_content (node),
-				      &layout_values);
-	      }
+              merge_subdir_by_name (tree,
+                                    directory,
+                                    menu_layout_node_get_content (node));
 	      break;
 
 	    case MENU_LAYOUT_NODE_FILENAME: 
@@ -4142,6 +4394,7 @@ gmenu_tree_build_from_layout (GMenuTree *tree)
 
   allocated = desktop_entry_set_new ();
 
+  /* create the menu structure */
   tree->root = process_layout (tree,
                                NULL,
                                find_menu_child (tree->layout),
@@ -4152,6 +4405,11 @@ gmenu_tree_build_from_layout (GMenuTree *tree)
 
       process_only_unallocated (tree, tree->root, allocated);
 
+      /* process the layout info part that can move/remove items:
+       * inline, show_empty, etc. */
+      preprocess_layout_info (tree, tree->root);
+      /* populate the menu structure that we got with the items, and order it
+       * according to the layout info */
       process_layout_info (tree, tree->root);
 
       menu_layout_node_root_add_entries_monitor (tree->layout,



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