summaryrefslogtreecommitdiff
path: root/libmenu/matemenu-tree.c
diff options
context:
space:
mode:
authorPerberos <[email protected]>2011-12-01 22:29:22 -0300
committerPerberos <[email protected]>2011-12-01 22:29:22 -0300
commit17e291fd05d66fe232e2f409d54331c495b2fbd0 (patch)
treed3f9b3bc149dedc9d192317e930d438c5e9ba21a /libmenu/matemenu-tree.c
downloadmate-menus-17e291fd05d66fe232e2f409d54331c495b2fbd0.tar.bz2
mate-menus-17e291fd05d66fe232e2f409d54331c495b2fbd0.tar.xz
moving from https://github.com/perberos/mate-desktop-environment
Diffstat (limited to 'libmenu/matemenu-tree.c')
-rw-r--r--libmenu/matemenu-tree.c4520
1 files changed, 4520 insertions, 0 deletions
diff --git a/libmenu/matemenu-tree.c b/libmenu/matemenu-tree.c
new file mode 100644
index 0000000..683eb55
--- /dev/null
+++ b/libmenu/matemenu-tree.c
@@ -0,0 +1,4520 @@
+/*
+ * Copyright (C) 2003, 2004 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+
+#include "matemenu-tree.h"
+
+#include <string.h>
+#include <errno.h>
+
+#include "menu-layout.h"
+#include "menu-monitor.h"
+#include "menu-util.h"
+#include "canonicalize.h"
+
+/*
+ * FIXME: it might be useful to be able to construct a menu
+ * tree from a traditional directory based menu hierarchy
+ * too.
+ */
+
+typedef enum
+{
+ MATEMENU_TREE_ABSOLUTE = 0,
+ MATEMENU_TREE_BASENAME = 1
+} MateMenuTreeType;
+
+struct MateMenuTree
+{
+ MateMenuTreeType type;
+ guint refcount;
+
+ char *basename;
+ char *absolute_path;
+ char *canonical_path;
+
+ MateMenuTreeFlags flags;
+ MateMenuTreeSortKey sort_key;
+
+ GSList *menu_file_monitors;
+
+ MenuLayoutNode *layout;
+ MateMenuTreeDirectory *root;
+
+ GSList *monitors;
+
+ gpointer user_data;
+ GDestroyNotify dnotify;
+
+ guint canonical : 1;
+};
+
+typedef struct
+{
+ MateMenuTreeChangedFunc callback;
+ gpointer user_data;
+} MateMenuTreeMonitor;
+
+struct MateMenuTreeItem
+{
+ MateMenuTreeItemType type;
+
+ MateMenuTreeDirectory *parent;
+
+ gpointer user_data;
+ GDestroyNotify dnotify;
+
+ guint refcount;
+};
+
+struct MateMenuTreeDirectory
+{
+ MateMenuTreeItem item;
+
+ DesktopEntry *directory_entry;
+ char *name;
+
+ GSList *entries;
+ GSList *subdirs;
+
+ MenuLayoutValues default_layout_values;
+ GSList *default_layout_info;
+ GSList *layout_info;
+ GSList *contents;
+
+ guint only_unallocated : 1;
+ 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
+{
+ MateMenuTreeDirectory directory;
+
+ MateMenuTree *tree;
+} MateMenuTreeDirectoryRoot;
+
+struct MateMenuTreeEntry
+{
+ MateMenuTreeItem item;
+
+ DesktopEntry *desktop_entry;
+ char *desktop_file_id;
+
+ guint is_excluded : 1;
+ guint is_nodisplay : 1;
+};
+
+struct MateMenuTreeSeparator
+{
+ MateMenuTreeItem item;
+};
+
+struct MateMenuTreeHeader
+{
+ MateMenuTreeItem item;
+
+ MateMenuTreeDirectory *directory;
+};
+
+struct MateMenuTreeAlias
+{
+ MateMenuTreeItem item;
+
+ MateMenuTreeDirectory *directory;
+ MateMenuTreeItem *aliased_item;
+};
+
+static MateMenuTree *matemenu_tree_new (MateMenuTreeType type,
+ const char *menu_file,
+ gboolean canonical,
+ MateMenuTreeFlags flags);
+static void matemenu_tree_load_layout (MateMenuTree *tree);
+static void matemenu_tree_force_reload (MateMenuTree *tree);
+static void matemenu_tree_build_from_layout (MateMenuTree *tree);
+static void matemenu_tree_force_rebuild (MateMenuTree *tree);
+static void matemenu_tree_resolve_files (MateMenuTree *tree,
+ GHashTable *loaded_menu_files,
+ MenuLayoutNode *layout);
+static void matemenu_tree_force_recanonicalize (MateMenuTree *tree);
+static void matemenu_tree_invoke_monitors (MateMenuTree *tree);
+
+static void matemenu_tree_item_unref_and_unset_parent (gpointer itemp);
+
+/*
+ * The idea is that we cache the menu tree for either a given
+ * menu basename or an absolute menu path.
+ * If no files exist in $XDG_DATA_DIRS for the basename or the
+ * absolute path doesn't exist we just return (and cache) the
+ * empty menu tree.
+ * We also add a file monitor for the basename in each dir in
+ * $XDG_DATA_DIRS, or the absolute path to the menu file, and
+ * re-compute if there are any changes.
+ */
+
+static GHashTable *matemenu_tree_cache = NULL;
+
+static inline char *
+get_cache_key (MateMenuTree *tree,
+ MateMenuTreeFlags flags)
+{
+ const char *tree_name;
+
+ switch (tree->type)
+ {
+ case MATEMENU_TREE_ABSOLUTE:
+ tree_name = tree->canonical ? tree->canonical_path : tree->absolute_path;
+ break;
+
+ case MATEMENU_TREE_BASENAME:
+ tree_name = tree->basename;
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ return g_strdup_printf ("%s:0x%x", tree_name, flags);
+}
+
+static void
+matemenu_tree_add_to_cache (MateMenuTree *tree,
+ MateMenuTreeFlags flags)
+{
+ char *cache_key;
+
+ if (matemenu_tree_cache == NULL)
+ {
+ matemenu_tree_cache =
+ g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+ }
+
+ cache_key = get_cache_key (tree, flags);
+
+ menu_verbose ("Adding menu tree to cache: %s\n", cache_key);
+
+ g_hash_table_replace (matemenu_tree_cache, cache_key, tree);
+}
+
+static void
+matemenu_tree_remove_from_cache (MateMenuTree *tree,
+ MateMenuTreeFlags flags)
+{
+ char *cache_key;
+
+ cache_key = get_cache_key (tree, flags);
+
+ menu_verbose ("Removing menu tree from cache: %s\n", cache_key);
+
+ g_hash_table_remove (matemenu_tree_cache, cache_key);
+
+ g_free (cache_key);
+
+ if (g_hash_table_size (matemenu_tree_cache) == 0)
+ {
+ g_hash_table_destroy (matemenu_tree_cache);
+ matemenu_tree_cache = NULL;
+
+ _entry_directory_list_empty_desktop_cache ();
+ }
+}
+
+static MateMenuTree *
+matemenu_tree_lookup_from_cache (const char *tree_name,
+ MateMenuTreeFlags flags)
+{
+ MateMenuTree *retval;
+ char *cache_key;
+
+ if (matemenu_tree_cache == NULL)
+ return NULL;
+
+ cache_key = g_strdup_printf ("%s:0x%x", tree_name, flags);
+
+ menu_verbose ("Looking up '%s' from menu cache\n", cache_key);
+
+ retval = g_hash_table_lookup (matemenu_tree_cache, cache_key);
+
+ g_free (cache_key);
+
+ return retval ? matemenu_tree_ref (retval) : NULL;
+}
+
+typedef enum
+{
+ MENU_FILE_MONITOR_INVALID = 0,
+ MENU_FILE_MONITOR_FILE,
+ MENU_FILE_MONITOR_NONEXISTENT_FILE,
+ MENU_FILE_MONITOR_DIRECTORY
+} MenuFileMonitorType;
+
+typedef struct
+{
+ MenuFileMonitorType type;
+ MenuMonitor *monitor;
+} MenuFileMonitor;
+
+static void
+handle_nonexistent_menu_file_changed (MenuMonitor *monitor,
+ MenuMonitorEvent event,
+ const char *path,
+ MateMenuTree *tree)
+{
+ if (event == MENU_MONITOR_EVENT_CHANGED ||
+ event == MENU_MONITOR_EVENT_CREATED)
+ {
+ menu_verbose ("\"%s\" %s, marking tree for recanonicalization\n",
+ path,
+ event == MENU_MONITOR_EVENT_CREATED ? "created" : "changed");
+
+ matemenu_tree_force_recanonicalize (tree);
+ matemenu_tree_invoke_monitors (tree);
+ }
+}
+
+static void
+handle_menu_file_changed (MenuMonitor *monitor,
+ MenuMonitorEvent event,
+ const char *path,
+ MateMenuTree *tree)
+{
+ menu_verbose ("\"%s\" %s, marking tree for recanicalization\n",
+ path,
+ event == MENU_MONITOR_EVENT_CREATED ? "created" :
+ event == MENU_MONITOR_EVENT_CHANGED ? "changed" : "deleted");
+
+ matemenu_tree_force_recanonicalize (tree);
+ matemenu_tree_invoke_monitors (tree);
+}
+
+static void
+handle_menu_file_directory_changed (MenuMonitor *monitor,
+ MenuMonitorEvent event,
+ const char *path,
+ MateMenuTree *tree)
+{
+ if (!g_str_has_suffix (path, ".menu"))
+ return;
+
+ menu_verbose ("\"%s\" %s, marking tree for recanicalization\n",
+ path,
+ event == MENU_MONITOR_EVENT_CREATED ? "created" :
+ event == MENU_MONITOR_EVENT_CHANGED ? "changed" : "deleted");
+
+ matemenu_tree_force_recanonicalize (tree);
+ matemenu_tree_invoke_monitors (tree);
+}
+
+static void
+matemenu_tree_add_menu_file_monitor (MateMenuTree *tree,
+ const char *path,
+ MenuFileMonitorType type)
+{
+ MenuFileMonitor *monitor;
+
+ monitor = g_new0 (MenuFileMonitor, 1);
+
+ monitor->type = type;
+
+ switch (type)
+ {
+ case MENU_FILE_MONITOR_FILE:
+ menu_verbose ("Adding a menu file monitor for \"%s\"\n", path);
+
+ monitor->monitor = menu_get_file_monitor (path);
+ menu_monitor_add_notify (monitor->monitor,
+ (MenuMonitorNotifyFunc) handle_menu_file_changed,
+ tree);
+ break;
+
+ case MENU_FILE_MONITOR_NONEXISTENT_FILE:
+ menu_verbose ("Adding a menu file monitor for non-existent \"%s\"\n", path);
+
+ monitor->monitor = menu_get_file_monitor (path);
+ menu_monitor_add_notify (monitor->monitor,
+ (MenuMonitorNotifyFunc) handle_nonexistent_menu_file_changed,
+ tree);
+ break;
+
+ case MENU_FILE_MONITOR_DIRECTORY:
+ menu_verbose ("Adding a menu directory monitor for \"%s\"\n", path);
+
+ monitor->monitor = menu_get_directory_monitor (path);
+ menu_monitor_add_notify (monitor->monitor,
+ (MenuMonitorNotifyFunc) handle_menu_file_directory_changed,
+ tree);
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ tree->menu_file_monitors = g_slist_prepend (tree->menu_file_monitors, monitor);
+}
+
+static void
+remove_menu_file_monitor (MenuFileMonitor *monitor,
+ MateMenuTree *tree)
+{
+ switch (monitor->type)
+ {
+ case MENU_FILE_MONITOR_FILE:
+ menu_monitor_remove_notify (monitor->monitor,
+ (MenuMonitorNotifyFunc) handle_menu_file_changed,
+ tree);
+ break;
+
+ case MENU_FILE_MONITOR_NONEXISTENT_FILE:
+ menu_monitor_remove_notify (monitor->monitor,
+ (MenuMonitorNotifyFunc) handle_nonexistent_menu_file_changed,
+ tree);
+ break;
+
+ case MENU_FILE_MONITOR_DIRECTORY:
+ menu_monitor_remove_notify (monitor->monitor,
+ (MenuMonitorNotifyFunc) handle_menu_file_directory_changed,
+ tree);
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ menu_monitor_unref (monitor->monitor);
+ monitor->monitor = NULL;
+
+ monitor->type = MENU_FILE_MONITOR_INVALID;
+
+ g_free (monitor);
+}
+
+static void
+matemenu_tree_remove_menu_file_monitors (MateMenuTree *tree)
+{
+ menu_verbose ("Removing all menu file monitors\n");
+
+ g_slist_foreach (tree->menu_file_monitors,
+ (GFunc) remove_menu_file_monitor,
+ tree);
+ g_slist_free (tree->menu_file_monitors);
+ tree->menu_file_monitors = NULL;
+}
+
+static MateMenuTree *
+matemenu_tree_lookup_absolute (const char *absolute,
+ MateMenuTreeFlags flags)
+{
+ MateMenuTree *tree;
+ gboolean canonical;
+ const char *canonical_path;
+ char *freeme;
+
+ menu_verbose ("Looking up absolute path in tree cache: \"%s\"\n", absolute);
+
+ if ((tree = matemenu_tree_lookup_from_cache (absolute, flags)) != NULL)
+ return tree;
+
+ canonical = TRUE;
+ canonical_path = freeme = menu_canonicalize_file_name (absolute, FALSE);
+ if (canonical_path == NULL)
+ {
+ menu_verbose ("Failed to canonicalize absolute menu path \"%s\": %s\n",
+ absolute, g_strerror (errno));
+ canonical = FALSE;
+ canonical_path = absolute;
+ }
+
+ if ((tree = matemenu_tree_lookup_from_cache (canonical_path, flags)) != NULL)
+ return tree;
+
+ tree = matemenu_tree_new (MATEMENU_TREE_ABSOLUTE, canonical_path, canonical, flags);
+
+ g_free (freeme);
+
+ return tree;
+}
+
+static MateMenuTree *
+matemenu_tree_lookup_basename (const char *basename,
+ MateMenuTreeFlags flags)
+{
+ MateMenuTree *tree;
+
+ menu_verbose ("Looking up menu file in tree cache: \"%s\"\n", basename);
+
+ if ((tree = matemenu_tree_lookup_from_cache (basename, flags)) != NULL)
+ return tree;
+
+ return matemenu_tree_new (MATEMENU_TREE_BASENAME, basename, FALSE, flags);
+}
+
+static gboolean
+canonicalize_basename_with_config_dir (MateMenuTree *tree,
+ const char *basename,
+ const char *config_dir)
+{
+ char *path;
+
+ path = g_build_filename (config_dir, "menus", basename, NULL);
+
+ tree->canonical_path = menu_canonicalize_file_name (path, FALSE);
+ if (tree->canonical_path)
+ {
+ tree->canonical = TRUE;
+ matemenu_tree_add_menu_file_monitor (tree,
+ tree->canonical_path,
+ MENU_FILE_MONITOR_FILE);
+ }
+ else
+ {
+ matemenu_tree_add_menu_file_monitor (tree,
+ path,
+ MENU_FILE_MONITOR_NONEXISTENT_FILE);
+ }
+
+ g_free (path);
+
+ return tree->canonical;
+}
+
+static void
+canonicalize_basename (MateMenuTree *tree,
+ const char *basename)
+{
+ if (!canonicalize_basename_with_config_dir (tree,
+ basename,
+ g_get_user_config_dir ()))
+ {
+ const char * const *system_config_dirs;
+ int i;
+
+ system_config_dirs = g_get_system_config_dirs ();
+
+ i = 0;
+ while (system_config_dirs[i] != NULL)
+ {
+ if (canonicalize_basename_with_config_dir (tree,
+ basename,
+ system_config_dirs[i]))
+ break;
+
+ ++i;
+ }
+ }
+}
+
+static gboolean matemenu_tree_canonicalize_path(MateMenuTree* tree)
+{
+ if (tree->canonical)
+ return TRUE;
+
+ g_assert(tree->canonical_path == NULL);
+
+ if (tree->type == MATEMENU_TREE_BASENAME)
+ {
+ matemenu_tree_remove_menu_file_monitors (tree);
+
+ if (strcmp(tree->basename, "mate-applications.menu") == 0 && g_getenv("XDG_MENU_PREFIX"))
+ {
+ char* prefixed_basename;
+ prefixed_basename = g_strdup_printf("%s%s", g_getenv("XDG_MENU_PREFIX"), tree->basename);
+ canonicalize_basename(tree, prefixed_basename);
+ g_free(prefixed_basename);
+ }
+
+ if (!tree->canonical)
+ canonicalize_basename(tree, tree->basename);
+
+ if (tree->canonical)
+ menu_verbose("Successfully looked up menu_file for \"%s\": %s\n", tree->basename, tree->canonical_path);
+ else
+ menu_verbose("Failed to look up menu_file for \"%s\"\n", tree->basename);
+ }
+ else /* if (tree->type == MATEMENU_TREE_ABSOLUTE) */
+ {
+ tree->canonical_path = menu_canonicalize_file_name(tree->absolute_path, FALSE);
+
+ if (tree->canonical_path != NULL)
+ {
+ menu_verbose("Successfully looked up menu_file for \"%s\": %s\n", tree->absolute_path, tree->canonical_path);
+
+ /*
+ * Replace the cache entry with the canonicalized version
+ */
+ matemenu_tree_remove_from_cache (tree, tree->flags);
+
+ matemenu_tree_remove_menu_file_monitors(tree);
+ matemenu_tree_add_menu_file_monitor(tree, tree->canonical_path, MENU_FILE_MONITOR_FILE);
+
+ tree->canonical = TRUE;
+
+ matemenu_tree_add_to_cache (tree, tree->flags);
+ }
+ else
+ {
+ menu_verbose("Failed to look up menu_file for \"%s\"\n", tree->absolute_path);
+ }
+ }
+
+ return tree->canonical;
+}
+
+static void
+matemenu_tree_force_recanonicalize (MateMenuTree *tree)
+{
+ matemenu_tree_remove_menu_file_monitors (tree);
+
+ if (tree->canonical)
+ {
+ matemenu_tree_force_reload (tree);
+
+ g_free (tree->canonical_path);
+ tree->canonical_path = NULL;
+
+ tree->canonical = FALSE;
+ }
+}
+
+MateMenuTree* matemenu_tree_lookup(const char* menu_file, MateMenuTreeFlags flags)
+{
+ MateMenuTree *retval;
+
+ g_return_val_if_fail (menu_file != NULL, NULL);
+
+ flags &= MATEMENU_TREE_FLAGS_MASK;
+
+ if (g_path_is_absolute (menu_file))
+ retval = matemenu_tree_lookup_absolute (menu_file, flags);
+ else
+ retval = matemenu_tree_lookup_basename (menu_file, flags);
+
+ g_assert (retval != NULL);
+
+ return retval;
+}
+
+static MateMenuTree *
+matemenu_tree_new (MateMenuTreeType type,
+ const char *menu_file,
+ gboolean canonical,
+ MateMenuTreeFlags flags)
+{
+ MateMenuTree *tree;
+
+ tree = g_new0 (MateMenuTree, 1);
+
+ tree->type = type;
+ tree->flags = flags;
+ tree->refcount = 1;
+
+ tree->sort_key = MATEMENU_TREE_SORT_NAME;
+
+ if (tree->type == MATEMENU_TREE_BASENAME)
+ {
+ g_assert (canonical == FALSE);
+ tree->basename = g_strdup (menu_file);
+ }
+ else
+ {
+ tree->canonical = canonical != FALSE;
+ tree->absolute_path = g_strdup (menu_file);
+
+ if (tree->canonical)
+ {
+ tree->canonical_path = g_strdup (menu_file);
+ matemenu_tree_add_menu_file_monitor (tree,
+ tree->canonical_path,
+ MENU_FILE_MONITOR_FILE);
+ }
+ else
+ {
+ matemenu_tree_add_menu_file_monitor (tree,
+ tree->absolute_path,
+ MENU_FILE_MONITOR_NONEXISTENT_FILE);
+ }
+ }
+
+ matemenu_tree_add_to_cache (tree, tree->flags);
+
+ return tree;
+}
+
+MateMenuTree *
+matemenu_tree_ref (MateMenuTree *tree)
+{
+ g_return_val_if_fail (tree != NULL, NULL);
+ g_return_val_if_fail (tree->refcount > 0, NULL);
+
+ tree->refcount++;
+
+ return tree;
+}
+
+void
+matemenu_tree_unref (MateMenuTree *tree)
+{
+ g_return_if_fail (tree != NULL);
+ g_return_if_fail (tree->refcount >= 1);
+
+ if (--tree->refcount > 0)
+ return;
+
+ if (tree->dnotify)
+ tree->dnotify (tree->user_data);
+ tree->user_data = NULL;
+ tree->dnotify = NULL;
+
+ matemenu_tree_remove_from_cache (tree, tree->flags);
+
+ matemenu_tree_force_recanonicalize (tree);
+
+ if (tree->basename != NULL)
+ g_free (tree->basename);
+ tree->basename = NULL;
+
+ if (tree->absolute_path != NULL)
+ g_free (tree->absolute_path);
+ tree->absolute_path = NULL;
+
+ g_slist_foreach (tree->monitors, (GFunc) g_free, NULL);
+ g_slist_free (tree->monitors);
+ tree->monitors = NULL;
+
+ g_free (tree);
+}
+
+void
+matemenu_tree_set_user_data (MateMenuTree *tree,
+ gpointer user_data,
+ GDestroyNotify dnotify)
+{
+ g_return_if_fail (tree != NULL);
+
+ if (tree->dnotify != NULL)
+ tree->dnotify (tree->user_data);
+
+ tree->dnotify = dnotify;
+ tree->user_data = user_data;
+}
+
+gpointer
+matemenu_tree_get_user_data (MateMenuTree *tree)
+{
+ g_return_val_if_fail (tree != NULL, NULL);
+
+ return tree->user_data;
+}
+
+const char *
+matemenu_tree_get_menu_file (MateMenuTree *tree)
+{
+ /* FIXME: this is horribly ugly. But it's done to keep the API. Would be bad
+ * to break the API only for a "const char *" => "char *" change. The other
+ * alternative is to leak the memory, which is bad too. */
+ static char *ugly_result_cache = NULL;
+
+ g_return_val_if_fail (tree != NULL, NULL);
+
+ /* we need to canonicalize the path so we actually find out the real menu
+ * file that is being used -- and take into account XDG_MENU_PREFIX */
+ if (!matemenu_tree_canonicalize_path (tree))
+ return NULL;
+
+ if (ugly_result_cache != NULL)
+ {
+ g_free (ugly_result_cache);
+ ugly_result_cache = NULL;
+ }
+
+ if (tree->type == MATEMENU_TREE_BASENAME)
+ {
+ ugly_result_cache = g_path_get_basename (tree->canonical_path);
+ return ugly_result_cache;
+ }
+ else
+ return tree->absolute_path;
+}
+
+MateMenuTreeDirectory *
+matemenu_tree_get_root_directory (MateMenuTree *tree)
+{
+ g_return_val_if_fail (tree != NULL, NULL);
+
+ if (!tree->root)
+ {
+ matemenu_tree_build_from_layout (tree);
+
+ if (!tree->root)
+ return NULL;
+ }
+
+ return matemenu_tree_item_ref (tree->root);
+}
+
+static MateMenuTreeDirectory *
+find_path (MateMenuTreeDirectory *directory,
+ const char *path)
+{
+ const char *name;
+ char *slash;
+ char *freeme;
+ GSList *tmp;
+
+ while (path[0] == G_DIR_SEPARATOR) path++;
+
+ if (path[0] == '\0')
+ return directory;
+
+ freeme = NULL;
+ slash = strchr (path, G_DIR_SEPARATOR);
+ if (slash)
+ {
+ name = freeme = g_strndup (path, slash - path);
+ path = slash + 1;
+ }
+ else
+ {
+ name = path;
+ path = NULL;
+ }
+
+ tmp = directory->contents;
+ while (tmp != NULL)
+ {
+ MateMenuTreeItem *item = tmp->data;
+
+ if (matemenu_tree_item_get_type (item) != MATEMENU_TREE_ITEM_DIRECTORY)
+ {
+ tmp = tmp->next;
+ continue;
+ }
+
+ if (!strcmp (name, MATEMENU_TREE_DIRECTORY (item)->name))
+ {
+ g_free (freeme);
+
+ if (path)
+ return find_path (MATEMENU_TREE_DIRECTORY (item), path);
+ else
+ return MATEMENU_TREE_DIRECTORY (item);
+ }
+
+ tmp = tmp->next;
+ }
+
+ g_free (freeme);
+
+ return NULL;
+}
+
+MateMenuTreeDirectory *
+matemenu_tree_get_directory_from_path (MateMenuTree *tree,
+ const char *path)
+{
+ MateMenuTreeDirectory *root;
+ MateMenuTreeDirectory *directory;
+
+ g_return_val_if_fail (tree != NULL, NULL);
+ g_return_val_if_fail (path != NULL, NULL);
+
+ if (path[0] != G_DIR_SEPARATOR)
+ return NULL;
+
+ if (!(root = matemenu_tree_get_root_directory (tree)))
+ return NULL;
+
+ directory = find_path (root, path);
+
+ matemenu_tree_item_unref (root);
+
+ return directory ? matemenu_tree_item_ref (directory) : NULL;
+}
+
+MateMenuTreeSortKey
+matemenu_tree_get_sort_key (MateMenuTree *tree)
+{
+ g_return_val_if_fail (tree != NULL, MATEMENU_TREE_SORT_NAME);
+ g_return_val_if_fail (tree->refcount > 0, MATEMENU_TREE_SORT_NAME);
+
+ return tree->sort_key;
+}
+
+void
+matemenu_tree_set_sort_key (MateMenuTree *tree,
+ MateMenuTreeSortKey sort_key)
+{
+ g_return_if_fail (tree != NULL);
+ g_return_if_fail (tree->refcount > 0);
+ g_return_if_fail (sort_key >= MATEMENU_TREE_SORT_FIRST);
+ g_return_if_fail (sort_key <= MATEMENU_TREE_SORT_LAST);
+
+ if (sort_key == tree->sort_key)
+ return;
+
+ tree->sort_key = sort_key;
+ matemenu_tree_force_rebuild (tree);
+}
+
+void
+matemenu_tree_add_monitor (MateMenuTree *tree,
+ MateMenuTreeChangedFunc callback,
+ gpointer user_data)
+{
+ MateMenuTreeMonitor *monitor;
+ GSList *tmp;
+
+ g_return_if_fail (tree != NULL);
+ g_return_if_fail (callback != NULL);
+
+ tmp = tree->monitors;
+ while (tmp != NULL)
+ {
+ monitor = tmp->data;
+
+ if (monitor->callback == callback &&
+ monitor->user_data == user_data)
+ break;
+
+ tmp = tmp->next;
+ }
+
+ if (tmp == NULL)
+ {
+ monitor = g_new0 (MateMenuTreeMonitor, 1);
+
+ monitor->callback = callback;
+ monitor->user_data = user_data;
+
+ tree->monitors = g_slist_append (tree->monitors, monitor);
+ }
+}
+
+void
+matemenu_tree_remove_monitor (MateMenuTree *tree,
+ MateMenuTreeChangedFunc callback,
+ gpointer user_data)
+{
+ GSList *tmp;
+
+ g_return_if_fail (tree != NULL);
+ g_return_if_fail (callback != NULL);
+
+ tmp = tree->monitors;
+ while (tmp != NULL)
+ {
+ MateMenuTreeMonitor *monitor = tmp->data;
+ GSList *next = tmp->next;
+
+ if (monitor->callback == callback &&
+ monitor->user_data == user_data)
+ {
+ tree->monitors = g_slist_delete_link (tree->monitors, tmp);
+ g_free (monitor);
+ }
+
+ tmp = next;
+ }
+}
+
+static void
+matemenu_tree_invoke_monitors (MateMenuTree *tree)
+{
+ GSList *tmp;
+
+ tmp = tree->monitors;
+ while (tmp != NULL)
+ {
+ MateMenuTreeMonitor *monitor = tmp->data;
+ GSList *next = tmp->next;
+
+ monitor->callback (tree, monitor->user_data);
+
+ tmp = next;
+ }
+}
+
+MateMenuTreeItemType
+matemenu_tree_item_get_type (MateMenuTreeItem *item)
+{
+ g_return_val_if_fail (item != NULL, 0);
+
+ return item->type;
+}
+
+MateMenuTreeDirectory *
+matemenu_tree_item_get_parent (MateMenuTreeItem *item)
+{
+ g_return_val_if_fail (item != NULL, NULL);
+
+ return item->parent ? matemenu_tree_item_ref (item->parent) : NULL;
+}
+
+static void
+matemenu_tree_item_set_parent (MateMenuTreeItem *item,
+ MateMenuTreeDirectory *parent)
+{
+ g_return_if_fail (item != NULL);
+
+ item->parent = parent;
+}
+
+GSList *
+matemenu_tree_directory_get_contents (MateMenuTreeDirectory *directory)
+{
+ GSList *retval;
+ GSList *tmp;
+
+ g_return_val_if_fail (directory != NULL, NULL);
+
+ retval = NULL;
+
+ tmp = directory->contents;
+ while (tmp != NULL)
+ {
+ retval = g_slist_prepend (retval,
+ matemenu_tree_item_ref (tmp->data));
+
+ tmp = tmp->next;
+ }
+
+ return g_slist_reverse (retval);
+}
+
+const char *
+matemenu_tree_directory_get_name (MateMenuTreeDirectory *directory)
+{
+ g_return_val_if_fail (directory != NULL, NULL);
+
+ if (!directory->directory_entry)
+ return directory->name;
+
+ return desktop_entry_get_name (directory->directory_entry);
+}
+
+const char *
+matemenu_tree_directory_get_comment (MateMenuTreeDirectory *directory)
+{
+ g_return_val_if_fail (directory != NULL, NULL);
+
+ if (!directory->directory_entry)
+ return NULL;
+
+ return desktop_entry_get_comment (directory->directory_entry);
+}
+
+const char* matemenu_tree_directory_get_icon(MateMenuTreeDirectory* directory)
+{
+ g_return_val_if_fail(directory != NULL, NULL);
+
+ if (!directory->directory_entry)
+ return NULL;
+
+ return desktop_entry_get_icon(directory->directory_entry);
+}
+
+const char *
+matemenu_tree_directory_get_desktop_file_path (MateMenuTreeDirectory *directory)
+{
+ g_return_val_if_fail (directory != NULL, NULL);
+
+ if (!directory->directory_entry)
+ return NULL;
+
+ return desktop_entry_get_path (directory->directory_entry);
+}
+
+const char *
+matemenu_tree_directory_get_menu_id (MateMenuTreeDirectory *directory)
+{
+ g_return_val_if_fail (directory != NULL, NULL);
+
+ return directory->name;
+}
+
+static void
+matemenu_tree_directory_set_tree (MateMenuTreeDirectory *directory,
+ MateMenuTree *tree)
+{
+ MateMenuTreeDirectoryRoot *root;
+
+ g_assert (directory != NULL);
+ g_assert (directory->is_root);
+
+ root = (MateMenuTreeDirectoryRoot *) directory;
+
+ root->tree = tree;
+}
+
+MateMenuTree *
+matemenu_tree_directory_get_tree (MateMenuTreeDirectory *directory)
+{
+ MateMenuTreeDirectoryRoot *root;
+
+ g_return_val_if_fail (directory != NULL, NULL);
+
+ while (MATEMENU_TREE_ITEM (directory)->parent != NULL)
+ directory = MATEMENU_TREE_DIRECTORY (MATEMENU_TREE_ITEM (directory)->parent);
+
+ if (!directory->is_root)
+ return NULL;
+
+ root = (MateMenuTreeDirectoryRoot *) directory;
+
+ if (root->tree)
+ matemenu_tree_ref (root->tree);
+
+ return root->tree;
+}
+
+gboolean
+matemenu_tree_directory_get_is_nodisplay (MateMenuTreeDirectory *directory)
+{
+ g_return_val_if_fail (directory != NULL, FALSE);
+
+ return directory->is_nodisplay;
+}
+
+static void
+append_directory_path (MateMenuTreeDirectory *directory,
+ GString *path)
+{
+
+ if (!directory->item.parent)
+ {
+ g_string_append_c (path, G_DIR_SEPARATOR);
+ return;
+ }
+
+ append_directory_path (directory->item.parent, path);
+
+ g_string_append (path, directory->name);
+ g_string_append_c (path, G_DIR_SEPARATOR);
+}
+
+char *
+matemenu_tree_directory_make_path (MateMenuTreeDirectory *directory,
+ MateMenuTreeEntry *entry)
+{
+ GString *path;
+
+ g_return_val_if_fail (directory != NULL, NULL);
+
+ path = g_string_new (NULL);
+
+ append_directory_path (directory, path);
+
+ if (entry != NULL)
+ g_string_append (path,
+ desktop_entry_get_basename (entry->desktop_entry));
+
+ return g_string_free (path, FALSE);
+}
+
+const char *
+matemenu_tree_entry_get_name (MateMenuTreeEntry *entry)
+{
+ g_return_val_if_fail (entry != NULL, NULL);
+
+ return desktop_entry_get_name (entry->desktop_entry);
+}
+
+const char *
+matemenu_tree_entry_get_generic_name (MateMenuTreeEntry *entry)
+{
+ g_return_val_if_fail (entry != NULL, NULL);
+
+ return desktop_entry_get_generic_name (entry->desktop_entry);
+}
+
+const char *
+matemenu_tree_entry_get_display_name (MateMenuTreeEntry *entry)
+{
+ const char *display_name;
+
+ g_return_val_if_fail (entry != NULL, NULL);
+
+ display_name = desktop_entry_get_full_name (entry->desktop_entry);
+ if (!display_name || display_name[0] == '\0')
+ display_name = desktop_entry_get_name (entry->desktop_entry);
+
+ return display_name;
+}
+
+const char *
+matemenu_tree_entry_get_comment (MateMenuTreeEntry *entry)
+{
+ g_return_val_if_fail (entry != NULL, NULL);
+
+ return desktop_entry_get_comment (entry->desktop_entry);
+}
+
+const char* matemenu_tree_entry_get_icon(MateMenuTreeEntry *entry)
+{
+ g_return_val_if_fail (entry != NULL, NULL);
+
+ return desktop_entry_get_icon(entry->desktop_entry);
+}
+
+const char* matemenu_tree_entry_get_exec(MateMenuTreeEntry* entry)
+{
+ g_return_val_if_fail(entry != NULL, NULL);
+
+ return desktop_entry_get_exec(entry->desktop_entry);
+}
+
+gboolean matemenu_tree_entry_get_launch_in_terminal(MateMenuTreeEntry* entry)
+{
+ g_return_val_if_fail(entry != NULL, FALSE);
+
+ return desktop_entry_get_launch_in_terminal(entry->desktop_entry);
+}
+
+const char* matemenu_tree_entry_get_desktop_file_path(MateMenuTreeEntry* entry)
+{
+ g_return_val_if_fail(entry != NULL, NULL);
+
+ return desktop_entry_get_path(entry->desktop_entry);
+}
+
+const char* matemenu_tree_entry_get_desktop_file_id(MateMenuTreeEntry* entry)
+{
+ g_return_val_if_fail(entry != NULL, NULL);
+
+ return entry->desktop_file_id;
+}
+
+gboolean matemenu_tree_entry_get_is_excluded(MateMenuTreeEntry* entry)
+{
+ g_return_val_if_fail(entry != NULL, FALSE);
+
+ return entry->is_excluded;
+}
+
+gboolean matemenu_tree_entry_get_is_nodisplay(MateMenuTreeEntry* entry)
+{
+ g_return_val_if_fail(entry != NULL, FALSE);
+
+ return entry->is_nodisplay;
+}
+
+MateMenuTreeDirectory* matemenu_tree_header_get_directory(MateMenuTreeHeader* header)
+{
+ g_return_val_if_fail (header != NULL, NULL);
+
+ return matemenu_tree_item_ref(header->directory);
+}
+
+MateMenuTreeDirectory* matemenu_tree_alias_get_directory(MateMenuTreeAlias* alias)
+{
+ g_return_val_if_fail (alias != NULL, NULL);
+
+ return matemenu_tree_item_ref(alias->directory);
+}
+
+MateMenuTreeItem *
+matemenu_tree_alias_get_item (MateMenuTreeAlias *alias)
+{
+ g_return_val_if_fail (alias != NULL, NULL);
+
+ return matemenu_tree_item_ref (alias->aliased_item);
+}
+
+static MateMenuTreeDirectory *
+matemenu_tree_directory_new (MateMenuTreeDirectory *parent,
+ const char *name,
+ gboolean is_root)
+{
+ MateMenuTreeDirectory *retval;
+
+ if (!is_root)
+ {
+ retval = g_new0 (MateMenuTreeDirectory, 1);
+ }
+ else
+ {
+ MateMenuTreeDirectoryRoot *root;
+
+ root = g_new0 (MateMenuTreeDirectoryRoot, 1);
+
+ retval = MATEMENU_TREE_DIRECTORY (root);
+
+ retval->is_root = TRUE;
+ }
+
+
+ retval->item.type = MATEMENU_TREE_ITEM_DIRECTORY;
+ retval->item.parent = parent;
+ retval->item.refcount = 1;
+
+ retval->name = g_strdup (name);
+ retval->directory_entry = NULL;
+ retval->entries = NULL;
+ retval->subdirs = NULL;
+ retval->default_layout_info = NULL;
+ retval->layout_info = NULL;
+ 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;
+ retval->default_layout_values.inline_menus = FALSE;
+ retval->default_layout_values.inline_limit = 4;
+ retval->default_layout_values.inline_header = FALSE;
+ retval->default_layout_values.inline_alias = FALSE;
+
+ return retval;
+}
+
+static void
+matemenu_tree_directory_finalize (MateMenuTreeDirectory *directory)
+{
+ g_assert (directory->item.refcount == 0);
+
+ g_slist_foreach (directory->contents,
+ (GFunc) matemenu_tree_item_unref_and_unset_parent,
+ NULL);
+ g_slist_free (directory->contents);
+ directory->contents = NULL;
+
+ g_slist_foreach (directory->default_layout_info,
+ (GFunc) menu_layout_node_unref,
+ NULL);
+ g_slist_free (directory->default_layout_info);
+ directory->default_layout_info = NULL;
+
+ g_slist_foreach (directory->layout_info,
+ (GFunc) menu_layout_node_unref,
+ NULL);
+ g_slist_free (directory->layout_info);
+ directory->layout_info = NULL;
+
+ g_slist_foreach (directory->subdirs,
+ (GFunc) matemenu_tree_item_unref_and_unset_parent,
+ NULL);
+ g_slist_free (directory->subdirs);
+ directory->subdirs = NULL;
+
+ g_slist_foreach (directory->entries,
+ (GFunc) matemenu_tree_item_unref_and_unset_parent,
+ NULL);
+ g_slist_free (directory->entries);
+ directory->entries = NULL;
+
+ if (directory->directory_entry)
+ desktop_entry_unref (directory->directory_entry);
+ directory->directory_entry = NULL;
+
+ g_free (directory->name);
+ directory->name = NULL;
+}
+
+static MateMenuTreeSeparator *
+matemenu_tree_separator_new (MateMenuTreeDirectory *parent)
+{
+ MateMenuTreeSeparator *retval;
+
+ retval = g_new0 (MateMenuTreeSeparator, 1);
+
+ retval->item.type = MATEMENU_TREE_ITEM_SEPARATOR;
+ retval->item.parent = parent;
+ retval->item.refcount = 1;
+
+ return retval;
+}
+
+static MateMenuTreeHeader *
+matemenu_tree_header_new (MateMenuTreeDirectory *parent,
+ MateMenuTreeDirectory *directory)
+{
+ MateMenuTreeHeader *retval;
+
+ retval = g_new0 (MateMenuTreeHeader, 1);
+
+ retval->item.type = MATEMENU_TREE_ITEM_HEADER;
+ retval->item.parent = parent;
+ retval->item.refcount = 1;
+
+ retval->directory = matemenu_tree_item_ref (directory);
+
+ matemenu_tree_item_set_parent (MATEMENU_TREE_ITEM (retval->directory), NULL);
+
+ return retval;
+}
+
+static void
+matemenu_tree_header_finalize (MateMenuTreeHeader *header)
+{
+ g_assert (header->item.refcount == 0);
+
+ if (header->directory != NULL)
+ matemenu_tree_item_unref (header->directory);
+ header->directory = NULL;
+}
+
+static MateMenuTreeAlias *
+matemenu_tree_alias_new (MateMenuTreeDirectory *parent,
+ MateMenuTreeDirectory *directory,
+ MateMenuTreeItem *item)
+{
+ MateMenuTreeAlias *retval;
+
+ retval = g_new0 (MateMenuTreeAlias, 1);
+
+ retval->item.type = MATEMENU_TREE_ITEM_ALIAS;
+ retval->item.parent = parent;
+ retval->item.refcount = 1;
+
+ retval->directory = matemenu_tree_item_ref (directory);
+ if (item->type != MATEMENU_TREE_ITEM_ALIAS)
+ retval->aliased_item = matemenu_tree_item_ref (item);
+ else
+ retval->aliased_item = matemenu_tree_item_ref (matemenu_tree_alias_get_item (MATEMENU_TREE_ALIAS (item)));
+
+ matemenu_tree_item_set_parent (MATEMENU_TREE_ITEM (retval->directory), NULL);
+ matemenu_tree_item_set_parent (retval->aliased_item, NULL);
+
+ return retval;
+}
+
+static void
+matemenu_tree_alias_finalize (MateMenuTreeAlias *alias)
+{
+ g_assert (alias->item.refcount == 0);
+
+ if (alias->directory != NULL)
+ matemenu_tree_item_unref (alias->directory);
+ alias->directory = NULL;
+
+ if (alias->aliased_item != NULL)
+ matemenu_tree_item_unref (alias->aliased_item);
+ alias->aliased_item = NULL;
+}
+
+static MateMenuTreeEntry *
+matemenu_tree_entry_new (MateMenuTreeDirectory *parent,
+ DesktopEntry *desktop_entry,
+ const char *desktop_file_id,
+ gboolean is_excluded,
+ gboolean is_nodisplay)
+{
+ MateMenuTreeEntry *retval;
+
+ retval = g_new0 (MateMenuTreeEntry, 1);
+
+ retval->item.type = MATEMENU_TREE_ITEM_ENTRY;
+ retval->item.parent = parent;
+ retval->item.refcount = 1;
+
+ retval->desktop_entry = desktop_entry_ref (desktop_entry);
+ retval->desktop_file_id = g_strdup (desktop_file_id);
+ retval->is_excluded = is_excluded != FALSE;
+ retval->is_nodisplay = is_nodisplay != FALSE;
+
+ return retval;
+}
+
+static void
+matemenu_tree_entry_finalize (MateMenuTreeEntry *entry)
+{
+ g_assert (entry->item.refcount == 0);
+
+ g_free (entry->desktop_file_id);
+ entry->desktop_file_id = NULL;
+
+ if (entry->desktop_entry)
+ desktop_entry_unref (entry->desktop_entry);
+ entry->desktop_entry = NULL;
+}
+
+static int
+matemenu_tree_entry_compare_by_id (MateMenuTreeItem *a,
+ MateMenuTreeItem *b)
+{
+ if (a->type == MATEMENU_TREE_ITEM_ALIAS)
+ a = MATEMENU_TREE_ALIAS (a)->aliased_item;
+
+ if (b->type == MATEMENU_TREE_ITEM_ALIAS)
+ b = MATEMENU_TREE_ALIAS (b)->aliased_item;
+
+ return strcmp (MATEMENU_TREE_ENTRY (a)->desktop_file_id,
+ MATEMENU_TREE_ENTRY (b)->desktop_file_id);
+}
+
+gpointer matemenu_tree_item_ref(gpointer itemp)
+{
+ MateMenuTreeItem* item = (MateMenuTreeItem*) itemp;
+
+ g_return_val_if_fail(item != NULL, NULL);
+ g_return_val_if_fail(item->refcount > 0, NULL);
+
+ item->refcount++;
+
+ return item;
+}
+
+void
+matemenu_tree_item_unref (gpointer itemp)
+{
+ MateMenuTreeItem *item;
+
+ item = (MateMenuTreeItem *) itemp;
+
+ g_return_if_fail (item != NULL);
+ g_return_if_fail (item->refcount > 0);
+
+ if (--item->refcount == 0)
+ {
+ switch (item->type)
+ {
+ case MATEMENU_TREE_ITEM_DIRECTORY:
+ matemenu_tree_directory_finalize (MATEMENU_TREE_DIRECTORY (item));
+ break;
+
+ case MATEMENU_TREE_ITEM_ENTRY:
+ matemenu_tree_entry_finalize (MATEMENU_TREE_ENTRY (item));
+ break;
+
+ case MATEMENU_TREE_ITEM_SEPARATOR:
+ break;
+
+ case MATEMENU_TREE_ITEM_HEADER:
+ matemenu_tree_header_finalize (MATEMENU_TREE_HEADER (item));
+ break;
+
+ case MATEMENU_TREE_ITEM_ALIAS:
+ matemenu_tree_alias_finalize (MATEMENU_TREE_ALIAS (item));
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ if (item->dnotify)
+ item->dnotify (item->user_data);
+ item->user_data = NULL;
+ item->dnotify = NULL;
+
+ item->parent = NULL;
+
+ g_free (item);
+ }
+}
+
+static void
+matemenu_tree_item_unref_and_unset_parent (gpointer itemp)
+{
+ MateMenuTreeItem *item;
+
+ item = (MateMenuTreeItem *) itemp;
+
+ g_return_if_fail (item != NULL);
+
+ matemenu_tree_item_set_parent (item, NULL);
+ matemenu_tree_item_unref (item);
+}
+
+void
+matemenu_tree_item_set_user_data (MateMenuTreeItem *item,
+ gpointer user_data,
+ GDestroyNotify dnotify)
+{
+ g_return_if_fail (item != NULL);
+
+ if (item->dnotify != NULL)
+ item->dnotify (item->user_data);
+
+ item->dnotify = dnotify;
+ item->user_data = user_data;
+}
+
+gpointer
+matemenu_tree_item_get_user_data (MateMenuTreeItem *item)
+{
+ g_return_val_if_fail (item != NULL, NULL);
+
+ return item->user_data;
+}
+
+static inline const char *
+matemenu_tree_item_compare_get_name_helper (MateMenuTreeItem *item,
+ MateMenuTreeSortKey sort_key)
+{
+ const char *name;
+
+ name = NULL;
+
+ switch (item->type)
+ {
+ case MATEMENU_TREE_ITEM_DIRECTORY:
+ if (MATEMENU_TREE_DIRECTORY (item)->directory_entry)
+ name = desktop_entry_get_name (MATEMENU_TREE_DIRECTORY (item)->directory_entry);
+ else
+ name = MATEMENU_TREE_DIRECTORY (item)->name;
+ break;
+
+ case MATEMENU_TREE_ITEM_ENTRY:
+ switch (sort_key)
+ {
+ case MATEMENU_TREE_SORT_NAME:
+ name = desktop_entry_get_name (MATEMENU_TREE_ENTRY (item)->desktop_entry);
+ break;
+ case MATEMENU_TREE_SORT_DISPLAY_NAME:
+ name = matemenu_tree_entry_get_display_name (MATEMENU_TREE_ENTRY (item));
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+ break;
+
+ case MATEMENU_TREE_ITEM_ALIAS:
+ {
+ MateMenuTreeItem *dir;
+ dir = MATEMENU_TREE_ITEM (MATEMENU_TREE_ALIAS (item)->directory);
+ name = matemenu_tree_item_compare_get_name_helper (dir, sort_key);
+ }
+ break;
+
+ case MATEMENU_TREE_ITEM_SEPARATOR:
+ case MATEMENU_TREE_ITEM_HEADER:
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ return name;
+}
+
+static int
+matemenu_tree_item_compare (MateMenuTreeItem *a,
+ MateMenuTreeItem *b,
+ gpointer sort_key_p)
+{
+ const char *name_a;
+ const char *name_b;
+ MateMenuTreeSortKey sort_key;
+
+ sort_key = GPOINTER_TO_INT (sort_key_p);
+
+ name_a = matemenu_tree_item_compare_get_name_helper (a, sort_key);
+ name_b = matemenu_tree_item_compare_get_name_helper (b, sort_key);
+
+ return g_utf8_collate (name_a, name_b);
+}
+
+static MenuLayoutNode *
+find_menu_child (MenuLayoutNode *layout)
+{
+ MenuLayoutNode *child;
+
+ child = menu_layout_node_get_children (layout);
+ while (child && menu_layout_node_get_type (child) != MENU_LAYOUT_NODE_MENU)
+ child = menu_layout_node_get_next (child);
+
+ return child;
+}
+
+static void
+merge_resolved_children (MateMenuTree *tree,
+ GHashTable *loaded_menu_files,
+ MenuLayoutNode *where,
+ MenuLayoutNode *from)
+{
+ MenuLayoutNode *insert_after;
+ MenuLayoutNode *menu_child;
+ MenuLayoutNode *from_child;
+
+ matemenu_tree_resolve_files (tree, loaded_menu_files, from);
+
+ insert_after = where;
+ g_assert (menu_layout_node_get_type (insert_after) != MENU_LAYOUT_NODE_ROOT);
+ g_assert (menu_layout_node_get_parent (insert_after) != NULL);
+
+ /* skip root node */
+ menu_child = find_menu_child (from);
+ g_assert (menu_child != NULL);
+ g_assert (menu_layout_node_get_type (menu_child) == MENU_LAYOUT_NODE_MENU);
+
+ /* merge children of toplevel <Menu> */
+ from_child = menu_layout_node_get_children (menu_child);
+ while (from_child != NULL)
+ {
+ MenuLayoutNode *next;
+
+ next = menu_layout_node_get_next (from_child);
+
+ menu_verbose ("Merging ");
+ menu_debug_print_layout (from_child, FALSE);
+ menu_verbose (" after ");
+ menu_debug_print_layout (insert_after, FALSE);
+
+ switch (menu_layout_node_get_type (from_child))
+ {
+ case MENU_LAYOUT_NODE_NAME:
+ menu_layout_node_unlink (from_child); /* delete this */
+ break;
+
+ default:
+ menu_layout_node_steal (from_child);
+ menu_layout_node_insert_after (insert_after, from_child);
+ menu_layout_node_unref (from_child);
+
+ insert_after = from_child;
+ break;
+ }
+
+ from_child = next;
+ }
+}
+
+static gboolean
+load_merge_file (MateMenuTree *tree,
+ GHashTable *loaded_menu_files,
+ const char *filename,
+ gboolean is_canonical,
+ gboolean add_monitor,
+ MenuLayoutNode *where)
+{
+ MenuLayoutNode *to_merge;
+ const char *canonical;
+ char *freeme;
+ gboolean retval;
+
+ freeme = NULL;
+ retval = FALSE;
+
+ if (!is_canonical)
+ {
+ canonical = freeme = menu_canonicalize_file_name (filename, FALSE);
+ if (canonical == NULL)
+ {
+ if (add_monitor)
+ matemenu_tree_add_menu_file_monitor (tree,
+ filename,
+ MENU_FILE_MONITOR_NONEXISTENT_FILE);
+
+ menu_verbose ("Failed to canonicalize merge file path \"%s\": %s\n",
+ filename, g_strerror (errno));
+ goto out;
+ }
+ }
+ else
+ {
+ canonical = filename;
+ }
+
+ if (g_hash_table_lookup (loaded_menu_files, canonical) != NULL)
+ {
+ g_warning ("Not loading \"%s\": recursive loop detected in .menu files",
+ canonical);
+ retval = TRUE;
+ goto out;
+ }
+
+ menu_verbose ("Merging file \"%s\"\n", canonical);
+
+ to_merge = menu_layout_load (canonical, NULL, NULL);
+ if (to_merge == NULL)
+ {
+ menu_verbose ("No menu for file \"%s\" found when merging\n",
+ canonical);
+ goto out;
+ }
+
+ retval = TRUE;
+
+ g_hash_table_insert (loaded_menu_files, (char *) canonical, GUINT_TO_POINTER (TRUE));
+
+ if (add_monitor)
+ matemenu_tree_add_menu_file_monitor (tree,
+ canonical,
+ MENU_FILE_MONITOR_FILE);
+
+ merge_resolved_children (tree, loaded_menu_files, where, to_merge);
+
+ g_hash_table_remove (loaded_menu_files, canonical);
+
+ menu_layout_node_unref (to_merge);
+
+ out:
+ if (freeme)
+ g_free (freeme);
+
+ return retval;
+}
+
+static gboolean
+load_merge_file_with_config_dir (MateMenuTree *tree,
+ GHashTable *loaded_menu_files,
+ const char *menu_file,
+ const char *config_dir,
+ MenuLayoutNode *where)
+{
+ char *merge_file;
+ gboolean loaded;
+
+ loaded = FALSE;
+
+ merge_file = g_build_filename (config_dir, "menus", menu_file, NULL);
+
+ if (load_merge_file (tree, loaded_menu_files, merge_file, FALSE, TRUE, where))
+ loaded = TRUE;
+
+ g_free (merge_file);
+
+ return loaded;
+}
+
+static gboolean
+compare_basedir_to_config_dir (const char *canonical_basedir,
+ const char *config_dir)
+{
+ char *dirname;
+ char *canonical_menus_dir;
+ gboolean retval;
+
+ menu_verbose ("Checking to see if basedir '%s' is in '%s'\n",
+ canonical_basedir, config_dir);
+
+ dirname = g_build_filename (config_dir, "menus", NULL);
+
+ retval = FALSE;
+
+ canonical_menus_dir = menu_canonicalize_file_name (dirname, FALSE);
+ if (canonical_menus_dir != NULL &&
+ strcmp (canonical_basedir, canonical_menus_dir) == 0)
+ {
+ retval = TRUE;
+ }
+
+ g_free (canonical_menus_dir);
+ g_free (dirname);
+
+ return retval;
+}
+
+static gboolean
+load_parent_merge_file_from_basename (MateMenuTree *tree,
+ GHashTable *loaded_menu_files,
+ MenuLayoutNode *layout,
+ const char *menu_file,
+ const char *canonical_basedir)
+{
+ gboolean found_basedir;
+ const char * const *system_config_dirs;
+ int i;
+
+ /* We're not interested in menu files that are in directories which are not a
+ * parent of the base directory of this menu file */
+ found_basedir = compare_basedir_to_config_dir (canonical_basedir,
+ g_get_user_config_dir ());
+
+ system_config_dirs = g_get_system_config_dirs ();
+
+ i = 0;
+ while (system_config_dirs[i] != NULL)
+ {
+ if (!found_basedir)
+ {
+ found_basedir = compare_basedir_to_config_dir (canonical_basedir,
+ system_config_dirs[i]);
+ }
+ else
+ {
+ menu_verbose ("Looking for parent menu file '%s' in '%s'\n",
+ menu_file, system_config_dirs[i]);
+
+ if (load_merge_file_with_config_dir (tree,
+ loaded_menu_files,
+ menu_file,
+ system_config_dirs[i],
+ layout))
+ {
+ break;
+ }
+ }
+
+ ++i;
+ }
+
+ return system_config_dirs[i] != NULL;
+}
+
+static gboolean load_parent_merge_file(MateMenuTree* tree, GHashTable* loaded_menu_files, MenuLayoutNode* layout)
+{
+ MenuLayoutNode* root;
+ const char* basedir;
+ const char* menu_name;
+ char* canonical_basedir;
+ char* menu_file;
+ gboolean found;
+
+ root = menu_layout_node_get_root(layout);
+
+ basedir = menu_layout_node_root_get_basedir(root);
+ menu_name = menu_layout_node_root_get_name(root);
+
+ canonical_basedir = menu_canonicalize_file_name(basedir, FALSE);
+
+ if (canonical_basedir == NULL)
+ {
+ menu_verbose("Menu basedir '%s' no longer exists, not merging parent\n", basedir);
+ return FALSE;
+ }
+
+ found = FALSE;
+ menu_file = g_strconcat(menu_name, ".menu", NULL);
+
+ if (strcmp(menu_file, "mate-applications.menu") == 0 && g_getenv("XDG_MENU_PREFIX"))
+ {
+ char* prefixed_basename;
+ prefixed_basename = g_strdup_printf("%s%s", g_getenv("XDG_MENU_PREFIX"), menu_file);
+ found = load_parent_merge_file_from_basename(tree, loaded_menu_files, layout, prefixed_basename, canonical_basedir);
+ g_free(prefixed_basename);
+ }
+
+ if (!found)
+ {
+ found = load_parent_merge_file_from_basename(tree, loaded_menu_files, layout, menu_file, canonical_basedir);
+ }
+
+ g_free(menu_file);
+ g_free(canonical_basedir);
+
+ return found;
+}
+
+static void
+load_merge_dir (MateMenuTree *tree,
+ GHashTable *loaded_menu_files,
+ const char *dirname,
+ MenuLayoutNode *where)
+{
+ GDir *dir;
+ const char *menu_file;
+
+ menu_verbose ("Loading merge dir \"%s\"\n", dirname);
+
+ matemenu_tree_add_menu_file_monitor (tree,
+ dirname,
+ MENU_FILE_MONITOR_DIRECTORY);
+
+ if ((dir = g_dir_open (dirname, 0, NULL)) == NULL)
+ return;
+
+ while ((menu_file = g_dir_read_name (dir)))
+ {
+ if (g_str_has_suffix (menu_file, ".menu"))
+ {
+ char *full_path;
+
+ full_path = g_build_filename (dirname, menu_file, NULL);
+
+ load_merge_file (tree, loaded_menu_files, full_path, TRUE, FALSE, where);
+
+ g_free (full_path);
+ }
+ }
+
+ g_dir_close (dir);
+}
+
+static void
+load_merge_dir_with_config_dir (MateMenuTree *tree,
+ GHashTable *loaded_menu_files,
+ const char *config_dir,
+ const char *dirname,
+ MenuLayoutNode *where)
+{
+ char *path;
+
+ path = g_build_filename (config_dir, "menus", dirname, NULL);
+
+ load_merge_dir (tree, loaded_menu_files, path, where);
+
+ g_free (path);
+}
+
+static void
+resolve_merge_file (MateMenuTree *tree,
+ GHashTable *loaded_menu_files,
+ MenuLayoutNode *layout)
+{
+ char *filename;
+
+ if (menu_layout_node_merge_file_get_type (layout) == MENU_MERGE_FILE_TYPE_PARENT)
+ {
+ if (load_parent_merge_file (tree, loaded_menu_files, layout))
+ return;
+ }
+
+ filename = menu_layout_node_get_content_as_path (layout);
+ if (filename == NULL)
+ {
+ menu_verbose ("didn't get node content as a path, not merging file\n");
+ }
+ else
+ {
+ load_merge_file (tree, loaded_menu_files, filename, FALSE, TRUE, layout);
+
+ g_free (filename);
+ }
+
+ /* remove the now-replaced node */
+ menu_layout_node_unlink (layout);
+}
+
+static void
+resolve_merge_dir (MateMenuTree *tree,
+ GHashTable *loaded_menu_files,
+ MenuLayoutNode *layout)
+{
+ char *path;
+
+ path = menu_layout_node_get_content_as_path (layout);
+ if (path == NULL)
+ {
+ menu_verbose ("didn't get layout node content as a path, not merging dir\n");
+ }
+ else
+ {
+ load_merge_dir (tree, loaded_menu_files, path, layout);
+
+ g_free (path);
+ }
+
+ /* remove the now-replaced node */
+ menu_layout_node_unlink (layout);
+}
+
+static MenuLayoutNode *
+add_app_dir (MateMenuTree *tree,
+ MenuLayoutNode *before,
+ const char *data_dir)
+{
+ MenuLayoutNode *tmp;
+ char *dirname;
+
+ tmp = menu_layout_node_new (MENU_LAYOUT_NODE_APP_DIR);
+ dirname = g_build_filename (data_dir, "applications", NULL);
+ menu_layout_node_set_content (tmp, dirname);
+ menu_layout_node_insert_before (before, tmp);
+ menu_layout_node_unref (before);
+
+ menu_verbose ("Adding <AppDir>%s</AppDir> in <DefaultAppDirs/>\n",
+ dirname);
+
+ g_free (dirname);
+
+ return tmp;
+}
+
+static void
+resolve_default_app_dirs (MateMenuTree *tree,
+ MenuLayoutNode *layout)
+{
+ MenuLayoutNode *before;
+ const char * const *system_data_dirs;
+ int i;
+
+ system_data_dirs = g_get_system_data_dirs ();
+
+ before = add_app_dir (tree,
+ menu_layout_node_ref (layout),
+ g_get_user_data_dir ());
+
+ i = 0;
+ while (system_data_dirs[i] != NULL)
+ {
+ before = add_app_dir (tree, before, system_data_dirs[i]);
+
+ ++i;
+ }
+
+ menu_layout_node_unref (before);
+
+ /* remove the now-replaced node */
+ menu_layout_node_unlink (layout);
+}
+
+static MenuLayoutNode* add_directory_dir(MateMenuTree* tree, MenuLayoutNode* before, const char* data_dir)
+{
+ MenuLayoutNode* tmp;
+ char* dirname;
+
+ tmp = menu_layout_node_new(MENU_LAYOUT_NODE_DIRECTORY_DIR);
+ dirname = g_build_filename(data_dir, "desktop-directories", NULL);
+ menu_layout_node_set_content(tmp, dirname);
+ menu_layout_node_insert_before(before, tmp);
+ menu_layout_node_unref(before);
+
+ menu_verbose("Adding <DirectoryDir>%s</DirectoryDir> in <DefaultDirectoryDirs/>\n", dirname);
+
+ g_free(dirname);
+
+ return tmp;
+}
+
+static void
+resolve_default_directory_dirs (MateMenuTree *tree,
+ MenuLayoutNode *layout)
+{
+ MenuLayoutNode *before;
+ const char * const *system_data_dirs;
+ int i;
+
+ system_data_dirs = g_get_system_data_dirs ();
+
+ before = add_directory_dir (tree,
+ menu_layout_node_ref (layout),
+ g_get_user_data_dir ());
+
+ i = 0;
+ while (system_data_dirs[i] != NULL)
+ {
+ before = add_directory_dir (tree, before, system_data_dirs[i]);
+
+ ++i;
+ }
+
+ menu_layout_node_unref (before);
+
+ /* remove the now-replaced node */
+ menu_layout_node_unlink (layout);
+}
+
+static void
+resolve_default_merge_dirs (MateMenuTree *tree,
+ GHashTable *loaded_menu_files,
+ MenuLayoutNode *layout)
+{
+ MenuLayoutNode *root;
+ const char *menu_name;
+ char *merge_name;
+ const char * const *system_config_dirs;
+ int i;
+
+ root = menu_layout_node_get_root (layout);
+ menu_name = menu_layout_node_root_get_name (root);
+
+ merge_name = g_strconcat (menu_name, "-merged", NULL);
+
+ system_config_dirs = g_get_system_config_dirs ();
+
+ /* Merge in reverse order */
+ i = 0;
+ while (system_config_dirs[i] != NULL) i++;
+ while (i > 0)
+ {
+ i--;
+ load_merge_dir_with_config_dir (tree,
+ loaded_menu_files,
+ system_config_dirs[i],
+ merge_name,
+ layout);
+ }
+
+ load_merge_dir_with_config_dir (tree,
+ loaded_menu_files,
+ g_get_user_config_dir (),
+ merge_name,
+ layout);
+
+ g_free (merge_name);
+
+ /* remove the now-replaced node */
+ menu_layout_node_unlink (layout);
+}
+
+static void
+add_filename_include (const char *desktop_file_id,
+ DesktopEntry *entry,
+ MenuLayoutNode *include)
+{
+ if (!desktop_entry_has_categories (entry))
+ {
+ MenuLayoutNode *node;
+
+ node = menu_layout_node_new (MENU_LAYOUT_NODE_FILENAME);
+ menu_layout_node_set_content (node, desktop_file_id);
+
+ menu_layout_node_append_child (include, node);
+ menu_layout_node_unref (node);
+ }
+}
+
+static void
+is_dot_directory (const char *basename,
+ DesktopEntry *entry,
+ gboolean *has_dot_directory)
+{
+ if (!strcmp (basename, ".directory"))
+ *has_dot_directory = TRUE;
+}
+
+static gboolean
+add_menu_for_legacy_dir (MenuLayoutNode *parent,
+ const char *legacy_dir,
+ const char *relative_path,
+ const char *legacy_prefix,
+ const char *menu_name)
+{
+ EntryDirectory *ed;
+ DesktopEntrySet *desktop_entries;
+ DesktopEntrySet *directory_entries;
+ GSList *subdirs;
+ gboolean menu_added;
+ gboolean has_dot_directory;
+
+ ed = entry_directory_new_legacy (DESKTOP_ENTRY_INVALID, legacy_dir, legacy_prefix);
+ if (!ed)
+ return FALSE;
+
+ subdirs = NULL;
+ desktop_entries = desktop_entry_set_new ();
+ directory_entries = desktop_entry_set_new ();
+
+ entry_directory_get_flat_contents (ed,
+ desktop_entries,
+ directory_entries,
+ &subdirs);
+ entry_directory_unref (ed);
+
+ has_dot_directory = FALSE;
+ desktop_entry_set_foreach (directory_entries,
+ (DesktopEntrySetForeachFunc) is_dot_directory,
+ &has_dot_directory);
+ desktop_entry_set_unref (directory_entries);
+
+ menu_added = FALSE;
+ if (desktop_entry_set_get_count (desktop_entries) > 0 || subdirs)
+ {
+ MenuLayoutNode *menu;
+ MenuLayoutNode *node;
+ GString *subdir_path;
+ GString *subdir_relative;
+ GSList *tmp;
+ int legacy_dir_len;
+ int relative_path_len;
+
+ menu = menu_layout_node_new (MENU_LAYOUT_NODE_MENU);
+ menu_layout_node_append_child (parent, menu);
+
+ menu_added = TRUE;
+
+ g_assert (menu_name != NULL);
+
+ node = menu_layout_node_new (MENU_LAYOUT_NODE_NAME);
+ menu_layout_node_set_content (node, menu_name);
+ menu_layout_node_append_child (menu, node);
+ menu_layout_node_unref (node);
+
+ if (has_dot_directory)
+ {
+ node = menu_layout_node_new (MENU_LAYOUT_NODE_DIRECTORY);
+ if (relative_path != NULL)
+ {
+ char *directory_entry_path;
+
+ directory_entry_path = g_strdup_printf ("%s/.directory", relative_path);
+ menu_layout_node_set_content (node, directory_entry_path);
+ g_free (directory_entry_path);
+ }
+ else
+ {
+ menu_layout_node_set_content (node, ".directory");
+ }
+ menu_layout_node_append_child (menu, node);
+ menu_layout_node_unref (node);
+ }
+
+ if (desktop_entry_set_get_count (desktop_entries) > 0)
+ {
+ MenuLayoutNode *include;
+
+ include = menu_layout_node_new (MENU_LAYOUT_NODE_INCLUDE);
+ menu_layout_node_append_child (menu, include);
+
+ desktop_entry_set_foreach (desktop_entries,
+ (DesktopEntrySetForeachFunc) add_filename_include,
+ include);
+
+ menu_layout_node_unref (include);
+ }
+
+ subdir_path = g_string_new (legacy_dir);
+ legacy_dir_len = strlen (legacy_dir);
+
+ subdir_relative = g_string_new (relative_path);
+ relative_path_len = relative_path ? strlen (relative_path) : 0;
+
+ tmp = subdirs;
+ while (tmp != NULL)
+ {
+ const char *subdir = tmp->data;
+
+ g_string_append_c (subdir_path, G_DIR_SEPARATOR);
+ g_string_append (subdir_path, subdir);
+
+ if (relative_path_len)
+ {
+ g_string_append_c (subdir_relative, G_DIR_SEPARATOR);
+ }
+ g_string_append (subdir_relative, subdir);
+
+ add_menu_for_legacy_dir (menu,
+ subdir_path->str,
+ subdir_relative->str,
+ legacy_prefix,
+ subdir);
+
+ g_string_truncate (subdir_relative, relative_path_len);
+ g_string_truncate (subdir_path, legacy_dir_len);
+
+ tmp = tmp->next;
+ }
+
+ g_string_free (subdir_path, TRUE);
+ g_string_free (subdir_relative, TRUE);
+
+ menu_layout_node_unref (menu);
+ }
+
+ desktop_entry_set_unref (desktop_entries);
+
+ g_slist_foreach (subdirs, (GFunc) g_free, NULL);
+ g_slist_free (subdirs);
+
+ return menu_added;
+}
+
+static void
+resolve_legacy_dir (MateMenuTree *tree,
+ GHashTable *loaded_menu_files,
+ MenuLayoutNode *legacy)
+{
+ MenuLayoutNode *to_merge;
+ MenuLayoutNode *menu;
+
+ to_merge = menu_layout_node_new (MENU_LAYOUT_NODE_ROOT);
+
+ menu = menu_layout_node_get_parent (legacy);
+ g_assert (menu_layout_node_get_type (menu) == MENU_LAYOUT_NODE_MENU);
+
+ if (add_menu_for_legacy_dir (to_merge,
+ menu_layout_node_get_content (legacy),
+ NULL,
+ menu_layout_node_legacy_dir_get_prefix (legacy),
+ menu_layout_node_menu_get_name (menu)))
+ {
+ merge_resolved_children (tree, loaded_menu_files, legacy, to_merge);
+ }
+
+ menu_layout_node_unref (to_merge);
+}
+
+static MenuLayoutNode *
+add_legacy_dir (MateMenuTree *tree,
+ GHashTable *loaded_menu_files,
+ MenuLayoutNode *before,
+ const char *data_dir)
+{
+ MenuLayoutNode *legacy;
+ char *dirname;
+
+ dirname = g_build_filename (data_dir, "applnk", NULL);
+
+ legacy = menu_layout_node_new (MENU_LAYOUT_NODE_LEGACY_DIR);
+ menu_layout_node_set_content (legacy, dirname);
+ menu_layout_node_legacy_dir_set_prefix (legacy, "kde");
+ menu_layout_node_insert_before (before, legacy);
+ menu_layout_node_unref (before);
+
+ menu_verbose ("Adding <LegacyDir>%s</LegacyDir> in <KDELegacyDirs/>\n",
+ dirname);
+
+ resolve_legacy_dir (tree, loaded_menu_files, legacy);
+
+ g_free (dirname);
+
+ return legacy;
+}
+
+static void
+resolve_kde_legacy_dirs (MateMenuTree *tree,
+ GHashTable *loaded_menu_files,
+ MenuLayoutNode *layout)
+{
+ MenuLayoutNode *before;
+ const char * const *system_data_dirs;
+ int i;
+
+ system_data_dirs = g_get_system_data_dirs ();
+
+ before = add_legacy_dir (tree,
+ loaded_menu_files,
+ menu_layout_node_ref (layout),
+ g_get_user_data_dir ());
+
+ i = 0;
+ while (system_data_dirs[i] != NULL)
+ {
+ before = add_legacy_dir (tree, loaded_menu_files, before, system_data_dirs[i]);
+
+ ++i;
+ }
+
+ menu_layout_node_unref (before);
+
+ /* remove the now-replaced node */
+ menu_layout_node_unlink (layout);
+}
+
+static void
+matemenu_tree_resolve_files (MateMenuTree *tree,
+ GHashTable *loaded_menu_files,
+ MenuLayoutNode *layout)
+{
+ MenuLayoutNode *child;
+
+ menu_verbose ("Resolving files in: ");
+ menu_debug_print_layout (layout, TRUE);
+
+ switch (menu_layout_node_get_type (layout))
+ {
+ case MENU_LAYOUT_NODE_MERGE_FILE:
+ resolve_merge_file (tree, loaded_menu_files, layout);
+ break;
+
+ case MENU_LAYOUT_NODE_MERGE_DIR:
+ resolve_merge_dir (tree, loaded_menu_files, layout);
+ break;
+
+ case MENU_LAYOUT_NODE_DEFAULT_APP_DIRS:
+ resolve_default_app_dirs (tree, layout);
+ break;
+
+ case MENU_LAYOUT_NODE_DEFAULT_DIRECTORY_DIRS:
+ resolve_default_directory_dirs (tree, layout);
+ break;
+
+ case MENU_LAYOUT_NODE_DEFAULT_MERGE_DIRS:
+ resolve_default_merge_dirs (tree, loaded_menu_files, layout);
+ break;
+
+ case MENU_LAYOUT_NODE_LEGACY_DIR:
+ resolve_legacy_dir (tree, loaded_menu_files, layout);
+ break;
+
+ case MENU_LAYOUT_NODE_KDE_LEGACY_DIRS:
+ resolve_kde_legacy_dirs (tree, loaded_menu_files, layout);
+ break;
+
+ case MENU_LAYOUT_NODE_PASSTHROUGH:
+ /* Just get rid of these, we don't need the memory usage */
+ menu_layout_node_unlink (layout);
+ break;
+
+ default:
+ /* Recurse */
+ child = menu_layout_node_get_children (layout);
+ while (child != NULL)
+ {
+ MenuLayoutNode *next = menu_layout_node_get_next (child);
+
+ matemenu_tree_resolve_files (tree, loaded_menu_files, child);
+
+ child = next;
+ }
+ break;
+ }
+}
+
+static void
+move_children (MenuLayoutNode *from,
+ MenuLayoutNode *to)
+{
+ MenuLayoutNode *from_child;
+ MenuLayoutNode *insert_before;
+
+ insert_before = menu_layout_node_get_children (to);
+ from_child = menu_layout_node_get_children (from);
+
+ while (from_child != NULL)
+ {
+ MenuLayoutNode *next;
+
+ next = menu_layout_node_get_next (from_child);
+
+ menu_layout_node_steal (from_child);
+
+ if (menu_layout_node_get_type (from_child) == MENU_LAYOUT_NODE_NAME)
+ {
+ ; /* just drop the Name in the old <Menu> */
+ }
+ else if (insert_before)
+ {
+ menu_layout_node_insert_before (insert_before, from_child);
+ g_assert (menu_layout_node_get_next (from_child) == insert_before);
+ }
+ else
+ {
+ menu_layout_node_append_child (to, from_child);
+ }
+
+ menu_layout_node_unref (from_child);
+
+ from_child = next;
+ }
+}
+
+static int
+null_safe_strcmp (const char *a,
+ const char *b)
+{
+ if (a == NULL && b == NULL)
+ return 0;
+ else if (a == NULL)
+ return -1;
+ else if (b == NULL)
+ return 1;
+ else
+ return strcmp (a, b);
+}
+
+static int
+node_compare_func (const void *a,
+ const void *b)
+{
+ MenuLayoutNode *node_a = (MenuLayoutNode*) a;
+ MenuLayoutNode *node_b = (MenuLayoutNode*) b;
+ MenuLayoutNodeType t_a = menu_layout_node_get_type (node_a);
+ MenuLayoutNodeType t_b = menu_layout_node_get_type (node_b);
+
+ if (t_a < t_b)
+ return -1;
+ else if (t_a > t_b)
+ return 1;
+ else
+ {
+ const char *c_a = menu_layout_node_get_content (node_a);
+ const char *c_b = menu_layout_node_get_content (node_b);
+
+ return null_safe_strcmp (c_a, c_b);
+ }
+}
+
+static int
+node_menu_compare_func (const void *a,
+ const void *b)
+{
+ MenuLayoutNode *node_a = (MenuLayoutNode*) a;
+ MenuLayoutNode *node_b = (MenuLayoutNode*) b;
+ MenuLayoutNode *parent_a = menu_layout_node_get_parent (node_a);
+ MenuLayoutNode *parent_b = menu_layout_node_get_parent (node_b);
+
+ if (parent_a < parent_b)
+ return -1;
+ else if (parent_a > parent_b)
+ return 1;
+ else
+ return null_safe_strcmp (menu_layout_node_menu_get_name (node_a),
+ menu_layout_node_menu_get_name (node_b));
+}
+
+static void
+matemenu_tree_strip_duplicate_children (MateMenuTree *tree,
+ MenuLayoutNode *layout)
+{
+ MenuLayoutNode *child;
+ GSList *simple_nodes;
+ GSList *menu_layout_nodes;
+ GSList *prev;
+ GSList *tmp;
+
+ /* to strip dups, we find all the child nodes where
+ * we want to kill dups, sort them,
+ * then nuke the adjacent nodes that are equal
+ */
+
+ simple_nodes = NULL;
+ menu_layout_nodes = NULL;
+
+ child = menu_layout_node_get_children (layout);
+ while (child != NULL)
+ {
+ switch (menu_layout_node_get_type (child))
+ {
+ /* These are dups if their content is the same */
+ case MENU_LAYOUT_NODE_APP_DIR:
+ case MENU_LAYOUT_NODE_DIRECTORY_DIR:
+ case MENU_LAYOUT_NODE_DIRECTORY:
+ simple_nodes = g_slist_prepend (simple_nodes, child);
+ break;
+
+ /* These have to be merged in a more complicated way,
+ * and then recursed
+ */
+ case MENU_LAYOUT_NODE_MENU:
+ menu_layout_nodes = g_slist_prepend (menu_layout_nodes, child);
+ break;
+
+ default:
+ break;
+ }
+
+ child = menu_layout_node_get_next (child);
+ }
+
+ /* Note that the lists are all backward. So we want to keep
+ * the items that are earlier in the list, because they were
+ * later in the file
+ */
+
+ /* stable sort the simple nodes */
+ simple_nodes = g_slist_sort (simple_nodes,
+ node_compare_func);
+
+ prev = NULL;
+ tmp = simple_nodes;
+ while (tmp != NULL)
+ {
+ GSList *next = tmp->next;
+
+ if (prev)
+ {
+ MenuLayoutNode *p = prev->data;
+ MenuLayoutNode *n = tmp->data;
+
+ if (node_compare_func (p, n) == 0)
+ {
+ /* nuke it! */
+ menu_layout_node_unlink (n);
+ simple_nodes = g_slist_delete_link (simple_nodes, tmp);
+ tmp = prev;
+ }
+ }
+
+ prev = tmp;
+ tmp = next;
+ }
+
+ g_slist_free (simple_nodes);
+ simple_nodes = NULL;
+
+ /* stable sort the menu nodes (the sort includes the
+ * parents of the nodes in the comparison). Remember
+ * the list is backward.
+ */
+ menu_layout_nodes = g_slist_sort (menu_layout_nodes,
+ node_menu_compare_func);
+
+ prev = NULL;
+ tmp = menu_layout_nodes;
+ while (tmp != NULL)
+ {
+ GSList *next = tmp->next;
+
+ if (prev)
+ {
+ MenuLayoutNode *p = prev->data;
+ MenuLayoutNode *n = tmp->data;
+
+ if (node_menu_compare_func (p, n) == 0)
+ {
+ /* Move children of first menu to the start of second
+ * menu and nuke the first menu
+ */
+ move_children (n, p);
+ menu_layout_node_unlink (n);
+ menu_layout_nodes = g_slist_delete_link (menu_layout_nodes, tmp);
+ tmp = prev;
+ }
+ }
+
+ prev = tmp;
+ tmp = next;
+ }
+
+ g_slist_free (menu_layout_nodes);
+ menu_layout_nodes = NULL;
+
+ /* Recursively clean up all children */
+ child = menu_layout_node_get_children (layout);
+ while (child != NULL)
+ {
+ if (menu_layout_node_get_type (child) == MENU_LAYOUT_NODE_MENU)
+ matemenu_tree_strip_duplicate_children (tree, child);
+
+ child = menu_layout_node_get_next (child);
+ }
+}
+
+static MenuLayoutNode *
+find_submenu (MenuLayoutNode *layout,
+ const char *path,
+ gboolean create_if_not_found)
+{
+ MenuLayoutNode *child;
+ const char *slash;
+ const char *next_path;
+ char *name;
+
+ menu_verbose (" (splitting \"%s\")\n", path);
+
+ if (path[0] == '\0' || path[0] == G_DIR_SEPARATOR)
+ return NULL;
+
+ slash = strchr (path, G_DIR_SEPARATOR);
+ if (slash != NULL)
+ {
+ name = g_strndup (path, slash - path);
+ next_path = slash + 1;
+ if (*next_path == '\0')
+ next_path = NULL;
+ }
+ else
+ {
+ name = g_strdup (path);
+ next_path = NULL;
+ }
+
+ child = menu_layout_node_get_children (layout);
+ while (child != NULL)
+ {
+ switch (menu_layout_node_get_type (child))
+ {
+ case MENU_LAYOUT_NODE_MENU:
+ {
+ if (strcmp (name, menu_layout_node_menu_get_name (child)) == 0)
+ {
+ menu_verbose ("MenuNode %p found for path component \"%s\"\n",
+ child, name);
+
+ g_free (name);
+
+ if (!next_path)
+ {
+ menu_verbose (" Found menu node %p parent is %p\n",
+ child, layout);
+ return child;
+ }
+
+ return find_submenu (child, next_path, create_if_not_found);
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ child = menu_layout_node_get_next (child);
+ }
+
+ if (create_if_not_found)
+ {
+ MenuLayoutNode *name_node;
+
+ child = menu_layout_node_new (MENU_LAYOUT_NODE_MENU);
+ menu_layout_node_append_child (layout, child);
+
+ name_node = menu_layout_node_new (MENU_LAYOUT_NODE_NAME);
+ menu_layout_node_set_content (name_node, name);
+ menu_layout_node_append_child (child, name_node);
+ menu_layout_node_unref (name_node);
+
+ menu_verbose (" Created menu node %p parent is %p\n",
+ child, layout);
+
+ menu_layout_node_unref (child);
+ g_free (name);
+
+ if (!next_path)
+ return child;
+
+ return find_submenu (child, next_path, create_if_not_found);
+ }
+ else
+ {
+ g_free (name);
+ return NULL;
+ }
+}
+
+/* To call this you first have to strip duplicate children once,
+ * otherwise when you move a menu Foo to Bar then you may only
+ * move one of Foo, not all the merged Foo.
+ */
+static void
+matemenu_tree_execute_moves (MateMenuTree *tree,
+ MenuLayoutNode *layout,
+ gboolean *need_remove_dups_p)
+{
+ MenuLayoutNode *child;
+ gboolean need_remove_dups;
+ GSList *move_nodes;
+ GSList *tmp;
+
+ need_remove_dups = FALSE;
+
+ move_nodes = NULL;
+
+ child = menu_layout_node_get_children (layout);
+ while (child != NULL)
+ {
+ switch (menu_layout_node_get_type (child))
+ {
+ case MENU_LAYOUT_NODE_MENU:
+ /* Recurse - we recurse first and process the current node
+ * second, as the spec dictates.
+ */
+ matemenu_tree_execute_moves (tree, child, &need_remove_dups);
+ break;
+
+ case MENU_LAYOUT_NODE_MOVE:
+ move_nodes = g_slist_prepend (move_nodes, child);
+ break;
+
+ default:
+ break;
+ }
+
+ child = menu_layout_node_get_next (child);
+ }
+
+ /* We need to execute the move operations in the order that they appear */
+ move_nodes = g_slist_reverse (move_nodes);
+
+ tmp = move_nodes;
+ while (tmp != NULL)
+ {
+ MenuLayoutNode *move_node = tmp->data;
+ MenuLayoutNode *old_node;
+ GSList *next = tmp->next;
+ const char *old;
+ const char *new;
+
+ old = menu_layout_node_move_get_old (move_node);
+ new = menu_layout_node_move_get_new (move_node);
+ g_assert (old != NULL && new != NULL);
+
+ menu_verbose ("executing <Move> old = \"%s\" new = \"%s\"\n",
+ old, new);
+
+ old_node = find_submenu (layout, old, FALSE);
+ if (old_node != NULL)
+ {
+ MenuLayoutNode *new_node;
+
+ /* here we can create duplicates anywhere below the
+ * node
+ */
+ need_remove_dups = TRUE;
+
+ /* look up new node creating it and its parents if
+ * required
+ */
+ new_node = find_submenu (layout, new, TRUE);
+ g_assert (new_node != NULL);
+
+ move_children (old_node, new_node);
+
+ menu_layout_node_unlink (old_node);
+ }
+
+ menu_layout_node_unlink (move_node);
+
+ tmp = next;
+ }
+
+ g_slist_free (move_nodes);
+
+ /* This oddness is to ensure we only remove dups once,
+ * at the root, instead of recursing the tree over
+ * and over.
+ */
+ if (need_remove_dups_p)
+ *need_remove_dups_p = need_remove_dups;
+ else if (need_remove_dups)
+ matemenu_tree_strip_duplicate_children (tree, layout);
+}
+
+static void
+matemenu_tree_load_layout (MateMenuTree *tree)
+{
+ GHashTable *loaded_menu_files;
+ GError *error;
+
+ if (tree->layout)
+ return;
+
+ if (!matemenu_tree_canonicalize_path (tree))
+ return;
+
+ menu_verbose ("Loading menu layout from \"%s\"\n",
+ tree->canonical_path);
+
+ error = NULL;
+ tree->layout = menu_layout_load (tree->canonical_path,
+ tree->type == MATEMENU_TREE_BASENAME ?
+ tree->basename : NULL,
+ &error);
+ if (tree->layout == NULL)
+ {
+ g_warning ("Error loading menu layout from \"%s\": %s",
+ tree->canonical_path, error->message);
+ g_error_free (error);
+ return;
+ }
+
+ loaded_menu_files = g_hash_table_new (g_str_hash, g_str_equal);
+ g_hash_table_insert (loaded_menu_files, tree->canonical_path, GUINT_TO_POINTER (TRUE));
+ matemenu_tree_resolve_files (tree, loaded_menu_files, tree->layout);
+ g_hash_table_destroy (loaded_menu_files);
+
+ matemenu_tree_strip_duplicate_children (tree, tree->layout);
+ matemenu_tree_execute_moves (tree, tree->layout, NULL);
+}
+
+static void
+matemenu_tree_force_reload (MateMenuTree *tree)
+{
+ matemenu_tree_force_rebuild (tree);
+
+ if (tree->layout)
+ menu_layout_node_unref (tree->layout);
+ tree->layout = NULL;
+}
+
+typedef struct
+{
+ DesktopEntrySet *set;
+ const char *category;
+} GetByCategoryForeachData;
+
+static void
+get_by_category_foreach (const char *file_id,
+ DesktopEntry *entry,
+ GetByCategoryForeachData *data)
+{
+ if (desktop_entry_has_category (entry, data->category))
+ desktop_entry_set_add_entry (data->set, entry, file_id);
+}
+
+static void
+get_by_category (DesktopEntrySet *entry_pool,
+ DesktopEntrySet *set,
+ const char *category)
+{
+ GetByCategoryForeachData data;
+
+ data.set = set;
+ data.category = category;
+
+ desktop_entry_set_foreach (entry_pool,
+ (DesktopEntrySetForeachFunc) get_by_category_foreach,
+ &data);
+}
+
+static DesktopEntrySet *
+process_include_rules (MenuLayoutNode *layout,
+ DesktopEntrySet *entry_pool)
+{
+ DesktopEntrySet *set = NULL;
+
+ switch (menu_layout_node_get_type (layout))
+ {
+ case MENU_LAYOUT_NODE_AND:
+ {
+ MenuLayoutNode *child;
+
+ menu_verbose ("Processing <And>\n");
+
+ child = menu_layout_node_get_children (layout);
+ while (child != NULL)
+ {
+ DesktopEntrySet *child_set;
+
+ child_set = process_include_rules (child, entry_pool);
+
+ if (set == NULL)
+ {
+ set = child_set;
+ }
+ else
+ {
+ desktop_entry_set_intersection (set, child_set);
+ desktop_entry_set_unref (child_set);
+ }
+
+ /* as soon as we get empty results, we can bail,
+ * because it's an AND
+ */
+ if (desktop_entry_set_get_count (set) == 0)
+ break;
+
+ child = menu_layout_node_get_next (child);
+ }
+ menu_verbose ("Processed <And>\n");
+ }
+ break;
+
+ case MENU_LAYOUT_NODE_OR:
+ {
+ MenuLayoutNode *child;
+
+ menu_verbose ("Processing <Or>\n");
+
+ child = menu_layout_node_get_children (layout);
+ while (child != NULL)
+ {
+ DesktopEntrySet *child_set;
+
+ child_set = process_include_rules (child, entry_pool);
+
+ if (set == NULL)
+ {
+ set = child_set;
+ }
+ else
+ {
+ desktop_entry_set_union (set, child_set);
+ desktop_entry_set_unref (child_set);
+ }
+
+ child = menu_layout_node_get_next (child);
+ }
+ menu_verbose ("Processed <Or>\n");
+ }
+ break;
+
+ case MENU_LAYOUT_NODE_NOT:
+ {
+ /* First get the OR of all the rules */
+ MenuLayoutNode *child;
+
+ menu_verbose ("Processing <Not>\n");
+
+ child = menu_layout_node_get_children (layout);
+ while (child != NULL)
+ {
+ DesktopEntrySet *child_set;
+
+ child_set = process_include_rules (child, entry_pool);
+
+ if (set == NULL)
+ {
+ set = child_set;
+ }
+ else
+ {
+ desktop_entry_set_union (set, child_set);
+ desktop_entry_set_unref (child_set);
+ }
+
+ child = menu_layout_node_get_next (child);
+ }
+
+ if (set != NULL)
+ {
+ DesktopEntrySet *inverted;
+
+ /* Now invert the result */
+ inverted = desktop_entry_set_new ();
+ desktop_entry_set_union (inverted, entry_pool);
+ desktop_entry_set_subtract (inverted, set);
+ desktop_entry_set_unref (set);
+ set = inverted;
+ }
+ menu_verbose ("Processed <Not>\n");
+ }
+ break;
+
+ case MENU_LAYOUT_NODE_ALL:
+ menu_verbose ("Processing <All>\n");
+ set = desktop_entry_set_new ();
+ desktop_entry_set_union (set, entry_pool);
+ menu_verbose ("Processed <All>\n");
+ break;
+
+ case MENU_LAYOUT_NODE_FILENAME:
+ {
+ DesktopEntry *entry;
+
+ menu_verbose ("Processing <Filename>%s</Filename>\n",
+ menu_layout_node_get_content (layout));
+
+ entry = desktop_entry_set_lookup (entry_pool,
+ menu_layout_node_get_content (layout));
+ if (entry != NULL)
+ {
+ set = desktop_entry_set_new ();
+ desktop_entry_set_add_entry (set,
+ entry,
+ menu_layout_node_get_content (layout));
+ }
+ menu_verbose ("Processed <Filename>%s</Filename>\n",
+ menu_layout_node_get_content (layout));
+ }
+ break;
+
+ case MENU_LAYOUT_NODE_CATEGORY:
+ menu_verbose ("Processing <Category>%s</Category>\n",
+ menu_layout_node_get_content (layout));
+ set = desktop_entry_set_new ();
+ get_by_category (entry_pool, set, menu_layout_node_get_content (layout));
+ menu_verbose ("Processed <Category>%s</Category>\n",
+ menu_layout_node_get_content (layout));
+ break;
+
+ default:
+ break;
+ }
+
+ if (set == NULL)
+ set = desktop_entry_set_new (); /* create an empty set */
+
+ menu_verbose ("Matched %d entries\n", desktop_entry_set_get_count (set));
+
+ return set;
+}
+
+static void
+collect_layout_info (MenuLayoutNode *layout,
+ GSList **layout_info)
+{
+ MenuLayoutNode *iter;
+
+ g_slist_foreach (*layout_info,
+ (GFunc) menu_layout_node_unref,
+ NULL);
+ g_slist_free (*layout_info);
+ *layout_info = NULL;
+
+ iter = menu_layout_node_get_children (layout);
+ while (iter != NULL)
+ {
+ switch (menu_layout_node_get_type (iter))
+ {
+ case MENU_LAYOUT_NODE_MENUNAME:
+ case MENU_LAYOUT_NODE_FILENAME:
+ case MENU_LAYOUT_NODE_SEPARATOR:
+ case MENU_LAYOUT_NODE_MERGE:
+ *layout_info = g_slist_prepend (*layout_info,
+ menu_layout_node_ref (iter));
+ break;
+
+ default:
+ break;
+ }
+
+ iter = menu_layout_node_get_next (iter);
+ }
+
+ *layout_info = g_slist_reverse (*layout_info);
+}
+
+static void
+entries_listify_foreach (const char *desktop_file_id,
+ DesktopEntry *desktop_entry,
+ MateMenuTreeDirectory *directory)
+{
+ directory->entries =
+ g_slist_prepend (directory->entries,
+ matemenu_tree_entry_new (directory,
+ desktop_entry,
+ desktop_file_id,
+ FALSE,
+ desktop_entry_get_no_display (desktop_entry)));
+}
+
+static void
+excluded_entries_listify_foreach (const char *desktop_file_id,
+ DesktopEntry *desktop_entry,
+ MateMenuTreeDirectory *directory)
+{
+ directory->entries =
+ g_slist_prepend (directory->entries,
+ matemenu_tree_entry_new (directory,
+ desktop_entry,
+ desktop_file_id,
+ TRUE,
+ desktop_entry_get_no_display (desktop_entry)));
+}
+
+static void
+set_default_layout_values (MateMenuTreeDirectory *parent,
+ MateMenuTreeDirectory *child)
+{
+ GSList *tmp;
+
+ /* if the child has a defined default layout, we don't want to override its
+ * values. The parent might have a non-defined layout info (ie, no child of
+ * the DefaultLayout node) but it doesn't meant the default layout values
+ * (ie, DefaultLayout attributes) aren't different from the global defaults.
+ */
+ if (child->default_layout_info != NULL ||
+ child->default_layout_values.mask != MENU_LAYOUT_VALUES_NONE)
+ return;
+
+ child->default_layout_values = parent->default_layout_values;
+
+ tmp = child->subdirs;
+ while (tmp != NULL)
+ {
+ MateMenuTreeDirectory *subdir = tmp->data;
+
+ set_default_layout_values (child, subdir);
+
+ tmp = tmp->next;
+ }
+}
+
+static MateMenuTreeDirectory *
+process_layout (MateMenuTree *tree,
+ MateMenuTreeDirectory *parent,
+ MenuLayoutNode *layout,
+ DesktopEntrySet *allocated)
+{
+ MenuLayoutNode *layout_iter;
+ MateMenuTreeDirectory *directory;
+ DesktopEntrySet *entry_pool;
+ DesktopEntrySet *entries;
+ DesktopEntrySet *allocated_set;
+ DesktopEntrySet *excluded_set;
+ gboolean deleted;
+ gboolean only_unallocated;
+ GSList *tmp;
+
+ g_assert (menu_layout_node_get_type (layout) == MENU_LAYOUT_NODE_MENU);
+ g_assert (menu_layout_node_menu_get_name (layout) != NULL);
+
+ directory = matemenu_tree_directory_new (parent,
+ menu_layout_node_menu_get_name (layout),
+ parent == NULL);
+
+ menu_verbose ("=== Menu name = %s ===\n", directory->name);
+
+
+ deleted = FALSE;
+ only_unallocated = FALSE;
+
+ entries = desktop_entry_set_new ();
+ allocated_set = desktop_entry_set_new ();
+
+ if (tree->flags & MATEMENU_TREE_FLAGS_INCLUDE_EXCLUDED)
+ excluded_set = desktop_entry_set_new ();
+ else
+ excluded_set = NULL;
+
+ entry_pool = _entry_directory_list_get_all_desktops (menu_layout_node_menu_get_app_dirs (layout));
+
+ layout_iter = menu_layout_node_get_children (layout);
+ while (layout_iter != NULL)
+ {
+ switch (menu_layout_node_get_type (layout_iter))
+ {
+ case MENU_LAYOUT_NODE_MENU:
+ /* recurse */
+ {
+ MateMenuTreeDirectory *child_dir;
+
+ menu_verbose ("Processing <Menu>\n");
+
+ child_dir = process_layout (tree,
+ directory,
+ layout_iter,
+ allocated);
+ if (child_dir)
+ directory->subdirs = g_slist_prepend (directory->subdirs,
+ child_dir);
+
+ menu_verbose ("Processed <Menu>\n");
+ }
+ break;
+
+ case MENU_LAYOUT_NODE_INCLUDE:
+ {
+ /* The match rule children of the <Include> are
+ * independent (logical OR) so we can process each one by
+ * itself
+ */
+ MenuLayoutNode *rule;
+
+ menu_verbose ("Processing <Include> (%d entries)\n",
+ desktop_entry_set_get_count (entries));
+
+ rule = menu_layout_node_get_children (layout_iter);
+ while (rule != NULL)
+ {
+ DesktopEntrySet *rule_set;
+
+ rule_set = process_include_rules (rule, entry_pool);
+ if (rule_set != NULL)
+ {
+ desktop_entry_set_union (entries, rule_set);
+ desktop_entry_set_union (allocated_set, rule_set);
+ if (excluded_set != NULL)
+ desktop_entry_set_subtract (excluded_set, rule_set);
+ desktop_entry_set_unref (rule_set);
+ }
+
+ rule = menu_layout_node_get_next (rule);
+ }
+
+ menu_verbose ("Processed <Include> (%d entries)\n",
+ desktop_entry_set_get_count (entries));
+ }
+ break;
+
+ case MENU_LAYOUT_NODE_EXCLUDE:
+ {
+ /* The match rule children of the <Exclude> are
+ * independent (logical OR) so we can process each one by
+ * itself
+ */
+ MenuLayoutNode *rule;
+
+ menu_verbose ("Processing <Exclude> (%d entries)\n",
+ desktop_entry_set_get_count (entries));
+
+ rule = menu_layout_node_get_children (layout_iter);
+ while (rule != NULL)
+ {
+ DesktopEntrySet *rule_set;
+
+ rule_set = process_include_rules (rule, entry_pool);
+ if (rule_set != NULL)
+ {
+ if (excluded_set != NULL)
+ desktop_entry_set_union (excluded_set, rule_set);
+ desktop_entry_set_subtract (entries, rule_set);
+ desktop_entry_set_unref (rule_set);
+ }
+
+ rule = menu_layout_node_get_next (rule);
+ }
+
+ menu_verbose ("Processed <Exclude> (%d entries)\n",
+ desktop_entry_set_get_count (entries));
+ }
+ break;
+
+ case MENU_LAYOUT_NODE_DIRECTORY:
+ {
+ DesktopEntry *entry;
+
+ menu_verbose ("Processing <Directory>%s</Directory>\n",
+ menu_layout_node_get_content (layout_iter));
+
+ /*
+ * The last <Directory> to exist wins, so we always try overwriting
+ */
+ entry = entry_directory_list_get_directory (menu_layout_node_menu_get_directory_dirs (layout),
+ menu_layout_node_get_content (layout_iter));
+
+ if (entry != NULL)
+ {
+ if (!desktop_entry_get_hidden (entry))
+ {
+ if (directory->directory_entry)
+ desktop_entry_unref (directory->directory_entry);
+ directory->directory_entry = entry; /* pass ref ownership */
+ }
+ else
+ {
+ desktop_entry_unref (entry);
+ }
+ }
+
+ menu_verbose ("Processed <Directory> new directory entry = %p (%s)\n",
+ directory->directory_entry,
+ directory->directory_entry? desktop_entry_get_path (directory->directory_entry) : "null");
+ }
+ break;
+
+ case MENU_LAYOUT_NODE_DELETED:
+ menu_verbose ("Processed <Deleted/>\n");
+ deleted = TRUE;
+ break;
+
+ case MENU_LAYOUT_NODE_NOT_DELETED:
+ menu_verbose ("Processed <NotDeleted/>\n");
+ deleted = FALSE;
+ break;
+
+ case MENU_LAYOUT_NODE_ONLY_UNALLOCATED:
+ menu_verbose ("Processed <OnlyUnallocated/>\n");
+ only_unallocated = TRUE;
+ break;
+
+ case MENU_LAYOUT_NODE_NOT_ONLY_UNALLOCATED:
+ menu_verbose ("Processed <NotOnlyUnallocated/>\n");
+ only_unallocated = FALSE;
+ break;
+
+ case MENU_LAYOUT_NODE_DEFAULT_LAYOUT:
+ menu_layout_node_default_layout_get_values (layout_iter,
+ &directory->default_layout_values);
+ collect_layout_info (layout_iter, &directory->default_layout_info);
+ menu_verbose ("Processed <DefaultLayout/>\n");
+ break;
+
+ case MENU_LAYOUT_NODE_LAYOUT:
+ collect_layout_info (layout_iter, &directory->layout_info);
+ menu_verbose ("Processed <Layout/>\n");
+ break;
+
+ default:
+ break;
+ }
+
+ layout_iter = menu_layout_node_get_next (layout_iter);
+ }
+
+ desktop_entry_set_unref (entry_pool);
+
+ directory->only_unallocated = only_unallocated;
+
+ if (!directory->only_unallocated)
+ desktop_entry_set_union (allocated, allocated_set);
+
+ desktop_entry_set_unref (allocated_set);
+
+ if (directory->directory_entry)
+ {
+ if (desktop_entry_get_no_display (directory->directory_entry))
+ {
+ directory->is_nodisplay = TRUE;
+
+ if (!(tree->flags & MATEMENU_TREE_FLAGS_INCLUDE_NODISPLAY))
+ {
+ menu_verbose ("Not showing menu %s because NoDisplay=true\n",
+ desktop_entry_get_name (directory->directory_entry));
+ deleted = TRUE;
+ }
+ }
+
+ if (!desktop_entry_get_show_in_mate (directory->directory_entry))
+ {
+ menu_verbose ("Not showing menu %s because OnlyShowIn!=MATE or NotShowIn=MATE\n",
+ desktop_entry_get_name (directory->directory_entry));
+ deleted = TRUE;
+ }
+ }
+
+ if (deleted)
+ {
+ if (excluded_set != NULL)
+ desktop_entry_set_unref (excluded_set);
+ desktop_entry_set_unref (entries);
+ matemenu_tree_item_unref (directory);
+ return NULL;
+ }
+
+ desktop_entry_set_foreach (entries,
+ (DesktopEntrySetForeachFunc) entries_listify_foreach,
+ directory);
+ desktop_entry_set_unref (entries);
+
+ if (excluded_set != NULL)
+ {
+ desktop_entry_set_foreach (excluded_set,
+ (DesktopEntrySetForeachFunc) excluded_entries_listify_foreach,
+ directory);
+ desktop_entry_set_unref (excluded_set);
+ }
+
+ tmp = directory->subdirs;
+ while (tmp != NULL)
+ {
+ MateMenuTreeDirectory *subdir = tmp->data;
+
+ set_default_layout_values (directory, subdir);
+
+ tmp = tmp->next;
+ }
+
+ tmp = directory->entries;
+ while (tmp != NULL)
+ {
+ MateMenuTreeEntry *entry = tmp->data;
+ GSList *next = tmp->next;
+ gboolean delete = FALSE;
+
+ if (desktop_entry_get_hidden (entry->desktop_entry))
+ {
+ menu_verbose ("Deleting %s because Hidden=true\n",
+ desktop_entry_get_name (entry->desktop_entry));
+ delete = TRUE;
+ }
+
+ if (!(tree->flags & MATEMENU_TREE_FLAGS_INCLUDE_NODISPLAY) &&
+ desktop_entry_get_no_display (entry->desktop_entry))
+ {
+ menu_verbose ("Deleting %s because NoDisplay=true\n",
+ desktop_entry_get_name (entry->desktop_entry));
+ delete = TRUE;
+ }
+
+ if (!desktop_entry_get_show_in_mate (entry->desktop_entry))
+ {
+ menu_verbose ("Deleting %s because OnlyShowIn!=MATE or NotShowIn=MATE\n",
+ desktop_entry_get_name (entry->desktop_entry));
+ delete = TRUE;
+ }
+
+ if (desktop_entry_get_tryexec_failed (entry->desktop_entry))
+ {
+ menu_verbose ("Deleting %s because TryExec failed\n",
+ desktop_entry_get_name (entry->desktop_entry));
+ delete = TRUE;
+ }
+
+ if (delete)
+ {
+ directory->entries = g_slist_delete_link (directory->entries,
+ tmp);
+ matemenu_tree_item_unref_and_unset_parent (entry);
+ }
+
+ tmp = next;
+ }
+
+ g_assert (directory->name != NULL);
+
+ return directory;
+}
+
+static void
+process_only_unallocated (MateMenuTree *tree,
+ MateMenuTreeDirectory *directory,
+ DesktopEntrySet *allocated)
+{
+ GSList *tmp;
+
+ /* For any directory marked only_unallocated, we have to remove any
+ * entries that were in fact allocated.
+ */
+
+ if (directory->only_unallocated)
+ {
+ tmp = directory->entries;
+ while (tmp != NULL)
+ {
+ MateMenuTreeEntry *entry = tmp->data;
+ GSList *next = tmp->next;
+
+ if (desktop_entry_set_lookup (allocated, entry->desktop_file_id))
+ {
+ directory->entries = g_slist_delete_link (directory->entries,
+ tmp);
+ matemenu_tree_item_unref_and_unset_parent (entry);
+ }
+
+ tmp = next;
+ }
+ }
+
+ tmp = directory->subdirs;
+ while (tmp != NULL)
+ {
+ MateMenuTreeDirectory *subdir = tmp->data;
+
+ process_only_unallocated (tree, subdir, allocated);
+
+ tmp = tmp->next;
+ }
+}
+
+static void preprocess_layout_info (MateMenuTree *tree,
+ MateMenuTreeDirectory *directory);
+
+static GSList *
+get_layout_info (MateMenuTreeDirectory *directory,
+ gboolean *is_default_layout)
+{
+ MateMenuTreeDirectory *iter;
+
+ if (directory->layout_info != NULL)
+ {
+ if (is_default_layout)
+ {
+ *is_default_layout = FALSE;
+ }
+ return directory->layout_info;
+ }
+
+ /* Even if there's no layout information at all, the result will be an
+ * implicit default layout */
+ if (is_default_layout)
+ {
+ *is_default_layout = TRUE;
+ }
+
+ iter = directory;
+ while (iter != NULL)
+ {
+ /* FIXME: this is broken: we might skip real parent in the
+ * XML structure, that are hidden because of inlining. */
+ if (iter->default_layout_info != NULL)
+ {
+ return iter->default_layout_info;
+ }
+
+ iter = MATEMENU_TREE_ITEM (iter)->parent;
+ }
+
+ return NULL;
+}
+
+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 guint
+get_real_subdirs_len (MateMenuTreeDirectory *directory)
+{
+ guint len;
+ GSList *tmp;
+
+ len = 0;
+
+ tmp = directory->subdirs;
+ while (tmp != NULL)
+ {
+ MateMenuTreeDirectory *subdir = tmp->data;
+
+ tmp = tmp->next;
+
+ if (subdir->will_inline_header != G_MAXUINT16)
+ {
+ len += get_real_subdirs_len (subdir) + g_slist_length (subdir->entries) + 1;
+ }
+ else
+ len += 1;
+ }
+
+ return len;
+}
+
+static void
+preprocess_layout_info_subdir_helper (MateMenuTree *tree,
+ MateMenuTreeDirectory *directory,
+ MateMenuTreeDirectory *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 & MATEMENU_TREE_FLAGS_SHOW_EMPTY) &&
+ !layout_values->show_empty)
+ {
+ menu_verbose ("Not showing empty menu '%s'\n", subdir->name);
+ *should_remove = TRUE;
+ }
+ }
+
+ else if (layout_values->inline_menus)
+ {
+ guint real_subdirs_len;
+
+ real_subdirs_len = get_real_subdirs_len (subdir);
+
+ if (layout_values->inline_alias &&
+ real_subdirs_len + g_slist_length (subdir->entries) == 1)
+ {
+ MateMenuTreeAlias *alias;
+ MateMenuTreeItem *item;
+ GSList *list;
+
+ if (subdir->subdirs != NULL)
+ list = subdir->subdirs;
+ else
+ list = subdir->entries;
+
+ item = MATEMENU_TREE_ITEM (list->data);
+
+ menu_verbose ("Inline aliasing '%s' to '%s'\n",
+ item->type == MATEMENU_TREE_ITEM_ENTRY ?
+ matemenu_tree_entry_get_name (MATEMENU_TREE_ENTRY (item)) :
+ (item->type == MATEMENU_TREE_ITEM_DIRECTORY ?
+ matemenu_tree_directory_get_name (MATEMENU_TREE_DIRECTORY (item)) :
+ matemenu_tree_directory_get_name (MATEMENU_TREE_ALIAS (item)->directory)),
+ subdir->name);
+
+ alias = matemenu_tree_alias_new (directory, subdir, item);
+
+ g_slist_foreach (list,
+ (GFunc) matemenu_tree_item_unref_and_unset_parent,
+ NULL);
+ g_slist_free (list);
+ subdir->subdirs = NULL;
+ subdir->entries = NULL;
+
+ if (item->type == MATEMENU_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) matemenu_tree_item_set_parent,
+ directory);
+ directory->subdirs = g_slist_concat (directory->subdirs,
+ subdir->subdirs);
+ subdir->subdirs = NULL;
+
+ g_slist_foreach (subdir->entries,
+ (GFunc) matemenu_tree_item_set_parent,
+ directory);
+ directory->entries = g_slist_concat (directory->entries,
+ subdir->entries);
+ subdir->entries = NULL;
+
+ *contents_added = TRUE;
+ *should_remove = TRUE;
+ }
+
+ menu_verbose ("Inlining directory contents of '%s' to '%s'\n",
+ subdir->name, directory->name);
+ }
+ }
+}
+
+static void
+preprocess_layout_info (MateMenuTree *tree,
+ MateMenuTreeDirectory *directory)
+{
+ GSList *tmp;
+ GSList *layout_info;
+ gboolean using_default_layout;
+ 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, &using_default_layout);
+ tmp = layout_info;
+ /* see comment below about Menuname to understand 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;
+ MateMenuTreeDirectory *subdir;
+ GSList *subdir_l;
+
+ tmp = tmp->next;
+
+ /* only Menuname nodes are relevant here */
+ if (menu_layout_node_get_type (node) != MENU_LAYOUT_NODE_MENUNAME)
+ continue;
+
+ get_values_with_defaults (node,
+ &layout_values,
+ &directory->default_layout_values);
+
+ /* 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)
+ {
+ subdir = subdir_l->data;
+
+ if (!strcmp (subdir->name, name))
+ break;
+
+ subdir = NULL;
+ subdir_l = subdir_l->next;
+
+ /* 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.
+ * Unless the layout is a default layout, in which case the Menuname
+ * does match the subdirectory. */
+ if (!using_default_layout && subdir_l == last_subdir)
+ {
+ subdir_l = NULL;
+ break;
+ }
+ }
+
+ if (subdir == NULL)
+ continue;
+
+ 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);
+ matemenu_tree_item_unref_and_unset_parent (MATEMENU_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)
+ {
+ MateMenuTreeDirectory *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);
+ matemenu_tree_item_unref_and_unset_parent (MATEMENU_TREE_ITEM (subdir));
+ }
+ else
+ tmp = tmp->next;
+ }
+
+ /* 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) matemenu_tree_entry_compare_by_id);
+ tmp = directory->entries;
+ while (tmp != NULL && tmp->next != NULL)
+ {
+ MateMenuTreeItem *a = tmp->data;
+ MateMenuTreeItem *b = tmp->next->data;
+
+ if (a->type == MATEMENU_TREE_ITEM_ALIAS)
+ a = MATEMENU_TREE_ALIAS (a)->aliased_item;
+
+ if (b->type == MATEMENU_TREE_ITEM_ALIAS)
+ b = MATEMENU_TREE_ALIAS (b)->aliased_item;
+
+ if (strcmp (MATEMENU_TREE_ENTRY (a)->desktop_file_id,
+ MATEMENU_TREE_ENTRY (b)->desktop_file_id) == 0)
+ {
+ tmp = g_slist_delete_link (tmp, tmp->next);
+ matemenu_tree_item_unref (b);
+ }
+ else
+ tmp = tmp->next;
+ }
+ }
+
+ directory->preprocessed = TRUE;
+}
+
+static void process_layout_info (MateMenuTree *tree,
+ MateMenuTreeDirectory *directory);
+
+static void
+check_pending_separator (MateMenuTreeDirectory *directory)
+{
+ if (directory->layout_pending_separator)
+ {
+ menu_verbose ("Adding pending separator in '%s'\n", directory->name);
+
+ directory->contents = g_slist_append (directory->contents,
+ matemenu_tree_separator_new (directory));
+ directory->layout_pending_separator = FALSE;
+ }
+}
+
+static void
+merge_alias (MateMenuTree *tree,
+ MateMenuTreeDirectory *directory,
+ MateMenuTreeAlias *alias)
+{
+ menu_verbose ("Merging alias '%s' in directory '%s'\n",
+ alias->directory->name, directory->name);
+
+ if (alias->aliased_item->type == MATEMENU_TREE_ITEM_DIRECTORY)
+ {
+ process_layout_info (tree, MATEMENU_TREE_DIRECTORY (alias->aliased_item));
+ }
+
+ check_pending_separator (directory);
+
+ directory->contents = g_slist_append (directory->contents,
+ matemenu_tree_item_ref (alias));
+}
+
+static void
+merge_subdir (MateMenuTree *tree,
+ MateMenuTreeDirectory *directory,
+ MateMenuTreeDirectory *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))
+ {
+ MateMenuTreeHeader *header;
+
+ header = matemenu_tree_header_new (directory, subdir);
+ directory->contents = g_slist_append (directory->contents, header);
+
+ g_slist_foreach (subdir->contents,
+ (GFunc) matemenu_tree_item_set_parent,
+ directory);
+ directory->contents = g_slist_concat (directory->contents,
+ subdir->contents);
+ subdir->contents = NULL;
+ subdir->will_inline_header = G_MAXUINT16;
+
+ matemenu_tree_item_set_parent (MATEMENU_TREE_ITEM (subdir), NULL);
+ }
+ else
+ {
+ directory->contents = g_slist_append (directory->contents,
+ matemenu_tree_item_ref (subdir));
+ }
+}
+
+static void
+merge_subdir_by_name (MateMenuTree *tree,
+ MateMenuTreeDirectory *directory,
+ const char *subdir_name)
+{
+ GSList *tmp;
+
+ menu_verbose ("Attempting to merge subdir '%s' in directory '%s'\n",
+ subdir_name, directory->name);
+
+ tmp = directory->subdirs;
+ while (tmp != NULL)
+ {
+ MateMenuTreeDirectory *subdir = tmp->data;
+ GSList *next = tmp->next;
+
+ /* if it's an alias, then it cannot be affected by
+ * the Merge nodes in the layout */
+ if (MATEMENU_TREE_ITEM (subdir)->type == MATEMENU_TREE_ITEM_ALIAS)
+ continue;
+
+ if (!strcmp (subdir->name, subdir_name))
+ {
+ directory->subdirs = g_slist_delete_link (directory->subdirs, tmp);
+ merge_subdir (tree, directory, subdir);
+ matemenu_tree_item_unref (subdir);
+ }
+
+ tmp = next;
+ }
+}
+
+static void
+merge_entry (MateMenuTree *tree,
+ MateMenuTreeDirectory *directory,
+ MateMenuTreeEntry *entry)
+{
+ menu_verbose ("Merging entry '%s' in directory '%s'\n",
+ entry->desktop_file_id, directory->name);
+
+ check_pending_separator (directory);
+ directory->contents = g_slist_append (directory->contents,
+ matemenu_tree_item_ref (entry));
+}
+
+static void
+merge_entry_by_id (MateMenuTree *tree,
+ MateMenuTreeDirectory *directory,
+ const char *file_id)
+{
+ GSList *tmp;
+
+ menu_verbose ("Attempting to merge entry '%s' in directory '%s'\n",
+ file_id, directory->name);
+
+ tmp = directory->entries;
+ while (tmp != NULL)
+ {
+ MateMenuTreeEntry *entry = tmp->data;
+ GSList *next = tmp->next;
+
+ /* if it's an alias, then it cannot be affected by
+ * the Merge nodes in the layout */
+ if (MATEMENU_TREE_ITEM (entry)->type == MATEMENU_TREE_ITEM_ALIAS)
+ continue;
+
+ if (!strcmp (entry->desktop_file_id, file_id))
+ {
+ directory->entries = g_slist_delete_link (directory->entries, tmp);
+ merge_entry (tree, directory, entry);
+ matemenu_tree_item_unref (entry);
+ }
+
+ tmp = next;
+ }
+}
+
+static inline gboolean
+find_name_in_list (const char *name,
+ GSList *list)
+{
+ while (list != NULL)
+ {
+ if (!strcmp (name, list->data))
+ return TRUE;
+
+ list = list->next;
+ }
+
+ return FALSE;
+}
+
+static void
+merge_subdirs (MateMenuTree *tree,
+ MateMenuTreeDirectory *directory,
+ GSList *except)
+{
+ GSList *subdirs;
+ GSList *tmp;
+
+ menu_verbose ("Merging subdirs in directory '%s'\n", directory->name);
+
+ subdirs = directory->subdirs;
+ directory->subdirs = NULL;
+
+ subdirs = g_slist_sort_with_data (subdirs,
+ (GCompareDataFunc) matemenu_tree_item_compare,
+ GINT_TO_POINTER (MATEMENU_TREE_SORT_NAME));
+
+ tmp = subdirs;
+ while (tmp != NULL)
+ {
+ MateMenuTreeDirectory *subdir = tmp->data;
+
+ if (MATEMENU_TREE_ITEM (subdir)->type == MATEMENU_TREE_ITEM_ALIAS)
+ {
+ merge_alias (tree, directory, MATEMENU_TREE_ALIAS (subdir));
+ matemenu_tree_item_unref (subdir);
+ }
+ else if (!find_name_in_list (subdir->name, except))
+ {
+ merge_subdir (tree, directory, subdir);
+ matemenu_tree_item_unref (subdir);
+ }
+ else
+ {
+ menu_verbose ("Not merging directory '%s' yet\n", subdir->name);
+ directory->subdirs = g_slist_append (directory->subdirs, subdir);
+ }
+
+ tmp = tmp->next;
+ }
+
+ g_slist_free (subdirs);
+ g_slist_free (except);
+}
+
+static void
+merge_entries (MateMenuTree *tree,
+ MateMenuTreeDirectory *directory,
+ GSList *except)
+{
+ GSList *entries;
+ GSList *tmp;
+
+ menu_verbose ("Merging entries in directory '%s'\n", directory->name);
+
+ entries = directory->entries;
+ directory->entries = NULL;
+
+ entries = g_slist_sort_with_data (entries,
+ (GCompareDataFunc) matemenu_tree_item_compare,
+ GINT_TO_POINTER (tree->sort_key));
+
+ tmp = entries;
+ while (tmp != NULL)
+ {
+ MateMenuTreeEntry *entry = tmp->data;
+
+ if (MATEMENU_TREE_ITEM (entry)->type == MATEMENU_TREE_ITEM_ALIAS)
+ {
+ merge_alias (tree, directory, MATEMENU_TREE_ALIAS (entry));
+ matemenu_tree_item_unref (entry);
+ }
+ else if (!find_name_in_list (entry->desktop_file_id, except))
+ {
+ merge_entry (tree, directory, entry);
+ matemenu_tree_item_unref (entry);
+ }
+ else
+ {
+ menu_verbose ("Not merging entry '%s' yet\n", entry->desktop_file_id);
+ directory->entries = g_slist_append (directory->entries, entry);
+ }
+
+ tmp = tmp->next;
+ }
+
+ g_slist_free (entries);
+ g_slist_free (except);
+}
+
+static void
+merge_subdirs_and_entries (MateMenuTree *tree,
+ MateMenuTreeDirectory *directory,
+ GSList *except_subdirs,
+ GSList *except_entries)
+{
+ GSList *items;
+ GSList *tmp;
+
+ menu_verbose ("Merging subdirs and entries together in directory %s\n",
+ directory->name);
+
+ items = g_slist_concat (directory->subdirs, directory->entries);
+
+ directory->subdirs = NULL;
+ directory->entries = NULL;
+
+ items = g_slist_sort_with_data (items,
+ (GCompareDataFunc) matemenu_tree_item_compare,
+ GINT_TO_POINTER (tree->sort_key));
+
+ tmp = items;
+ while (tmp != NULL)
+ {
+ MateMenuTreeItem *item = tmp->data;
+ MateMenuTreeItemType type;
+
+ type = matemenu_tree_item_get_type (item);
+
+ if (type == MATEMENU_TREE_ITEM_ALIAS)
+ {
+ merge_alias (tree, directory, MATEMENU_TREE_ALIAS (item));
+ matemenu_tree_item_unref (item);
+ }
+ else if (type == MATEMENU_TREE_ITEM_DIRECTORY)
+ {
+ if (!find_name_in_list (MATEMENU_TREE_DIRECTORY (item)->name, except_subdirs))
+ {
+ merge_subdir (tree,
+ directory,
+ MATEMENU_TREE_DIRECTORY (item));
+ matemenu_tree_item_unref (item);
+ }
+ else
+ {
+ menu_verbose ("Not merging directory '%s' yet\n",
+ MATEMENU_TREE_DIRECTORY (item)->name);
+ directory->subdirs = g_slist_append (directory->subdirs, item);
+ }
+ }
+ else if (type == MATEMENU_TREE_ITEM_ENTRY)
+ {
+ if (!find_name_in_list (MATEMENU_TREE_ENTRY (item)->desktop_file_id, except_entries))
+ {
+ merge_entry (tree, directory, MATEMENU_TREE_ENTRY (item));
+ matemenu_tree_item_unref (item);
+ }
+ else
+ {
+ menu_verbose ("Not merging entry '%s' yet\n",
+ MATEMENU_TREE_ENTRY (item)->desktop_file_id);
+ directory->entries = g_slist_append (directory->entries, item);
+ }
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+
+ tmp = tmp->next;
+ }
+
+ g_slist_free (items);
+ g_slist_free (except_subdirs);
+ g_slist_free (except_entries);
+}
+
+static GSList *
+get_subdirs_from_layout_info (GSList *layout_info)
+{
+ GSList *subdirs;
+ GSList *tmp;
+
+ subdirs = NULL;
+
+ tmp = layout_info;
+ while (tmp != NULL)
+ {
+ MenuLayoutNode *node = tmp->data;
+
+ if (menu_layout_node_get_type (node) == MENU_LAYOUT_NODE_MENUNAME)
+ {
+ subdirs = g_slist_append (subdirs,
+ (char *) menu_layout_node_get_content (node));
+ }
+
+ tmp = tmp->next;
+ }
+
+ return subdirs;
+}
+
+static GSList *
+get_entries_from_layout_info (GSList *layout_info)
+{
+ GSList *entries;
+ GSList *tmp;
+
+ entries = NULL;
+
+ tmp = layout_info;
+ while (tmp != NULL)
+ {
+ MenuLayoutNode *node = tmp->data;
+
+ if (menu_layout_node_get_type (node) == MENU_LAYOUT_NODE_FILENAME)
+ {
+ entries = g_slist_append (entries,
+ (char *) menu_layout_node_get_content (node));
+ }
+
+ tmp = tmp->next;
+ }
+
+ return entries;
+}
+
+static void
+process_layout_info (MateMenuTree *tree,
+ MateMenuTreeDirectory *directory)
+{
+ GSList *layout_info;
+
+ menu_verbose ("Processing menu layout hints for %s\n", directory->name);
+
+ g_slist_foreach (directory->contents,
+ (GFunc) matemenu_tree_item_unref_and_unset_parent,
+ NULL);
+ g_slist_free (directory->contents);
+ directory->contents = NULL;
+ directory->layout_pending_separator = FALSE;
+
+ layout_info = get_layout_info (directory, NULL);
+
+ if (layout_info == NULL)
+ {
+ merge_subdirs (tree, directory, NULL);
+ merge_entries (tree, directory, NULL);
+ }
+ else
+ {
+ GSList *tmp;
+
+ tmp = layout_info;
+ while (tmp != NULL)
+ {
+ MenuLayoutNode *node = tmp->data;
+
+ switch (menu_layout_node_get_type (node))
+ {
+ case MENU_LAYOUT_NODE_MENUNAME:
+ merge_subdir_by_name (tree,
+ directory,
+ menu_layout_node_get_content (node));
+ break;
+
+ case MENU_LAYOUT_NODE_FILENAME:
+ merge_entry_by_id (tree,
+ directory,
+ menu_layout_node_get_content (node));
+ break;
+
+ case MENU_LAYOUT_NODE_SEPARATOR:
+ /* Unless explicitly told to show all separators, do not show a
+ * separator at the beginning of a menu. Note that we don't add
+ * the separators now, and instead make it pending. This way, we
+ * won't show two consecutive separators nor will we show a
+ * separator at the end of a menu. */
+ if (tree->flags & MATEMENU_TREE_FLAGS_SHOW_ALL_SEPARATORS)
+ {
+ directory->layout_pending_separator = TRUE;
+ check_pending_separator (directory);
+ }
+ else if (directory->contents)
+ {
+ menu_verbose ("Adding a potential separator in '%s'\n",
+ directory->name);
+
+ directory->layout_pending_separator = TRUE;
+ }
+ else
+ {
+ menu_verbose ("Skipping separator at the beginning of '%s'\n",
+ directory->name);
+ }
+ break;
+
+ case MENU_LAYOUT_NODE_MERGE:
+ switch (menu_layout_node_merge_get_type (node))
+ {
+ case MENU_LAYOUT_MERGE_NONE:
+ break;
+
+ case MENU_LAYOUT_MERGE_MENUS:
+ merge_subdirs (tree,
+ directory,
+ get_subdirs_from_layout_info (tmp->next));
+ break;
+
+ case MENU_LAYOUT_MERGE_FILES:
+ merge_entries (tree,
+ directory,
+ get_entries_from_layout_info (tmp->next));
+ break;
+
+ case MENU_LAYOUT_MERGE_ALL:
+ merge_subdirs_and_entries (tree,
+ directory,
+ get_subdirs_from_layout_info (tmp->next),
+ get_entries_from_layout_info (tmp->next));
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ tmp = tmp->next;
+ }
+ }
+
+ g_slist_foreach (directory->subdirs,
+ (GFunc) matemenu_tree_item_unref,
+ NULL);
+ g_slist_free (directory->subdirs);
+ directory->subdirs = NULL;
+
+ g_slist_foreach (directory->entries,
+ (GFunc) matemenu_tree_item_unref,
+ NULL);
+ g_slist_free (directory->entries);
+ directory->entries = NULL;
+
+ g_slist_foreach (directory->default_layout_info,
+ (GFunc) menu_layout_node_unref,
+ NULL);
+ g_slist_free (directory->default_layout_info);
+ directory->default_layout_info = NULL;
+
+ g_slist_foreach (directory->layout_info,
+ (GFunc) menu_layout_node_unref,
+ NULL);
+ g_slist_free (directory->layout_info);
+ directory->layout_info = NULL;
+}
+
+static void
+handle_entries_changed (MenuLayoutNode *layout,
+ MateMenuTree *tree)
+{
+ if (tree->layout == layout)
+ {
+ matemenu_tree_force_rebuild (tree);
+ matemenu_tree_invoke_monitors (tree);
+ }
+}
+
+static void
+matemenu_tree_build_from_layout (MateMenuTree *tree)
+{
+ DesktopEntrySet *allocated;
+
+ if (tree->root)
+ return;
+
+ matemenu_tree_load_layout (tree);
+ if (!tree->layout)
+ return;
+
+ menu_verbose ("Building menu tree from layout\n");
+
+ allocated = desktop_entry_set_new ();
+
+ /* create the menu structure */
+ tree->root = process_layout (tree,
+ NULL,
+ find_menu_child (tree->layout),
+ allocated);
+ if (tree->root)
+ {
+ matemenu_tree_directory_set_tree (tree->root, 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,
+ (MenuLayoutNodeEntriesChangedFunc) handle_entries_changed,
+ tree);
+ }
+
+ desktop_entry_set_unref (allocated);
+}
+
+static void
+matemenu_tree_force_rebuild (MateMenuTree *tree)
+{
+ if (tree->root)
+ {
+ matemenu_tree_directory_set_tree (tree->root, NULL);
+ matemenu_tree_item_unref (tree->root);
+ tree->root = NULL;
+
+ g_assert (tree->layout != NULL);
+
+ menu_layout_node_root_remove_entries_monitor (tree->layout,
+ (MenuLayoutNodeEntriesChangedFunc) handle_entries_changed,
+ tree);
+ }
+}