summaryrefslogtreecommitdiff
path: root/libmenu/menu-layout.c
diff options
context:
space:
mode:
Diffstat (limited to 'libmenu/menu-layout.c')
-rw-r--r--libmenu/menu-layout.c2359
1 files changed, 2359 insertions, 0 deletions
diff --git a/libmenu/menu-layout.c b/libmenu/menu-layout.c
new file mode 100644
index 0000000..d447d2d
--- /dev/null
+++ b/libmenu/menu-layout.c
@@ -0,0 +1,2359 @@
+/* Menu layout in-memory data structure (a custom "DOM tree") */
+
+/*
+ * Copyright (C) 2002 - 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 "menu-layout.h"
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "canonicalize.h"
+#include "entry-directories.h"
+#include "menu-util.h"
+
+typedef struct MenuLayoutNodeMenu MenuLayoutNodeMenu;
+typedef struct MenuLayoutNodeRoot MenuLayoutNodeRoot;
+typedef struct MenuLayoutNodeLegacyDir MenuLayoutNodeLegacyDir;
+typedef struct MenuLayoutNodeMergeFile MenuLayoutNodeMergeFile;
+typedef struct MenuLayoutNodeDefaultLayout MenuLayoutNodeDefaultLayout;
+typedef struct MenuLayoutNodeMenuname MenuLayoutNodeMenuname;
+typedef struct MenuLayoutNodeMerge MenuLayoutNodeMerge;
+
+struct MenuLayoutNode
+{
+ /* Node lists are circular, for length-one lists
+ * prev/next point back to the node itself.
+ */
+ MenuLayoutNode *prev;
+ MenuLayoutNode *next;
+ MenuLayoutNode *parent;
+ MenuLayoutNode *children;
+
+ char *content;
+
+ guint refcount : 20;
+ guint type : 7;
+};
+
+struct MenuLayoutNodeRoot
+{
+ MenuLayoutNode node;
+
+ char *basedir;
+ char *name;
+
+ GSList *monitors;
+};
+
+struct MenuLayoutNodeMenu
+{
+ MenuLayoutNode node;
+
+ MenuLayoutNode *name_node; /* cache of the <Name> node */
+
+ EntryDirectoryList *app_dirs;
+ EntryDirectoryList *dir_dirs;
+};
+
+struct MenuLayoutNodeLegacyDir
+{
+ MenuLayoutNode node;
+
+ char *prefix;
+};
+
+struct MenuLayoutNodeMergeFile
+{
+ MenuLayoutNode node;
+
+ MenuMergeFileType type;
+};
+
+struct MenuLayoutNodeDefaultLayout
+{
+ MenuLayoutNode node;
+
+ MenuLayoutValues layout_values;
+};
+
+struct MenuLayoutNodeMenuname
+{
+ MenuLayoutNode node;
+
+ MenuLayoutValues layout_values;
+};
+
+struct MenuLayoutNodeMerge
+{
+ MenuLayoutNode node;
+
+ MenuLayoutMergeType merge_type;
+};
+
+typedef struct
+{
+ MenuLayoutNodeEntriesChangedFunc callback;
+ gpointer user_data;
+} MenuLayoutNodeEntriesMonitor;
+
+
+static inline MenuLayoutNode *
+node_next (MenuLayoutNode *node)
+{
+ /* root nodes (no parent) never have siblings */
+ if (node->parent == NULL)
+ return NULL;
+
+ /* circular list */
+ if (node->next == node->parent->children)
+ return NULL;
+
+ return node->next;
+}
+
+static void
+handle_entry_directory_changed (EntryDirectory *dir,
+ MenuLayoutNode *node)
+{
+ MenuLayoutNodeRoot *nr;
+ GSList *tmp;
+
+ g_assert (node->type == MENU_LAYOUT_NODE_MENU);
+
+ nr = (MenuLayoutNodeRoot *) menu_layout_node_get_root (node);
+
+ tmp = nr->monitors;
+ while (tmp != NULL)
+ {
+ MenuLayoutNodeEntriesMonitor *monitor = tmp->data;
+ GSList *next = tmp->next;
+
+ monitor->callback ((MenuLayoutNode *) nr, monitor->user_data);
+
+ tmp = next;
+ }
+}
+
+static void
+remove_entry_directory_list (MenuLayoutNodeMenu *nm,
+ EntryDirectoryList **dirs)
+{
+ if (*dirs)
+ {
+ entry_directory_list_remove_monitors (*dirs,
+ (EntryDirectoryChangedFunc) handle_entry_directory_changed,
+ nm);
+ entry_directory_list_unref (*dirs);
+ *dirs = NULL;
+ }
+}
+
+MenuLayoutNode *
+menu_layout_node_ref (MenuLayoutNode *node)
+{
+ g_return_val_if_fail (node != NULL, NULL);
+
+ node->refcount += 1;
+
+ return node;
+}
+
+void
+menu_layout_node_unref (MenuLayoutNode *node)
+{
+ g_return_if_fail (node != NULL);
+ g_return_if_fail (node->refcount > 0);
+
+ node->refcount -= 1;
+ if (node->refcount == 0)
+ {
+ MenuLayoutNode *iter;
+
+ iter = node->children;
+ while (iter != NULL)
+ {
+ MenuLayoutNode *next = node_next (iter);
+
+ menu_layout_node_unref (iter);
+
+ iter = next;
+ }
+
+ if (node->type == MENU_LAYOUT_NODE_MENU)
+ {
+ MenuLayoutNodeMenu *nm = (MenuLayoutNodeMenu *) node;
+
+ if (nm->name_node)
+ menu_layout_node_unref (nm->name_node);
+
+ remove_entry_directory_list (nm, &nm->app_dirs);
+ remove_entry_directory_list (nm, &nm->dir_dirs);
+ }
+ else if (node->type == MENU_LAYOUT_NODE_LEGACY_DIR)
+ {
+ MenuLayoutNodeLegacyDir *legacy = (MenuLayoutNodeLegacyDir *) node;
+
+ g_free (legacy->prefix);
+ }
+ else if (node->type == MENU_LAYOUT_NODE_ROOT)
+ {
+ MenuLayoutNodeRoot *nr = (MenuLayoutNodeRoot*) node;
+
+ g_slist_foreach (nr->monitors, (GFunc) g_free, NULL);
+ g_slist_free (nr->monitors);
+
+ g_free (nr->basedir);
+ g_free (nr->name);
+ }
+
+ g_free (node->content);
+ g_free (node);
+ }
+}
+
+MenuLayoutNode *
+menu_layout_node_new (MenuLayoutNodeType type)
+{
+ MenuLayoutNode *node;
+
+ switch (type)
+ {
+ case MENU_LAYOUT_NODE_MENU:
+ node = (MenuLayoutNode *) g_new0 (MenuLayoutNodeMenu, 1);
+ break;
+
+ case MENU_LAYOUT_NODE_LEGACY_DIR:
+ node = (MenuLayoutNode *) g_new0 (MenuLayoutNodeLegacyDir, 1);
+ break;
+
+ case MENU_LAYOUT_NODE_ROOT:
+ node = (MenuLayoutNode*) g_new0 (MenuLayoutNodeRoot, 1);
+ break;
+
+ case MENU_LAYOUT_NODE_MERGE_FILE:
+ node = (MenuLayoutNode *) g_new0 (MenuLayoutNodeMergeFile, 1);
+ break;
+
+ case MENU_LAYOUT_NODE_DEFAULT_LAYOUT:
+ node = (MenuLayoutNode *) g_new0 (MenuLayoutNodeDefaultLayout, 1);
+ break;
+
+ case MENU_LAYOUT_NODE_MENUNAME:
+ node = (MenuLayoutNode *) g_new0 (MenuLayoutNodeMenuname, 1);
+ break;
+
+ case MENU_LAYOUT_NODE_MERGE:
+ node = (MenuLayoutNode *) g_new0 (MenuLayoutNodeMerge, 1);
+ break;
+
+ default:
+ node = g_new0 (MenuLayoutNode, 1);
+ break;
+ }
+
+ node->type = type;
+
+ node->refcount = 1;
+
+ /* we're in a list of one node */
+ node->next = node;
+ node->prev = node;
+
+ return node;
+}
+
+MenuLayoutNode *
+menu_layout_node_get_next (MenuLayoutNode *node)
+{
+ return node_next (node);
+}
+
+MenuLayoutNode *
+menu_layout_node_get_parent (MenuLayoutNode *node)
+{
+ return node->parent;
+}
+
+MenuLayoutNode *
+menu_layout_node_get_children (MenuLayoutNode *node)
+{
+ return node->children;
+}
+
+MenuLayoutNode *
+menu_layout_node_get_root (MenuLayoutNode *node)
+{
+ MenuLayoutNode *parent;
+
+ parent = node;
+ while (parent->parent != NULL)
+ parent = parent->parent;
+
+ g_assert (parent->type == MENU_LAYOUT_NODE_ROOT);
+
+ return parent;
+}
+
+char *
+menu_layout_node_get_content_as_path (MenuLayoutNode *node)
+{
+ if (node->content == NULL)
+ {
+ menu_verbose (" (node has no content to get as a path)\n");
+ return NULL;
+ }
+
+ if (g_path_is_absolute (node->content))
+ {
+ return g_strdup (node->content);
+ }
+ else
+ {
+ MenuLayoutNodeRoot *root;
+
+ root = (MenuLayoutNodeRoot *) menu_layout_node_get_root (node);
+
+ if (root->basedir == NULL)
+ {
+ menu_verbose ("No basedir available, using \"%s\" as-is\n",
+ node->content);
+ return g_strdup (node->content);
+ }
+ else
+ {
+ menu_verbose ("Using basedir \"%s\" filename \"%s\"\n",
+ root->basedir, node->content);
+ return g_build_filename (root->basedir, node->content, NULL);
+ }
+ }
+}
+
+#define RETURN_IF_NO_PARENT(node) G_STMT_START { \
+ if ((node)->parent == NULL) \
+ { \
+ g_warning ("To add siblings to a menu node, " \
+ "it must not be the root node, " \
+ "and must be linked in below some root node\n" \
+ "node parent = %p and type = %d", \
+ (node)->parent, (node)->type); \
+ return; \
+ } \
+ } G_STMT_END
+
+#define RETURN_IF_HAS_ENTRY_DIRS(node) G_STMT_START { \
+ if ((node)->type == MENU_LAYOUT_NODE_MENU && \
+ (((MenuLayoutNodeMenu*)(node))->app_dirs != NULL || \
+ ((MenuLayoutNodeMenu*)(node))->dir_dirs != NULL)) \
+ { \
+ g_warning ("node acquired ->app_dirs or ->dir_dirs " \
+ "while not rooted in a tree\n"); \
+ return; \
+ } \
+ } G_STMT_END \
+
+void
+menu_layout_node_insert_before (MenuLayoutNode *node,
+ MenuLayoutNode *new_sibling)
+{
+ g_return_if_fail (new_sibling != NULL);
+ g_return_if_fail (new_sibling->parent == NULL);
+
+ RETURN_IF_NO_PARENT (node);
+ RETURN_IF_HAS_ENTRY_DIRS (new_sibling);
+
+ new_sibling->next = node;
+ new_sibling->prev = node->prev;
+
+ node->prev = new_sibling;
+ new_sibling->prev->next = new_sibling;
+
+ new_sibling->parent = node->parent;
+
+ if (node == node->parent->children)
+ node->parent->children = new_sibling;
+
+ menu_layout_node_ref (new_sibling);
+}
+
+void
+menu_layout_node_insert_after (MenuLayoutNode *node,
+ MenuLayoutNode *new_sibling)
+{
+ g_return_if_fail (new_sibling != NULL);
+ g_return_if_fail (new_sibling->parent == NULL);
+
+ RETURN_IF_NO_PARENT (node);
+ RETURN_IF_HAS_ENTRY_DIRS (new_sibling);
+
+ new_sibling->prev = node;
+ new_sibling->next = node->next;
+
+ node->next = new_sibling;
+ new_sibling->next->prev = new_sibling;
+
+ new_sibling->parent = node->parent;
+
+ menu_layout_node_ref (new_sibling);
+}
+
+void
+menu_layout_node_prepend_child (MenuLayoutNode *parent,
+ MenuLayoutNode *new_child)
+{
+ RETURN_IF_HAS_ENTRY_DIRS (new_child);
+
+ if (parent->children)
+ {
+ menu_layout_node_insert_before (parent->children, new_child);
+ }
+ else
+ {
+ parent->children = menu_layout_node_ref (new_child);
+ new_child->parent = parent;
+ }
+}
+
+void
+menu_layout_node_append_child (MenuLayoutNode *parent,
+ MenuLayoutNode *new_child)
+{
+ RETURN_IF_HAS_ENTRY_DIRS (new_child);
+
+ if (parent->children)
+ {
+ menu_layout_node_insert_after (parent->children->prev, new_child);
+ }
+ else
+ {
+ parent->children = menu_layout_node_ref (new_child);
+ new_child->parent = parent;
+ }
+}
+
+void
+menu_layout_node_unlink (MenuLayoutNode *node)
+{
+ g_return_if_fail (node != NULL);
+ g_return_if_fail (node->parent != NULL);
+
+ menu_layout_node_steal (node);
+ menu_layout_node_unref (node);
+}
+
+static void
+recursive_clean_entry_directory_lists (MenuLayoutNode *node,
+ gboolean apps)
+{
+ EntryDirectoryList **dirs;
+ MenuLayoutNodeMenu *nm;
+ MenuLayoutNode *iter;
+
+ if (node->type != MENU_LAYOUT_NODE_MENU)
+ return;
+
+ nm = (MenuLayoutNodeMenu *) node;
+
+ dirs = apps ? &nm->app_dirs : &nm->dir_dirs;
+
+ if (*dirs == NULL || entry_directory_list_get_length (*dirs) == 0)
+ return; /* child menus continue to have valid lists */
+
+ remove_entry_directory_list (nm, dirs);
+
+ iter = node->children;
+ while (iter != NULL)
+ {
+ if (iter->type == MENU_LAYOUT_NODE_MENU)
+ recursive_clean_entry_directory_lists (iter, apps);
+
+ iter = node_next (iter);
+ }
+}
+
+void
+menu_layout_node_steal (MenuLayoutNode *node)
+{
+ g_return_if_fail (node != NULL);
+ g_return_if_fail (node->parent != NULL);
+
+ switch (node->type)
+ {
+ case MENU_LAYOUT_NODE_NAME:
+ {
+ MenuLayoutNodeMenu *nm = (MenuLayoutNodeMenu *) node->parent;
+
+ if (nm->name_node == node)
+ {
+ menu_layout_node_unref (nm->name_node);
+ nm->name_node = NULL;
+ }
+ }
+ break;
+
+ case MENU_LAYOUT_NODE_APP_DIR:
+ recursive_clean_entry_directory_lists (node->parent, TRUE);
+ break;
+
+ case MENU_LAYOUT_NODE_DIRECTORY_DIR:
+ recursive_clean_entry_directory_lists (node->parent, FALSE);
+ break;
+
+ default:
+ break;
+ }
+
+ if (node->parent && node->parent->children == node)
+ {
+ if (node->next != node)
+ node->parent->children = node->next;
+ else
+ node->parent->children = NULL;
+ }
+
+ /* these are no-ops for length-one node lists */
+ node->prev->next = node->next;
+ node->next->prev = node->prev;
+
+ node->parent = NULL;
+
+ /* point to ourselves, now we're length one */
+ node->next = node;
+ node->prev = node;
+}
+
+MenuLayoutNodeType
+menu_layout_node_get_type (MenuLayoutNode *node)
+{
+ return node->type;
+}
+
+const char *
+menu_layout_node_get_content (MenuLayoutNode *node)
+{
+ return node->content;
+}
+
+void
+menu_layout_node_set_content (MenuLayoutNode *node,
+ const char *content)
+{
+ if (node->content == content)
+ return;
+
+ g_free (node->content);
+ node->content = g_strdup (content);
+}
+
+const char *
+menu_layout_node_root_get_name (MenuLayoutNode *node)
+{
+ MenuLayoutNodeRoot *nr;
+
+ g_return_val_if_fail (node->type == MENU_LAYOUT_NODE_ROOT, NULL);
+
+ nr = (MenuLayoutNodeRoot*) node;
+
+ return nr->name;
+}
+
+const char *
+menu_layout_node_root_get_basedir (MenuLayoutNode *node)
+{
+ MenuLayoutNodeRoot *nr;
+
+ g_return_val_if_fail (node->type == MENU_LAYOUT_NODE_ROOT, NULL);
+
+ nr = (MenuLayoutNodeRoot*) node;
+
+ return nr->basedir;
+}
+
+const char *
+menu_layout_node_menu_get_name (MenuLayoutNode *node)
+{
+ MenuLayoutNodeMenu *nm;
+
+ g_return_val_if_fail (node->type == MENU_LAYOUT_NODE_MENU, NULL);
+
+ nm = (MenuLayoutNodeMenu*) node;
+
+ if (nm->name_node == NULL)
+ {
+ MenuLayoutNode *iter;
+
+ iter = node->children;
+ while (iter != NULL)
+ {
+ if (iter->type == MENU_LAYOUT_NODE_NAME)
+ {
+ nm->name_node = menu_layout_node_ref (iter);
+ break;
+ }
+
+ iter = node_next (iter);
+ }
+ }
+
+ if (nm->name_node == NULL)
+ return NULL;
+
+ return menu_layout_node_get_content (nm->name_node);
+}
+
+static void
+ensure_dir_lists (MenuLayoutNodeMenu *nm)
+{
+ MenuLayoutNode *node;
+ MenuLayoutNode *iter;
+ EntryDirectoryList *app_dirs;
+ EntryDirectoryList *dir_dirs;
+
+ node = (MenuLayoutNode *) nm;
+
+ if (nm->app_dirs && nm->dir_dirs)
+ return;
+
+ app_dirs = NULL;
+ dir_dirs = NULL;
+
+ if (nm->app_dirs == NULL)
+ {
+ app_dirs = entry_directory_list_new ();
+
+ if (node->parent && node->parent->type == MENU_LAYOUT_NODE_MENU)
+ {
+ EntryDirectoryList *dirs;
+
+ if ((dirs = menu_layout_node_menu_get_app_dirs (node->parent)))
+ entry_directory_list_append_list (app_dirs, dirs);
+ }
+ }
+
+ if (nm->dir_dirs == NULL)
+ {
+ dir_dirs = entry_directory_list_new ();
+
+ if (node->parent && node->parent->type == MENU_LAYOUT_NODE_MENU)
+ {
+ EntryDirectoryList *dirs;
+
+ if ((dirs = menu_layout_node_menu_get_directory_dirs (node->parent)))
+ entry_directory_list_append_list (dir_dirs, dirs);
+ }
+ }
+
+ iter = node->children;
+ while (iter != NULL)
+ {
+ EntryDirectory *ed;
+
+ if (app_dirs != NULL && iter->type == MENU_LAYOUT_NODE_APP_DIR)
+ {
+ char *path;
+
+ path = menu_layout_node_get_content_as_path (iter);
+
+ ed = entry_directory_new (DESKTOP_ENTRY_DESKTOP, path);
+ if (ed != NULL)
+ {
+ entry_directory_list_prepend (app_dirs, ed);
+ entry_directory_unref (ed);
+ }
+
+ g_free (path);
+ }
+
+ if (dir_dirs != NULL && iter->type == MENU_LAYOUT_NODE_DIRECTORY_DIR)
+ {
+ char *path;
+
+ path = menu_layout_node_get_content_as_path (iter);
+
+ ed = entry_directory_new (DESKTOP_ENTRY_DIRECTORY, path);
+ if (ed != NULL)
+ {
+ entry_directory_list_prepend (dir_dirs, ed);
+ entry_directory_unref (ed);
+ }
+
+ g_free (path);
+ }
+
+ if (iter->type == MENU_LAYOUT_NODE_LEGACY_DIR)
+ {
+ MenuLayoutNodeLegacyDir *legacy = (MenuLayoutNodeLegacyDir *) iter;
+ char *path;
+
+ path = menu_layout_node_get_content_as_path (iter);
+
+ if (app_dirs != NULL) /* we're loading app dirs */
+ {
+ ed = entry_directory_new_legacy (DESKTOP_ENTRY_DESKTOP,
+ path,
+ legacy->prefix);
+ if (ed != NULL)
+ {
+ entry_directory_list_prepend (app_dirs, ed);
+ entry_directory_unref (ed);
+ }
+ }
+
+ if (dir_dirs != NULL) /* we're loading dir dirs */
+ {
+ ed = entry_directory_new_legacy (DESKTOP_ENTRY_DIRECTORY,
+ path,
+ legacy->prefix);
+ if (ed != NULL)
+ {
+ entry_directory_list_prepend (dir_dirs, ed);
+ entry_directory_unref (ed);
+ }
+ }
+
+ g_free (path);
+ }
+
+ iter = node_next (iter);
+ }
+
+ if (app_dirs)
+ {
+ g_assert (nm->app_dirs == NULL);
+
+ nm->app_dirs = app_dirs;
+ entry_directory_list_add_monitors (nm->app_dirs,
+ (EntryDirectoryChangedFunc) handle_entry_directory_changed,
+ nm);
+ }
+
+ if (dir_dirs)
+ {
+ g_assert (nm->dir_dirs == NULL);
+
+ nm->dir_dirs = dir_dirs;
+ entry_directory_list_add_monitors (nm->dir_dirs,
+ (EntryDirectoryChangedFunc) handle_entry_directory_changed,
+ nm);
+ }
+}
+
+EntryDirectoryList *
+menu_layout_node_menu_get_app_dirs (MenuLayoutNode *node)
+{
+ MenuLayoutNodeMenu *nm;
+
+ g_return_val_if_fail (node->type == MENU_LAYOUT_NODE_MENU, NULL);
+
+ nm = (MenuLayoutNodeMenu *) node;
+
+ ensure_dir_lists (nm);
+
+ return nm->app_dirs;
+}
+
+EntryDirectoryList *
+menu_layout_node_menu_get_directory_dirs (MenuLayoutNode *node)
+{
+ MenuLayoutNodeMenu *nm;
+
+ g_return_val_if_fail (node->type == MENU_LAYOUT_NODE_MENU, NULL);
+
+ nm = (MenuLayoutNodeMenu *) node;
+
+ ensure_dir_lists (nm);
+
+ return nm->dir_dirs;
+}
+
+const char *
+menu_layout_node_move_get_old (MenuLayoutNode *node)
+{
+ MenuLayoutNode *iter;
+
+ iter = node->children;
+ while (iter != NULL)
+ {
+ if (iter->type == MENU_LAYOUT_NODE_OLD)
+ return iter->content;
+
+ iter = node_next (iter);
+ }
+
+ return NULL;
+}
+
+const char *
+menu_layout_node_move_get_new (MenuLayoutNode *node)
+{
+ MenuLayoutNode *iter;
+
+ iter = node->children;
+ while (iter != NULL)
+ {
+ if (iter->type == MENU_LAYOUT_NODE_NEW)
+ return iter->content;
+
+ iter = node_next (iter);
+ }
+
+ return NULL;
+}
+
+const char *
+menu_layout_node_legacy_dir_get_prefix (MenuLayoutNode *node)
+{
+ MenuLayoutNodeLegacyDir *legacy;
+
+ g_return_val_if_fail (node->type == MENU_LAYOUT_NODE_LEGACY_DIR, NULL);
+
+ legacy = (MenuLayoutNodeLegacyDir *) node;
+
+ return legacy->prefix;
+}
+
+void
+menu_layout_node_legacy_dir_set_prefix (MenuLayoutNode *node,
+ const char *prefix)
+{
+ MenuLayoutNodeLegacyDir *legacy;
+
+ g_return_if_fail (node->type == MENU_LAYOUT_NODE_LEGACY_DIR);
+
+ legacy = (MenuLayoutNodeLegacyDir *) node;
+
+ g_free (legacy->prefix);
+ legacy->prefix = g_strdup (prefix);
+}
+
+MenuMergeFileType
+menu_layout_node_merge_file_get_type (MenuLayoutNode *node)
+{
+ MenuLayoutNodeMergeFile *merge_file;
+
+ g_return_val_if_fail (node->type == MENU_LAYOUT_NODE_MERGE_FILE, FALSE);
+
+ merge_file = (MenuLayoutNodeMergeFile *) node;
+
+ return merge_file->type;
+}
+
+void
+menu_layout_node_merge_file_set_type (MenuLayoutNode *node,
+ MenuMergeFileType type)
+{
+ MenuLayoutNodeMergeFile *merge_file;
+
+ g_return_if_fail (node->type == MENU_LAYOUT_NODE_MERGE_FILE);
+
+ merge_file = (MenuLayoutNodeMergeFile *) node;
+
+ merge_file->type = type;
+}
+
+MenuLayoutMergeType
+menu_layout_node_merge_get_type (MenuLayoutNode *node)
+{
+ MenuLayoutNodeMerge *merge;
+
+ g_return_val_if_fail (node->type == MENU_LAYOUT_NODE_MERGE, 0);
+
+ merge = (MenuLayoutNodeMerge *) node;
+
+ return merge->merge_type;
+}
+
+static void
+menu_layout_node_merge_set_type (MenuLayoutNode *node,
+ const char *merge_type)
+{
+ MenuLayoutNodeMerge *merge;
+
+ g_return_if_fail (node->type == MENU_LAYOUT_NODE_MERGE);
+
+ merge = (MenuLayoutNodeMerge *) node;
+
+ merge->merge_type = MENU_LAYOUT_MERGE_NONE;
+
+ if (strcmp (merge_type, "menus") == 0)
+ {
+ merge->merge_type = MENU_LAYOUT_MERGE_MENUS;
+ }
+ else if (strcmp (merge_type, "files") == 0)
+ {
+ merge->merge_type = MENU_LAYOUT_MERGE_FILES;
+ }
+ else if (strcmp (merge_type, "all") == 0)
+ {
+ merge->merge_type = MENU_LAYOUT_MERGE_ALL;
+ }
+}
+
+void
+menu_layout_node_default_layout_get_values (MenuLayoutNode *node,
+ MenuLayoutValues *values)
+{
+ MenuLayoutNodeDefaultLayout *default_layout;
+
+ g_return_if_fail (node->type == MENU_LAYOUT_NODE_DEFAULT_LAYOUT);
+ g_return_if_fail (values != NULL);
+
+ default_layout = (MenuLayoutNodeDefaultLayout *) node;
+
+ *values = default_layout->layout_values;
+}
+
+void
+menu_layout_node_menuname_get_values (MenuLayoutNode *node,
+ MenuLayoutValues *values)
+{
+ MenuLayoutNodeMenuname *menuname;
+
+ g_return_if_fail (node->type == MENU_LAYOUT_NODE_MENUNAME);
+ g_return_if_fail (values != NULL);
+
+ menuname = (MenuLayoutNodeMenuname *) node;
+
+ *values = menuname->layout_values;
+}
+
+static void
+menu_layout_values_set (MenuLayoutValues *values,
+ const char *show_empty,
+ const char *inline_menus,
+ const char *inline_limit,
+ const char *inline_header,
+ const char *inline_alias)
+{
+ values->mask = MENU_LAYOUT_VALUES_NONE;
+ values->show_empty = FALSE;
+ values->inline_menus = FALSE;
+ values->inline_limit = 4;
+ values->inline_header = FALSE;
+ values->inline_alias = FALSE;
+
+ if (show_empty != NULL)
+ {
+ values->show_empty = strcmp (show_empty, "true") == 0;
+ values->mask |= MENU_LAYOUT_VALUES_SHOW_EMPTY;
+ }
+
+ if (inline_menus != NULL)
+ {
+ values->inline_menus = strcmp (inline_menus, "true") == 0;
+ values->mask |= MENU_LAYOUT_VALUES_INLINE_MENUS;
+ }
+
+ if (inline_limit != NULL)
+ {
+ char *end;
+ int limit;
+
+ limit = strtol (inline_limit, &end, 10);
+ if (*end == '\0')
+ {
+ values->inline_limit = limit;
+ values->mask |= MENU_LAYOUT_VALUES_INLINE_LIMIT;
+ }
+ }
+
+ if (inline_header != NULL)
+ {
+ values->inline_header = strcmp (inline_header, "true") == 0;
+ values->mask |= MENU_LAYOUT_VALUES_INLINE_HEADER;
+ }
+
+ if (inline_alias != NULL)
+ {
+ values->inline_alias = strcmp (inline_alias, "true") == 0;
+ values->mask |= MENU_LAYOUT_VALUES_INLINE_ALIAS;
+ }
+}
+
+static void
+menu_layout_node_default_layout_set_values (MenuLayoutNode *node,
+ const char *show_empty,
+ const char *inline_menus,
+ const char *inline_limit,
+ const char *inline_header,
+ const char *inline_alias)
+{
+ MenuLayoutNodeDefaultLayout *default_layout;
+
+ g_return_if_fail (node->type == MENU_LAYOUT_NODE_DEFAULT_LAYOUT);
+
+ default_layout = (MenuLayoutNodeDefaultLayout *) node;
+
+ menu_layout_values_set (&default_layout->layout_values,
+ show_empty,
+ inline_menus,
+ inline_limit,
+ inline_header,
+ inline_alias);
+}
+
+static void
+menu_layout_node_menuname_set_values (MenuLayoutNode *node,
+ const char *show_empty,
+ const char *inline_menus,
+ const char *inline_limit,
+ const char *inline_header,
+ const char *inline_alias)
+{
+ MenuLayoutNodeMenuname *menuname;
+
+ g_return_if_fail (node->type == MENU_LAYOUT_NODE_MENUNAME);
+
+ menuname = (MenuLayoutNodeMenuname *) node;
+
+ menu_layout_values_set (&menuname->layout_values,
+ show_empty,
+ inline_menus,
+ inline_limit,
+ inline_header,
+ inline_alias);
+}
+
+void
+menu_layout_node_root_add_entries_monitor (MenuLayoutNode *node,
+ MenuLayoutNodeEntriesChangedFunc callback,
+ gpointer user_data)
+{
+ MenuLayoutNodeEntriesMonitor *monitor;
+ MenuLayoutNodeRoot *nr;
+ GSList *tmp;
+
+ g_return_if_fail (node->type == MENU_LAYOUT_NODE_ROOT);
+
+ nr = (MenuLayoutNodeRoot *) node;
+
+ tmp = nr->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 (MenuLayoutNodeEntriesMonitor, 1);
+ monitor->callback = callback;
+ monitor->user_data = user_data;
+
+ nr->monitors = g_slist_append (nr->monitors, monitor);
+ }
+}
+
+void
+menu_layout_node_root_remove_entries_monitor (MenuLayoutNode *node,
+ MenuLayoutNodeEntriesChangedFunc callback,
+ gpointer user_data)
+{
+ MenuLayoutNodeRoot *nr;
+ GSList *tmp;
+
+ g_return_if_fail (node->type == MENU_LAYOUT_NODE_ROOT);
+
+ nr = (MenuLayoutNodeRoot *) node;
+
+ tmp = nr->monitors;
+ while (tmp != NULL)
+ {
+ MenuLayoutNodeEntriesMonitor *monitor = tmp->data;
+ GSList *next = tmp->next;
+
+ if (monitor->callback == callback &&
+ monitor->user_data == user_data)
+ {
+ nr->monitors = g_slist_delete_link (nr->monitors, tmp);
+ g_free (monitor);
+ }
+
+ tmp = next;
+ }
+}
+
+
+/*
+ * Menu file parsing
+ */
+
+typedef struct
+{
+ MenuLayoutNode *root;
+ MenuLayoutNode *stack_top;
+} MenuParser;
+
+static void set_error (GError **err,
+ GMarkupParseContext *context,
+ int error_domain,
+ int error_code,
+ const char *format,
+ ...) G_GNUC_PRINTF (5, 6);
+
+static void add_context_to_error (GError **err,
+ GMarkupParseContext *context);
+
+static void start_element_handler (GMarkupParseContext *context,
+ const char *element_name,
+ const char **attribute_names,
+ const char **attribute_values,
+ gpointer user_data,
+ GError **error);
+static void end_element_handler (GMarkupParseContext *context,
+ const char *element_name,
+ gpointer user_data,
+ GError **error);
+static void text_handler (GMarkupParseContext *context,
+ const char *text,
+ gsize text_len,
+ gpointer user_data,
+ GError **error);
+static void passthrough_handler (GMarkupParseContext *context,
+ const char *passthrough_text,
+ gsize text_len,
+ gpointer user_data,
+ GError **error);
+
+
+static GMarkupParser menu_funcs = {
+ start_element_handler,
+ end_element_handler,
+ text_handler,
+ passthrough_handler,
+ NULL
+};
+
+static void
+set_error (GError **err,
+ GMarkupParseContext *context,
+ int error_domain,
+ int error_code,
+ const char *format,
+ ...)
+{
+ int line, ch;
+ va_list args;
+ char *str;
+
+ g_markup_parse_context_get_position (context, &line, &ch);
+
+ va_start (args, format);
+ str = g_strdup_vprintf (format, args);
+ va_end (args);
+
+ g_set_error (err, error_domain, error_code,
+ "Line %d character %d: %s",
+ line, ch, str);
+
+ g_free (str);
+}
+
+static void
+add_context_to_error (GError **err,
+ GMarkupParseContext *context)
+{
+ int line, ch;
+ char *str;
+
+ if (err == NULL || *err == NULL)
+ return;
+
+ g_markup_parse_context_get_position (context, &line, &ch);
+
+ str = g_strdup_printf ("Line %d character %d: %s",
+ line, ch, (*err)->message);
+ g_free ((*err)->message);
+ (*err)->message = str;
+}
+
+#define ELEMENT_IS(name) (strcmp (element_name, (name)) == 0)
+
+typedef struct
+{
+ const char *name;
+ const char **retloc;
+} LocateAttr;
+
+static gboolean
+locate_attributes (GMarkupParseContext *context,
+ const char *element_name,
+ const char **attribute_names,
+ const char **attribute_values,
+ GError **error,
+ const char *first_attribute_name,
+ const char **first_attribute_retloc,
+ ...)
+{
+#define MAX_ATTRS 24
+ LocateAttr attrs[MAX_ATTRS];
+ int n_attrs;
+ va_list args;
+ const char *name;
+ const char **retloc;
+ gboolean retval;
+ int i;
+
+ g_return_val_if_fail (first_attribute_name != NULL, FALSE);
+ g_return_val_if_fail (first_attribute_retloc != NULL, FALSE);
+
+ retval = TRUE;
+
+ n_attrs = 1;
+ attrs[0].name = first_attribute_name;
+ attrs[0].retloc = first_attribute_retloc;
+ *first_attribute_retloc = NULL;
+
+ va_start (args, first_attribute_retloc);
+
+ name = va_arg (args, const char *);
+ retloc = va_arg (args, const char **);
+
+ while (name != NULL)
+ {
+ g_return_val_if_fail (retloc != NULL, FALSE);
+
+ g_assert (n_attrs < MAX_ATTRS);
+
+ attrs[n_attrs].name = name;
+ attrs[n_attrs].retloc = retloc;
+ n_attrs += 1;
+ *retloc = NULL;
+
+ name = va_arg (args, const char *);
+ retloc = va_arg (args, const char **);
+ }
+
+ va_end (args);
+
+ i = 0;
+ while (attribute_names[i])
+ {
+ int j;
+
+ j = 0;
+ while (j < n_attrs)
+ {
+ if (strcmp (attrs[j].name, attribute_names[i]) == 0)
+ {
+ retloc = attrs[j].retloc;
+
+ if (*retloc != NULL)
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ "Attribute \"%s\" repeated twice on the same <%s> element",
+ attrs[j].name, element_name);
+ retval = FALSE;
+ goto out;
+ }
+
+ *retloc = attribute_values[i];
+ break;
+ }
+
+ ++j;
+ }
+
+ if (j == n_attrs)
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ "Attribute \"%s\" is invalid on <%s> element in this context",
+ attribute_names[i], element_name);
+ retval = FALSE;
+ goto out;
+ }
+
+ ++i;
+ }
+
+ out:
+ return retval;
+
+#undef MAX_ATTRS
+}
+
+static gboolean
+check_no_attributes (GMarkupParseContext *context,
+ const char *element_name,
+ const char **attribute_names,
+ const char **attribute_values,
+ GError **error)
+{
+ if (attribute_names[0] != NULL)
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ "Attribute \"%s\" is invalid on <%s> element in this context",
+ attribute_names[0], element_name);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static int
+has_child_of_type (MenuLayoutNode *node,
+ MenuLayoutNodeType type)
+{
+ MenuLayoutNode *iter;
+
+ iter = node->children;
+ while (iter)
+ {
+ if (iter->type == type)
+ return TRUE;
+
+ iter = node_next (iter);
+ }
+
+ return FALSE;
+}
+
+static void
+push_node (MenuParser *parser,
+ MenuLayoutNodeType type)
+{
+ MenuLayoutNode *node;
+
+ node = menu_layout_node_new (type);
+ menu_layout_node_append_child (parser->stack_top, node);
+ menu_layout_node_unref (node);
+
+ parser->stack_top = node;
+}
+
+static void
+start_menu_element (MenuParser *parser,
+ GMarkupParseContext *context,
+ const char *element_name,
+ const char **attribute_names,
+ const char **attribute_values,
+ GError **error)
+{
+ if (!check_no_attributes (context, element_name,
+ attribute_names, attribute_values,
+ error))
+ return;
+
+ if (!(parser->stack_top->type == MENU_LAYOUT_NODE_ROOT ||
+ parser->stack_top->type == MENU_LAYOUT_NODE_MENU))
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ "<Menu> element can only appear below other <Menu> elements or at toplevel\n");
+ }
+ else
+ {
+ push_node (parser, MENU_LAYOUT_NODE_MENU);
+ }
+}
+
+static void
+start_menu_child_element (MenuParser *parser,
+ GMarkupParseContext *context,
+ const char *element_name,
+ const char **attribute_names,
+ const char **attribute_values,
+ GError **error)
+{
+ if (ELEMENT_IS ("LegacyDir"))
+ {
+ const char *prefix;
+
+ push_node (parser, MENU_LAYOUT_NODE_LEGACY_DIR);
+
+ if (!locate_attributes (context, element_name,
+ attribute_names, attribute_values,
+ error,
+ "prefix", &prefix,
+ NULL))
+ return;
+
+ menu_layout_node_legacy_dir_set_prefix (parser->stack_top, prefix);
+ }
+ else if (ELEMENT_IS ("MergeFile"))
+ {
+ const char *type;
+
+ push_node (parser, MENU_LAYOUT_NODE_MERGE_FILE);
+
+ if (!locate_attributes (context, element_name,
+ attribute_names, attribute_values,
+ error,
+ "type", &type,
+ NULL))
+ return;
+
+ if (type != NULL && strcmp (type, "parent") == 0)
+ {
+ menu_layout_node_merge_file_set_type (parser->stack_top,
+ MENU_MERGE_FILE_TYPE_PARENT);
+ }
+ }
+ else if (ELEMENT_IS ("DefaultLayout"))
+ {
+ const char *show_empty;
+ const char *inline_menus;
+ const char *inline_limit;
+ const char *inline_header;
+ const char *inline_alias;
+
+ push_node (parser, MENU_LAYOUT_NODE_DEFAULT_LAYOUT);
+
+ locate_attributes (context, element_name,
+ attribute_names, attribute_values,
+ error,
+ "show_empty", &show_empty,
+ "inline", &inline_menus,
+ "inline_limit", &inline_limit,
+ "inline_header", &inline_header,
+ "inline_alias", &inline_alias,
+ NULL);
+
+ menu_layout_node_default_layout_set_values (parser->stack_top,
+ show_empty,
+ inline_menus,
+ inline_limit,
+ inline_header,
+ inline_alias);
+ }
+ else
+ {
+ if (!check_no_attributes (context, element_name,
+ attribute_names, attribute_values,
+ error))
+ return;
+
+ if (ELEMENT_IS ("AppDir"))
+ {
+ push_node (parser, MENU_LAYOUT_NODE_APP_DIR);
+ }
+ else if (ELEMENT_IS ("DefaultAppDirs"))
+ {
+ push_node (parser, MENU_LAYOUT_NODE_DEFAULT_APP_DIRS);
+ }
+ else if (ELEMENT_IS ("DirectoryDir"))
+ {
+ push_node (parser, MENU_LAYOUT_NODE_DIRECTORY_DIR);
+ }
+ else if (ELEMENT_IS ("DefaultDirectoryDirs"))
+ {
+ push_node (parser, MENU_LAYOUT_NODE_DEFAULT_DIRECTORY_DIRS);
+ }
+ else if (ELEMENT_IS ("DefaultMergeDirs"))
+ {
+ push_node (parser, MENU_LAYOUT_NODE_DEFAULT_MERGE_DIRS);
+ }
+ else if (ELEMENT_IS ("Name"))
+ {
+ if (has_child_of_type (parser->stack_top, MENU_LAYOUT_NODE_NAME))
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ "Multiple <Name> elements in a <Menu> element is not allowed\n");
+ return;
+ }
+
+ push_node (parser, MENU_LAYOUT_NODE_NAME);
+ }
+ else if (ELEMENT_IS ("Directory"))
+ {
+ push_node (parser, MENU_LAYOUT_NODE_DIRECTORY);
+ }
+ else if (ELEMENT_IS ("OnlyUnallocated"))
+ {
+ push_node (parser, MENU_LAYOUT_NODE_ONLY_UNALLOCATED);
+ }
+ else if (ELEMENT_IS ("NotOnlyUnallocated"))
+ {
+ push_node (parser, MENU_LAYOUT_NODE_NOT_ONLY_UNALLOCATED);
+ }
+ else if (ELEMENT_IS ("Include"))
+ {
+ push_node (parser, MENU_LAYOUT_NODE_INCLUDE);
+ }
+ else if (ELEMENT_IS ("Exclude"))
+ {
+ push_node (parser, MENU_LAYOUT_NODE_EXCLUDE);
+ }
+ else if (ELEMENT_IS ("MergeDir"))
+ {
+ push_node (parser, MENU_LAYOUT_NODE_MERGE_DIR);
+ }
+ else if (ELEMENT_IS ("KDELegacyDirs"))
+ {
+ push_node (parser, MENU_LAYOUT_NODE_KDE_LEGACY_DIRS);
+ }
+ else if (ELEMENT_IS ("Move"))
+ {
+ push_node (parser, MENU_LAYOUT_NODE_MOVE);
+ }
+ else if (ELEMENT_IS ("Deleted"))
+ {
+ push_node (parser, MENU_LAYOUT_NODE_DELETED);
+
+ }
+ else if (ELEMENT_IS ("NotDeleted"))
+ {
+ push_node (parser, MENU_LAYOUT_NODE_NOT_DELETED);
+ }
+ else if (ELEMENT_IS ("Layout"))
+ {
+ push_node (parser, MENU_LAYOUT_NODE_LAYOUT);
+ }
+ else
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+ "Element <%s> may not appear below <%s>\n",
+ element_name, "Menu");
+ }
+ }
+}
+
+static void
+start_matching_rule_element (MenuParser *parser,
+ GMarkupParseContext *context,
+ const char *element_name,
+ const char **attribute_names,
+ const char **attribute_values,
+ GError **error)
+{
+ if (!check_no_attributes (context, element_name,
+ attribute_names, attribute_values,
+ error))
+ return;
+
+
+ if (ELEMENT_IS ("Filename"))
+ {
+ push_node (parser, MENU_LAYOUT_NODE_FILENAME);
+ }
+ else if (ELEMENT_IS ("Category"))
+ {
+ push_node (parser, MENU_LAYOUT_NODE_CATEGORY);
+ }
+ else if (ELEMENT_IS ("All"))
+ {
+ push_node (parser, MENU_LAYOUT_NODE_ALL);
+ }
+ else if (ELEMENT_IS ("And"))
+ {
+ push_node (parser, MENU_LAYOUT_NODE_AND);
+ }
+ else if (ELEMENT_IS ("Or"))
+ {
+ push_node (parser, MENU_LAYOUT_NODE_OR);
+ }
+ else if (ELEMENT_IS ("Not"))
+ {
+ push_node (parser, MENU_LAYOUT_NODE_NOT);
+ }
+ else
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+ "Element <%s> may not appear in this context\n",
+ element_name);
+ }
+}
+
+static void
+start_move_child_element (MenuParser *parser,
+ GMarkupParseContext *context,
+ const char *element_name,
+ const char **attribute_names,
+ const char **attribute_values,
+ GError **error)
+{
+ if (!check_no_attributes (context, element_name,
+ attribute_names, attribute_values,
+ error))
+ return;
+
+ if (ELEMENT_IS ("Old"))
+ {
+ push_node (parser, MENU_LAYOUT_NODE_OLD);
+ }
+ else if (ELEMENT_IS ("New"))
+ {
+ push_node (parser, MENU_LAYOUT_NODE_NEW);
+ }
+ else
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+ "Element <%s> may not appear below <%s>\n",
+ element_name, "Move");
+ }
+}
+
+static void
+start_layout_child_element (MenuParser *parser,
+ GMarkupParseContext *context,
+ const char *element_name,
+ const char **attribute_names,
+ const char **attribute_values,
+ GError **error)
+{
+ if (ELEMENT_IS ("Menuname"))
+ {
+ const char *show_empty;
+ const char *inline_menus;
+ const char *inline_limit;
+ const char *inline_header;
+ const char *inline_alias;
+
+ push_node (parser, MENU_LAYOUT_NODE_MENUNAME);
+
+ locate_attributes (context, element_name,
+ attribute_names, attribute_values,
+ error,
+ "show_empty", &show_empty,
+ "inline", &inline_menus,
+ "inline_limit", &inline_limit,
+ "inline_header", &inline_header,
+ "inline_alias", &inline_alias,
+ NULL);
+
+ menu_layout_node_menuname_set_values (parser->stack_top,
+ show_empty,
+ inline_menus,
+ inline_limit,
+ inline_header,
+ inline_alias);
+ }
+ else if (ELEMENT_IS ("Merge"))
+ {
+ const char *type;
+
+ push_node (parser, MENU_LAYOUT_NODE_MERGE);
+
+ locate_attributes (context, element_name,
+ attribute_names, attribute_values,
+ error,
+ "type", &type,
+ NULL);
+
+ menu_layout_node_merge_set_type (parser->stack_top, type);
+ }
+ else
+ {
+ if (!check_no_attributes (context, element_name,
+ attribute_names, attribute_values,
+ error))
+ return;
+
+ if (ELEMENT_IS ("Filename"))
+ {
+ push_node (parser, MENU_LAYOUT_NODE_FILENAME);
+ }
+ else if (ELEMENT_IS ("Separator"))
+ {
+ push_node (parser, MENU_LAYOUT_NODE_SEPARATOR);
+ }
+ else
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+ "Element <%s> may not appear below <%s>\n",
+ element_name, "Move");
+ }
+ }
+}
+
+static void
+start_element_handler (GMarkupParseContext *context,
+ const char *element_name,
+ const char **attribute_names,
+ const char **attribute_values,
+ gpointer user_data,
+ GError **error)
+{
+ MenuParser *parser = user_data;
+
+ if (ELEMENT_IS ("Menu"))
+ {
+ if (parser->stack_top == parser->root &&
+ has_child_of_type (parser->root, MENU_LAYOUT_NODE_MENU))
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ "Multiple root elements in menu file, only one toplevel <Menu> is allowed\n");
+ return;
+ }
+
+ start_menu_element (parser, context, element_name,
+ attribute_names, attribute_values,
+ error);
+ }
+ else if (parser->stack_top == parser->root)
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ "Root element in a menu file must be <Menu>, not <%s>\n",
+ element_name);
+ }
+ else if (parser->stack_top->type == MENU_LAYOUT_NODE_MENU)
+ {
+ start_menu_child_element (parser, context, element_name,
+ attribute_names, attribute_values,
+ error);
+ }
+ else if (parser->stack_top->type == MENU_LAYOUT_NODE_INCLUDE ||
+ parser->stack_top->type == MENU_LAYOUT_NODE_EXCLUDE ||
+ parser->stack_top->type == MENU_LAYOUT_NODE_AND ||
+ parser->stack_top->type == MENU_LAYOUT_NODE_OR ||
+ parser->stack_top->type == MENU_LAYOUT_NODE_NOT)
+ {
+ start_matching_rule_element (parser, context, element_name,
+ attribute_names, attribute_values,
+ error);
+ }
+ else if (parser->stack_top->type == MENU_LAYOUT_NODE_MOVE)
+ {
+ start_move_child_element (parser, context, element_name,
+ attribute_names, attribute_values,
+ error);
+ }
+ else if (parser->stack_top->type == MENU_LAYOUT_NODE_LAYOUT ||
+ parser->stack_top->type == MENU_LAYOUT_NODE_DEFAULT_LAYOUT)
+ {
+ start_layout_child_element (parser, context, element_name,
+ attribute_names, attribute_values,
+ error);
+ }
+ else
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+ "Element <%s> may not appear in this context\n",
+ element_name);
+ }
+
+ add_context_to_error (error, context);
+}
+
+/* we want to make sure that the <Layout> or <DefaultLayout> is either empty,
+ * or contain one <Merge> of type "all", or contain one <Merge> of type "menus"
+ * and one <Merge> of type "files". If this is not the case, we try to clean up
+ * things:
+ * + if there is at least one <Merge> of type "all", then we only keep the
+ * last <Merge> of type "all" and remove all others <Merge>
+ * + if there is no <Merge> with type "all", we keep only the last <Merge> of
+ * type "menus" and the last <Merge> of type "files". If there's no <Merge>
+ * of type "menus" we append one, and then if there's no <Merge> of type
+ * "files", we append one. (So menus are before files)
+ */
+static gboolean
+fixup_layout_node (GMarkupParseContext *context,
+ MenuParser *parser,
+ MenuLayoutNode *node,
+ GError **error)
+{
+ MenuLayoutNode *child;
+ MenuLayoutNode *last_all;
+ MenuLayoutNode *last_menus;
+ MenuLayoutNode *last_files;
+ int n_all;
+ int n_menus;
+ int n_files;
+
+ if (!node->children)
+ {
+ return TRUE;
+ }
+
+ last_all = NULL;
+ last_menus = NULL;
+ last_files = NULL;
+ n_all = 0;
+ n_menus = 0;
+ n_files = 0;
+
+ child = node->children;
+ while (child != NULL)
+ {
+ switch (child->type)
+ {
+ case MENU_LAYOUT_NODE_MERGE:
+ switch (menu_layout_node_merge_get_type (child))
+ {
+ case MENU_LAYOUT_MERGE_NONE:
+ break;
+
+ case MENU_LAYOUT_MERGE_MENUS:
+ last_menus = child;
+ n_menus++;
+ break;
+
+ case MENU_LAYOUT_MERGE_FILES:
+ last_files = child;
+ n_files++;
+ break;
+
+ case MENU_LAYOUT_MERGE_ALL:
+ last_all = child;
+ n_all++;
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ child = node_next (child);
+ }
+
+ if ((n_all == 1 && n_menus == 0 && n_files == 0) ||
+ (n_all == 0 && n_menus == 1 && n_files == 1))
+ {
+ return TRUE;
+ }
+ else if (n_all > 1 || n_menus > 1 || n_files > 1 ||
+ (n_all == 1 && (n_menus != 0 || n_files != 0)))
+ {
+ child = node->children;
+ while (child != NULL)
+ {
+ MenuLayoutNode *next;
+
+ next = node_next (child);
+
+ switch (child->type)
+ {
+ case MENU_LAYOUT_NODE_MERGE:
+ switch (menu_layout_node_merge_get_type (child))
+ {
+ case MENU_LAYOUT_MERGE_NONE:
+ break;
+
+ case MENU_LAYOUT_MERGE_MENUS:
+ if (n_all || last_menus != child)
+ {
+ menu_verbose ("removing duplicated merge menus element\n");
+ menu_layout_node_unlink (child);
+ }
+ break;
+
+ case MENU_LAYOUT_MERGE_FILES:
+ if (n_all || last_files != child)
+ {
+ menu_verbose ("removing duplicated merge files element\n");
+ menu_layout_node_unlink (child);
+ }
+ break;
+
+ case MENU_LAYOUT_MERGE_ALL:
+ if (last_all != child)
+ {
+ menu_verbose ("removing duplicated merge all element\n");
+ menu_layout_node_unlink (child);
+ }
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ child = next;
+ }
+ }
+
+ if (n_all == 0 && n_menus == 0)
+ {
+ last_menus = menu_layout_node_new (MENU_LAYOUT_NODE_MERGE);
+ menu_layout_node_merge_set_type (last_menus, "menus");
+ menu_verbose ("appending missing merge menus element\n");
+ menu_layout_node_append_child (node, last_menus);
+ }
+
+ if (n_all == 0 && n_files == 0)
+ {
+ last_files = menu_layout_node_new (MENU_LAYOUT_NODE_MERGE);
+ menu_layout_node_merge_set_type (last_files, "files");
+ menu_verbose ("appending missing merge files element\n");
+ menu_layout_node_append_child (node, last_files);
+ }
+
+ return TRUE;
+}
+
+/* we want to a) check that we have old-new pairs and b) canonicalize
+ * such that each <Move> has exactly one old-new pair
+ */
+static gboolean
+fixup_move_node (GMarkupParseContext *context,
+ MenuParser *parser,
+ MenuLayoutNode *node,
+ GError **error)
+{
+ MenuLayoutNode *child;
+ int n_old;
+ int n_new;
+
+ n_old = 0;
+ n_new = 0;
+
+ child = node->children;
+ while (child != NULL)
+ {
+ switch (child->type)
+ {
+ case MENU_LAYOUT_NODE_OLD:
+ if (n_new != n_old)
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ "<Old>/<New> elements not paired properly\n");
+ return FALSE;
+ }
+
+ n_old += 1;
+
+ break;
+
+ case MENU_LAYOUT_NODE_NEW:
+ n_new += 1;
+
+ if (n_new != n_old)
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ "<Old>/<New> elements not paired properly\n");
+ return FALSE;
+ }
+
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ child = node_next (child);
+ }
+
+ if (n_new == 0 || n_old == 0)
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ "<Old>/<New> elements missing under <Move>\n");
+ return FALSE;
+ }
+
+ g_assert (n_new == n_old);
+ g_assert ((n_new + n_old) % 2 == 0);
+
+ if (n_new > 1)
+ {
+ MenuLayoutNode *prev;
+ MenuLayoutNode *append_after;
+
+ /* Need to split the <Move> into multiple <Move> */
+
+ n_old = 0;
+ n_new = 0;
+ prev = NULL;
+ append_after = node;
+
+ child = node->children;
+ while (child != NULL)
+ {
+ MenuLayoutNode *next;
+
+ next = node_next (child);
+
+ switch (child->type)
+ {
+ case MENU_LAYOUT_NODE_OLD:
+ n_old += 1;
+ break;
+
+ case MENU_LAYOUT_NODE_NEW:
+ n_new += 1;
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ if (n_old == n_new &&
+ n_old > 1)
+ {
+ /* Move the just-completed pair */
+ MenuLayoutNode *new_move;
+
+ g_assert (prev != NULL);
+
+ new_move = menu_layout_node_new (MENU_LAYOUT_NODE_MOVE);
+ menu_verbose ("inserting new_move after append_after\n");
+ menu_layout_node_insert_after (append_after, new_move);
+ append_after = new_move;
+
+ menu_layout_node_steal (prev);
+ menu_layout_node_steal (child);
+
+ menu_verbose ("appending prev to new_move\n");
+ menu_layout_node_append_child (new_move, prev);
+ menu_verbose ("appending child to new_move\n");
+ menu_layout_node_append_child (new_move, child);
+
+ menu_verbose ("Created new move element old = %s new = %s\n",
+ menu_layout_node_move_get_old (new_move),
+ menu_layout_node_move_get_new (new_move));
+
+ menu_layout_node_unref (new_move);
+ menu_layout_node_unref (prev);
+ menu_layout_node_unref (child);
+
+ prev = NULL;
+ }
+ else
+ {
+ prev = child;
+ }
+
+ prev = child;
+ child = next;
+ }
+ }
+
+ return TRUE;
+}
+
+static void
+end_element_handler (GMarkupParseContext *context,
+ const char *element_name,
+ gpointer user_data,
+ GError **error)
+{
+ MenuParser *parser = user_data;
+
+ g_assert (parser->stack_top != NULL);
+
+ switch (parser->stack_top->type)
+ {
+ case MENU_LAYOUT_NODE_APP_DIR:
+ case MENU_LAYOUT_NODE_DIRECTORY_DIR:
+ case MENU_LAYOUT_NODE_NAME:
+ case MENU_LAYOUT_NODE_DIRECTORY:
+ case MENU_LAYOUT_NODE_FILENAME:
+ case MENU_LAYOUT_NODE_CATEGORY:
+ case MENU_LAYOUT_NODE_MERGE_DIR:
+ case MENU_LAYOUT_NODE_LEGACY_DIR:
+ case MENU_LAYOUT_NODE_OLD:
+ case MENU_LAYOUT_NODE_NEW:
+ case MENU_LAYOUT_NODE_MENUNAME:
+ if (menu_layout_node_get_content (parser->stack_top) == NULL)
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
+ "Element <%s> is required to contain text and was empty\n",
+ element_name);
+ goto out;
+ }
+ break;
+
+ case MENU_LAYOUT_NODE_MENU:
+ if (!has_child_of_type (parser->stack_top, MENU_LAYOUT_NODE_NAME))
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ "<Menu> elements are required to contain a <Name> element\n");
+ goto out;
+ }
+ break;
+
+ case MENU_LAYOUT_NODE_ROOT:
+ case MENU_LAYOUT_NODE_PASSTHROUGH:
+ case MENU_LAYOUT_NODE_DEFAULT_APP_DIRS:
+ case MENU_LAYOUT_NODE_DEFAULT_DIRECTORY_DIRS:
+ case MENU_LAYOUT_NODE_DEFAULT_MERGE_DIRS:
+ case MENU_LAYOUT_NODE_ONLY_UNALLOCATED:
+ case MENU_LAYOUT_NODE_NOT_ONLY_UNALLOCATED:
+ case MENU_LAYOUT_NODE_INCLUDE:
+ case MENU_LAYOUT_NODE_EXCLUDE:
+ case MENU_LAYOUT_NODE_ALL:
+ case MENU_LAYOUT_NODE_AND:
+ case MENU_LAYOUT_NODE_OR:
+ case MENU_LAYOUT_NODE_NOT:
+ case MENU_LAYOUT_NODE_KDE_LEGACY_DIRS:
+ case MENU_LAYOUT_NODE_DELETED:
+ case MENU_LAYOUT_NODE_NOT_DELETED:
+ case MENU_LAYOUT_NODE_SEPARATOR:
+ case MENU_LAYOUT_NODE_MERGE:
+ case MENU_LAYOUT_NODE_MERGE_FILE:
+ break;
+
+ case MENU_LAYOUT_NODE_LAYOUT:
+ case MENU_LAYOUT_NODE_DEFAULT_LAYOUT:
+ if (!fixup_layout_node (context, parser, parser->stack_top, error))
+ goto out;
+ break;
+
+ case MENU_LAYOUT_NODE_MOVE:
+ if (!fixup_move_node (context, parser, parser->stack_top, error))
+ goto out;
+ break;
+ }
+
+ out:
+ parser->stack_top = parser->stack_top->parent;
+}
+
+static gboolean
+all_whitespace (const char *text,
+ int text_len)
+{
+ const char *p;
+ const char *end;
+
+ p = text;
+ end = text + text_len;
+
+ while (p != end)
+ {
+ if (!g_ascii_isspace (*p))
+ return FALSE;
+
+ p = g_utf8_next_char (p);
+ }
+
+ return TRUE;
+}
+
+static void
+text_handler (GMarkupParseContext *context,
+ const char *text,
+ gsize text_len,
+ gpointer user_data,
+ GError **error)
+{
+ MenuParser *parser = user_data;
+
+ switch (parser->stack_top->type)
+ {
+ case MENU_LAYOUT_NODE_APP_DIR:
+ case MENU_LAYOUT_NODE_DIRECTORY_DIR:
+ case MENU_LAYOUT_NODE_NAME:
+ case MENU_LAYOUT_NODE_DIRECTORY:
+ case MENU_LAYOUT_NODE_FILENAME:
+ case MENU_LAYOUT_NODE_CATEGORY:
+ case MENU_LAYOUT_NODE_MERGE_FILE:
+ case MENU_LAYOUT_NODE_MERGE_DIR:
+ case MENU_LAYOUT_NODE_LEGACY_DIR:
+ case MENU_LAYOUT_NODE_OLD:
+ case MENU_LAYOUT_NODE_NEW:
+ case MENU_LAYOUT_NODE_MENUNAME:
+ g_assert (menu_layout_node_get_content (parser->stack_top) == NULL);
+
+ menu_layout_node_set_content (parser->stack_top, text);
+ break;
+
+ case MENU_LAYOUT_NODE_ROOT:
+ case MENU_LAYOUT_NODE_PASSTHROUGH:
+ case MENU_LAYOUT_NODE_MENU:
+ case MENU_LAYOUT_NODE_DEFAULT_APP_DIRS:
+ case MENU_LAYOUT_NODE_DEFAULT_DIRECTORY_DIRS:
+ case MENU_LAYOUT_NODE_DEFAULT_MERGE_DIRS:
+ case MENU_LAYOUT_NODE_ONLY_UNALLOCATED:
+ case MENU_LAYOUT_NODE_NOT_ONLY_UNALLOCATED:
+ case MENU_LAYOUT_NODE_INCLUDE:
+ case MENU_LAYOUT_NODE_EXCLUDE:
+ case MENU_LAYOUT_NODE_ALL:
+ case MENU_LAYOUT_NODE_AND:
+ case MENU_LAYOUT_NODE_OR:
+ case MENU_LAYOUT_NODE_NOT:
+ case MENU_LAYOUT_NODE_KDE_LEGACY_DIRS:
+ case MENU_LAYOUT_NODE_MOVE:
+ case MENU_LAYOUT_NODE_DELETED:
+ case MENU_LAYOUT_NODE_NOT_DELETED:
+ case MENU_LAYOUT_NODE_LAYOUT:
+ case MENU_LAYOUT_NODE_DEFAULT_LAYOUT:
+ case MENU_LAYOUT_NODE_SEPARATOR:
+ case MENU_LAYOUT_NODE_MERGE:
+ if (!all_whitespace (text, text_len))
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ "No text is allowed inside element <%s>",
+ g_markup_parse_context_get_element (context));
+ }
+ break;
+ }
+
+ add_context_to_error (error, context);
+}
+
+static void
+passthrough_handler (GMarkupParseContext *context,
+ const char *passthrough_text,
+ gsize text_len,
+ gpointer user_data,
+ GError **error)
+{
+ MenuParser *parser = user_data;
+ MenuLayoutNode *node;
+
+ /* don't push passthrough on the stack, it's not an element */
+
+ node = menu_layout_node_new (MENU_LAYOUT_NODE_PASSTHROUGH);
+ menu_layout_node_set_content (node, passthrough_text);
+
+ menu_layout_node_append_child (parser->stack_top, node);
+ menu_layout_node_unref (node);
+
+ add_context_to_error (error, context);
+}
+
+static void
+menu_parser_init (MenuParser *parser)
+{
+ parser->root = menu_layout_node_new (MENU_LAYOUT_NODE_ROOT);
+ parser->stack_top = parser->root;
+}
+
+static void
+menu_parser_free (MenuParser *parser)
+{
+ if (parser->root)
+ menu_layout_node_unref (parser->root);
+}
+
+MenuLayoutNode *
+menu_layout_load (const char *filename,
+ const char *non_prefixed_basename,
+ GError **err)
+{
+ GMarkupParseContext *context;
+ MenuLayoutNodeRoot *root;
+ MenuLayoutNode *retval;
+ MenuParser parser;
+ GError *error;
+ GString *str;
+ char *text;
+ char *s;
+ gsize length;
+
+ text = NULL;
+ length = 0;
+ retval = NULL;
+ context = NULL;
+
+ menu_verbose ("Loading \"%s\" from disk\n", filename);
+
+ if (!g_file_get_contents (filename,
+ &text,
+ &length,
+ err))
+ {
+ menu_verbose ("Failed to load \"%s\"\n",
+ filename);
+ return NULL;
+ }
+
+ g_assert (text != NULL);
+
+ menu_parser_init (&parser);
+
+ root = (MenuLayoutNodeRoot *) parser.root;
+
+ root->basedir = g_path_get_dirname (filename);
+ menu_verbose ("Set basedir \"%s\"\n", root->basedir);
+
+ if (non_prefixed_basename)
+ s = g_strdup (non_prefixed_basename);
+ else
+ s = g_path_get_basename (filename);
+ str = g_string_new (s);
+ if (g_str_has_suffix (str->str, ".menu"))
+ g_string_truncate (str, str->len - strlen (".menu"));
+
+ root->name = str->str;
+ menu_verbose ("Set menu name \"%s\"\n", root->name);
+
+ g_string_free (str, FALSE);
+ g_free (s);
+
+ context = g_markup_parse_context_new (&menu_funcs, 0, &parser, NULL);
+
+ error = NULL;
+ if (!g_markup_parse_context_parse (context,
+ text,
+ length,
+ &error))
+ goto out;
+
+ error = NULL;
+ g_markup_parse_context_end_parse (context, &error);
+
+ out:
+ if (context)
+ g_markup_parse_context_free (context);
+ g_free (text);
+
+ if (error)
+ {
+ menu_verbose ("Error \"%s\" loading \"%s\"\n",
+ error->message, filename);
+ g_propagate_error (err, error);
+ }
+ else if (has_child_of_type (parser.root, MENU_LAYOUT_NODE_MENU))
+ {
+ menu_verbose ("File loaded OK\n");
+ retval = parser.root;
+ parser.root = NULL;
+ }
+ else
+ {
+ menu_verbose ("Did not have a root element in file\n");
+ g_set_error (err, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ "Menu file %s did not contain a root <Menu> element",
+ filename);
+ }
+
+ menu_parser_free (&parser);
+
+ return retval;
+}