I've been hacking on libxfce4menu in a local branch for the last couple of days and I think I'm finally happy with the API changes I have done. The version that will be shipped with Xfce 4.6 has a clean API for reading from menus but unfortunately it lacks support for menu merging and is not really usable for writing a menu editor.
 So there is a lot to improve for Xfce 4.8.

libxfce4menu uses a GObject class called XfceMenu as a representation of the entire menu tree. The downside of this approach is that some tree operations are hard to implement if possible at all. In a DOM structure child elements are ordered. In XfceMenu, they are not. In fact, the concept of a child element in XfceMenu is problematic. In a DOM all child elements are treated equal. XfceMenu on the opposite treats Menu elements different than e.g. AppDir elements. It throws them into different lists. And that's why merging is hard to implement with the current API. There is no DOM representation of the tree and thus, XfceMenu does not know whether an AppDir element came before a certain DirectoryDir element for instance. But when doing merges this matters. Actually, in most cases this won't be a problem, but theoretically it is. If you don't care about the order, you break the specification.

So, libxfce4menu in Xfce 4.8 is going to build a DOM representation for the menu tree to make merging and other things easier to implement.

But there still is the goal to improve the API with regards to menu editing support. To work properly, menu editors need to know information about the menu tree after parsing the root .menu file - before any modifications. This is something the current implementation of libxfce4menu hides completely.

So, here is what I came up with for 4.8. First of all, you have several classes:

  1. GNode is used for the DOM representation.
  2. Every node has a data object which is an instance of XfceMenuNode, except for <Menu> nodes.
  3. To parse a menu there is XfceMenuParser which creates a GNode DOM representation for a .menu file. It doesn't do merging, so you'll only get the DOM for the XML of the .menu file itself, not of other .menu files specified using <MergeFile> elements.
  4. To load and merge the rest, there is XfceMenuMerger. It takes the DOM representation generated by the parser and basically does everything that explained in this part of the specification.
  5. XfceMenu, XfceMenuLayout and XfceMenuItem will still be around. They take the merged DOM representation from XfceMenuMerger, load all the desktop entries and wrap the entire information about the resulting menu tree with a nice and easy-to-use API. There will also still be a class for monitoring and stuff like that.
  6. And then there is a nother class: XfceMenuEditor which takes the DOM representation from XfceMenuParser and provides functions for operations required by graphical menu editors. It will be able to add <Move> elements if the user moves menus around, it can add <Deleted> elements if the user decides to hide a menu and so forth. It will also support writing the tree to a .menu file, maybe using another class called XfceMenuWriter.
  7. There will be an interface called XfceMenuTreeProvider which is implemented by XfceMenuParser, XfceMenuMerger and XfceMenuEditor.

The only thing that is not covered is how desktop entries are edited. But they are not really part of the menu tree anyway. Here are two uses cases for the usage of the API presented above.

Reading Menus (For Hardliners)

Someone wants to read and build the menu from a file called applications.menu.
XfceMenuParser *parser;
XfceMenuMerger *merger;
XfceMenu *menu;
GError *error = NULL;
GFile *file;

file = g_file_new_for_path ("applications.menu");
parser = xfce_menu_parser_new (file, &error);
g_object_unref (file);

/* The optional second argument is a GCancellable */
if (!xfce_menu_parser_run (parser, NULL, &error))
{
g_error (error->message);

g_error_free (error);
g_object_unref (parser);
return;
}

merger = xfce_menu_merger_new (XFCE_MENU_TREE_PROVIDER (parser));
g_object_unref (parser);

if (!xfce_menu_merger_run (merger, NULL, &error))
{
g_error (error->message);

g_error_free (error);
g_object_unref (merger);
return;
}

menu = xfce_menu_new_from_tree_provider (XFCE_MENU_TREE_PROVIDER (merger));
g_object_unref (merger);

/* Now do something with the menu */

g_object_unref (menu);

Reading Menus (The Easy Way)

Of course there will be a much easier-to-use function for creating an XfceMenu, like this one:

XfceMenu *xfce_menu_create_for_file (GFile   *file, 
GError **error);

Writing a Menu Editor

XfceMenuParser *parser;
XfceMenuEditor *editor;
XfceMenuWriter *writer;

/* I'll leave out the error handling this time */
parser = xfce_menu_parser_new (file);
xfce_menu_parser_run (parser, NULL, &error);

editor = xfce_menu_editor_new (XFCE_MENU_TREE_PROVIDER (parser));
g_object_unref (parser);

/* Use XfceMenuMerger and XfceMenu here to get an XfceMenu object for use in the GUI */

/* Assuming that the user moves the menu "A/B" to "C/D" using the GUI */
xfce_menu_editor_move (editor, "A/B", "C/D");

/* Assuming that the user decided to hide the menu "Accessories" */
xfce_menu_editor_set_deleted (editor, "Accessories", TRUE);

/* Save the menu to a file, assuming we have a GFile object to write to */
writer = xfce_menu_writer_new (XFCE_MENU_TREE_PROVIDER (editor));
xfce_menu_writer_write (writer, file, NULL, &error);
g_object_unref (writer);

Of course nothing of this is final. This is just what I came up with today. But I can imagine that this would work quit well.

So, what do you think?