1 /*
2  * Copyright (C) 2003, 2004 Red Hat, Inc.
3  * Copyright (C) 2012-2021 MATE Developers
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  */
20 
21 #include <config.h>
22 
23 #include "matemenu-tree.h"
24 
25 #include <gio/gio.h>
26 #include <string.h>
27 #include <errno.h>
28 #include <stdlib.h>
29 
30 #include "menu-layout.h"
31 #include "menu-monitor.h"
32 #include "menu-util.h"
33 
34 /* private */
35 typedef struct MateMenuTreeItem MateMenuTreeItem;
36 #define MATEMENU_TREE_ITEM(i)      ((MateMenuTreeItem *)(i))
37 #define MATEMENU_TREE_DIRECTORY(i) ((MateMenuTreeDirectory *)(i))
38 #define MATEMENU_TREE_ENTRY(i)     ((MateMenuTreeEntry *)(i))
39 #define MATEMENU_TREE_SEPARATOR(i) ((MateMenuTreeSeparator *)(i))
40 #define MATEMENU_TREE_HEADER(i)    ((MateMenuTreeHeader *)(i))
41 #define MATEMENU_TREE_ALIAS(i)     ((MateMenuTreeAlias *)(i))
42 
43 enum {
44   PROP_0,
45 
46   PROP_MENU_BASENAME,
47   PROP_MENU_PATH,
48   PROP_FLAGS
49 };
50 
51 typedef enum
52 {
53   OBJECT_DRAWER,
54   OBJECT_MENU,
55   OBJECT_LAUNCHER,
56   OBJECT_APPLET,
57   OBJECT_ACTION,
58   OBJECT_MENU_BAR,
59   OBJECT_SEPARATOR,
60 } ObjectType;
61 
62 /* Signals */
63 enum
64 {
65   CHANGED,
66   LAST_SIGNAL
67 };
68 
69 static guint matemenu_tree_signals [LAST_SIGNAL] = { 0 };
70 
71 struct _MateMenuTree
72 {
73   GObject       parent_instance;
74 
75   char *basename;
76   char *non_prefixed_basename;
77   char *path;
78   char *canonical_path;
79   GPtrArray *collection_applet;
80   MateMenuTreeFlags flags;
81 
82   GSList *menu_file_monitors;
83 
84   MenuLayoutNode *layout;
85   MateMenuTreeDirectory *root;
86   GHashTable *entries_by_id;
87 
88   guint canonical : 1;
89   guint loaded    : 1;
90   GSettings  *settings;
91 };
92 
93 G_DEFINE_TYPE (MateMenuTree, matemenu_tree, G_TYPE_OBJECT)
94 
95 struct MateMenuTreeItem
96 {
97   volatile gint refcount;
98 
99   MateMenuTreeItemType type;
100 
101   MateMenuTreeDirectory *parent;
102   MateMenuTree *tree;
103 };
104 
105 struct MateMenuTreeIter
106 {
107   volatile gint refcount;
108 
109   MateMenuTreeItem *item;
110   GSList        *contents;
111   GSList        *contents_iter;
112 };
113 
114 struct MateMenuTreeDirectory
115 {
116   MateMenuTreeItem  item;
117 
118   DesktopEntry     *directory_entry;
119   char             *name;
120 
121   GSList           *entries;
122   GSList           *subdirs;
123 
124   MenuLayoutValues  default_layout_values;
125   GSList           *default_layout_info;
126   GSList           *layout_info;
127   GSList           *contents;
128 
129   guint             only_unallocated         : 1;
130   guint             is_nodisplay             : 1;
131   guint             layout_pending_separator : 1;
132   guint             preprocessed             : 1;
133 
134   /* 16 bits should be more than enough; G_MAXUINT16 means no inline header */
135   guint             will_inline_header       : 16;
136 };
137 
138 struct MateMenuTreeEntry
139 {
140   MateMenuTreeItem  item;
141 
142   DesktopEntry     *desktop_entry;
143   char             *desktop_file_id;
144 
145   guint             is_excluded    : 1;
146   guint             is_unallocated : 1;
147 };
148 
149 struct MateMenuTreeSeparator
150 {
151   MateMenuTreeItem item;
152 };
153 
154 struct MateMenuTreeHeader
155 {
156   MateMenuTreeItem item;
157 
158   MateMenuTreeDirectory *directory;
159 };
160 
161 struct MateMenuTreeAlias
162 {
163   MateMenuTreeItem item;
164 
165   MateMenuTreeDirectory *directory;
166   MateMenuTreeItem      *aliased_item;
167 };
168 
169 static gboolean  matemenu_tree_load_layout          (MateMenuTree       *tree,
170                                                   GError         **error);
171 static void      matemenu_tree_force_reload         (MateMenuTree       *tree);
172 static gboolean  matemenu_tree_build_from_layout    (MateMenuTree       *tree,
173                                                   GError         **error);
174 static void      matemenu_tree_force_rebuild        (MateMenuTree       *tree);
175 static void      matemenu_tree_resolve_files        (MateMenuTree       *tree,
176 						  GHashTable      *loaded_menu_files,
177 						  MenuLayoutNode  *layout);
178 static void      matemenu_tree_force_recanonicalize (MateMenuTree       *tree);
179 static void      matemenu_tree_invoke_monitors      (MateMenuTree       *tree);
180 
181 static void matemenu_tree_item_unref_and_unset_parent (gpointer itemp);
182 
183 static void collection_applet_changed (GSettings    *settings,
184                                        gchar        *key,
185                                        MateMenuTree *self);
186 
187 typedef enum
188 {
189   MENU_FILE_MONITOR_INVALID = 0,
190   MENU_FILE_MONITOR_FILE,
191   MENU_FILE_MONITOR_NONEXISTENT_FILE,
192   MENU_FILE_MONITOR_DIRECTORY
193 } MenuFileMonitorType;
194 
195 typedef struct
196 {
197   MenuFileMonitorType  type;
198   MenuMonitor         *monitor;
199 } MenuFileMonitor;
200 
201 static void
handle_nonexistent_menu_file_changed(MenuMonitor * monitor,MenuMonitorEvent event,const char * path,MateMenuTree * tree)202 handle_nonexistent_menu_file_changed (MenuMonitor      *monitor,
203 				      MenuMonitorEvent  event,
204 				      const char       *path,
205 				      MateMenuTree        *tree)
206 {
207   if (event == MENU_MONITOR_EVENT_CHANGED ||
208       event == MENU_MONITOR_EVENT_CREATED)
209     {
210       menu_verbose ("\"%s\" %s, marking tree for recanonicalization\n",
211                     path,
212                     event == MENU_MONITOR_EVENT_CREATED ? "created" : "changed");
213 
214       matemenu_tree_force_recanonicalize (tree);
215       matemenu_tree_invoke_monitors (tree);
216     }
217 }
218 
219 static void
handle_menu_file_changed(MenuMonitor * monitor,MenuMonitorEvent event,const char * path,MateMenuTree * tree)220 handle_menu_file_changed (MenuMonitor      *monitor,
221 			  MenuMonitorEvent  event,
222 			  const char       *path,
223                           MateMenuTree        *tree)
224 {
225   menu_verbose ("\"%s\" %s, marking tree for recanicalization\n",
226 		path,
227 		event == MENU_MONITOR_EVENT_CREATED ? "created" :
228 		event == MENU_MONITOR_EVENT_CHANGED ? "changed" : "deleted");
229 
230   matemenu_tree_force_recanonicalize (tree);
231   matemenu_tree_invoke_monitors (tree);
232 }
233 
234 static void
handle_menu_file_directory_changed(MenuMonitor * monitor,MenuMonitorEvent event,const char * path,MateMenuTree * tree)235 handle_menu_file_directory_changed (MenuMonitor      *monitor,
236 				    MenuMonitorEvent  event,
237 				    const char       *path,
238 				    MateMenuTree        *tree)
239 {
240   if (!g_str_has_suffix (path, ".menu"))
241     return;
242 
243   menu_verbose ("\"%s\" %s, marking tree for recanicalization\n",
244 		path,
245 		event == MENU_MONITOR_EVENT_CREATED ? "created" :
246 		event == MENU_MONITOR_EVENT_CHANGED ? "changed" : "deleted");
247 
248   matemenu_tree_force_recanonicalize (tree);
249   matemenu_tree_invoke_monitors (tree);
250 }
251 
252 static void
matemenu_tree_add_menu_file_monitor(MateMenuTree * tree,const char * path,MenuFileMonitorType type)253 matemenu_tree_add_menu_file_monitor (MateMenuTree           *tree,
254 				  const char          *path,
255 				  MenuFileMonitorType  type)
256 {
257   MenuFileMonitor *monitor;
258 
259   monitor = g_slice_new0 (MenuFileMonitor);
260 
261   monitor->type = type;
262 
263   switch (type)
264     {
265     case MENU_FILE_MONITOR_FILE:
266       menu_verbose ("Adding a menu file monitor for \"%s\"\n", path);
267 
268       monitor->monitor = menu_get_file_monitor (path);
269       menu_monitor_add_notify (monitor->monitor,
270 			       (MenuMonitorNotifyFunc) handle_menu_file_changed,
271 			       tree);
272       break;
273 
274     case MENU_FILE_MONITOR_NONEXISTENT_FILE:
275       menu_verbose ("Adding a menu file monitor for non-existent \"%s\"\n", path);
276 
277       monitor->monitor = menu_get_file_monitor (path);
278       menu_monitor_add_notify (monitor->monitor,
279 			       (MenuMonitorNotifyFunc) handle_nonexistent_menu_file_changed,
280 			       tree);
281       break;
282 
283     case MENU_FILE_MONITOR_DIRECTORY:
284       menu_verbose ("Adding a menu directory monitor for \"%s\"\n", path);
285 
286       monitor->monitor = menu_get_directory_monitor (path);
287       menu_monitor_add_notify (monitor->monitor,
288 			       (MenuMonitorNotifyFunc) handle_menu_file_directory_changed,
289 			       tree);
290       break;
291 
292     default:
293       g_assert_not_reached ();
294       break;
295     }
296 
297   tree->menu_file_monitors = g_slist_prepend (tree->menu_file_monitors, monitor);
298 }
299 
300 static void
remove_menu_file_monitor(MenuFileMonitor * monitor,MateMenuTree * tree)301 remove_menu_file_monitor (MenuFileMonitor *monitor,
302 			  MateMenuTree       *tree)
303 {
304   switch (monitor->type)
305     {
306     case MENU_FILE_MONITOR_FILE:
307       menu_monitor_remove_notify (monitor->monitor,
308 				  (MenuMonitorNotifyFunc) handle_menu_file_changed,
309 				  tree);
310       break;
311 
312     case MENU_FILE_MONITOR_NONEXISTENT_FILE:
313       menu_monitor_remove_notify (monitor->monitor,
314 				  (MenuMonitorNotifyFunc) handle_nonexistent_menu_file_changed,
315 				  tree);
316       break;
317 
318     case MENU_FILE_MONITOR_DIRECTORY:
319       menu_monitor_remove_notify (monitor->monitor,
320 				  (MenuMonitorNotifyFunc) handle_menu_file_directory_changed,
321 				  tree);
322       break;
323 
324     default:
325       g_assert_not_reached ();
326       break;
327     }
328 
329   menu_monitor_unref (monitor->monitor);
330   monitor->monitor = NULL;
331 
332   monitor->type = MENU_FILE_MONITOR_INVALID;
333 
334   g_slice_free (MenuFileMonitor, monitor);
335 }
336 
337 static void
matemenu_tree_remove_menu_file_monitors(MateMenuTree * tree)338 matemenu_tree_remove_menu_file_monitors (MateMenuTree *tree)
339 {
340   menu_verbose ("Removing all menu file monitors\n");
341 
342   g_slist_foreach (tree->menu_file_monitors,
343                    (GFunc) remove_menu_file_monitor,
344                    tree);
345   g_slist_free (tree->menu_file_monitors);
346   tree->menu_file_monitors = NULL;
347 }
348 
349 static gboolean
canonicalize_path(MateMenuTree * tree,const char * path)350 canonicalize_path (MateMenuTree  *tree,
351                    const char *path)
352 {
353   tree->canonical_path = realpath (path, NULL);
354   if (tree->canonical_path)
355     {
356       tree->canonical = TRUE;
357       matemenu_tree_add_menu_file_monitor (tree,
358 					tree->canonical_path,
359 					MENU_FILE_MONITOR_FILE);
360     }
361   else
362     {
363       matemenu_tree_add_menu_file_monitor (tree,
364 					path,
365 					MENU_FILE_MONITOR_NONEXISTENT_FILE);
366     }
367 
368   return tree->canonical;
369 }
370 
371 static gboolean
canonicalize_basename_with_config_dir(MateMenuTree * tree,const char * basename,const char * config_dir)372 canonicalize_basename_with_config_dir (MateMenuTree   *tree,
373                                        const char *basename,
374                                        const char *config_dir)
375 {
376   gboolean  ret;
377   char     *path;
378 
379   path = g_build_filename (config_dir, "menus",  basename,  NULL);
380   ret = canonicalize_path (tree, path);
381   g_free (path);
382 
383   return ret;
384 }
385 
386 static void
canonicalize_basename(MateMenuTree * tree,const char * basename)387 canonicalize_basename (MateMenuTree  *tree,
388                        const char *basename)
389 {
390   if (!canonicalize_basename_with_config_dir (tree,
391                                               basename,
392                                               g_get_user_config_dir ()))
393     {
394       const char * const *system_config_dirs;
395       int                 i;
396 
397       system_config_dirs = g_get_system_config_dirs ();
398 
399       i = 0;
400       while (system_config_dirs[i] != NULL)
401         {
402           if (canonicalize_basename_with_config_dir (tree,
403                                                      basename,
404                                                      system_config_dirs[i]))
405             break;
406 
407           ++i;
408         }
409     }
410 }
411 
matemenu_tree_canonicalize_path(MateMenuTree * tree,GError ** error)412 static gboolean matemenu_tree_canonicalize_path(MateMenuTree* tree,
413                               GError   **error)
414 {
415   const char *menu_file = NULL;
416 
417   if (tree->canonical)
418     return TRUE;
419 
420 	g_assert(tree->canonical_path == NULL);
421 
422   matemenu_tree_remove_menu_file_monitors (tree);
423 
424   if (tree->path)
425     {
426       menu_file = tree->path;
427       canonicalize_path (tree, tree->path);
428     }
429   else
430     {
431       const gchar *xdg_menu_prefix;
432 
433       menu_file = tree->basename;
434       xdg_menu_prefix = g_getenv ("XDG_MENU_PREFIX");
435 
436       if (xdg_menu_prefix != NULL)
437         {
438           gchar *prefixed_basename;
439 
440           prefixed_basename = g_strdup_printf ("%sapplications.menu",
441                                                xdg_menu_prefix);
442 
443           /* Some gnome-menus using applications just use "applications.menu"
444            * as the basename and expect gnome-menus to prefix it. Others (e.g.
445            * Alacarte) explicitly use "${XDG_MENU_PREFIX}applications.menu" as
446            * the basename, because they want to save changes to the right files
447            * in ~. In both cases, we want to use "applications-merged" as the
448            * merge directory (as required by the fd.o menu spec), so we save
449            * the non-prefixed basename and use it later when calling
450            * menu_layout_load().
451            */
452           if (!g_strcmp0 (tree->basename, "mate-applications.menu") ||
453               !g_strcmp0 (tree->basename, prefixed_basename))
454             {
455               canonicalize_basename (tree, prefixed_basename);
456               g_free (tree->non_prefixed_basename);
457               tree->non_prefixed_basename = g_strdup ("mate-applications.menu");
458             }
459           g_free (prefixed_basename);
460         }
461 
462       if (!tree->canonical)
463         canonicalize_basename (tree, tree->basename);
464     }
465 
466   if (tree->canonical)
467     {
468       menu_verbose ("Successfully looked up menu_file for \"%s\": %s\n",
469                     menu_file, tree->canonical_path);
470       return TRUE;
471     }
472   else
473     {
474       g_set_error (error,
475                    G_IO_ERROR,
476                    G_IO_ERROR_FAILED,
477                    "Failed to look up menu_file for \"%s\"\n",
478                    menu_file);
479       return FALSE;
480     }
481 }
482 
483 static void
matemenu_tree_force_recanonicalize(MateMenuTree * tree)484 matemenu_tree_force_recanonicalize (MateMenuTree *tree)
485 {
486   matemenu_tree_remove_menu_file_monitors (tree);
487 
488   if (tree->canonical)
489     {
490       matemenu_tree_force_reload (tree);
491 
492       g_free (tree->canonical_path);
493       tree->canonical_path = NULL;
494 
495       tree->canonical = FALSE;
496     }
497 }
498 
499 /**
500  * matemenu_tree_new:
501  * @menu_basename: Basename of menu file
502  * @flags: Flags controlling menu content
503  *
504  * Returns: (transfer full): A new #MateMenuTree instance
505  */
506 MateMenuTree *
matemenu_tree_new(const char * menu_basename,MateMenuTreeFlags flags)507 matemenu_tree_new (const char     *menu_basename,
508                 MateMenuTreeFlags  flags)
509 {
510   g_return_val_if_fail (menu_basename != NULL, NULL);
511 
512   return g_object_new (MATEMENU_TYPE_TREE,
513                        "menu-basename", menu_basename,
514                        "flags", flags,
515                        NULL);
516 }
517 
518 /**
519  * matemenu_tree_new_fo_path:
520  * @menu_path: Path of menu file
521  * @flags: Flags controlling menu content
522  *
523  * Returns: (transfer full): A new #MateMenuTree instance
524  */
525 MateMenuTree *
matemenu_tree_new_for_path(const char * menu_path,MateMenuTreeFlags flags)526 matemenu_tree_new_for_path (const char     *menu_path,
527                          MateMenuTreeFlags  flags)
528 {
529   g_return_val_if_fail (menu_path != NULL, NULL);
530 
531   return g_object_new (MATEMENU_TYPE_TREE,
532                        "menu-path", menu_path,
533                        "flags", flags,
534                        NULL);
535 }
536 
537 static GObject *
matemenu_tree_constructor(GType type,guint n_construct_properties,GObjectConstructParam * construct_properties)538 matemenu_tree_constructor (GType                  type,
539                         guint                  n_construct_properties,
540                         GObjectConstructParam *construct_properties)
541 {
542 	GObject   *obj;
543 	MateMenuTree *self;
544 
545 	obj = G_OBJECT_CLASS (matemenu_tree_parent_class)->constructor (type,
546                                                                      n_construct_properties,
547                                                                      construct_properties);
548 
549         /* If MateMenuTree:menu-path is set, then we should make sure that
550          * MateMenuTree:menu-basename is unset (especially as it has a default
551          * value). This has to be done here, in the constructor, since the
552          * properties are construct-only. */
553 
554 	self = MATEMENU_TREE (obj);
555 
556         if (self->path != NULL)
557           g_object_set (self, "menu-basename", NULL, NULL);
558 
559 	return obj;
560 }
561 
562 static void
matemenu_tree_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)563 matemenu_tree_set_property (GObject         *object,
564                          guint            prop_id,
565                          const GValue    *value,
566                          GParamSpec      *pspec)
567 {
568   MateMenuTree *self = MATEMENU_TREE (object);
569 
570   switch (prop_id)
571     {
572     case PROP_MENU_BASENAME:
573       self->basename = g_value_dup_string (value);
574       break;
575 
576     case PROP_MENU_PATH:
577       self->path = g_value_dup_string (value);
578       break;
579 
580     case PROP_FLAGS:
581       self->flags = g_value_get_flags (value);
582       break;
583 
584     default:
585       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
586       break;
587     }
588 }
589 
590 static void
matemenu_tree_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)591 matemenu_tree_get_property (GObject         *object,
592                          guint            prop_id,
593                          GValue          *value,
594                          GParamSpec      *pspec)
595 {
596   MateMenuTree *self = MATEMENU_TREE (object);
597 
598   switch (prop_id)
599     {
600     case PROP_MENU_BASENAME:
601       g_value_set_string (value, self->basename);
602       break;
603     case PROP_MENU_PATH:
604       g_value_set_string (value, self->path);
605       break;
606     case PROP_FLAGS:
607       g_value_set_flags (value, self->flags);
608       break;
609     default:
610       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
611       break;
612     }
613 }
614 
615 static void
matemenu_tree_finalize(GObject * object)616 matemenu_tree_finalize (GObject *object)
617 {
618   MateMenuTree *tree = MATEMENU_TREE (object);
619 
620   matemenu_tree_force_recanonicalize (tree);
621 
622   if (tree->basename != NULL)
623     g_free (tree->basename);
624   tree->basename = NULL;
625 
626   g_free (tree->non_prefixed_basename);
627   tree->non_prefixed_basename = NULL;
628 
629   if (tree->path != NULL)
630     g_free (tree->path);
631   tree->path = NULL;
632 
633   if (tree->canonical_path != NULL)
634     g_free (tree->canonical_path);
635   tree->canonical_path = NULL;
636 
637   g_hash_table_destroy (tree->entries_by_id);
638   tree->entries_by_id = NULL;
639   if (tree->collection_applet != NULL)
640   {
641     g_ptr_array_foreach (tree->collection_applet, (GFunc) g_free, NULL);
642     g_ptr_array_free (tree->collection_applet, TRUE);
643     tree->collection_applet = NULL;
644   }
645   g_signal_handlers_disconnect_by_func (tree->settings,
646                                         G_CALLBACK (collection_applet_changed),
647                                         tree);
648 
649   g_object_unref (tree->settings);
650   G_OBJECT_CLASS (matemenu_tree_parent_class)->finalize (object);
651 }
652 
653 static void
load_object(char * id,MateMenuTree * self)654 load_object (char         *id,
655              MateMenuTree *self)
656 {
657   ObjectType   object_type;
658   char        *object_path;
659   GSettings   *settings;
660 
661   object_path = g_strdup_printf ("/org/mate/panel/objects/%s/", id);
662   settings = g_settings_new_with_path ("org.mate.panel.object", object_path);
663 
664   object_type = g_settings_get_enum (settings, "object-type");
665   if (object_type == OBJECT_LAUNCHER)
666   {
667     char *location;
668     char *desktop_name;
669 
670     if (self->collection_applet == NULL)
671       self->collection_applet = g_ptr_array_new ();
672 
673     location = g_settings_get_string (settings, "launcher-location");
674     desktop_name = g_path_get_basename (location);
675     if (strstr (desktop_name, "-1.") != NULL )
676     {
677       char **str;
678 
679       str = g_strsplit (desktop_name, "-1.", -1);
680       g_free (desktop_name);
681       desktop_name = g_strdup_printf ("%s.%s", str[0], str[1]);
682       g_strfreev (str);
683     }
684     g_ptr_array_add (self->collection_applet, desktop_name);
685     g_free (location);
686   }
687   g_free (object_path);
688   g_object_unref (settings);
689 }
690 
691 static gboolean
emit_changed_signal(gpointer data)692 emit_changed_signal (gpointer data)
693 {
694   MateMenuTree *self = data;
695   matemenu_tree_force_rebuild (self);
696   matemenu_tree_invoke_monitors (self);
697 
698   return FALSE;
699 }
700 
701 static void
get_panel_collection_applet(MateMenuTree * self)702 get_panel_collection_applet (MateMenuTree *self)
703 {
704   gchar **list;
705   guint   i;
706 
707   list = g_settings_get_strv (self->settings, "object-id-list");
708   for (i = 0; list[i]; i++)
709   {
710     load_object (list[i], self);
711   }
712   g_strfreev (list);
713 }
714 
715 static void
collection_applet_changed(GSettings * settings,gchar * key,MateMenuTree * self)716 collection_applet_changed (GSettings    *settings,
717                            gchar        *key,
718                            MateMenuTree *self)
719 {
720   if (self->collection_applet != NULL)
721   {
722     g_ptr_array_foreach (self->collection_applet, (GFunc) g_free, NULL);
723     g_ptr_array_free (self->collection_applet, TRUE);
724     self->collection_applet = NULL;
725   }
726   get_panel_collection_applet (self);
727   g_idle_add (emit_changed_signal, (gpointer)self);
728 }
729 
730 static void
matemenu_tree_init(MateMenuTree * self)731 matemenu_tree_init (MateMenuTree *self)
732 {
733   self->entries_by_id = g_hash_table_new (g_str_hash, g_str_equal);
734   self->collection_applet = NULL;
735   self->settings = g_settings_new ("org.mate.panel");
736   get_panel_collection_applet (self);
737   g_signal_connect (self->settings, "changed::object-id-list",
738                     G_CALLBACK (collection_applet_changed),
739                     self);
740 }
741 
742 static void
matemenu_tree_class_init(MateMenuTreeClass * klass)743 matemenu_tree_class_init (MateMenuTreeClass *klass)
744 {
745   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
746 
747   gobject_class->constructor = matemenu_tree_constructor;
748   gobject_class->get_property = matemenu_tree_get_property;
749   gobject_class->set_property = matemenu_tree_set_property;
750   gobject_class->finalize = matemenu_tree_finalize;
751 
752   /**
753    * MateMenuTree:menu-basename:
754    *
755    * The name of the menu file; must be a basename or a relative path. The file
756    * will be looked up in $XDG_CONFIG_DIRS/menus/. See the Desktop Menu
757    * specification.
758    */
759   g_object_class_install_property (gobject_class,
760                                    PROP_MENU_BASENAME,
761                                    g_param_spec_string ("menu-basename", "", "",
762                                                         "applications.menu",
763                                                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
764   /**
765    * MateMenuTree:menu-path:
766    *
767    * The full path of the menu file. If set, MateMenuTree:menu-basename will get
768    * ignored.
769    */
770   g_object_class_install_property (gobject_class,
771                                    PROP_MENU_PATH,
772                                    g_param_spec_string ("menu-path", "", "",
773                                                         NULL,
774                                                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
775   /**
776    * MateMenuTree:flags:
777    *
778    * Flags controlling the content of the menu.
779    */
780   g_object_class_install_property (gobject_class,
781                                    PROP_FLAGS,
782                                    g_param_spec_flags ("flags", "", "",
783                                                        MATEMENU_TYPE_TREE_FLAGS,
784                                                        MATEMENU_TREE_FLAGS_NONE,
785                                                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
786 
787   /**
788    * MateMenuTree:changed:
789    *
790    * This signal is emitted when applications are added, removed, or
791    * upgraded.  But note the new data will only be visible after
792    * matemenu_tree_load_sync() or a variant thereof is invoked.
793    */
794   matemenu_tree_signals[CHANGED] =
795       g_signal_new ("changed",
796                     G_TYPE_FROM_CLASS (klass),
797                     G_SIGNAL_RUN_LAST,
798                     0,
799                     NULL, NULL,
800                     g_cclosure_marshal_VOID__VOID,
801                     G_TYPE_NONE, 0);
802 }
803 
804 /**
805  * matemenu_tree_get_canonical_menu_path:
806  * @tree: a #MateMenuTree
807  *
808  * This function is only available if the tree has been loaded via
809  * matemenu_tree_load_sync() or a variant thereof.
810  *
811  * Returns: The absolute and canonicalized path to the loaded menu file
812  */
813 const char *
matemenu_tree_get_canonical_menu_path(MateMenuTree * tree)814 matemenu_tree_get_canonical_menu_path (MateMenuTree *tree)
815 {
816   g_return_val_if_fail (MATEMENU_IS_TREE (tree), NULL);
817   g_return_val_if_fail (tree->loaded, NULL);
818 
819   return tree->canonical_path;
820 }
821 
822 /**
823  * matemenu_tree_load_sync:
824  * @tree: a #MateMenuTree
825  * @error: a #GError
826  *
827  * Synchronously load the menu contents.  This function
828  * performs a significant amount of blocking I/O if the
829  * tree has not been loaded yet.
830  *
831  * Returns: %TRUE on success, %FALSE on error
832  */
833 gboolean
matemenu_tree_load_sync(MateMenuTree * tree,GError ** error)834 matemenu_tree_load_sync (MateMenuTree  *tree,
835                       GError    **error)
836 {
837   GError *local_error = NULL;
838 
839   if (tree->loaded)
840     return TRUE;
841 
842   if (!matemenu_tree_build_from_layout (tree, &local_error))
843     {
844       if (local_error)
845         g_propagate_error (error, local_error);
846       return FALSE;
847     }
848 
849   tree->loaded = TRUE;
850 
851   return TRUE;
852 }
853 
854 /**
855  * matemenu_tree_get_root_directory:
856  * @tree: a #MateMenuTree
857  *
858  * Get the root directory; you must have loaded the tree first (at
859  * least once) via matemenu_tree_load_sync() or a variant thereof.
860  *
861  * Returns: (transfer full): Root of the tree
862  */
863 MateMenuTreeDirectory *
matemenu_tree_get_root_directory(MateMenuTree * tree)864 matemenu_tree_get_root_directory (MateMenuTree *tree)
865 {
866   g_return_val_if_fail (tree != NULL, NULL);
867   g_return_val_if_fail (tree->loaded, NULL);
868 
869   return matemenu_tree_item_ref (tree->root);
870 }
871 
872 static MateMenuTreeDirectory *
find_path(MateMenuTreeDirectory * directory,const char * path)873 find_path (MateMenuTreeDirectory *directory,
874 	   const char         *path)
875 {
876   const char *name;
877   char       *slash;
878   char       *freeme;
879   GSList     *tmp;
880 
881   while (path[0] == G_DIR_SEPARATOR) path++;
882 
883   if (path[0] == '\0')
884     return directory;
885 
886   freeme = NULL;
887   slash = strchr (path, G_DIR_SEPARATOR);
888   if (slash)
889     {
890       name = freeme = g_strndup (path, (gsize)(slash - path));
891       path = slash + 1;
892     }
893   else
894     {
895       name = path;
896       path = NULL;
897     }
898 
899   tmp = directory->contents;
900   while (tmp != NULL)
901     {
902       MateMenuTreeItem *item = tmp->data;
903 
904       if (item->type != MATEMENU_TREE_ITEM_DIRECTORY)
905         {
906           tmp = tmp->next;
907           continue;
908         }
909 
910       if (!strcmp (name, MATEMENU_TREE_DIRECTORY (item)->name))
911 	{
912 	  g_free (freeme);
913 
914 	  if (path)
915 	    return find_path (MATEMENU_TREE_DIRECTORY (item), path);
916 	  else
917 	    return MATEMENU_TREE_DIRECTORY (item);
918 	}
919 
920       tmp = tmp->next;
921     }
922 
923   g_free (freeme);
924 
925   return NULL;
926 }
927 
928 MateMenuTreeDirectory *
matemenu_tree_get_directory_from_path(MateMenuTree * tree,const char * path)929 matemenu_tree_get_directory_from_path (MateMenuTree  *tree,
930 				    const char *path)
931 {
932   MateMenuTreeDirectory *root;
933   MateMenuTreeDirectory *directory;
934 
935   g_return_val_if_fail (tree != NULL, NULL);
936   g_return_val_if_fail (path != NULL, NULL);
937 
938   if (path[0] != G_DIR_SEPARATOR)
939     return NULL;
940 
941   if (!(root = matemenu_tree_get_root_directory (tree)))
942     return NULL;
943 
944   directory = find_path (root, path);
945 
946   matemenu_tree_item_unref (root);
947 
948   return directory ? matemenu_tree_item_ref (directory) : NULL;
949 }
950 
951 /**
952  * matemenu_tree_get_entry_by_id:
953  * @tree: a #MateMenuTree
954  * @id: a desktop file ID
955  *
956  * Look up the entry corresponding to the given "desktop file id".
957  *
958  * Returns: (transfer full): A newly referenced #MateMenuTreeEntry, or %NULL if none
959  */
960 MateMenuTreeEntry     *
matemenu_tree_get_entry_by_id(MateMenuTree * tree,const char * id)961 matemenu_tree_get_entry_by_id (MateMenuTree  *tree,
962 			    const char *id)
963 {
964   MateMenuTreeEntry *entry;
965 
966   g_return_val_if_fail (tree->loaded, NULL);
967 
968   entry = g_hash_table_lookup (tree->entries_by_id, id);
969   if (entry != NULL)
970     matemenu_tree_item_ref (entry);
971 
972   return entry;
973 }
974 
975 static void
matemenu_tree_invoke_monitors(MateMenuTree * tree)976 matemenu_tree_invoke_monitors (MateMenuTree *tree)
977 {
978   g_signal_emit (tree, matemenu_tree_signals[CHANGED], 0);
979 }
980 
981 static MateMenuTreeDirectory *
get_parent(MateMenuTreeItem * item)982 get_parent (MateMenuTreeItem *item)
983 {
984   g_return_val_if_fail (item != NULL, NULL);
985   return item->parent ? matemenu_tree_item_ref (item->parent) : NULL;
986 }
987 
988 /**
989  * matemenu_tree_directory_get_parent:
990  * @directory: a #MateMenuTreeDirectory
991  *
992  * Returns: (transfer full): The parent directory, or %NULL if none
993  */
994 MateMenuTreeDirectory *
matemenu_tree_directory_get_parent(MateMenuTreeDirectory * directory)995 matemenu_tree_directory_get_parent (MateMenuTreeDirectory *directory)
996 {
997   return get_parent ((MateMenuTreeItem *)directory);
998 }
999 
1000 /**
1001  * matemenu_tree_entry_get_parent:
1002  * @entry: a #MateMenuTreeEntry
1003  *
1004  * Returns: (transfer full): The parent directory, or %NULL if none
1005  */
1006 MateMenuTreeDirectory *
matemenu_tree_entry_get_parent(MateMenuTreeEntry * entry)1007 matemenu_tree_entry_get_parent (MateMenuTreeEntry *entry)
1008 {
1009   return get_parent ((MateMenuTreeItem *)entry);
1010 }
1011 
1012 /**
1013  * matemenu_tree_alias_get_parent:
1014  * @alias: a #MateMenuTreeAlias
1015  *
1016  * Returns: (transfer full): The parent directory, or %NULL if none
1017  */
1018 MateMenuTreeDirectory *
matemenu_tree_alias_get_parent(MateMenuTreeAlias * alias)1019 matemenu_tree_alias_get_parent (MateMenuTreeAlias *alias)
1020 {
1021   return get_parent ((MateMenuTreeItem *)alias);
1022 }
1023 
1024 /**
1025  * matemenu_tree_header_get_parent:
1026  * @header: a #MateMenuTreeHeader
1027  *
1028  * Returns: (transfer full): The parent directory, or %NULL if none
1029  */
1030 MateMenuTreeDirectory *
matemenu_tree_header_get_parent(MateMenuTreeHeader * header)1031 matemenu_tree_header_get_parent (MateMenuTreeHeader *header)
1032 {
1033   return get_parent ((MateMenuTreeItem *)header);
1034 }
1035 
1036 /**
1037  * matemenu_tree_separator_get_parent:
1038  * @separator: a #MateMenuTreeSeparator
1039  *
1040  * Returns: (transfer full): The parent directory, or %NULL if none
1041  */
1042 MateMenuTreeDirectory *
matemenu_tree_separator_get_parent(MateMenuTreeSeparator * separator)1043 matemenu_tree_separator_get_parent (MateMenuTreeSeparator *separator)
1044 {
1045   return get_parent ((MateMenuTreeItem *)separator);
1046 }
1047 
1048 static void
matemenu_tree_item_set_parent(MateMenuTreeItem * item,MateMenuTreeDirectory * parent)1049 matemenu_tree_item_set_parent (MateMenuTreeItem      *item,
1050 			    MateMenuTreeDirectory *parent)
1051 {
1052   g_return_if_fail (item != NULL);
1053 
1054   item->parent = parent;
1055 }
1056 
1057 /**
1058  * matemenu_tree_iter_ref: (skip)
1059  * @iter: iter
1060  *
1061  * Increment the reference count of @iter
1062  */
1063 MateMenuTreeIter *
matemenu_tree_iter_ref(MateMenuTreeIter * iter)1064 matemenu_tree_iter_ref (MateMenuTreeIter *iter)
1065 {
1066   g_atomic_int_inc (&iter->refcount);
1067   return iter;
1068 }
1069 
1070 /**
1071  * matemenu_tree_iter_unref: (skip)
1072  * @iter: iter
1073  *
1074  * Decrement the reference count of @iter
1075  */
1076 void
matemenu_tree_iter_unref(MateMenuTreeIter * iter)1077 matemenu_tree_iter_unref (MateMenuTreeIter *iter)
1078 {
1079   if (!g_atomic_int_dec_and_test (&iter->refcount))
1080     return;
1081 
1082   g_slist_foreach (iter->contents, (GFunc)matemenu_tree_item_unref, NULL);
1083   g_slist_free (iter->contents);
1084 
1085   g_slice_free (MateMenuTreeIter, iter);
1086 }
1087 
1088 /**
1089  * matemenu_tree_directory_iter:
1090  * @directory: directory
1091  *
1092  * Returns: (transfer full): A new iterator over the directory contents
1093  */
1094 MateMenuTreeIter *
matemenu_tree_directory_iter(MateMenuTreeDirectory * directory)1095 matemenu_tree_directory_iter (MateMenuTreeDirectory *directory)
1096 {
1097   MateMenuTreeIter *iter;
1098 
1099   g_return_val_if_fail (directory != NULL, NULL);
1100 
1101   iter = g_slice_new0 (MateMenuTreeIter);
1102   iter->refcount = 1;
1103 
1104   iter->contents = g_slist_copy (directory->contents);
1105   iter->contents_iter = iter->contents;
1106   g_slist_foreach (iter->contents, (GFunc) matemenu_tree_item_ref, NULL);
1107 
1108   return iter;
1109 }
1110 
1111 /**
1112  * matemenu_tree_iter_next:
1113  * @iter: iter
1114  *
1115  * Change the iterator to the next item, and return its type.  If
1116  * there are no more items, %MATEMENU_TREE_ITEM_INVALID is returned.
1117  *
1118  * Returns: The type of the next item that can be retrived from the iterator
1119  */
1120 MateMenuTreeItemType
matemenu_tree_iter_next(MateMenuTreeIter * iter)1121 matemenu_tree_iter_next (MateMenuTreeIter *iter)
1122 {
1123   g_return_val_if_fail (iter != NULL, MATEMENU_TREE_ITEM_INVALID);
1124 
1125   if (iter->contents_iter)
1126     {
1127       iter->item = iter->contents_iter->data;
1128       iter->contents_iter = iter->contents_iter->next;
1129       return iter->item->type;
1130     }
1131   else
1132     return MATEMENU_TREE_ITEM_INVALID;
1133 }
1134 
1135 /**
1136  * matemenu_tree_iter_get_directory:
1137  * @iter: iter
1138  *
1139  * This method may only be called if matemenu_tree_iter_next()
1140  * returned MATEMENU_TREE_ITEM_DIRECTORY.
1141  *
1142  * Returns: (transfer full): A directory
1143  */
1144 MateMenuTreeDirectory *
matemenu_tree_iter_get_directory(MateMenuTreeIter * iter)1145 matemenu_tree_iter_get_directory (MateMenuTreeIter *iter)
1146 {
1147   g_return_val_if_fail (iter != NULL, NULL);
1148   g_return_val_if_fail (iter->item != NULL, NULL);
1149   g_return_val_if_fail (iter->item->type == MATEMENU_TREE_ITEM_DIRECTORY, NULL);
1150 
1151   return (MateMenuTreeDirectory*)matemenu_tree_item_ref (iter->item);
1152 }
1153 
1154 /**
1155  * matemenu_tree_iter_get_entry:
1156  * @iter: iter
1157  *
1158  * This method may only be called if matemenu_tree_iter_next()
1159  * returned MATEMENU_TREE_ITEM_ENTRY.
1160  *
1161  * Returns: (transfer full): An entry
1162  */
1163 MateMenuTreeEntry *
matemenu_tree_iter_get_entry(MateMenuTreeIter * iter)1164 matemenu_tree_iter_get_entry (MateMenuTreeIter *iter)
1165 {
1166   g_return_val_if_fail (iter != NULL, NULL);
1167   g_return_val_if_fail (iter->item != NULL, NULL);
1168   g_return_val_if_fail (iter->item->type == MATEMENU_TREE_ITEM_ENTRY, NULL);
1169 
1170   return (MateMenuTreeEntry*)matemenu_tree_item_ref (iter->item);
1171 }
1172 
1173 /**
1174  * matemenu_tree_iter_get_header:
1175  * @iter: iter
1176  *
1177  * This method may only be called if matemenu_tree_iter_next()
1178  * returned MATEMENU_TREE_ITEM_HEADER.
1179  *
1180  * Returns: (transfer full): A header
1181  */
1182 MateMenuTreeHeader *
matemenu_tree_iter_get_header(MateMenuTreeIter * iter)1183 matemenu_tree_iter_get_header (MateMenuTreeIter *iter)
1184 {
1185   g_return_val_if_fail (iter != NULL, NULL);
1186   g_return_val_if_fail (iter->item != NULL, NULL);
1187   g_return_val_if_fail (iter->item->type == MATEMENU_TREE_ITEM_HEADER, NULL);
1188 
1189   return (MateMenuTreeHeader*)matemenu_tree_item_ref (iter->item);
1190 }
1191 
1192 /**
1193  * matemenu_tree_iter_get_alias:
1194  * @iter: iter
1195  *
1196  * This method may only be called if matemenu_tree_iter_next()
1197  * returned MATEMENU_TREE_ITEM_ALIAS.
1198  *
1199  * Returns: (transfer full): An alias
1200  */
1201 MateMenuTreeAlias *
matemenu_tree_iter_get_alias(MateMenuTreeIter * iter)1202 matemenu_tree_iter_get_alias (MateMenuTreeIter *iter)
1203 {
1204   g_return_val_if_fail (iter != NULL, NULL);
1205   g_return_val_if_fail (iter->item != NULL, NULL);
1206   g_return_val_if_fail (iter->item->type == MATEMENU_TREE_ITEM_ALIAS, NULL);
1207 
1208   return (MateMenuTreeAlias*)matemenu_tree_item_ref (iter->item);
1209 }
1210 
1211 /**
1212  * matemenu_tree_iter_get_separator:
1213  * @iter: iter
1214  *
1215  * This method may only be called if matemenu_tree_iter_next()
1216  * returned #MATEMENU_TREE_ITEM_SEPARATOR.
1217  *
1218  * Returns: (transfer full): A separator
1219  */
1220 MateMenuTreeSeparator *
matemenu_tree_iter_get_separator(MateMenuTreeIter * iter)1221 matemenu_tree_iter_get_separator (MateMenuTreeIter *iter)
1222 {
1223   g_return_val_if_fail (iter != NULL, NULL);
1224   g_return_val_if_fail (iter->item != NULL, NULL);
1225   g_return_val_if_fail (iter->item->type == MATEMENU_TREE_ITEM_SEPARATOR, NULL);
1226 
1227   return (MateMenuTreeSeparator*)matemenu_tree_item_ref (iter->item);
1228 }
1229 
1230 const char *
matemenu_tree_directory_get_name(MateMenuTreeDirectory * directory)1231 matemenu_tree_directory_get_name (MateMenuTreeDirectory *directory)
1232 {
1233   g_return_val_if_fail (directory != NULL, NULL);
1234 
1235   if (!directory->directory_entry)
1236     return directory->name;
1237 
1238   return desktop_entry_get_name (directory->directory_entry);
1239 }
1240 
1241 const char *
matemenu_tree_directory_get_generic_name(MateMenuTreeDirectory * directory)1242 matemenu_tree_directory_get_generic_name (MateMenuTreeDirectory *directory)
1243 {
1244   g_return_val_if_fail (directory != NULL, NULL);
1245 
1246   if (!directory->directory_entry)
1247     return NULL;
1248 
1249   return desktop_entry_get_generic_name (directory->directory_entry);
1250 }
1251 
1252 const char *
matemenu_tree_directory_get_comment(MateMenuTreeDirectory * directory)1253 matemenu_tree_directory_get_comment (MateMenuTreeDirectory *directory)
1254 {
1255   g_return_val_if_fail (directory != NULL, NULL);
1256 
1257   if (!directory->directory_entry)
1258     return NULL;
1259 
1260   return desktop_entry_get_comment (directory->directory_entry);
1261 }
1262 
1263 /**
1264  * matemenu_tree_directory_get_icon:
1265  * @directory: a #MateMenuTreeDirectory
1266  *
1267  * Gets the icon for the directory.
1268  *
1269  * Returns: (transfer none): The #GIcon for this directory
1270  */
1271 GIcon *
matemenu_tree_directory_get_icon(MateMenuTreeDirectory * directory)1272 matemenu_tree_directory_get_icon (MateMenuTreeDirectory *directory)
1273 {
1274 	g_return_val_if_fail(directory != NULL, NULL);
1275 
1276 	if (!directory->directory_entry)
1277 		return NULL;
1278 
1279 	return desktop_entry_get_icon(directory->directory_entry);
1280 }
1281 
1282 const char *
matemenu_tree_directory_get_desktop_file_path(MateMenuTreeDirectory * directory)1283 matemenu_tree_directory_get_desktop_file_path (MateMenuTreeDirectory *directory)
1284 {
1285   g_return_val_if_fail (directory != NULL, NULL);
1286 
1287   if (!directory->directory_entry)
1288     return NULL;
1289 
1290   return desktop_entry_get_path (directory->directory_entry);
1291 }
1292 
1293 const char *
matemenu_tree_directory_get_menu_id(MateMenuTreeDirectory * directory)1294 matemenu_tree_directory_get_menu_id (MateMenuTreeDirectory *directory)
1295 {
1296   g_return_val_if_fail (directory != NULL, NULL);
1297 
1298   return directory->name;
1299 }
1300 
1301 gboolean
matemenu_tree_directory_get_is_nodisplay(MateMenuTreeDirectory * directory)1302 matemenu_tree_directory_get_is_nodisplay (MateMenuTreeDirectory *directory)
1303 {
1304   g_return_val_if_fail (directory != NULL, FALSE);
1305 
1306   return directory->is_nodisplay;
1307 }
1308 
1309 /**
1310  * matemenu_tree_directory_get_tree:
1311  * @directory: A #MateMenuTreeDirectory
1312  *
1313  * Grab the tree associated with a #MateMenuTreeItem.
1314  *
1315  * Returns: (transfer full): The #MateMenuTree
1316  */
1317 MateMenuTree *
matemenu_tree_directory_get_tree(MateMenuTreeDirectory * directory)1318 matemenu_tree_directory_get_tree (MateMenuTreeDirectory *directory)
1319 {
1320   g_return_val_if_fail (directory != NULL, NULL);
1321 
1322   return g_object_ref (directory->item.tree);
1323 }
1324 
1325 static void
append_directory_path(MateMenuTreeDirectory * directory,GString * path)1326 append_directory_path (MateMenuTreeDirectory *directory,
1327 		       GString            *path)
1328 {
1329 
1330   if (!directory->item.parent)
1331     {
1332       g_string_append_c (path, G_DIR_SEPARATOR);
1333       return;
1334     }
1335 
1336   append_directory_path (directory->item.parent, path);
1337 
1338   g_string_append (path, directory->name);
1339   g_string_append_c (path, G_DIR_SEPARATOR);
1340 }
1341 
1342 char *
matemenu_tree_directory_make_path(MateMenuTreeDirectory * directory,MateMenuTreeEntry * entry)1343 matemenu_tree_directory_make_path (MateMenuTreeDirectory *directory,
1344 				MateMenuTreeEntry     *entry)
1345 {
1346   GString *path;
1347 
1348   g_return_val_if_fail (directory != NULL, NULL);
1349 
1350   path = g_string_new (NULL);
1351 
1352   append_directory_path (directory, path);
1353 
1354   if (entry != NULL)
1355     g_string_append (path,
1356 		     desktop_entry_get_basename (entry->desktop_entry));
1357 
1358   return g_string_free (path, FALSE);
1359 }
1360 
1361 /**
1362  * matemenu_tree_entry_get_app_info:
1363  * @entry: a #MateMenuTreeEntry
1364  *
1365  * Returns: (transfer none): The #GDesktopAppInfo for this entry
1366  */
1367 GDesktopAppInfo *
matemenu_tree_entry_get_app_info(MateMenuTreeEntry * entry)1368 matemenu_tree_entry_get_app_info (MateMenuTreeEntry *entry)
1369 {
1370   g_return_val_if_fail (entry != NULL, NULL);
1371 
1372   return desktop_entry_get_app_info (entry->desktop_entry);
1373 }
1374 
1375 const char *
matemenu_tree_entry_get_desktop_file_path(MateMenuTreeEntry * entry)1376 matemenu_tree_entry_get_desktop_file_path (MateMenuTreeEntry *entry)
1377 {
1378   g_return_val_if_fail (entry != NULL, NULL);
1379 
1380   return desktop_entry_get_path (entry->desktop_entry);
1381 }
1382 
1383 const char *
matemenu_tree_entry_get_desktop_file_id(MateMenuTreeEntry * entry)1384 matemenu_tree_entry_get_desktop_file_id (MateMenuTreeEntry *entry)
1385 {
1386   g_return_val_if_fail (entry != NULL, NULL);
1387 
1388   return entry->desktop_file_id;
1389 }
1390 
1391 gboolean
matemenu_tree_entry_get_is_nodisplay_recurse(MateMenuTreeEntry * entry)1392 matemenu_tree_entry_get_is_nodisplay_recurse (MateMenuTreeEntry *entry)
1393 {
1394   MateMenuTreeDirectory *directory;
1395   GDesktopAppInfo *app_info;
1396 
1397   g_return_val_if_fail (entry != NULL, FALSE);
1398 
1399   app_info = matemenu_tree_entry_get_app_info (entry);
1400 
1401   if (g_desktop_app_info_get_nodisplay (app_info))
1402     return TRUE;
1403 
1404   directory = entry->item.parent;
1405   while (directory != NULL)
1406     {
1407       if (directory->is_nodisplay)
1408         return TRUE;
1409 
1410       directory = directory->item.parent;
1411     }
1412 
1413   return FALSE;
1414 }
1415 
1416 gboolean
matemenu_tree_entry_get_is_excluded(MateMenuTreeEntry * entry)1417 matemenu_tree_entry_get_is_excluded (MateMenuTreeEntry *entry)
1418 {
1419   g_return_val_if_fail(entry != NULL, FALSE);
1420 
1421   return entry->is_excluded;
1422 }
1423 
1424 gboolean
matemenu_tree_entry_get_is_unallocated(MateMenuTreeEntry * entry)1425 matemenu_tree_entry_get_is_unallocated (MateMenuTreeEntry *entry)
1426 {
1427   g_return_val_if_fail (entry != NULL, FALSE);
1428 
1429   return entry->is_unallocated;
1430 }
1431 
1432 /**
1433  * matemenu_tree_entry_get_tree:
1434  * @entry: A #MateMenuTreeEntry
1435  *
1436  * Grab the tree associated with a #MateMenuTreeEntry.
1437  *
1438  * Returns: (transfer full): The #MateMenuTree
1439  */
1440 MateMenuTree *
matemenu_tree_entry_get_tree(MateMenuTreeEntry * entry)1441 matemenu_tree_entry_get_tree (MateMenuTreeEntry *entry)
1442 {
1443 	g_return_val_if_fail(entry != NULL, NULL);
1444 
1445   return g_object_ref (entry->item.tree);
1446 }
1447 
1448 MateMenuTreeDirectory *
matemenu_tree_header_get_directory(MateMenuTreeHeader * header)1449 matemenu_tree_header_get_directory (MateMenuTreeHeader *header)
1450 {
1451   g_return_val_if_fail (header != NULL, NULL);
1452 
1453   return matemenu_tree_item_ref (header->directory);
1454 }
1455 
1456 /**
1457  * matemenu_tree_header_get_tree:
1458  * @header: A #MateMenuTreeHeader
1459  *
1460  * Grab the tree associated with a #MateMenuTreeHeader.
1461  *
1462  * Returns: (transfer full): The #MateMenuTree
1463  */
1464 MateMenuTree *
matemenu_tree_header_get_tree(MateMenuTreeHeader * header)1465 matemenu_tree_header_get_tree (MateMenuTreeHeader *header)
1466 {
1467   g_return_val_if_fail (header != NULL, NULL);
1468 
1469   return g_object_ref (header->item.tree);
1470 }
1471 
1472 MateMenuTreeItemType
matemenu_tree_alias_get_aliased_item_type(MateMenuTreeAlias * alias)1473 matemenu_tree_alias_get_aliased_item_type (MateMenuTreeAlias *alias)
1474 {
1475   g_return_val_if_fail (alias != NULL, MATEMENU_TREE_ITEM_INVALID);
1476 
1477   g_assert (alias->aliased_item != NULL);
1478   return alias->aliased_item->type;
1479 }
1480 
matemenu_tree_alias_get_directory(MateMenuTreeAlias * alias)1481 MateMenuTreeDirectory* matemenu_tree_alias_get_directory(MateMenuTreeAlias* alias)
1482 {
1483 	g_return_val_if_fail (alias != NULL, NULL);
1484 
1485 	return matemenu_tree_item_ref(alias->directory);
1486 }
1487 
1488 /**
1489  * matemenu_tree_alias_get_tree:
1490  * @alias: A #MateMenuTreeAlias
1491  *
1492  * Grab the tree associated with a #MateMenuTreeAlias.
1493  *
1494  * Returns: (transfer full): The #MateMenuTree
1495  */
1496 MateMenuTree *
matemenu_tree_alias_get_tree(MateMenuTreeAlias * alias)1497 matemenu_tree_alias_get_tree (MateMenuTreeAlias *alias)
1498 {
1499   g_return_val_if_fail (alias != NULL, NULL);
1500 
1501   return g_object_ref (alias->item.tree);
1502 }
1503 
1504 /**
1505  * matemenu_tree_separator_get_tree:
1506  * @separator: A #MateMenuTreeSeparator
1507  *
1508  * Grab the tree associated with a #MateMenuTreeSeparator.
1509  *
1510  * Returns: (transfer full): The #MateMenuTree
1511  */
1512 MateMenuTree *
matemenu_tree_separator_get_tree(MateMenuTreeSeparator * separator)1513 matemenu_tree_separator_get_tree (MateMenuTreeSeparator *separator)
1514 {
1515   g_return_val_if_fail (separator != NULL, NULL);
1516 
1517   return g_object_ref (separator->item.tree);
1518 }
1519 
1520 /**
1521  * matemenu_tree_alias_get_aliased_directory:
1522  * @alias: alias
1523  *
1524  * Returns: (transfer full): The aliased directory entry
1525  */
1526 MateMenuTreeDirectory *
matemenu_tree_alias_get_aliased_directory(MateMenuTreeAlias * alias)1527 matemenu_tree_alias_get_aliased_directory (MateMenuTreeAlias *alias)
1528 {
1529   g_return_val_if_fail (alias != NULL, NULL);
1530   g_return_val_if_fail (alias->aliased_item->type == MATEMENU_TREE_ITEM_DIRECTORY, NULL);
1531 
1532   return (MateMenuTreeDirectory *) matemenu_tree_item_ref (alias->aliased_item);
1533 }
1534 
1535 /**
1536  * matemenu_tree_alias_get_aliased_entry:
1537  * @alias: alias
1538  *
1539  * Returns: (transfer full): The aliased entry
1540  */
1541 MateMenuTreeEntry *
matemenu_tree_alias_get_aliased_entry(MateMenuTreeAlias * alias)1542 matemenu_tree_alias_get_aliased_entry (MateMenuTreeAlias *alias)
1543 {
1544   g_return_val_if_fail (alias != NULL, NULL);
1545   g_return_val_if_fail (alias->aliased_item->type == MATEMENU_TREE_ITEM_ENTRY, NULL);
1546 
1547   return (MateMenuTreeEntry *) matemenu_tree_item_ref (alias->aliased_item);
1548 }
1549 
1550 static MateMenuTreeDirectory *
matemenu_tree_directory_new(MateMenuTree * tree,MateMenuTreeDirectory * parent,const char * name)1551 matemenu_tree_directory_new (MateMenuTree          *tree,
1552                           MateMenuTreeDirectory *parent,
1553 			  const char         *name)
1554 {
1555   MateMenuTreeDirectory *retval;
1556 
1557   retval = g_slice_new0 (MateMenuTreeDirectory);
1558 
1559   retval->item.type     = MATEMENU_TREE_ITEM_DIRECTORY;
1560   retval->item.parent   = parent;
1561   retval->item.refcount = 1;
1562   retval->item.tree     = tree;
1563 
1564   retval->name                = g_strdup (name);
1565   retval->directory_entry     = NULL;
1566   retval->entries             = NULL;
1567   retval->subdirs             = NULL;
1568   retval->default_layout_info = NULL;
1569   retval->layout_info         = NULL;
1570   retval->contents            = NULL;
1571   retval->only_unallocated    = FALSE;
1572   retval->is_nodisplay        = FALSE;
1573   retval->layout_pending_separator = FALSE;
1574   retval->preprocessed        = FALSE;
1575   retval->will_inline_header  = G_MAXUINT16;
1576 
1577   retval->default_layout_values.mask          = MENU_LAYOUT_VALUES_NONE;
1578   retval->default_layout_values.show_empty    = FALSE;
1579   retval->default_layout_values.inline_menus  = FALSE;
1580   retval->default_layout_values.inline_limit  = 4;
1581   retval->default_layout_values.inline_header = FALSE;
1582   retval->default_layout_values.inline_alias  = FALSE;
1583 
1584   return retval;
1585 }
1586 
1587 static void
matemenu_tree_directory_finalize(MateMenuTreeDirectory * directory)1588 matemenu_tree_directory_finalize (MateMenuTreeDirectory *directory)
1589 {
1590   g_assert (directory->item.refcount == 0);
1591 
1592   g_slist_foreach (directory->contents,
1593 		   (GFunc) matemenu_tree_item_unref_and_unset_parent,
1594 		   NULL);
1595   g_slist_free (directory->contents);
1596   directory->contents = NULL;
1597 
1598   g_slist_foreach (directory->default_layout_info,
1599 		   (GFunc) menu_layout_node_unref,
1600 		   NULL);
1601   g_slist_free (directory->default_layout_info);
1602   directory->default_layout_info = NULL;
1603 
1604   g_slist_foreach (directory->layout_info,
1605 		   (GFunc) menu_layout_node_unref,
1606 		   NULL);
1607   g_slist_free (directory->layout_info);
1608   directory->layout_info = NULL;
1609 
1610   g_slist_foreach (directory->subdirs,
1611 		   (GFunc) matemenu_tree_item_unref_and_unset_parent,
1612 		   NULL);
1613   g_slist_free (directory->subdirs);
1614   directory->subdirs = NULL;
1615 
1616   g_slist_foreach (directory->entries,
1617 		   (GFunc) matemenu_tree_item_unref_and_unset_parent,
1618 		   NULL);
1619   g_slist_free (directory->entries);
1620   directory->entries = NULL;
1621 
1622   if (directory->directory_entry)
1623     desktop_entry_unref (directory->directory_entry);
1624   directory->directory_entry = NULL;
1625 
1626   g_free (directory->name);
1627   directory->name = NULL;
1628 
1629   g_slice_free (MateMenuTreeDirectory, directory);
1630 }
1631 
1632 static MateMenuTreeSeparator *
matemenu_tree_separator_new(MateMenuTreeDirectory * parent)1633 matemenu_tree_separator_new (MateMenuTreeDirectory *parent)
1634 {
1635   MateMenuTreeSeparator *retval;
1636 
1637   retval = g_slice_new0 (MateMenuTreeSeparator);
1638 
1639   retval->item.type     = MATEMENU_TREE_ITEM_SEPARATOR;
1640   retval->item.parent   = parent;
1641   retval->item.refcount = 1;
1642   retval->item.tree     = parent->item.tree;
1643 
1644   return retval;
1645 }
1646 
1647 static void
matemenu_tree_separator_finalize(MateMenuTreeSeparator * separator)1648 matemenu_tree_separator_finalize (MateMenuTreeSeparator *separator)
1649 {
1650   g_assert (separator->item.refcount == 0);
1651 
1652   g_slice_free (MateMenuTreeSeparator, separator);
1653 }
1654 
1655 static MateMenuTreeHeader *
matemenu_tree_header_new(MateMenuTreeDirectory * parent,MateMenuTreeDirectory * directory)1656 matemenu_tree_header_new (MateMenuTreeDirectory *parent,
1657 		       MateMenuTreeDirectory *directory)
1658 {
1659   MateMenuTreeHeader *retval;
1660 
1661   retval = g_slice_new0 (MateMenuTreeHeader);
1662 
1663   retval->item.type     = MATEMENU_TREE_ITEM_HEADER;
1664   retval->item.parent   = parent;
1665   retval->item.refcount = 1;
1666   retval->item.tree     = parent->item.tree;
1667 
1668   retval->directory = matemenu_tree_item_ref (directory);
1669 
1670   matemenu_tree_item_set_parent (MATEMENU_TREE_ITEM (retval->directory), NULL);
1671 
1672   return retval;
1673 }
1674 
1675 static void
matemenu_tree_header_finalize(MateMenuTreeHeader * header)1676 matemenu_tree_header_finalize (MateMenuTreeHeader *header)
1677 {
1678   g_assert (header->item.refcount == 0);
1679 
1680   if (header->directory != NULL)
1681     matemenu_tree_item_unref (header->directory);
1682   header->directory = NULL;
1683 
1684   g_slice_free (MateMenuTreeHeader, header);
1685 }
1686 
1687 static MateMenuTreeAlias *
matemenu_tree_alias_new(MateMenuTreeDirectory * parent,MateMenuTreeDirectory * directory,MateMenuTreeItem * item)1688 matemenu_tree_alias_new (MateMenuTreeDirectory *parent,
1689 		      MateMenuTreeDirectory *directory,
1690 		      MateMenuTreeItem      *item)
1691 {
1692   MateMenuTreeAlias *retval;
1693 
1694   retval = g_slice_new0 (MateMenuTreeAlias);
1695 
1696   retval->item.type     = MATEMENU_TREE_ITEM_ALIAS;
1697   retval->item.parent   = parent;
1698   retval->item.refcount = 1;
1699   retval->item.tree     = parent->item.tree;
1700 
1701   retval->directory    = matemenu_tree_item_ref (directory);
1702   if (item->type != MATEMENU_TREE_ITEM_ALIAS)
1703     retval->aliased_item = matemenu_tree_item_ref (item);
1704   else
1705     {
1706       MateMenuTreeAlias *alias = MATEMENU_TREE_ALIAS (item);
1707       retval->aliased_item = matemenu_tree_item_ref (alias->aliased_item);
1708     }
1709 
1710   matemenu_tree_item_set_parent (MATEMENU_TREE_ITEM (retval->directory), NULL);
1711   matemenu_tree_item_set_parent (retval->aliased_item, NULL);
1712 
1713   return retval;
1714 }
1715 
1716 static void
matemenu_tree_alias_finalize(MateMenuTreeAlias * alias)1717 matemenu_tree_alias_finalize (MateMenuTreeAlias *alias)
1718 {
1719   g_assert (alias->item.refcount == 0);
1720 
1721   if (alias->directory != NULL)
1722     matemenu_tree_item_unref (alias->directory);
1723   alias->directory = NULL;
1724 
1725   if (alias->aliased_item != NULL)
1726     matemenu_tree_item_unref (alias->aliased_item);
1727   alias->aliased_item = NULL;
1728 
1729   g_slice_free (MateMenuTreeAlias, alias);
1730 }
1731 
1732 static MateMenuTreeEntry *
matemenu_tree_entry_new(MateMenuTreeDirectory * parent,DesktopEntry * desktop_entry,const char * desktop_file_id,gboolean is_excluded,gboolean is_unallocated)1733 matemenu_tree_entry_new (MateMenuTreeDirectory *parent,
1734 		      DesktopEntry       *desktop_entry,
1735 		      const char         *desktop_file_id,
1736 		      gboolean            is_excluded,
1737                       gboolean            is_unallocated)
1738 {
1739   MateMenuTreeEntry *retval;
1740 
1741   retval = g_slice_new0 (MateMenuTreeEntry);
1742 
1743   retval->item.type     = MATEMENU_TREE_ITEM_ENTRY;
1744   retval->item.parent   = parent;
1745   retval->item.refcount = 1;
1746   retval->item.tree     = parent->item.tree;
1747 
1748   retval->desktop_entry   = desktop_entry_ref (desktop_entry);
1749   retval->desktop_file_id = g_strdup (desktop_file_id);
1750   retval->is_excluded     = is_excluded != FALSE;
1751   retval->is_unallocated  = is_unallocated != FALSE;
1752 
1753   return retval;
1754 }
1755 
1756 static void
matemenu_tree_entry_finalize(MateMenuTreeEntry * entry)1757 matemenu_tree_entry_finalize (MateMenuTreeEntry *entry)
1758 {
1759   g_assert (entry->item.refcount == 0);
1760 
1761   g_free (entry->desktop_file_id);
1762   entry->desktop_file_id = NULL;
1763 
1764   if (entry->desktop_entry)
1765     desktop_entry_unref (entry->desktop_entry);
1766   entry->desktop_entry = NULL;
1767 
1768   g_slice_free (MateMenuTreeEntry, entry);
1769 }
1770 
1771 static int
matemenu_tree_entry_compare_by_id(MateMenuTreeItem * a,MateMenuTreeItem * b)1772 matemenu_tree_entry_compare_by_id (MateMenuTreeItem *a,
1773 				MateMenuTreeItem *b)
1774 {
1775   if (a->type == MATEMENU_TREE_ITEM_ALIAS)
1776     a = MATEMENU_TREE_ALIAS (a)->aliased_item;
1777 
1778   if (b->type == MATEMENU_TREE_ITEM_ALIAS)
1779     b = MATEMENU_TREE_ALIAS (b)->aliased_item;
1780 
1781   return strcmp (MATEMENU_TREE_ENTRY (a)->desktop_file_id,
1782                  MATEMENU_TREE_ENTRY (b)->desktop_file_id);
1783 }
1784 
1785 /**
1786  * matemenu_tree_item_ref:
1787  * @item: a #MateMenuTreeItem
1788  *
1789  * Returns: (transfer full): The same @item, or %NULL if @item is not a valid #MateMenuTreeItem
1790  */
1791 gpointer
matemenu_tree_item_ref(gpointer itemp)1792 matemenu_tree_item_ref (gpointer itemp)
1793 {
1794 	MateMenuTreeItem* item = (MateMenuTreeItem*) itemp;
1795 
1796 	g_return_val_if_fail(item != NULL, NULL);
1797 	g_return_val_if_fail(item->refcount > 0, NULL);
1798 
1799   g_atomic_int_inc (&item->refcount);
1800 
1801 	return item;
1802 }
1803 
1804 void
matemenu_tree_item_unref(gpointer itemp)1805 matemenu_tree_item_unref (gpointer itemp)
1806 {
1807   MateMenuTreeItem *item;
1808 
1809   item = (MateMenuTreeItem *) itemp;
1810 
1811   g_return_if_fail (item != NULL);
1812   g_return_if_fail (item->refcount > 0);
1813 
1814   if (g_atomic_int_dec_and_test (&(item->refcount)))
1815     {
1816       switch (item->type)
1817 	{
1818 	case MATEMENU_TREE_ITEM_DIRECTORY:
1819 	  matemenu_tree_directory_finalize (MATEMENU_TREE_DIRECTORY (item));
1820 	  break;
1821 
1822 	case MATEMENU_TREE_ITEM_ENTRY:
1823 	  matemenu_tree_entry_finalize (MATEMENU_TREE_ENTRY (item));
1824 	  break;
1825 
1826 	case MATEMENU_TREE_ITEM_SEPARATOR:
1827 	  matemenu_tree_separator_finalize (MATEMENU_TREE_SEPARATOR (item));
1828 	  break;
1829 
1830 	case MATEMENU_TREE_ITEM_HEADER:
1831 	  matemenu_tree_header_finalize (MATEMENU_TREE_HEADER (item));
1832 	  break;
1833 
1834 	case MATEMENU_TREE_ITEM_ALIAS:
1835 	  matemenu_tree_alias_finalize (MATEMENU_TREE_ALIAS (item));
1836 	  break;
1837 
1838 	default:
1839 	  g_assert_not_reached ();
1840 	  break;
1841 	}
1842     }
1843 }
1844 
1845 static void
matemenu_tree_item_unref_and_unset_parent(gpointer itemp)1846 matemenu_tree_item_unref_and_unset_parent (gpointer itemp)
1847 {
1848   MateMenuTreeItem *item;
1849 
1850   item = (MateMenuTreeItem *) itemp;
1851 
1852   g_return_if_fail (item != NULL);
1853 
1854   matemenu_tree_item_set_parent (item, NULL);
1855   matemenu_tree_item_unref (item);
1856 }
1857 
1858 static inline const char *
matemenu_tree_item_compare_get_name_helper(MateMenuTreeItem * item,MateMenuTreeFlags flags)1859 matemenu_tree_item_compare_get_name_helper (MateMenuTreeItem    *item,
1860 					 MateMenuTreeFlags    flags)
1861 {
1862   const char *name;
1863 
1864   name = NULL;
1865 
1866   switch (item->type)
1867     {
1868     case MATEMENU_TREE_ITEM_DIRECTORY:
1869       if (MATEMENU_TREE_DIRECTORY (item)->directory_entry)
1870 	name = desktop_entry_get_name (MATEMENU_TREE_DIRECTORY (item)->directory_entry);
1871       else
1872 	name = MATEMENU_TREE_DIRECTORY (item)->name;
1873       break;
1874 
1875     case MATEMENU_TREE_ITEM_ENTRY:
1876       if (flags & MATEMENU_TREE_FLAGS_SORT_DISPLAY_NAME)
1877         name = g_app_info_get_display_name (G_APP_INFO (matemenu_tree_entry_get_app_info (MATEMENU_TREE_ENTRY (item))));
1878       else
1879         name = desktop_entry_get_name (MATEMENU_TREE_ENTRY (item)->desktop_entry);
1880       break;
1881 
1882     case MATEMENU_TREE_ITEM_ALIAS:
1883       {
1884         MateMenuTreeItem *dir;
1885         dir = MATEMENU_TREE_ITEM (MATEMENU_TREE_ALIAS (item)->directory);
1886         name = matemenu_tree_item_compare_get_name_helper (dir, flags);
1887       }
1888       break;
1889 
1890     case MATEMENU_TREE_ITEM_SEPARATOR:
1891     case MATEMENU_TREE_ITEM_HEADER:
1892     default:
1893       g_assert_not_reached ();
1894       break;
1895     }
1896 
1897   return name;
1898 }
1899 
1900 static int
matemenu_tree_item_compare(MateMenuTreeItem * a,MateMenuTreeItem * b,gpointer flags_p)1901 matemenu_tree_item_compare (MateMenuTreeItem *a,
1902 			 MateMenuTreeItem *b,
1903 			 gpointer       flags_p)
1904 {
1905   const char       *name_a;
1906   const char       *name_b;
1907   MateMenuTreeFlags    flags;
1908 
1909   flags = GPOINTER_TO_INT (flags_p);
1910 
1911   name_a = matemenu_tree_item_compare_get_name_helper (a, flags);
1912   name_b = matemenu_tree_item_compare_get_name_helper (b, flags);
1913 
1914   return g_utf8_collate (name_a, name_b);
1915 }
1916 
1917 static MenuLayoutNode *
find_menu_child(MenuLayoutNode * layout)1918 find_menu_child (MenuLayoutNode *layout)
1919 {
1920   MenuLayoutNode *child;
1921 
1922   child = menu_layout_node_get_children (layout);
1923   while (child && menu_layout_node_get_type (child) != MENU_LAYOUT_NODE_MENU)
1924     child = menu_layout_node_get_next (child);
1925 
1926   return child;
1927 }
1928 
1929 static void
merge_resolved_children(MateMenuTree * tree,GHashTable * loaded_menu_files,MenuLayoutNode * where,MenuLayoutNode * from)1930 merge_resolved_children (MateMenuTree      *tree,
1931 			 GHashTable     *loaded_menu_files,
1932                          MenuLayoutNode *where,
1933                          MenuLayoutNode *from)
1934 {
1935   MenuLayoutNode *insert_after;
1936   MenuLayoutNode *menu_child;
1937   MenuLayoutNode *from_child;
1938 
1939   matemenu_tree_resolve_files (tree, loaded_menu_files, from);
1940 
1941   insert_after = where;
1942   g_assert (menu_layout_node_get_type (insert_after) != MENU_LAYOUT_NODE_ROOT);
1943   g_assert (menu_layout_node_get_parent (insert_after) != NULL);
1944 
1945   /* skip root node */
1946   menu_child = find_menu_child (from);
1947   g_assert (menu_child != NULL);
1948   g_assert (menu_layout_node_get_type (menu_child) == MENU_LAYOUT_NODE_MENU);
1949 
1950   /* merge children of toplevel <Menu> */
1951   from_child = menu_layout_node_get_children (menu_child);
1952   while (from_child != NULL)
1953     {
1954       MenuLayoutNode *next;
1955 
1956       next = menu_layout_node_get_next (from_child);
1957 
1958       menu_verbose ("Merging ");
1959       menu_debug_print_layout (from_child, FALSE);
1960       menu_verbose (" after ");
1961       menu_debug_print_layout (insert_after, FALSE);
1962 
1963       switch (menu_layout_node_get_type (from_child))
1964         {
1965         case MENU_LAYOUT_NODE_NAME:
1966           menu_layout_node_unlink (from_child); /* delete this */
1967           break;
1968 
1969         default:
1970           menu_layout_node_steal (from_child);
1971           menu_layout_node_insert_after (insert_after, from_child);
1972           menu_layout_node_unref (from_child);
1973 
1974           insert_after = from_child;
1975           break;
1976         }
1977 
1978       from_child = next;
1979     }
1980 }
1981 
1982 static gboolean
load_merge_file(MateMenuTree * tree,GHashTable * loaded_menu_files,const char * filename,gboolean is_canonical,gboolean add_monitor,MenuLayoutNode * where)1983 load_merge_file (MateMenuTree      *tree,
1984 		 GHashTable     *loaded_menu_files,
1985                  const char     *filename,
1986                  gboolean        is_canonical,
1987 		 gboolean        add_monitor,
1988                  MenuLayoutNode *where)
1989 {
1990   MenuLayoutNode *to_merge;
1991   const char     *canonical;
1992   char           *freeme;
1993   gboolean        retval;
1994 
1995   freeme = NULL;
1996   retval = FALSE;
1997 
1998   if (!is_canonical)
1999     {
2000       canonical = freeme = realpath (filename, NULL);
2001       if (canonical == NULL)
2002         {
2003 	  if (add_monitor)
2004 	    matemenu_tree_add_menu_file_monitor (tree,
2005 					     filename,
2006 					     MENU_FILE_MONITOR_NONEXISTENT_FILE);
2007 
2008           menu_verbose ("Failed to canonicalize merge file path \"%s\": %s\n",
2009                         filename, g_strerror (errno));
2010 	  goto out;
2011         }
2012     }
2013   else
2014     {
2015       canonical = filename;
2016     }
2017 
2018   if (g_hash_table_lookup (loaded_menu_files, canonical) != NULL)
2019     {
2020       g_warning ("Not loading \"%s\": recursive loop detected in .menu files",
2021 		 canonical);
2022       retval = TRUE;
2023       goto out;
2024     }
2025 
2026   menu_verbose ("Merging file \"%s\"\n", canonical);
2027 
2028   to_merge = menu_layout_load (canonical, tree->non_prefixed_basename, NULL);
2029   if (to_merge == NULL)
2030     {
2031       menu_verbose ("No menu for file \"%s\" found when merging\n",
2032                     canonical);
2033       goto out;
2034     }
2035 
2036   retval = TRUE;
2037 
2038   g_hash_table_insert (loaded_menu_files, (char *) canonical, GUINT_TO_POINTER (TRUE));
2039 
2040   if (add_monitor)
2041     matemenu_tree_add_menu_file_monitor (tree,
2042 				      canonical,
2043 				      MENU_FILE_MONITOR_FILE);
2044 
2045   merge_resolved_children (tree, loaded_menu_files, where, to_merge);
2046 
2047   g_hash_table_remove (loaded_menu_files, canonical);
2048 
2049   menu_layout_node_unref (to_merge);
2050 
2051  out:
2052   if (freeme)
2053     g_free (freeme);
2054 
2055   return retval;
2056 }
2057 
2058 static gboolean
load_merge_file_with_config_dir(MateMenuTree * tree,GHashTable * loaded_menu_files,const char * menu_file,const char * config_dir,MenuLayoutNode * where)2059 load_merge_file_with_config_dir (MateMenuTree      *tree,
2060 				 GHashTable     *loaded_menu_files,
2061 				 const char     *menu_file,
2062 				 const char     *config_dir,
2063 				 MenuLayoutNode *where)
2064 {
2065   char     *merge_file;
2066   gboolean  loaded;
2067 
2068   loaded = FALSE;
2069 
2070   merge_file = g_build_filename (config_dir, "menus", menu_file, NULL);
2071 
2072   if (load_merge_file (tree, loaded_menu_files, merge_file, FALSE, TRUE, where))
2073     loaded = TRUE;
2074 
2075   g_free (merge_file);
2076 
2077   return loaded;
2078 }
2079 
2080 static gboolean
compare_basedir_to_config_dir(const char * canonical_basedir,const char * config_dir)2081 compare_basedir_to_config_dir (const char *canonical_basedir,
2082 			       const char *config_dir)
2083 {
2084   char     *dirname;
2085   char     *canonical_menus_dir;
2086   gboolean  retval;
2087 
2088   menu_verbose ("Checking to see if basedir '%s' is in '%s'\n",
2089 		canonical_basedir, config_dir);
2090 
2091   dirname = g_build_filename (config_dir, "menus", NULL);
2092 
2093   retval = FALSE;
2094 
2095   canonical_menus_dir = realpath (dirname, NULL);
2096   if (canonical_menus_dir != NULL &&
2097       strcmp (canonical_basedir, canonical_menus_dir) == 0)
2098     {
2099       retval = TRUE;
2100     }
2101 
2102   g_free (canonical_menus_dir);
2103   g_free (dirname);
2104 
2105   return retval;
2106 }
2107 
2108 static gboolean
load_parent_merge_file_from_basename(MateMenuTree * tree,GHashTable * loaded_menu_files,MenuLayoutNode * layout,const char * menu_file,const char * canonical_basedir)2109 load_parent_merge_file_from_basename (MateMenuTree      *tree,
2110                                       GHashTable     *loaded_menu_files,
2111 			              MenuLayoutNode *layout,
2112                                       const char     *menu_file,
2113                                       const char     *canonical_basedir)
2114 {
2115   gboolean            found_basedir;
2116   const char * const *system_config_dirs;
2117   int                 i;
2118 
2119   /* We're not interested in menu files that are in directories which are not a
2120    * parent of the base directory of this menu file */
2121   found_basedir = compare_basedir_to_config_dir (canonical_basedir,
2122 						 g_get_user_config_dir ());
2123 
2124   system_config_dirs = g_get_system_config_dirs ();
2125 
2126   i = 0;
2127   while (system_config_dirs[i] != NULL)
2128     {
2129       if (!found_basedir)
2130 	{
2131 	  found_basedir = compare_basedir_to_config_dir (canonical_basedir,
2132 							 system_config_dirs[i]);
2133 	}
2134       else
2135 	{
2136 	  menu_verbose ("Looking for parent menu file '%s' in '%s'\n",
2137 			menu_file, system_config_dirs[i]);
2138 
2139 	  if (load_merge_file_with_config_dir (tree,
2140 					       loaded_menu_files,
2141 					       menu_file,
2142 					       system_config_dirs[i],
2143 					       layout))
2144 	    {
2145 	      break;
2146 	    }
2147 	}
2148 
2149       ++i;
2150     }
2151 
2152   return system_config_dirs[i] != NULL;
2153 }
2154 
load_parent_merge_file(MateMenuTree * tree,GHashTable * loaded_menu_files,MenuLayoutNode * layout)2155 static gboolean load_parent_merge_file(MateMenuTree* tree, GHashTable* loaded_menu_files, MenuLayoutNode* layout)
2156 {
2157 	MenuLayoutNode* root;
2158 	const char* basedir;
2159 	const char* menu_name;
2160 	char* canonical_basedir;
2161 	char* menu_file;
2162 	gboolean found;
2163 
2164 	root = menu_layout_node_get_root(layout);
2165 
2166 	basedir   = menu_layout_node_root_get_basedir(root);
2167 	menu_name = menu_layout_node_root_get_name(root);
2168 
2169 	canonical_basedir = realpath (basedir, NULL);
2170 
2171 	if (canonical_basedir == NULL)
2172 	{
2173 		menu_verbose("Menu basedir '%s' no longer exists, not merging parent\n", basedir);
2174 		return FALSE;
2175 	}
2176 
2177 	found = FALSE;
2178 	menu_file = g_strconcat(menu_name, ".menu", NULL);
2179 
2180 	if (strcmp(menu_file, "mate-applications.menu") == 0 && g_getenv("XDG_MENU_PREFIX"))
2181 	{
2182 		char* prefixed_basename;
2183 		prefixed_basename = g_strdup_printf("%s%s", g_getenv("XDG_MENU_PREFIX"), menu_file);
2184 		found = load_parent_merge_file_from_basename(tree, loaded_menu_files, layout, prefixed_basename, canonical_basedir);
2185 		g_free(prefixed_basename);
2186 	}
2187 
2188 	if (!found)
2189 	{
2190 		found = load_parent_merge_file_from_basename(tree, loaded_menu_files, layout, menu_file, canonical_basedir);
2191 	}
2192 
2193 	g_free(menu_file);
2194 	g_free(canonical_basedir);
2195 
2196 	return found;
2197 }
2198 
2199 static void
load_merge_dir(MateMenuTree * tree,GHashTable * loaded_menu_files,const char * dirname,MenuLayoutNode * where)2200 load_merge_dir (MateMenuTree      *tree,
2201 		GHashTable     *loaded_menu_files,
2202                 const char     *dirname,
2203                 MenuLayoutNode *where)
2204 {
2205   GDir       *dir;
2206   const char *menu_file;
2207 
2208   menu_verbose ("Loading merge dir \"%s\"\n", dirname);
2209 
2210   matemenu_tree_add_menu_file_monitor (tree,
2211 				    dirname,
2212 				    MENU_FILE_MONITOR_DIRECTORY);
2213 
2214   if ((dir = g_dir_open (dirname, 0, NULL)) == NULL)
2215     return;
2216 
2217   while ((menu_file = g_dir_read_name (dir)))
2218     {
2219       if (g_str_has_suffix (menu_file, ".menu"))
2220         {
2221           char *full_path;
2222 
2223           full_path = g_build_filename (dirname, menu_file, NULL);
2224 
2225           load_merge_file (tree, loaded_menu_files, full_path, TRUE, FALSE, where);
2226 
2227           g_free (full_path);
2228         }
2229     }
2230 
2231   g_dir_close (dir);
2232 }
2233 
2234 static void
load_merge_dir_with_config_dir(MateMenuTree * tree,GHashTable * loaded_menu_files,const char * config_dir,const char * dirname,MenuLayoutNode * where)2235 load_merge_dir_with_config_dir (MateMenuTree      *tree,
2236 				GHashTable     *loaded_menu_files,
2237                                 const char     *config_dir,
2238                                 const char     *dirname,
2239                                 MenuLayoutNode *where)
2240 {
2241   char *path;
2242 
2243   path = g_build_filename (config_dir, "menus", dirname, NULL);
2244 
2245   load_merge_dir (tree, loaded_menu_files, path, where);
2246 
2247   g_free (path);
2248 }
2249 
2250 static void
resolve_merge_file(MateMenuTree * tree,GHashTable * loaded_menu_files,MenuLayoutNode * layout)2251 resolve_merge_file (MateMenuTree      *tree,
2252 		    GHashTable     *loaded_menu_files,
2253                     MenuLayoutNode *layout)
2254 {
2255   char *filename;
2256 
2257   if (menu_layout_node_merge_file_get_type (layout) == MENU_MERGE_FILE_TYPE_PARENT)
2258     {
2259       if (load_parent_merge_file (tree, loaded_menu_files, layout))
2260         return;
2261     }
2262 
2263   filename = menu_layout_node_get_content_as_path (layout);
2264   if (filename == NULL)
2265     {
2266       menu_verbose ("didn't get node content as a path, not merging file\n");
2267     }
2268   else
2269     {
2270       load_merge_file (tree, loaded_menu_files, filename, FALSE, TRUE, layout);
2271 
2272       g_free (filename);
2273     }
2274 
2275   /* remove the now-replaced node */
2276   menu_layout_node_unlink (layout);
2277 }
2278 
2279 static void
resolve_merge_dir(MateMenuTree * tree,GHashTable * loaded_menu_files,MenuLayoutNode * layout)2280 resolve_merge_dir (MateMenuTree      *tree,
2281 		   GHashTable     *loaded_menu_files,
2282                    MenuLayoutNode *layout)
2283 {
2284   char *path;
2285 
2286   path = menu_layout_node_get_content_as_path (layout);
2287   if (path == NULL)
2288     {
2289       menu_verbose ("didn't get layout node content as a path, not merging dir\n");
2290     }
2291   else
2292     {
2293       load_merge_dir (tree, loaded_menu_files, path, layout);
2294 
2295       g_free (path);
2296     }
2297 
2298   /* remove the now-replaced node */
2299   menu_layout_node_unlink (layout);
2300 }
2301 
2302 static MenuLayoutNode *
add_app_dir(MateMenuTree * tree,MenuLayoutNode * before,const char * data_dir)2303 add_app_dir (MateMenuTree      *tree,
2304              MenuLayoutNode *before,
2305              const char     *data_dir)
2306 {
2307   MenuLayoutNode *tmp;
2308   char           *dirname;
2309 
2310   tmp = menu_layout_node_new (MENU_LAYOUT_NODE_APP_DIR);
2311   dirname = g_build_filename (data_dir, "applications", NULL);
2312   menu_layout_node_set_content (tmp, dirname);
2313   menu_layout_node_insert_before (before, tmp);
2314   menu_layout_node_unref (before);
2315 
2316   menu_verbose ("Adding <AppDir>%s</AppDir> in <DefaultAppDirs/>\n",
2317                 dirname);
2318 
2319   g_free (dirname);
2320 
2321   return tmp;
2322 }
2323 
2324 static void
resolve_default_app_dirs(MateMenuTree * tree,MenuLayoutNode * layout)2325 resolve_default_app_dirs (MateMenuTree      *tree,
2326                           MenuLayoutNode *layout)
2327 {
2328   MenuLayoutNode     *before;
2329   const char * const *system_data_dirs;
2330   int                 i;
2331 
2332   system_data_dirs = g_get_system_data_dirs ();
2333 
2334   before = add_app_dir (tree,
2335 			menu_layout_node_ref (layout),
2336 			g_get_user_data_dir ());
2337 
2338   i = 0;
2339   while (system_data_dirs[i] != NULL)
2340     {
2341       before = add_app_dir (tree, before, system_data_dirs[i]);
2342 
2343       ++i;
2344     }
2345 
2346   menu_layout_node_unref (before);
2347 
2348   /* remove the now-replaced node */
2349   menu_layout_node_unlink (layout);
2350 }
2351 
add_directory_dir(MateMenuTree * tree,MenuLayoutNode * before,const char * data_dir)2352 static MenuLayoutNode* add_directory_dir(MateMenuTree* tree, MenuLayoutNode* before, const char* data_dir)
2353 {
2354 	MenuLayoutNode* tmp;
2355 	char* dirname;
2356 
2357 	tmp = menu_layout_node_new(MENU_LAYOUT_NODE_DIRECTORY_DIR);
2358 	dirname = g_build_filename(data_dir, "desktop-directories", NULL);
2359 	menu_layout_node_set_content(tmp, dirname);
2360 	menu_layout_node_insert_before(before, tmp);
2361 	menu_layout_node_unref(before);
2362 
2363 	menu_verbose("Adding <DirectoryDir>%s</DirectoryDir> in <DefaultDirectoryDirs/>\n", dirname);
2364 
2365 	g_free(dirname);
2366 
2367 	return tmp;
2368 }
2369 
2370 /* According to desktop spec, since our menu file is called 'mate-applications', our
2371  * merged menu folders need to be called 'mate-applications-merged'.  We'll setup the folder
2372  * 'applications-merged' if it doesn't exist yet, and a symlink pointing to it in the
2373  * ~/.config/menus directory
2374  */
2375 static void
setup_merge_dir_symlink(void)2376 setup_merge_dir_symlink(void)
2377 {
2378     gchar *user_config = (gchar *) g_get_user_config_dir();
2379     gchar *merge_path = g_build_filename (user_config, "menus", "applications-merged", NULL);
2380     GFile *merge_file = g_file_new_for_path (merge_path);
2381     gchar *sym_path;
2382     GFile *sym_file;
2383 
2384     g_file_make_directory_with_parents (merge_file, NULL, NULL);
2385 
2386     sym_path = g_build_filename (user_config, "menus", "mate-applications-merged", NULL);
2387     sym_file = g_file_new_for_path (sym_path);
2388     if (!g_file_query_exists (sym_file, NULL)) {
2389         g_file_make_symbolic_link (sym_file, merge_path, NULL, NULL);
2390     }
2391 
2392     g_free (merge_path);
2393     g_free (sym_path);
2394     g_object_unref (merge_file);
2395     g_object_unref (sym_file);
2396 }
2397 
2398 static void
resolve_default_directory_dirs(MateMenuTree * tree,MenuLayoutNode * layout)2399 resolve_default_directory_dirs (MateMenuTree      *tree,
2400                                 MenuLayoutNode *layout)
2401 {
2402   MenuLayoutNode     *before;
2403   const char * const *system_data_dirs;
2404   int                 i;
2405 
2406   system_data_dirs = g_get_system_data_dirs ();
2407 
2408   before = add_directory_dir (tree,
2409 			      menu_layout_node_ref (layout),
2410 			      g_get_user_data_dir ());
2411 
2412   i = 0;
2413   while (system_data_dirs[i] != NULL)
2414     {
2415 		/* Parche para tomar las carpetas /mate/ */
2416 		char* path = g_build_filename(system_data_dirs[i], "mate", NULL);
2417 		before = add_directory_dir(tree, before, path);
2418 		g_free(path);
2419 		/* /fin parche */
2420       before = add_directory_dir (tree, before, system_data_dirs[i]);
2421 
2422       ++i;
2423     }
2424 
2425   menu_layout_node_unref (before);
2426 
2427   /* remove the now-replaced node */
2428   menu_layout_node_unlink (layout);
2429 }
2430 
2431 static void
resolve_default_merge_dirs(MateMenuTree * tree,GHashTable * loaded_menu_files,MenuLayoutNode * layout)2432 resolve_default_merge_dirs (MateMenuTree      *tree,
2433 			    GHashTable     *loaded_menu_files,
2434                             MenuLayoutNode *layout)
2435 {
2436   MenuLayoutNode     *root;
2437   const char         *menu_name;
2438   char               *merge_name;
2439   const char * const *system_config_dirs;
2440   int                 i;
2441 
2442   setup_merge_dir_symlink();
2443 
2444   root = menu_layout_node_get_root (layout);
2445   menu_name = menu_layout_node_root_get_name (root);
2446 
2447   merge_name = g_strconcat (menu_name, "-merged", NULL);
2448 
2449   system_config_dirs = g_get_system_config_dirs ();
2450 
2451   /* Merge in reverse order */
2452   i = 0;
2453   while (system_config_dirs[i] != NULL) i++;
2454   while (i > 0)
2455     {
2456       i--;
2457       load_merge_dir_with_config_dir (tree,
2458 				      loaded_menu_files,
2459                                       system_config_dirs[i],
2460                                       merge_name,
2461                                       layout);
2462     }
2463 
2464   load_merge_dir_with_config_dir (tree,
2465 				  loaded_menu_files,
2466                                   g_get_user_config_dir (),
2467                                   merge_name,
2468                                   layout);
2469 
2470   g_free (merge_name);
2471 
2472   /* remove the now-replaced node */
2473   menu_layout_node_unlink (layout);
2474 }
2475 
2476 static void
add_filename_include(const char * desktop_file_id,DesktopEntry * entry,MenuLayoutNode * include)2477 add_filename_include (const char     *desktop_file_id,
2478                       DesktopEntry   *entry,
2479                       MenuLayoutNode *include)
2480 {
2481   if (!desktop_entry_has_categories (entry))
2482     {
2483       MenuLayoutNode *node;
2484 
2485       node = menu_layout_node_new (MENU_LAYOUT_NODE_FILENAME);
2486       menu_layout_node_set_content (node, desktop_file_id);
2487 
2488       menu_layout_node_append_child (include, node);
2489       menu_layout_node_unref (node);
2490     }
2491 }
2492 
2493 static void
is_dot_directory(const char * basename,DesktopEntry * entry,gboolean * has_dot_directory)2494 is_dot_directory (const char   *basename,
2495 		  DesktopEntry *entry,
2496 		  gboolean     *has_dot_directory)
2497 {
2498   if (!strcmp (basename, ".directory"))
2499     *has_dot_directory = TRUE;
2500 }
2501 
2502 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)2503 add_menu_for_legacy_dir (MenuLayoutNode *parent,
2504                          const char     *legacy_dir,
2505                 	 const char     *relative_path,
2506                          const char     *legacy_prefix,
2507                          const char     *menu_name)
2508 {
2509   EntryDirectory  *ed;
2510   DesktopEntrySet *desktop_entries;
2511   DesktopEntrySet *directory_entries;
2512   GSList          *subdirs;
2513   gboolean         menu_added;
2514   gboolean         has_dot_directory;
2515 
2516   ed = entry_directory_new_legacy (DESKTOP_ENTRY_INVALID, legacy_dir, legacy_prefix);
2517   if (!ed)
2518     return FALSE;
2519 
2520   subdirs = NULL;
2521   desktop_entries   = desktop_entry_set_new ();
2522   directory_entries = desktop_entry_set_new ();
2523 
2524   entry_directory_get_flat_contents (ed,
2525                                      desktop_entries,
2526                                      directory_entries,
2527                                      &subdirs);
2528   entry_directory_unref (ed);
2529 
2530   has_dot_directory = FALSE;
2531   desktop_entry_set_foreach (directory_entries,
2532 			     (DesktopEntrySetForeachFunc) is_dot_directory,
2533 			     &has_dot_directory);
2534   desktop_entry_set_unref (directory_entries);
2535 
2536   menu_added = FALSE;
2537   if (desktop_entry_set_get_count (desktop_entries) > 0 || subdirs)
2538     {
2539       MenuLayoutNode *menu;
2540       MenuLayoutNode *node;
2541       GString        *subdir_path;
2542       GString        *subdir_relative;
2543       GSList         *tmp;
2544       size_t          legacy_dir_len;
2545       size_t          relative_path_len;
2546 
2547       menu = menu_layout_node_new (MENU_LAYOUT_NODE_MENU);
2548       menu_layout_node_append_child (parent, menu);
2549 
2550       menu_added = TRUE;
2551 
2552       g_assert (menu_name != NULL);
2553 
2554       node = menu_layout_node_new (MENU_LAYOUT_NODE_NAME);
2555       menu_layout_node_set_content (node, menu_name);
2556       menu_layout_node_append_child (menu, node);
2557       menu_layout_node_unref (node);
2558 
2559       if (has_dot_directory)
2560 	{
2561 	  node = menu_layout_node_new (MENU_LAYOUT_NODE_DIRECTORY);
2562 	  if (relative_path != NULL)
2563 	    {
2564 	      char *directory_entry_path;
2565 
2566 	      directory_entry_path = g_strdup_printf ("%s/.directory", relative_path);
2567 	      menu_layout_node_set_content (node, directory_entry_path);
2568 	      g_free (directory_entry_path);
2569 	    }
2570 	  else
2571 	    {
2572 	      menu_layout_node_set_content (node, ".directory");
2573 	    }
2574 	  menu_layout_node_append_child (menu, node);
2575 	  menu_layout_node_unref (node);
2576 	}
2577 
2578       if (desktop_entry_set_get_count (desktop_entries) > 0)
2579 	{
2580 	  MenuLayoutNode *include;
2581 
2582 	  include = menu_layout_node_new (MENU_LAYOUT_NODE_INCLUDE);
2583 	  menu_layout_node_append_child (menu, include);
2584 
2585 	  desktop_entry_set_foreach (desktop_entries,
2586 				     (DesktopEntrySetForeachFunc) add_filename_include,
2587 				     include);
2588 
2589 	  menu_layout_node_unref (include);
2590 	}
2591 
2592       subdir_path = g_string_new (legacy_dir);
2593       legacy_dir_len = strlen (legacy_dir);
2594 
2595       subdir_relative = g_string_new (relative_path);
2596       relative_path_len = relative_path ? strlen (relative_path) : 0;
2597 
2598       tmp = subdirs;
2599       while (tmp != NULL)
2600         {
2601           const char *subdir = tmp->data;
2602 
2603           g_string_append_c (subdir_path, G_DIR_SEPARATOR);
2604           g_string_append (subdir_path, subdir);
2605 
2606 	  if (relative_path_len)
2607 	    {
2608 	      g_string_append_c (subdir_relative, G_DIR_SEPARATOR);
2609 	    }
2610           g_string_append (subdir_relative, subdir);
2611 
2612           add_menu_for_legacy_dir (menu,
2613                                    subdir_path->str,
2614 				   subdir_relative->str,
2615                                    legacy_prefix,
2616                                    subdir);
2617 
2618           g_string_truncate (subdir_relative, relative_path_len);
2619           g_string_truncate (subdir_path, legacy_dir_len);
2620 
2621           tmp = tmp->next;
2622         }
2623 
2624       g_string_free (subdir_path, TRUE);
2625       g_string_free (subdir_relative, TRUE);
2626 
2627       menu_layout_node_unref (menu);
2628     }
2629 
2630   desktop_entry_set_unref (desktop_entries);
2631 
2632   g_slist_foreach (subdirs, (GFunc) g_free, NULL);
2633   g_slist_free (subdirs);
2634 
2635   return menu_added;
2636 }
2637 
2638 static void
resolve_legacy_dir(MateMenuTree * tree,GHashTable * loaded_menu_files,MenuLayoutNode * legacy)2639 resolve_legacy_dir (MateMenuTree      *tree,
2640 		    GHashTable     *loaded_menu_files,
2641                     MenuLayoutNode *legacy)
2642 {
2643   MenuLayoutNode *to_merge;
2644   MenuLayoutNode *menu;
2645 
2646   to_merge = menu_layout_node_new (MENU_LAYOUT_NODE_ROOT);
2647 
2648   menu = menu_layout_node_get_parent (legacy);
2649   g_assert (menu_layout_node_get_type (menu) == MENU_LAYOUT_NODE_MENU);
2650 
2651   if (add_menu_for_legacy_dir (to_merge,
2652                                menu_layout_node_get_content (legacy),
2653 			       NULL,
2654                                menu_layout_node_legacy_dir_get_prefix (legacy),
2655                                menu_layout_node_menu_get_name (menu)))
2656     {
2657       merge_resolved_children (tree, loaded_menu_files, legacy, to_merge);
2658     }
2659 
2660   menu_layout_node_unref (to_merge);
2661 }
2662 
2663 static MenuLayoutNode *
add_legacy_dir(MateMenuTree * tree,GHashTable * loaded_menu_files,MenuLayoutNode * before,const char * data_dir)2664 add_legacy_dir (MateMenuTree      *tree,
2665 		GHashTable     *loaded_menu_files,
2666                 MenuLayoutNode *before,
2667                 const char     *data_dir)
2668 {
2669   MenuLayoutNode *legacy;
2670   char           *dirname;
2671 
2672   dirname = g_build_filename (data_dir, "applnk", NULL);
2673 
2674   legacy = menu_layout_node_new (MENU_LAYOUT_NODE_LEGACY_DIR);
2675   menu_layout_node_set_content (legacy, dirname);
2676   menu_layout_node_legacy_dir_set_prefix (legacy, "kde");
2677   menu_layout_node_insert_before (before, legacy);
2678   menu_layout_node_unref (before);
2679 
2680   menu_verbose ("Adding <LegacyDir>%s</LegacyDir> in <KDELegacyDirs/>\n",
2681                 dirname);
2682 
2683   resolve_legacy_dir (tree, loaded_menu_files, legacy);
2684 
2685   g_free (dirname);
2686 
2687   return legacy;
2688 }
2689 
2690 static void
resolve_kde_legacy_dirs(MateMenuTree * tree,GHashTable * loaded_menu_files,MenuLayoutNode * layout)2691 resolve_kde_legacy_dirs (MateMenuTree      *tree,
2692 			 GHashTable     *loaded_menu_files,
2693                          MenuLayoutNode *layout)
2694 {
2695   MenuLayoutNode     *before;
2696   const char * const *system_data_dirs;
2697   int                 i;
2698 
2699   system_data_dirs = g_get_system_data_dirs ();
2700 
2701   before = add_legacy_dir (tree,
2702 			   loaded_menu_files,
2703 			   menu_layout_node_ref (layout),
2704 			   g_get_user_data_dir ());
2705 
2706   i = 0;
2707   while (system_data_dirs[i] != NULL)
2708     {
2709       before = add_legacy_dir (tree, loaded_menu_files, before, system_data_dirs[i]);
2710 
2711       ++i;
2712     }
2713 
2714   menu_layout_node_unref (before);
2715 
2716   /* remove the now-replaced node */
2717   menu_layout_node_unlink (layout);
2718 }
2719 
2720 static void
matemenu_tree_resolve_files(MateMenuTree * tree,GHashTable * loaded_menu_files,MenuLayoutNode * layout)2721 matemenu_tree_resolve_files (MateMenuTree      *tree,
2722 			  GHashTable     *loaded_menu_files,
2723 			  MenuLayoutNode *layout)
2724 {
2725   MenuLayoutNode *child;
2726 
2727   menu_verbose ("Resolving files in: ");
2728   menu_debug_print_layout (layout, TRUE);
2729 
2730   switch (menu_layout_node_get_type (layout))
2731     {
2732     case MENU_LAYOUT_NODE_MERGE_FILE:
2733       resolve_merge_file (tree, loaded_menu_files, layout);
2734       break;
2735 
2736     case MENU_LAYOUT_NODE_MERGE_DIR:
2737       resolve_merge_dir (tree, loaded_menu_files, layout);
2738       break;
2739 
2740     case MENU_LAYOUT_NODE_DEFAULT_APP_DIRS:
2741       resolve_default_app_dirs (tree, layout);
2742       break;
2743 
2744     case MENU_LAYOUT_NODE_DEFAULT_DIRECTORY_DIRS:
2745       resolve_default_directory_dirs (tree, layout);
2746       break;
2747 
2748     case MENU_LAYOUT_NODE_DEFAULT_MERGE_DIRS:
2749       resolve_default_merge_dirs (tree, loaded_menu_files, layout);
2750       break;
2751 
2752     case MENU_LAYOUT_NODE_LEGACY_DIR:
2753       resolve_legacy_dir (tree, loaded_menu_files, layout);
2754       break;
2755 
2756     case MENU_LAYOUT_NODE_KDE_LEGACY_DIRS:
2757       resolve_kde_legacy_dirs (tree, loaded_menu_files, layout);
2758       break;
2759 
2760     case MENU_LAYOUT_NODE_PASSTHROUGH:
2761       /* Just get rid of these, we don't need the memory usage */
2762       menu_layout_node_unlink (layout);
2763       break;
2764 
2765     default:
2766       /* Recurse */
2767       child = menu_layout_node_get_children (layout);
2768       while (child != NULL)
2769         {
2770           MenuLayoutNode *next = menu_layout_node_get_next (child);
2771 
2772           matemenu_tree_resolve_files (tree, loaded_menu_files, child);
2773 
2774           child = next;
2775         }
2776       break;
2777     }
2778 }
2779 
2780 static void
move_children(MenuLayoutNode * from,MenuLayoutNode * to)2781 move_children (MenuLayoutNode *from,
2782                MenuLayoutNode *to)
2783 {
2784   MenuLayoutNode *from_child;
2785   MenuLayoutNode *insert_before;
2786 
2787   insert_before = menu_layout_node_get_children (to);
2788   from_child    = menu_layout_node_get_children (from);
2789 
2790   while (from_child != NULL)
2791     {
2792       MenuLayoutNode *next;
2793 
2794       next = menu_layout_node_get_next (from_child);
2795 
2796       menu_layout_node_steal (from_child);
2797 
2798       if (menu_layout_node_get_type (from_child) == MENU_LAYOUT_NODE_NAME)
2799         {
2800           ; /* just drop the Name in the old <Menu> */
2801         }
2802       else if (insert_before)
2803         {
2804           menu_layout_node_insert_before (insert_before, from_child);
2805           g_assert (menu_layout_node_get_next (from_child) == insert_before);
2806         }
2807       else
2808         {
2809           menu_layout_node_append_child (to, from_child);
2810         }
2811 
2812       menu_layout_node_unref (from_child);
2813 
2814       from_child = next;
2815     }
2816 }
2817 
2818 static int
null_safe_strcmp(const char * a,const char * b)2819 null_safe_strcmp (const char *a,
2820                   const char *b)
2821 {
2822   if (a == NULL && b == NULL)
2823     return 0;
2824   else if (a == NULL)
2825     return -1;
2826   else if (b == NULL)
2827     return 1;
2828   else
2829     return strcmp (a, b);
2830 }
2831 
2832 static int
node_compare_func(const void * a,const void * b)2833 node_compare_func (const void *a,
2834                    const void *b)
2835 {
2836   MenuLayoutNode *node_a = (MenuLayoutNode*) a;
2837   MenuLayoutNode *node_b = (MenuLayoutNode*) b;
2838   MenuLayoutNodeType t_a = menu_layout_node_get_type (node_a);
2839   MenuLayoutNodeType t_b = menu_layout_node_get_type (node_b);
2840 
2841   if (t_a < t_b)
2842     return -1;
2843   else if (t_a > t_b)
2844     return 1;
2845   else
2846     {
2847       const char *c_a = menu_layout_node_get_content (node_a);
2848       const char *c_b = menu_layout_node_get_content (node_b);
2849 
2850       return null_safe_strcmp (c_a, c_b);
2851     }
2852 }
2853 
2854 static int
node_menu_compare_func(const void * a,const void * b)2855 node_menu_compare_func (const void *a,
2856                         const void *b)
2857 {
2858   MenuLayoutNode *node_a = (MenuLayoutNode*) a;
2859   MenuLayoutNode *node_b = (MenuLayoutNode*) b;
2860   MenuLayoutNode *parent_a = menu_layout_node_get_parent (node_a);
2861   MenuLayoutNode *parent_b = menu_layout_node_get_parent (node_b);
2862 
2863   if (parent_a < parent_b)
2864     return -1;
2865   else if (parent_a > parent_b)
2866     return 1;
2867   else
2868     return null_safe_strcmp (menu_layout_node_menu_get_name (node_a),
2869                              menu_layout_node_menu_get_name (node_b));
2870 }
2871 
2872 static void
matemenu_tree_strip_duplicate_children(MateMenuTree * tree,MenuLayoutNode * layout)2873 matemenu_tree_strip_duplicate_children (MateMenuTree      *tree,
2874 				     MenuLayoutNode *layout)
2875 {
2876   MenuLayoutNode *child;
2877   GSList         *simple_nodes;
2878   GSList         *menu_layout_nodes;
2879   GSList         *prev;
2880   GSList         *tmp;
2881 
2882   /* to strip dups, we find all the child nodes where
2883    * we want to kill dups, sort them,
2884    * then nuke the adjacent nodes that are equal
2885    */
2886 
2887   simple_nodes = NULL;
2888   menu_layout_nodes = NULL;
2889 
2890   child = menu_layout_node_get_children (layout);
2891   while (child != NULL)
2892     {
2893       switch (menu_layout_node_get_type (child))
2894         {
2895           /* These are dups if their content is the same */
2896         case MENU_LAYOUT_NODE_APP_DIR:
2897         case MENU_LAYOUT_NODE_DIRECTORY_DIR:
2898         case MENU_LAYOUT_NODE_DIRECTORY:
2899           simple_nodes = g_slist_prepend (simple_nodes, child);
2900           break;
2901 
2902           /* These have to be merged in a more complicated way,
2903            * and then recursed
2904            */
2905         case MENU_LAYOUT_NODE_MENU:
2906           menu_layout_nodes = g_slist_prepend (menu_layout_nodes, child);
2907           break;
2908 
2909         default:
2910           break;
2911         }
2912 
2913       child = menu_layout_node_get_next (child);
2914     }
2915 
2916   /* Note that the lists are all backward. So we want to keep
2917    * the items that are earlier in the list, because they were
2918    * later in the file
2919    */
2920 
2921   /* stable sort the simple nodes */
2922   simple_nodes = g_slist_sort (simple_nodes,
2923                                node_compare_func);
2924 
2925   prev = NULL;
2926   tmp = simple_nodes;
2927   while (tmp != NULL)
2928     {
2929       GSList *next = tmp->next;
2930 
2931       if (prev)
2932         {
2933           MenuLayoutNode *p = prev->data;
2934           MenuLayoutNode *n = tmp->data;
2935 
2936           if (node_compare_func (p, n) == 0)
2937             {
2938               /* nuke it! */
2939               menu_layout_node_unlink (n);
2940 	      simple_nodes = g_slist_delete_link (simple_nodes, tmp);
2941 	      tmp = prev;
2942             }
2943         }
2944 
2945       prev = tmp;
2946       tmp = next;
2947     }
2948 
2949   g_slist_free (simple_nodes);
2950   simple_nodes = NULL;
2951 
2952   /* stable sort the menu nodes (the sort includes the
2953    * parents of the nodes in the comparison). Remember
2954    * the list is backward.
2955    */
2956   menu_layout_nodes = g_slist_sort (menu_layout_nodes,
2957 				    node_menu_compare_func);
2958 
2959   prev = NULL;
2960   tmp = menu_layout_nodes;
2961   while (tmp != NULL)
2962     {
2963       GSList *next = tmp->next;
2964 
2965       if (prev)
2966         {
2967           MenuLayoutNode *p = prev->data;
2968           MenuLayoutNode *n = tmp->data;
2969 
2970           if (node_menu_compare_func (p, n) == 0)
2971             {
2972               /* Move children of first menu to the start of second
2973                * menu and nuke the first menu
2974                */
2975               move_children (n, p);
2976               menu_layout_node_unlink (n);
2977 	      menu_layout_nodes = g_slist_delete_link (menu_layout_nodes, tmp);
2978 	      tmp = prev;
2979             }
2980         }
2981 
2982       prev = tmp;
2983       tmp = next;
2984     }
2985 
2986   g_slist_free (menu_layout_nodes);
2987   menu_layout_nodes = NULL;
2988 
2989   /* Recursively clean up all children */
2990   child = menu_layout_node_get_children (layout);
2991   while (child != NULL)
2992     {
2993       if (menu_layout_node_get_type (child) == MENU_LAYOUT_NODE_MENU)
2994         matemenu_tree_strip_duplicate_children (tree, child);
2995 
2996       child = menu_layout_node_get_next (child);
2997     }
2998 }
2999 
3000 static MenuLayoutNode *
find_submenu(MenuLayoutNode * layout,const char * path,gboolean create_if_not_found)3001 find_submenu (MenuLayoutNode *layout,
3002               const char     *path,
3003               gboolean        create_if_not_found)
3004 {
3005   MenuLayoutNode *child;
3006   const char     *slash;
3007   const char     *next_path;
3008   char           *name;
3009 
3010   menu_verbose (" (splitting \"%s\")\n", path);
3011 
3012   if (path[0] == '\0' || path[0] == G_DIR_SEPARATOR)
3013     return NULL;
3014 
3015   slash = strchr (path, G_DIR_SEPARATOR);
3016   if (slash != NULL)
3017     {
3018       name = g_strndup (path, (gsize)(slash - path));
3019       next_path = slash + 1;
3020       if (*next_path == '\0')
3021         next_path = NULL;
3022     }
3023   else
3024     {
3025       name = g_strdup (path);
3026       next_path = NULL;
3027     }
3028 
3029   child = menu_layout_node_get_children (layout);
3030   while (child != NULL)
3031     {
3032       switch (menu_layout_node_get_type (child))
3033         {
3034         case MENU_LAYOUT_NODE_MENU:
3035           {
3036             if (strcmp (name, menu_layout_node_menu_get_name (child)) == 0)
3037               {
3038                 menu_verbose ("MenuNode %p found for path component \"%s\"\n",
3039                               child, name);
3040 
3041                 g_free (name);
3042 
3043                 if (!next_path)
3044                   {
3045                     menu_verbose (" Found menu node %p parent is %p\n",
3046                                   child, layout);
3047                     return child;
3048                   }
3049 
3050                 return find_submenu (child, next_path, create_if_not_found);
3051               }
3052           }
3053           break;
3054 
3055         default:
3056           break;
3057         }
3058 
3059       child = menu_layout_node_get_next (child);
3060     }
3061 
3062   if (create_if_not_found)
3063     {
3064       MenuLayoutNode *name_node;
3065 
3066       child = menu_layout_node_new (MENU_LAYOUT_NODE_MENU);
3067       menu_layout_node_append_child (layout, child);
3068 
3069       name_node = menu_layout_node_new (MENU_LAYOUT_NODE_NAME);
3070       menu_layout_node_set_content (name_node, name);
3071       menu_layout_node_append_child (child, name_node);
3072       menu_layout_node_unref (name_node);
3073 
3074       menu_verbose (" Created menu node %p parent is %p\n",
3075                     child, layout);
3076 
3077       menu_layout_node_unref (child);
3078       g_free (name);
3079 
3080       if (!next_path)
3081         return child;
3082 
3083       return find_submenu (child, next_path, create_if_not_found);
3084     }
3085   else
3086     {
3087       g_free (name);
3088       return NULL;
3089     }
3090 }
3091 
3092 /* To call this you first have to strip duplicate children once,
3093  * otherwise when you move a menu Foo to Bar then you may only
3094  * move one of Foo, not all the merged Foo.
3095  */
3096 static void
matemenu_tree_execute_moves(MateMenuTree * tree,MenuLayoutNode * layout,gboolean * need_remove_dups_p)3097 matemenu_tree_execute_moves (MateMenuTree      *tree,
3098 			  MenuLayoutNode *layout,
3099 			  gboolean       *need_remove_dups_p)
3100 {
3101   MenuLayoutNode *child;
3102   gboolean        need_remove_dups;
3103   GSList         *move_nodes;
3104   GSList         *tmp;
3105 
3106   need_remove_dups = FALSE;
3107 
3108   move_nodes = NULL;
3109 
3110   child = menu_layout_node_get_children (layout);
3111   while (child != NULL)
3112     {
3113       switch (menu_layout_node_get_type (child))
3114         {
3115         case MENU_LAYOUT_NODE_MENU:
3116           /* Recurse - we recurse first and process the current node
3117            * second, as the spec dictates.
3118            */
3119           matemenu_tree_execute_moves (tree, child, &need_remove_dups);
3120           break;
3121 
3122         case MENU_LAYOUT_NODE_MOVE:
3123           move_nodes = g_slist_prepend (move_nodes, child);
3124           break;
3125 
3126         default:
3127           break;
3128         }
3129 
3130       child = menu_layout_node_get_next (child);
3131     }
3132 
3133   /* We need to execute the move operations in the order that they appear */
3134   move_nodes = g_slist_reverse (move_nodes);
3135 
3136   tmp = move_nodes;
3137   while (tmp != NULL)
3138     {
3139       MenuLayoutNode *move_node = tmp->data;
3140       MenuLayoutNode *old_node;
3141       GSList         *next = tmp->next;
3142       const char     *old;
3143       const char     *new;
3144 
3145       old = menu_layout_node_move_get_old (move_node);
3146       new = menu_layout_node_move_get_new (move_node);
3147       g_assert (old != NULL && new != NULL);
3148 
3149       menu_verbose ("executing <Move> old = \"%s\" new = \"%s\"\n",
3150                     old, new);
3151 
3152       old_node = find_submenu (layout, old, FALSE);
3153       if (old_node != NULL)
3154         {
3155           MenuLayoutNode *new_node;
3156 
3157           /* here we can create duplicates anywhere below the
3158            * node
3159            */
3160           need_remove_dups = TRUE;
3161 
3162           /* look up new node creating it and its parents if
3163            * required
3164            */
3165           new_node = find_submenu (layout, new, TRUE);
3166           g_assert (new_node != NULL);
3167 
3168           move_children (old_node, new_node);
3169 
3170           menu_layout_node_unlink (old_node);
3171         }
3172 
3173       menu_layout_node_unlink (move_node);
3174 
3175       tmp = next;
3176     }
3177 
3178   g_slist_free (move_nodes);
3179 
3180   /* This oddness is to ensure we only remove dups once,
3181    * at the root, instead of recursing the tree over
3182    * and over.
3183    */
3184   if (need_remove_dups_p)
3185     *need_remove_dups_p = need_remove_dups;
3186   else if (need_remove_dups)
3187     matemenu_tree_strip_duplicate_children (tree, layout);
3188 }
3189 
3190 static gboolean
matemenu_tree_load_layout(MateMenuTree * tree,GError ** error)3191 matemenu_tree_load_layout (MateMenuTree  *tree,
3192                         GError    **error)
3193 {
3194   GHashTable *loaded_menu_files;
3195 
3196   if (tree->layout)
3197     return TRUE;
3198 
3199   if (!matemenu_tree_canonicalize_path (tree, error))
3200     return FALSE;
3201 
3202   menu_verbose ("Loading menu layout from \"%s\"\n",
3203                 tree->canonical_path);
3204 
3205   tree->layout = menu_layout_load (tree->canonical_path,
3206                                    tree->non_prefixed_basename,
3207                                    error);
3208   if (!tree->layout)
3209     return FALSE;
3210 
3211   loaded_menu_files = g_hash_table_new (g_str_hash, g_str_equal);
3212   g_hash_table_insert (loaded_menu_files, tree->canonical_path, GUINT_TO_POINTER (TRUE));
3213   matemenu_tree_resolve_files (tree, loaded_menu_files, tree->layout);
3214   g_hash_table_destroy (loaded_menu_files);
3215 
3216   matemenu_tree_strip_duplicate_children (tree, tree->layout);
3217   matemenu_tree_execute_moves (tree, tree->layout, NULL);
3218 
3219   return TRUE;
3220 }
3221 
3222 static void
matemenu_tree_force_reload(MateMenuTree * tree)3223 matemenu_tree_force_reload (MateMenuTree *tree)
3224 {
3225   matemenu_tree_force_rebuild (tree);
3226 
3227   if (tree->layout)
3228     menu_layout_node_unref (tree->layout);
3229   tree->layout = NULL;
3230 }
3231 
3232 typedef struct
3233 {
3234   DesktopEntrySet *set;
3235   const char      *category;
3236 } GetByCategoryForeachData;
3237 
3238 static void
get_by_category_foreach(const char * file_id,DesktopEntry * entry,GetByCategoryForeachData * data)3239 get_by_category_foreach (const char               *file_id,
3240 			 DesktopEntry             *entry,
3241 			 GetByCategoryForeachData *data)
3242 {
3243   if (desktop_entry_has_category (entry, data->category))
3244     desktop_entry_set_add_entry (data->set, entry, file_id);
3245 }
3246 
3247 static void
get_by_desktop_foreach(const char * file_id,DesktopEntry * entry,GetByCategoryForeachData * data)3248 get_by_desktop_foreach (const char               *file_id,
3249                         DesktopEntry             *entry,
3250                         GetByCategoryForeachData *data)
3251 {
3252   if (g_strcmp0 (file_id, data->category) == 0)
3253     desktop_entry_set_add_entry (data->set, entry, file_id);
3254 }
3255 
3256 static void
get_by_category(DesktopEntrySet * entry_pool,DesktopEntrySet * set,const char * category)3257 get_by_category (DesktopEntrySet *entry_pool,
3258 		 DesktopEntrySet *set,
3259 		 const char      *category)
3260 {
3261   GetByCategoryForeachData data;
3262 
3263   data.set      = set;
3264   data.category = category;
3265 
3266   desktop_entry_set_foreach (entry_pool,
3267                              (DesktopEntrySetForeachFunc) get_by_category_foreach,
3268                              &data);
3269 }
3270 
3271 static void
get_by_desktop(DesktopEntrySet * entry_pool,DesktopEntrySet * set,const char * desktop_name)3272 get_by_desktop (DesktopEntrySet *entry_pool,
3273                 DesktopEntrySet *set,
3274                 const char      *desktop_name)
3275 {
3276   GetByCategoryForeachData data;
3277 
3278   data.set      = set;
3279   data.category = desktop_name;
3280 
3281   desktop_entry_set_foreach (entry_pool,
3282                              (DesktopEntrySetForeachFunc) get_by_desktop_foreach,
3283                              &data);
3284 }
3285 
3286 static DesktopEntrySet *
process_include_rules(MenuLayoutNode * layout,DesktopEntrySet * entry_pool)3287 process_include_rules (MenuLayoutNode  *layout,
3288 		       DesktopEntrySet *entry_pool)
3289 {
3290   DesktopEntrySet *set = NULL;
3291 
3292   switch (menu_layout_node_get_type (layout))
3293     {
3294     case MENU_LAYOUT_NODE_AND:
3295       {
3296         MenuLayoutNode *child;
3297 
3298 	menu_verbose ("Processing <And>\n");
3299 
3300         child = menu_layout_node_get_children (layout);
3301         while (child != NULL)
3302           {
3303             DesktopEntrySet *child_set;
3304 
3305             child_set = process_include_rules (child, entry_pool);
3306 
3307             if (set == NULL)
3308               {
3309                 set = child_set;
3310               }
3311             else
3312               {
3313                 desktop_entry_set_intersection (set, child_set);
3314                 desktop_entry_set_unref (child_set);
3315               }
3316 
3317             /* as soon as we get empty results, we can bail,
3318              * because it's an AND
3319              */
3320             if (desktop_entry_set_get_count (set) == 0)
3321               break;
3322 
3323             child = menu_layout_node_get_next (child);
3324           }
3325 	menu_verbose ("Processed <And>\n");
3326       }
3327       break;
3328 
3329     case MENU_LAYOUT_NODE_OR:
3330       {
3331         MenuLayoutNode *child;
3332 
3333 	menu_verbose ("Processing <Or>\n");
3334 
3335         child = menu_layout_node_get_children (layout);
3336         while (child != NULL)
3337           {
3338             DesktopEntrySet *child_set;
3339 
3340             child_set = process_include_rules (child, entry_pool);
3341 
3342             if (set == NULL)
3343               {
3344                 set = child_set;
3345               }
3346             else
3347               {
3348                 desktop_entry_set_union (set, child_set);
3349                 desktop_entry_set_unref (child_set);
3350               }
3351 
3352             child = menu_layout_node_get_next (child);
3353           }
3354 	menu_verbose ("Processed <Or>\n");
3355       }
3356       break;
3357 
3358     case MENU_LAYOUT_NODE_NOT:
3359       {
3360         /* First get the OR of all the rules */
3361         MenuLayoutNode *child;
3362 
3363 	menu_verbose ("Processing <Not>\n");
3364 
3365         child = menu_layout_node_get_children (layout);
3366         while (child != NULL)
3367           {
3368             DesktopEntrySet *child_set;
3369 
3370             child_set = process_include_rules (child, entry_pool);
3371 
3372             if (set == NULL)
3373               {
3374                 set = child_set;
3375               }
3376             else
3377               {
3378                 desktop_entry_set_union (set, child_set);
3379                 desktop_entry_set_unref (child_set);
3380               }
3381 
3382             child = menu_layout_node_get_next (child);
3383           }
3384 
3385         if (set != NULL)
3386           {
3387 	    DesktopEntrySet *inverted;
3388 
3389 	    /* Now invert the result */
3390 	    inverted = desktop_entry_set_new ();
3391 	    desktop_entry_set_union (inverted, entry_pool);
3392 	    desktop_entry_set_subtract (inverted, set);
3393 	    desktop_entry_set_unref (set);
3394 	    set = inverted;
3395           }
3396 	menu_verbose ("Processed <Not>\n");
3397       }
3398       break;
3399 
3400     case MENU_LAYOUT_NODE_ALL:
3401       menu_verbose ("Processing <All>\n");
3402       set = desktop_entry_set_new ();
3403       desktop_entry_set_union (set, entry_pool);
3404       menu_verbose ("Processed <All>\n");
3405       break;
3406 
3407     case MENU_LAYOUT_NODE_FILENAME:
3408       {
3409         DesktopEntry *entry;
3410 
3411 	menu_verbose ("Processing <Filename>%s</Filename>\n",
3412 		      menu_layout_node_get_content (layout));
3413 
3414         entry = desktop_entry_set_lookup (entry_pool,
3415 					  menu_layout_node_get_content (layout));
3416         if (entry != NULL)
3417           {
3418             set = desktop_entry_set_new ();
3419             desktop_entry_set_add_entry (set,
3420                                          entry,
3421                                          menu_layout_node_get_content (layout));
3422           }
3423 	menu_verbose ("Processed <Filename>%s</Filename>\n",
3424 		      menu_layout_node_get_content (layout));
3425       }
3426       break;
3427 
3428     case MENU_LAYOUT_NODE_CATEGORY:
3429       menu_verbose ("Processing <Category>%s</Category>\n",
3430 		    menu_layout_node_get_content (layout));
3431       set = desktop_entry_set_new ();
3432       get_by_category (entry_pool, set, menu_layout_node_get_content (layout));
3433       menu_verbose ("Processed <Category>%s</Category>\n",
3434 		    menu_layout_node_get_content (layout));
3435       break;
3436 
3437     default:
3438       break;
3439     }
3440 
3441   if (set == NULL)
3442     set = desktop_entry_set_new (); /* create an empty set */
3443 
3444   menu_verbose ("Matched %u entries\n", desktop_entry_set_get_count (set));
3445 
3446   return set;
3447 }
3448 
3449 static void
collect_layout_info(MenuLayoutNode * layout,GSList ** layout_info)3450 collect_layout_info (MenuLayoutNode  *layout,
3451 		     GSList         **layout_info)
3452 {
3453   MenuLayoutNode *iter;
3454 
3455   g_slist_foreach (*layout_info,
3456 		   (GFunc) menu_layout_node_unref,
3457 		   NULL);
3458   g_slist_free (*layout_info);
3459   *layout_info = NULL;
3460 
3461   iter = menu_layout_node_get_children (layout);
3462   while (iter != NULL)
3463     {
3464       switch (menu_layout_node_get_type (iter))
3465 	{
3466 	case MENU_LAYOUT_NODE_MENUNAME:
3467 	case MENU_LAYOUT_NODE_FILENAME:
3468 	case MENU_LAYOUT_NODE_SEPARATOR:
3469 	case MENU_LAYOUT_NODE_MERGE:
3470 	  *layout_info = g_slist_prepend (*layout_info,
3471 					  menu_layout_node_ref (iter));
3472 	  break;
3473 
3474 	default:
3475 	  break;
3476 	}
3477 
3478       iter = menu_layout_node_get_next (iter);
3479     }
3480 
3481   *layout_info = g_slist_reverse (*layout_info);
3482 }
3483 
3484 static void
entries_listify_foreach(const char * desktop_file_id,DesktopEntry * desktop_entry,MateMenuTreeDirectory * directory)3485 entries_listify_foreach (const char         *desktop_file_id,
3486                          DesktopEntry       *desktop_entry,
3487                          MateMenuTreeDirectory *directory)
3488 {
3489   directory->entries =
3490     g_slist_prepend (directory->entries,
3491 		     matemenu_tree_entry_new (directory,
3492                                            desktop_entry,
3493                                            desktop_file_id,
3494                                            FALSE,
3495                                            FALSE));
3496 }
3497 
3498 static void
excluded_entries_listify_foreach(const char * desktop_file_id,DesktopEntry * desktop_entry,MateMenuTreeDirectory * directory)3499 excluded_entries_listify_foreach (const char         *desktop_file_id,
3500 				  DesktopEntry       *desktop_entry,
3501 				  MateMenuTreeDirectory *directory)
3502 {
3503   directory->entries =
3504     g_slist_prepend (directory->entries,
3505 		     matemenu_tree_entry_new (directory,
3506 					   desktop_entry,
3507 					   desktop_file_id,
3508 					   TRUE,
3509                                            FALSE));
3510 }
3511 
3512 static void
unallocated_entries_listify_foreach(const char * desktop_file_id,DesktopEntry * desktop_entry,MateMenuTreeDirectory * directory)3513 unallocated_entries_listify_foreach (const char         *desktop_file_id,
3514                                      DesktopEntry       *desktop_entry,
3515                                      MateMenuTreeDirectory *directory)
3516 {
3517   directory->entries =
3518     g_slist_prepend (directory->entries,
3519 		     matemenu_tree_entry_new (directory,
3520                                            desktop_entry,
3521                                            desktop_file_id,
3522                                            FALSE,
3523                                            TRUE));
3524 }
3525 
3526 static void
set_default_layout_values(MateMenuTreeDirectory * parent,MateMenuTreeDirectory * child)3527 set_default_layout_values (MateMenuTreeDirectory *parent,
3528                            MateMenuTreeDirectory *child)
3529 {
3530   GSList *tmp;
3531 
3532   /* if the child has a defined default layout, we don't want to override its
3533    * values. The parent might have a non-defined layout info (ie, no child of
3534    * the DefaultLayout node) but it doesn't meant the default layout values
3535    * (ie, DefaultLayout attributes) aren't different from the global defaults.
3536    */
3537   if (child->default_layout_info != NULL ||
3538       child->default_layout_values.mask != MENU_LAYOUT_VALUES_NONE)
3539     return;
3540 
3541   child->default_layout_values = parent->default_layout_values;
3542 
3543   tmp = child->subdirs;
3544   while (tmp != NULL)
3545     {
3546       MateMenuTreeDirectory *subdir = tmp->data;
3547 
3548       set_default_layout_values (child, subdir);
3549 
3550       tmp = tmp->next;
3551    }
3552 }
3553 
3554 static MateMenuTreeDirectory *
process_layout(MateMenuTree * tree,MateMenuTreeDirectory * parent,MenuLayoutNode * layout,DesktopEntrySet * allocated)3555 process_layout (MateMenuTree          *tree,
3556                 MateMenuTreeDirectory *parent,
3557                 MenuLayoutNode     *layout,
3558                 DesktopEntrySet    *allocated)
3559 {
3560   MenuLayoutNode     *layout_iter;
3561   MateMenuTreeDirectory *directory;
3562   DesktopEntrySet    *entry_pool;
3563   DesktopEntrySet    *entries;
3564   DesktopEntrySet    *allocated_set;
3565   DesktopEntrySet    *excluded_set;
3566   gboolean            deleted;
3567   gboolean            only_unallocated;
3568   GSList             *tmp;
3569 
3570   g_assert (menu_layout_node_get_type (layout) == MENU_LAYOUT_NODE_MENU);
3571   g_assert (menu_layout_node_menu_get_name (layout) != NULL);
3572 
3573   directory = matemenu_tree_directory_new (tree, parent,
3574 					menu_layout_node_menu_get_name (layout));
3575 
3576   menu_verbose ("=== Menu name = %s ===\n", directory->name);
3577 
3578 
3579   deleted = FALSE;
3580   only_unallocated = FALSE;
3581 
3582   entries = desktop_entry_set_new ();
3583   allocated_set = desktop_entry_set_new ();
3584 
3585   if (tree->flags & MATEMENU_TREE_FLAGS_INCLUDE_EXCLUDED)
3586     excluded_set = desktop_entry_set_new ();
3587   else
3588     excluded_set = NULL;
3589 
3590   entry_pool = _entry_directory_list_get_all_desktops (menu_layout_node_menu_get_app_dirs (layout));
3591 
3592   layout_iter = menu_layout_node_get_children (layout);
3593   while (layout_iter != NULL)
3594     {
3595       switch (menu_layout_node_get_type (layout_iter))
3596         {
3597         case MENU_LAYOUT_NODE_MENU:
3598           /* recurse */
3599           {
3600             MateMenuTreeDirectory *child_dir;
3601 
3602 	    menu_verbose ("Processing <Menu>\n");
3603 
3604             child_dir = process_layout (tree,
3605                                         directory,
3606                                         layout_iter,
3607                                         allocated);
3608             if (child_dir)
3609               directory->subdirs = g_slist_prepend (directory->subdirs,
3610                                                     child_dir);
3611 
3612 	    menu_verbose ("Processed <Menu>\n");
3613           }
3614           break;
3615 
3616         case MENU_LAYOUT_NODE_INCLUDE:
3617           {
3618             /* The match rule children of the <Include> are
3619              * independent (logical OR) so we can process each one by
3620              * itself
3621              */
3622             MenuLayoutNode *rule;
3623 
3624 	    menu_verbose ("Processing <Include> (%u entries)\n",
3625 			  desktop_entry_set_get_count (entries));
3626 
3627             rule = menu_layout_node_get_children (layout_iter);
3628             while (rule != NULL)
3629               {
3630                 DesktopEntrySet *rule_set;
3631 
3632                 rule_set = process_include_rules (rule, entry_pool);
3633                 if (rule_set != NULL)
3634                   {
3635                     desktop_entry_set_union (entries, rule_set);
3636                     desktop_entry_set_union (allocated_set, rule_set);
3637 		    if (excluded_set != NULL)
3638 		      desktop_entry_set_subtract (excluded_set, rule_set);
3639                     desktop_entry_set_unref (rule_set);
3640                   }
3641 
3642                 rule = menu_layout_node_get_next (rule);
3643               }
3644 
3645 	    menu_verbose ("Processed <Include> (%u entries)\n",
3646 			  desktop_entry_set_get_count (entries));
3647           }
3648           break;
3649 
3650         case MENU_LAYOUT_NODE_EXCLUDE:
3651           {
3652             /* The match rule children of the <Exclude> are
3653              * independent (logical OR) so we can process each one by
3654              * itself
3655              */
3656             MenuLayoutNode *rule;
3657 
3658 	    menu_verbose ("Processing <Exclude> (%u entries)\n",
3659 			  desktop_entry_set_get_count (entries));
3660 
3661             rule = menu_layout_node_get_children (layout_iter);
3662             while (rule != NULL)
3663               {
3664                 DesktopEntrySet *rule_set;
3665 
3666                 rule_set = process_include_rules (rule, entry_pool);
3667                 if (rule_set != NULL)
3668                   {
3669 		    if (excluded_set != NULL)
3670 		      desktop_entry_set_union (excluded_set, rule_set);
3671 		    desktop_entry_set_subtract (entries, rule_set);
3672 		    desktop_entry_set_unref (rule_set);
3673                   }
3674 
3675                 rule = menu_layout_node_get_next (rule);
3676               }
3677 
3678 	    menu_verbose ("Processed <Exclude> (%u entries)\n",
3679 			  desktop_entry_set_get_count (entries));
3680           }
3681           break;
3682 
3683         case MENU_LAYOUT_NODE_DIRECTORY:
3684           {
3685             DesktopEntry *entry;
3686 
3687 	    menu_verbose ("Processing <Directory>%s</Directory>\n",
3688 			  menu_layout_node_get_content (layout_iter));
3689 
3690 	    /*
3691              * The last <Directory> to exist wins, so we always try overwriting
3692              */
3693             entry = entry_directory_list_get_directory (menu_layout_node_menu_get_directory_dirs (layout),
3694                                                         menu_layout_node_get_content (layout_iter));
3695 
3696             if (entry != NULL)
3697               {
3698                 if (!desktop_entry_get_hidden (entry))
3699                   {
3700                     if (directory->directory_entry)
3701                       desktop_entry_unref (directory->directory_entry);
3702                     directory->directory_entry = entry; /* pass ref ownership */
3703                   }
3704                 else
3705                   {
3706                     desktop_entry_unref (entry);
3707                   }
3708               }
3709 
3710             menu_verbose ("Processed <Directory> new directory entry = %p (%s)\n",
3711                           directory->directory_entry,
3712 			  directory->directory_entry? desktop_entry_get_path (directory->directory_entry) : "null");
3713           }
3714           break;
3715 
3716         case MENU_LAYOUT_NODE_DELETED:
3717 	  menu_verbose ("Processed <Deleted/>\n");
3718           deleted = TRUE;
3719           break;
3720 
3721         case MENU_LAYOUT_NODE_NOT_DELETED:
3722 	  menu_verbose ("Processed <NotDeleted/>\n");
3723           deleted = FALSE;
3724           break;
3725 
3726         case MENU_LAYOUT_NODE_ONLY_UNALLOCATED:
3727 	  menu_verbose ("Processed <OnlyUnallocated/>\n");
3728           only_unallocated = TRUE;
3729           break;
3730 
3731         case MENU_LAYOUT_NODE_NOT_ONLY_UNALLOCATED:
3732 	  menu_verbose ("Processed <NotOnlyUnallocated/>\n");
3733           only_unallocated = FALSE;
3734           break;
3735 
3736 	case MENU_LAYOUT_NODE_DEFAULT_LAYOUT:
3737 	  menu_layout_node_default_layout_get_values (layout_iter,
3738 						      &directory->default_layout_values);
3739 	  collect_layout_info (layout_iter, &directory->default_layout_info);
3740 	  menu_verbose ("Processed <DefaultLayout/>\n");
3741 	  break;
3742 
3743 	case MENU_LAYOUT_NODE_LAYOUT:
3744 	  collect_layout_info (layout_iter, &directory->layout_info);
3745 	  menu_verbose ("Processed <Layout/>\n");
3746 	  break;
3747 
3748         default:
3749           break;
3750         }
3751 
3752       layout_iter = menu_layout_node_get_next (layout_iter);
3753     }
3754 
3755   desktop_entry_set_unref (entry_pool);
3756 
3757   directory->only_unallocated = only_unallocated != FALSE;
3758 
3759   if (!directory->only_unallocated)
3760     desktop_entry_set_union (allocated, allocated_set);
3761 
3762   desktop_entry_set_unref (allocated_set);
3763 
3764   if (directory->directory_entry)
3765     {
3766       if (desktop_entry_get_no_display (directory->directory_entry))
3767         {
3768           directory->is_nodisplay = TRUE;
3769 
3770           if (!(tree->flags & MATEMENU_TREE_FLAGS_INCLUDE_NODISPLAY))
3771             {
3772               menu_verbose ("Not showing menu %s because NoDisplay=true\n",
3773                         desktop_entry_get_name (directory->directory_entry));
3774               deleted = TRUE;
3775             }
3776         }
3777 
3778       if (!desktop_entry_get_show_in (directory->directory_entry))
3779         {
3780           menu_verbose ("Not showing menu %s because OnlyShowIn!=$DESKTOP or NotShowIn=$DESKTOP (with $DESKTOP=${XDG_CURRENT_DESKTOP:-GNOME})\n",
3781                         desktop_entry_get_name (directory->directory_entry));
3782           deleted = TRUE;
3783         }
3784     }
3785 
3786   if (deleted)
3787     {
3788       if (excluded_set != NULL)
3789 	desktop_entry_set_unref (excluded_set);
3790       desktop_entry_set_unref (entries);
3791       matemenu_tree_item_unref (directory);
3792       return NULL;
3793     }
3794   if (tree->collection_applet && !g_strcmp0 (directory->name, "Collection"))
3795     {
3796       guint i;
3797       for (i = 0; i < tree->collection_applet->len; i++)
3798       {
3799         const char *desktop_name = g_ptr_array_index (tree->collection_applet, i);
3800         get_by_desktop (entry_pool, entries, desktop_name);
3801       }
3802     }
3803 
3804   desktop_entry_set_foreach (entries,
3805                              (DesktopEntrySetForeachFunc) entries_listify_foreach,
3806                              directory);
3807   desktop_entry_set_unref (entries);
3808 
3809   if (excluded_set != NULL)
3810     {
3811       desktop_entry_set_foreach (excluded_set,
3812 				 (DesktopEntrySetForeachFunc) excluded_entries_listify_foreach,
3813 				 directory);
3814       desktop_entry_set_unref (excluded_set);
3815     }
3816 
3817   tmp = directory->subdirs;
3818   while (tmp != NULL)
3819     {
3820       MateMenuTreeDirectory *subdir = tmp->data;
3821 
3822       set_default_layout_values (directory, subdir);
3823 
3824       tmp = tmp->next;
3825     }
3826 
3827   tmp = directory->entries;
3828   while (tmp != NULL)
3829     {
3830       MateMenuTreeEntry *entry = tmp->data;
3831       GSList         *next  = tmp->next;
3832       gboolean        delete = FALSE;
3833 
3834       /* If adding a new condition to delete here, it has to be added to
3835        * get_still_unallocated_foreach() too */
3836 
3837       if (desktop_entry_get_hidden (entry->desktop_entry))
3838         {
3839           menu_verbose ("Deleting %s because Hidden=true\n",
3840                         desktop_entry_get_name (entry->desktop_entry));
3841           delete = TRUE;
3842         }
3843 
3844       if (!(tree->flags & MATEMENU_TREE_FLAGS_INCLUDE_NODISPLAY) &&
3845           desktop_entry_get_no_display (entry->desktop_entry))
3846         {
3847           menu_verbose ("Deleting %s because NoDisplay=true\n",
3848                         desktop_entry_get_name (entry->desktop_entry));
3849           delete = TRUE;
3850         }
3851 
3852       if (!desktop_entry_get_show_in (entry->desktop_entry))
3853         {
3854           menu_verbose ("Deleting %s because OnlyShowIn!=$DESKTOP or NotShowIn=$DESKTOP (with $DESKTOP=${XDG_CURRENT_DESKTOP:-GNOME})\n",
3855                         desktop_entry_get_name (entry->desktop_entry));
3856           delete = TRUE;
3857         }
3858 
3859       /* No need to filter out based on TryExec since GDesktopAppInfo cannot
3860        * deal with .desktop files with a failed TryExec. */
3861 
3862       if (delete)
3863         {
3864           directory->entries = g_slist_delete_link (directory->entries,
3865                                                    tmp);
3866           matemenu_tree_item_unref_and_unset_parent (entry);
3867         }
3868 
3869       tmp = next;
3870     }
3871 
3872   g_assert (directory->name != NULL);
3873 
3874   return directory;
3875 }
3876 
3877 static void
process_only_unallocated(MateMenuTree * tree,MateMenuTreeDirectory * directory,DesktopEntrySet * allocated,DesktopEntrySet * unallocated_used)3878 process_only_unallocated (MateMenuTree          *tree,
3879 			  MateMenuTreeDirectory *directory,
3880 			  DesktopEntrySet    *allocated,
3881 			  DesktopEntrySet    *unallocated_used)
3882 {
3883   GSList *tmp;
3884 
3885   /* For any directory marked only_unallocated, we have to remove any
3886    * entries that were in fact allocated.
3887    */
3888 
3889   if (directory->only_unallocated)
3890     {
3891       tmp = directory->entries;
3892       while (tmp != NULL)
3893         {
3894           MateMenuTreeEntry *entry = tmp->data;
3895           GSList         *next  = tmp->next;
3896 
3897           if (desktop_entry_set_lookup (allocated, entry->desktop_file_id))
3898             {
3899               directory->entries = g_slist_delete_link (directory->entries,
3900                                                         tmp);
3901               matemenu_tree_item_unref_and_unset_parent (entry);
3902             }
3903           else
3904             {
3905               desktop_entry_set_add_entry (unallocated_used, entry->desktop_entry, entry->desktop_file_id);
3906             }
3907 
3908           tmp = next;
3909         }
3910     }
3911 
3912   tmp = directory->subdirs;
3913   while (tmp != NULL)
3914     {
3915       MateMenuTreeDirectory *subdir = tmp->data;
3916 
3917       process_only_unallocated (tree, subdir, allocated, unallocated_used);
3918 
3919       tmp = tmp->next;
3920    }
3921 }
3922 
3923 typedef struct
3924 {
3925   MateMenuTree *tree;
3926   DesktopEntrySet *allocated;
3927   DesktopEntrySet *unallocated_used;
3928   DesktopEntrySet *still_unallocated;
3929 } GetStillUnallocatedForeachData;
3930 
3931 static void
get_still_unallocated_foreach(const char * file_id,DesktopEntry * entry,GetStillUnallocatedForeachData * data)3932 get_still_unallocated_foreach (const char                     *file_id,
3933                                DesktopEntry                   *entry,
3934                                GetStillUnallocatedForeachData *data)
3935 {
3936   if (desktop_entry_set_lookup (data->allocated, file_id))
3937     return;
3938 
3939   if (desktop_entry_set_lookup (data->unallocated_used, file_id))
3940     return;
3941 
3942   /* Same rules than at the end of process_layout() */
3943   if (desktop_entry_get_hidden (entry))
3944     return;
3945 
3946   if (!(data->tree->flags & MATEMENU_TREE_FLAGS_INCLUDE_NODISPLAY) &&
3947       desktop_entry_get_no_display (entry))
3948     return;
3949 
3950   if (!desktop_entry_get_show_in (entry))
3951     return;
3952 
3953   desktop_entry_set_add_entry (data->still_unallocated, entry, file_id);
3954 }
3955 
3956 static void preprocess_layout_info (MateMenuTree          *tree,
3957                                     MateMenuTreeDirectory *directory);
3958 
3959 static GSList *
get_layout_info(MateMenuTreeDirectory * directory,gboolean * is_default_layout)3960 get_layout_info (MateMenuTreeDirectory *directory,
3961                  gboolean           *is_default_layout)
3962 {
3963   MateMenuTreeDirectory *iter;
3964 
3965   if (directory->layout_info != NULL)
3966     {
3967       if (is_default_layout)
3968         {
3969           *is_default_layout = FALSE;
3970         }
3971       return directory->layout_info;
3972     }
3973 
3974   /* Even if there's no layout information at all, the result will be an
3975    * implicit default layout */
3976   if (is_default_layout)
3977     {
3978       *is_default_layout = TRUE;
3979     }
3980 
3981   iter = directory;
3982   while (iter != NULL)
3983     {
3984       /* FIXME: this is broken: we might skip real parent in the
3985        * XML structure, that are hidden because of inlining. */
3986       if (iter->default_layout_info != NULL)
3987 	{
3988 	  return iter->default_layout_info;
3989 	}
3990 
3991       iter = MATEMENU_TREE_ITEM (iter)->parent;
3992     }
3993 
3994   return NULL;
3995 }
3996 
3997 static void
get_values_with_defaults(MenuLayoutNode * node,MenuLayoutValues * layout_values,MenuLayoutValues * default_layout_values)3998 get_values_with_defaults (MenuLayoutNode   *node,
3999 			  MenuLayoutValues *layout_values,
4000 			  MenuLayoutValues *default_layout_values)
4001 {
4002   menu_layout_node_menuname_get_values (node, layout_values);
4003 
4004   if (!(layout_values->mask & MENU_LAYOUT_VALUES_SHOW_EMPTY))
4005     layout_values->show_empty = default_layout_values->show_empty;
4006 
4007   if (!(layout_values->mask & MENU_LAYOUT_VALUES_INLINE_MENUS))
4008     layout_values->inline_menus = default_layout_values->inline_menus;
4009 
4010   if (!(layout_values->mask & MENU_LAYOUT_VALUES_INLINE_LIMIT))
4011     layout_values->inline_limit = default_layout_values->inline_limit;
4012 
4013   if (!(layout_values->mask & MENU_LAYOUT_VALUES_INLINE_HEADER))
4014     layout_values->inline_header = default_layout_values->inline_header;
4015 
4016   if (!(layout_values->mask & MENU_LAYOUT_VALUES_INLINE_ALIAS))
4017     layout_values->inline_alias = default_layout_values->inline_alias;
4018 }
4019 
4020 static guint
get_real_subdirs_len(MateMenuTreeDirectory * directory)4021 get_real_subdirs_len (MateMenuTreeDirectory *directory)
4022 {
4023   guint   len;
4024   GSList *tmp;
4025 
4026   len = 0;
4027 
4028   tmp = directory->subdirs;
4029   while (tmp != NULL)
4030     {
4031       MateMenuTreeDirectory *subdir = tmp->data;
4032 
4033       tmp = tmp->next;
4034 
4035       if (subdir->will_inline_header != G_MAXUINT16)
4036         {
4037           len += get_real_subdirs_len (subdir) + g_slist_length (subdir->entries) + 1;
4038         }
4039       else
4040         len += 1;
4041     }
4042 
4043   return len;
4044 }
4045 
4046 static void
preprocess_layout_info_subdir_helper(MateMenuTree * tree,MateMenuTreeDirectory * directory,MateMenuTreeDirectory * subdir,MenuLayoutValues * layout_values,gboolean * contents_added,gboolean * should_remove)4047 preprocess_layout_info_subdir_helper (MateMenuTree          *tree,
4048                                       MateMenuTreeDirectory *directory,
4049                                       MateMenuTreeDirectory *subdir,
4050                                       MenuLayoutValues   *layout_values,
4051                                       gboolean           *contents_added,
4052                                       gboolean           *should_remove)
4053 {
4054   preprocess_layout_info (tree, subdir);
4055 
4056   *should_remove = FALSE;
4057   *contents_added = FALSE;
4058 
4059   if (subdir->subdirs == NULL && subdir->entries == NULL)
4060     {
4061       if (!(tree->flags & MATEMENU_TREE_FLAGS_SHOW_EMPTY) &&
4062           !layout_values->show_empty)
4063 	{
4064 	  menu_verbose ("Not showing empty menu '%s'\n", subdir->name);
4065 	  *should_remove = TRUE;
4066 	}
4067     }
4068 
4069   else if (layout_values->inline_menus)
4070     {
4071       guint real_subdirs_len;
4072 
4073       real_subdirs_len = get_real_subdirs_len (subdir);
4074 
4075       if (layout_values->inline_alias &&
4076           real_subdirs_len + g_slist_length (subdir->entries) == 1)
4077         {
4078           MateMenuTreeAlias *alias;
4079           MateMenuTreeItem  *item;
4080           GSList         *list;
4081 
4082           if (subdir->subdirs != NULL)
4083             list = subdir->subdirs;
4084           else
4085             list = subdir->entries;
4086 
4087           item = MATEMENU_TREE_ITEM (list->data);
4088 
4089           menu_verbose ("Inline aliasing '%s' to '%s'\n",
4090                         item->type == MATEMENU_TREE_ITEM_ENTRY ?
4091                           g_app_info_get_name (G_APP_INFO (matemenu_tree_entry_get_app_info (MATEMENU_TREE_ENTRY (item)))) :
4092                           (item->type == MATEMENU_TREE_ITEM_DIRECTORY ?
4093                              matemenu_tree_directory_get_name (MATEMENU_TREE_DIRECTORY (item)) :
4094                              matemenu_tree_directory_get_name (MATEMENU_TREE_ALIAS (item)->directory)),
4095                         subdir->name);
4096 
4097           alias = matemenu_tree_alias_new (directory, subdir, item);
4098 
4099           g_slist_foreach (list,
4100                            (GFunc) matemenu_tree_item_unref_and_unset_parent,
4101                            NULL);
4102           g_slist_free (list);
4103           subdir->subdirs = NULL;
4104           subdir->entries = NULL;
4105 
4106           if (item->type == MATEMENU_TREE_ITEM_DIRECTORY)
4107             directory->subdirs = g_slist_append (directory->subdirs, alias);
4108           else
4109             directory->entries = g_slist_append (directory->entries, alias);
4110 
4111           *contents_added = TRUE;
4112           *should_remove = TRUE;
4113         }
4114 
4115       else if (layout_values->inline_limit == 0 ||
4116                layout_values->inline_limit >= real_subdirs_len + g_slist_length (subdir->entries))
4117         {
4118           if (layout_values->inline_header)
4119             {
4120               menu_verbose ("Creating inline header with name '%s'\n", subdir->name);
4121               /* we're limited to 16-bits to spare some memory; if the limit is
4122                * higher than that (would be crazy), we just consider it's
4123                * unlimited */
4124               if (layout_values->inline_limit < G_MAXUINT16)
4125                 subdir->will_inline_header = (guint16) layout_values->inline_limit;
4126               else
4127                 subdir->will_inline_header = 0;
4128             }
4129           else
4130             {
4131               g_slist_foreach (subdir->subdirs,
4132                                (GFunc) matemenu_tree_item_set_parent,
4133                                directory);
4134               directory->subdirs = g_slist_concat (directory->subdirs,
4135                                                    subdir->subdirs);
4136               subdir->subdirs = NULL;
4137 
4138               g_slist_foreach (subdir->entries,
4139                                (GFunc) matemenu_tree_item_set_parent,
4140                                directory);
4141               directory->entries = g_slist_concat (directory->entries,
4142                                                    subdir->entries);
4143               subdir->entries = NULL;
4144 
4145               *contents_added = TRUE;
4146               *should_remove = TRUE;
4147             }
4148 
4149           menu_verbose ("Inlining directory contents of '%s' to '%s'\n",
4150                         subdir->name, directory->name);
4151         }
4152     }
4153 }
4154 
4155 static void
preprocess_layout_info(MateMenuTree * tree,MateMenuTreeDirectory * directory)4156 preprocess_layout_info (MateMenuTree          *tree,
4157                         MateMenuTreeDirectory *directory)
4158 {
4159   GSList   *tmp;
4160   GSList   *layout_info;
4161   gboolean  using_default_layout;
4162   GSList   *last_subdir;
4163   gboolean  strip_duplicates;
4164   gboolean  contents_added;
4165   gboolean  should_remove;
4166   GSList   *subdirs_sentinel;
4167 
4168   /* Note: we need to preprocess all menus, even if the layout mask for a menu
4169    * is MENU_LAYOUT_VALUES_NONE: in this case, we need to remove empty menus;
4170    * and the layout mask can be different for a submenu anyway */
4171 
4172   menu_verbose ("Processing menu layout inline hints for %s\n", directory->name);
4173   g_assert (!directory->preprocessed);
4174 
4175   strip_duplicates = FALSE;
4176   /* we use last_subdir to track the last non-inlined subdirectory */
4177   last_subdir = g_slist_last (directory->subdirs);
4178 
4179   /*
4180    * First process subdirectories with explicit layout
4181    */
4182   layout_info = get_layout_info (directory, &using_default_layout);
4183   tmp = layout_info;
4184   /* see comment below about Menuname to understand why we leave the loop if
4185    * last_subdir is NULL */
4186   while (tmp != NULL && last_subdir != NULL)
4187     {
4188       MenuLayoutNode     *node = tmp->data;
4189       MenuLayoutValues    layout_values;
4190       const char         *name;
4191       MateMenuTreeDirectory *subdir;
4192       GSList             *subdir_l;
4193 
4194       tmp = tmp->next;
4195 
4196       /* only Menuname nodes are relevant here */
4197       if (menu_layout_node_get_type (node) != MENU_LAYOUT_NODE_MENUNAME)
4198         continue;
4199 
4200       get_values_with_defaults (node,
4201                                 &layout_values,
4202                                 &directory->default_layout_values);
4203 
4204       /* find the subdirectory that is affected by those attributes */
4205       name = menu_layout_node_get_content (node);
4206       subdir = NULL;
4207       subdir_l = directory->subdirs;
4208       while (subdir_l != NULL)
4209         {
4210           subdir = subdir_l->data;
4211 
4212           if (!strcmp (subdir->name, name))
4213             break;
4214 
4215           subdir = NULL;
4216           subdir_l = subdir_l->next;
4217 
4218           /* We do not want to use Menuname on a menu that appeared via
4219            * inlining: without inlining, the Menuname wouldn't have matched
4220            * anything, and we want to keep the same behavior.
4221            * Unless the layout is a default layout, in which case the Menuname
4222            * does match the subdirectory. */
4223           if (!using_default_layout && subdir_l == last_subdir)
4224             {
4225               subdir_l = NULL;
4226               break;
4227             }
4228         }
4229 
4230       if (subdir == NULL)
4231         continue;
4232 
4233       preprocess_layout_info_subdir_helper (tree, directory,
4234                                             subdir, &layout_values,
4235                                             &contents_added, &should_remove);
4236       strip_duplicates = strip_duplicates || contents_added;
4237       if (should_remove)
4238         {
4239           if (last_subdir == subdir_l)
4240             {
4241               /* we need to recompute last_subdir since we'll remove it from
4242                * the list */
4243               GSList *buf;
4244 
4245               if (subdir_l == directory->subdirs)
4246                 last_subdir = NULL;
4247               else
4248                 {
4249                   buf = directory->subdirs;
4250                   while (buf != NULL && buf->next != subdir_l)
4251                     buf = buf->next;
4252                   last_subdir = buf;
4253                 }
4254             }
4255 
4256           directory->subdirs = g_slist_remove (directory->subdirs, subdir);
4257           matemenu_tree_item_unref_and_unset_parent (MATEMENU_TREE_ITEM (subdir));
4258         }
4259     }
4260 
4261   /*
4262    * Now process the subdirectories with no explicit layout
4263    */
4264   /* this is bogus data, but we just need the pointer anyway */
4265   subdirs_sentinel = g_slist_prepend (directory->subdirs, PACKAGE);
4266   directory->subdirs = subdirs_sentinel;
4267 
4268   tmp = directory->subdirs;
4269   while (tmp->next != NULL)
4270     {
4271       MateMenuTreeDirectory *subdir = tmp->next->data;
4272 
4273       if (subdir->preprocessed)
4274         {
4275           tmp = tmp->next;
4276           continue;
4277         }
4278 
4279       preprocess_layout_info_subdir_helper (tree, directory,
4280                                             subdir, &directory->default_layout_values,
4281                                             &contents_added, &should_remove);
4282       strip_duplicates = strip_duplicates || contents_added;
4283       if (should_remove)
4284         {
4285           tmp = g_slist_delete_link (tmp, tmp->next);
4286           matemenu_tree_item_unref_and_unset_parent (MATEMENU_TREE_ITEM (subdir));
4287         }
4288       else
4289         tmp = tmp->next;
4290     }
4291 
4292   /* remove the sentinel */
4293   directory->subdirs = g_slist_delete_link (directory->subdirs,
4294                                             directory->subdirs);
4295 
4296   /*
4297    * Finally, remove duplicates if needed
4298    */
4299   if (strip_duplicates)
4300     {
4301       /* strip duplicate entries; there should be no duplicate directories */
4302       directory->entries = g_slist_sort (directory->entries,
4303                                          (GCompareFunc) matemenu_tree_entry_compare_by_id);
4304       tmp = directory->entries;
4305       while (tmp != NULL && tmp->next != NULL)
4306         {
4307           MateMenuTreeItem *a = tmp->data;
4308           MateMenuTreeItem *b = tmp->next->data;
4309 
4310           if (a->type == MATEMENU_TREE_ITEM_ALIAS)
4311             a = MATEMENU_TREE_ALIAS (a)->aliased_item;
4312 
4313           if (b->type == MATEMENU_TREE_ITEM_ALIAS)
4314             b = MATEMENU_TREE_ALIAS (b)->aliased_item;
4315 
4316           if (strcmp (MATEMENU_TREE_ENTRY (a)->desktop_file_id,
4317                       MATEMENU_TREE_ENTRY (b)->desktop_file_id) == 0)
4318             {
4319               tmp = g_slist_delete_link (tmp, tmp->next);
4320               matemenu_tree_item_unref (b);
4321             }
4322           else
4323             tmp = tmp->next;
4324         }
4325     }
4326 
4327   directory->preprocessed = TRUE;
4328 }
4329 
4330 static void process_layout_info (MateMenuTree          *tree,
4331 				 MateMenuTreeDirectory *directory);
4332 
4333 static void
check_pending_separator(MateMenuTreeDirectory * directory)4334 check_pending_separator (MateMenuTreeDirectory *directory)
4335 {
4336   if (directory->layout_pending_separator)
4337     {
4338       menu_verbose ("Adding pending separator in '%s'\n", directory->name);
4339 
4340       directory->contents = g_slist_append (directory->contents,
4341 					    matemenu_tree_separator_new (directory));
4342       directory->layout_pending_separator = FALSE;
4343     }
4344 }
4345 
4346 static void
merge_alias(MateMenuTree * tree,MateMenuTreeDirectory * directory,MateMenuTreeAlias * alias)4347 merge_alias (MateMenuTree          *tree,
4348 	     MateMenuTreeDirectory *directory,
4349 	     MateMenuTreeAlias     *alias)
4350 {
4351   menu_verbose ("Merging alias '%s' in directory '%s'\n",
4352 		alias->directory->name, directory->name);
4353 
4354   if (alias->aliased_item->type == MATEMENU_TREE_ITEM_DIRECTORY)
4355     {
4356       process_layout_info (tree, MATEMENU_TREE_DIRECTORY (alias->aliased_item));
4357     }
4358 
4359   check_pending_separator (directory);
4360 
4361   directory->contents = g_slist_append (directory->contents,
4362 					matemenu_tree_item_ref (alias));
4363 }
4364 
4365 static void
merge_subdir(MateMenuTree * tree,MateMenuTreeDirectory * directory,MateMenuTreeDirectory * subdir)4366 merge_subdir (MateMenuTree          *tree,
4367 	      MateMenuTreeDirectory *directory,
4368 	      MateMenuTreeDirectory *subdir)
4369 {
4370   menu_verbose ("Merging subdir '%s' in directory '%s'\n",
4371 		subdir->name, directory->name);
4372 
4373   process_layout_info (tree, subdir);
4374 
4375   check_pending_separator (directory);
4376 
4377   if (subdir->will_inline_header == 0 ||
4378       (subdir->will_inline_header != G_MAXUINT16 &&
4379        g_slist_length (subdir->contents) <= subdir->will_inline_header))
4380     {
4381       MateMenuTreeHeader *header;
4382 
4383       header = matemenu_tree_header_new (directory, subdir);
4384       directory->contents = g_slist_append (directory->contents, header);
4385 
4386       g_slist_foreach (subdir->contents,
4387                        (GFunc) matemenu_tree_item_set_parent,
4388                        directory);
4389       directory->contents = g_slist_concat (directory->contents,
4390                                             subdir->contents);
4391       subdir->contents = NULL;
4392       subdir->will_inline_header = G_MAXUINT16;
4393 
4394       matemenu_tree_item_set_parent (MATEMENU_TREE_ITEM (subdir), NULL);
4395     }
4396   else
4397     {
4398       directory->contents = g_slist_append (directory->contents,
4399 					    matemenu_tree_item_ref (subdir));
4400     }
4401 }
4402 
4403 static void
merge_subdir_by_name(MateMenuTree * tree,MateMenuTreeDirectory * directory,const char * subdir_name)4404 merge_subdir_by_name (MateMenuTree          *tree,
4405 		      MateMenuTreeDirectory *directory,
4406 		      const char         *subdir_name)
4407 {
4408   GSList *tmp;
4409 
4410   menu_verbose ("Attempting to merge subdir '%s' in directory '%s'\n",
4411 		subdir_name, directory->name);
4412 
4413   tmp = directory->subdirs;
4414   while (tmp != NULL)
4415     {
4416       MateMenuTreeDirectory *subdir = tmp->data;
4417       GSList             *next = tmp->next;
4418 
4419       /* if it's an alias, then it cannot be affected by
4420        * the Merge nodes in the layout */
4421       if (MATEMENU_TREE_ITEM (subdir)->type == MATEMENU_TREE_ITEM_ALIAS)
4422         continue;
4423 
4424       if (!strcmp (subdir->name, subdir_name))
4425 	{
4426 	  directory->subdirs = g_slist_delete_link (directory->subdirs, tmp);
4427 	  merge_subdir (tree, directory, subdir);
4428 	  matemenu_tree_item_unref (subdir);
4429 	}
4430 
4431       tmp = next;
4432     }
4433 }
4434 
4435 static void
merge_entry(MateMenuTree * tree,MateMenuTreeDirectory * directory,MateMenuTreeEntry * entry)4436 merge_entry (MateMenuTree          *tree,
4437 	     MateMenuTreeDirectory *directory,
4438 	     MateMenuTreeEntry     *entry)
4439 {
4440   menu_verbose ("Merging entry '%s' in directory '%s'\n",
4441 		entry->desktop_file_id, directory->name);
4442 
4443   check_pending_separator (directory);
4444   directory->contents = g_slist_append (directory->contents,
4445 					matemenu_tree_item_ref (entry));
4446 }
4447 
4448 static void
merge_entry_by_id(MateMenuTree * tree,MateMenuTreeDirectory * directory,const char * file_id)4449 merge_entry_by_id (MateMenuTree          *tree,
4450 		   MateMenuTreeDirectory *directory,
4451 		   const char         *file_id)
4452 {
4453   GSList *tmp;
4454 
4455   menu_verbose ("Attempting to merge entry '%s' in directory '%s'\n",
4456 		file_id, directory->name);
4457 
4458   tmp = directory->entries;
4459   while (tmp != NULL)
4460     {
4461       MateMenuTreeEntry *entry = tmp->data;
4462       GSList         *next = tmp->next;
4463 
4464       /* if it's an alias, then it cannot be affected by
4465        * the Merge nodes in the layout */
4466       if (MATEMENU_TREE_ITEM (entry)->type == MATEMENU_TREE_ITEM_ALIAS)
4467         continue;
4468 
4469       if (!strcmp (entry->desktop_file_id, file_id))
4470 	{
4471 	  directory->entries = g_slist_delete_link (directory->entries, tmp);
4472 	  merge_entry (tree, directory, entry);
4473 	  matemenu_tree_item_unref (entry);
4474 	}
4475 
4476       tmp = next;
4477     }
4478 }
4479 
4480 static inline gboolean
find_name_in_list(const char * name,GSList * list)4481 find_name_in_list (const char *name,
4482 		   GSList     *list)
4483 {
4484   while (list != NULL)
4485     {
4486       if (!strcmp (name, list->data))
4487 	return TRUE;
4488 
4489       list = list->next;
4490     }
4491 
4492   return FALSE;
4493 }
4494 
4495 static void
merge_subdirs(MateMenuTree * tree,MateMenuTreeDirectory * directory,GSList * except)4496 merge_subdirs (MateMenuTree          *tree,
4497 	       MateMenuTreeDirectory *directory,
4498 	       GSList             *except)
4499 {
4500   GSList *subdirs;
4501   GSList *tmp;
4502 
4503   menu_verbose ("Merging subdirs in directory '%s'\n", directory->name);
4504 
4505   subdirs = directory->subdirs;
4506   directory->subdirs = NULL;
4507 
4508   subdirs = g_slist_sort_with_data (subdirs,
4509 				    (GCompareDataFunc) matemenu_tree_item_compare,
4510                                     GINT_TO_POINTER (MATEMENU_TREE_FLAGS_NONE));
4511 
4512   tmp = subdirs;
4513   while (tmp != NULL)
4514     {
4515       MateMenuTreeDirectory *subdir = tmp->data;
4516 
4517       if (MATEMENU_TREE_ITEM (subdir)->type == MATEMENU_TREE_ITEM_ALIAS)
4518         {
4519 	  merge_alias (tree, directory, MATEMENU_TREE_ALIAS (subdir));
4520 	  matemenu_tree_item_unref (subdir);
4521         }
4522       else if (!find_name_in_list (subdir->name, except))
4523 	{
4524 	  merge_subdir (tree, directory, subdir);
4525 	  matemenu_tree_item_unref (subdir);
4526 	}
4527       else
4528 	{
4529 	  menu_verbose ("Not merging directory '%s' yet\n", subdir->name);
4530 	  directory->subdirs = g_slist_append (directory->subdirs, subdir);
4531 	}
4532 
4533       tmp = tmp->next;
4534     }
4535 
4536   g_slist_free (subdirs);
4537   g_slist_free (except);
4538 }
4539 
4540 static void
merge_entries(MateMenuTree * tree,MateMenuTreeDirectory * directory,GSList * except)4541 merge_entries (MateMenuTree          *tree,
4542 	       MateMenuTreeDirectory *directory,
4543 	       GSList             *except)
4544 {
4545   GSList *entries;
4546   GSList *tmp;
4547 
4548   menu_verbose ("Merging entries in directory '%s'\n", directory->name);
4549 
4550   entries = directory->entries;
4551   directory->entries = NULL;
4552 
4553   entries = g_slist_sort_with_data (entries,
4554 				    (GCompareDataFunc) matemenu_tree_item_compare,
4555                                     GINT_TO_POINTER (tree->flags));
4556 
4557   tmp = entries;
4558   while (tmp != NULL)
4559     {
4560       MateMenuTreeEntry *entry = tmp->data;
4561 
4562       if (MATEMENU_TREE_ITEM (entry)->type == MATEMENU_TREE_ITEM_ALIAS)
4563         {
4564 	  merge_alias (tree, directory, MATEMENU_TREE_ALIAS (entry));
4565 	  matemenu_tree_item_unref (entry);
4566         }
4567       else if (!find_name_in_list (entry->desktop_file_id, except))
4568 	{
4569 	  merge_entry (tree, directory, entry);
4570 	  matemenu_tree_item_unref (entry);
4571 	}
4572       else
4573 	{
4574 	  menu_verbose ("Not merging entry '%s' yet\n", entry->desktop_file_id);
4575 	  directory->entries = g_slist_append (directory->entries, entry);
4576 	}
4577 
4578       tmp = tmp->next;
4579     }
4580 
4581   g_slist_free (entries);
4582   g_slist_free (except);
4583 }
4584 
4585 static void
merge_subdirs_and_entries(MateMenuTree * tree,MateMenuTreeDirectory * directory,GSList * except_subdirs,GSList * except_entries)4586 merge_subdirs_and_entries (MateMenuTree          *tree,
4587 			   MateMenuTreeDirectory *directory,
4588 			   GSList             *except_subdirs,
4589 			   GSList             *except_entries)
4590 {
4591   GSList *items;
4592   GSList *tmp;
4593 
4594   menu_verbose ("Merging subdirs and entries together in directory %s\n",
4595 		directory->name);
4596 
4597   items = g_slist_concat (directory->subdirs, directory->entries);
4598 
4599   directory->subdirs = NULL;
4600   directory->entries = NULL;
4601 
4602   items = g_slist_sort_with_data (items,
4603 				  (GCompareDataFunc) matemenu_tree_item_compare,
4604                                   GINT_TO_POINTER (tree->flags));
4605 
4606   tmp = items;
4607   while (tmp != NULL)
4608     {
4609       MateMenuTreeItem     *item = tmp->data;
4610       MateMenuTreeItemType  type;
4611 
4612       type = item->type;
4613 
4614       if (type == MATEMENU_TREE_ITEM_ALIAS)
4615         {
4616           merge_alias (tree, directory, MATEMENU_TREE_ALIAS (item));
4617           matemenu_tree_item_unref (item);
4618         }
4619       else if (type == MATEMENU_TREE_ITEM_DIRECTORY)
4620 	{
4621 	  if (!find_name_in_list (MATEMENU_TREE_DIRECTORY (item)->name, except_subdirs))
4622 	    {
4623 	      merge_subdir (tree,
4624 			    directory,
4625 			    MATEMENU_TREE_DIRECTORY (item));
4626 	      matemenu_tree_item_unref (item);
4627 	    }
4628 	  else
4629 	    {
4630 	      menu_verbose ("Not merging directory '%s' yet\n",
4631 			    MATEMENU_TREE_DIRECTORY (item)->name);
4632 	      directory->subdirs = g_slist_append (directory->subdirs, item);
4633 	    }
4634 	}
4635       else if (type == MATEMENU_TREE_ITEM_ENTRY)
4636 	{
4637 	  if (!find_name_in_list (MATEMENU_TREE_ENTRY (item)->desktop_file_id, except_entries))
4638 	    {
4639 	      merge_entry (tree, directory, MATEMENU_TREE_ENTRY (item));
4640 	      matemenu_tree_item_unref (item);
4641 	    }
4642 	  else
4643 	    {
4644 	      menu_verbose ("Not merging entry '%s' yet\n",
4645 			    MATEMENU_TREE_ENTRY (item)->desktop_file_id);
4646 	      directory->entries = g_slist_append (directory->entries, item);
4647 	    }
4648 	}
4649       else
4650         {
4651           g_assert_not_reached ();
4652         }
4653 
4654       tmp = tmp->next;
4655     }
4656 
4657   g_slist_free (items);
4658   g_slist_free (except_subdirs);
4659   g_slist_free (except_entries);
4660 }
4661 
4662 static GSList *
get_subdirs_from_layout_info(GSList * layout_info)4663 get_subdirs_from_layout_info (GSList *layout_info)
4664 {
4665   GSList *subdirs;
4666   GSList *tmp;
4667 
4668   subdirs = NULL;
4669 
4670   tmp = layout_info;
4671   while (tmp != NULL)
4672     {
4673       MenuLayoutNode *node = tmp->data;
4674 
4675       if (menu_layout_node_get_type (node) == MENU_LAYOUT_NODE_MENUNAME)
4676 	{
4677 	  subdirs = g_slist_append (subdirs,
4678 				    (char *) menu_layout_node_get_content (node));
4679 	}
4680 
4681       tmp = tmp->next;
4682     }
4683 
4684   return subdirs;
4685 }
4686 
4687 static GSList *
get_entries_from_layout_info(GSList * layout_info)4688 get_entries_from_layout_info (GSList *layout_info)
4689 {
4690   GSList *entries;
4691   GSList *tmp;
4692 
4693   entries = NULL;
4694 
4695   tmp = layout_info;
4696   while (tmp != NULL)
4697     {
4698       MenuLayoutNode *node = tmp->data;
4699 
4700       if (menu_layout_node_get_type (node) == MENU_LAYOUT_NODE_FILENAME)
4701 	{
4702 	  entries = g_slist_append (entries,
4703 				    (char *) menu_layout_node_get_content (node));
4704 	}
4705 
4706       tmp = tmp->next;
4707     }
4708 
4709   return entries;
4710 }
4711 
4712 static void
process_layout_info(MateMenuTree * tree,MateMenuTreeDirectory * directory)4713 process_layout_info (MateMenuTree          *tree,
4714 		     MateMenuTreeDirectory *directory)
4715 {
4716   GSList *layout_info;
4717 
4718   menu_verbose ("Processing menu layout hints for %s\n", directory->name);
4719 
4720   g_slist_foreach (directory->contents,
4721 		   (GFunc) matemenu_tree_item_unref_and_unset_parent,
4722 		   NULL);
4723   g_slist_free (directory->contents);
4724   directory->contents = NULL;
4725   directory->layout_pending_separator = FALSE;
4726 
4727   layout_info = get_layout_info (directory, NULL);
4728 
4729   if (layout_info == NULL)
4730     {
4731       merge_subdirs (tree, directory, NULL);
4732       merge_entries (tree, directory, NULL);
4733     }
4734   else
4735     {
4736       GSList *tmp;
4737 
4738       tmp = layout_info;
4739       while (tmp != NULL)
4740 	{
4741 	  MenuLayoutNode *node = tmp->data;
4742 
4743 	  switch (menu_layout_node_get_type (node))
4744 	    {
4745 	    case MENU_LAYOUT_NODE_MENUNAME:
4746               merge_subdir_by_name (tree,
4747                                     directory,
4748                                     menu_layout_node_get_content (node));
4749 	      break;
4750 
4751 	    case MENU_LAYOUT_NODE_FILENAME:
4752 	      merge_entry_by_id (tree,
4753 				 directory,
4754 				 menu_layout_node_get_content (node));
4755 	      break;
4756 
4757 	    case MENU_LAYOUT_NODE_SEPARATOR:
4758 	      /* Unless explicitly told to show all separators, do not show a
4759 	       * separator at the beginning of a menu. Note that we don't add
4760 	       * the separators now, and instead make it pending. This way, we
4761 	       * won't show two consecutive separators nor will we show a
4762 	       * separator at the end of a menu. */
4763               if (tree->flags & MATEMENU_TREE_FLAGS_SHOW_ALL_SEPARATORS)
4764 		{
4765 		  directory->layout_pending_separator = TRUE;
4766 		  check_pending_separator (directory);
4767 		}
4768 	      else if (directory->contents)
4769 		{
4770 		  menu_verbose ("Adding a potential separator in '%s'\n",
4771 				directory->name);
4772 
4773 		  directory->layout_pending_separator = TRUE;
4774 		}
4775 	      else
4776 		{
4777 		  menu_verbose ("Skipping separator at the beginning of '%s'\n",
4778 				directory->name);
4779 		}
4780 	      break;
4781 
4782 	    case MENU_LAYOUT_NODE_MERGE:
4783 	      switch (menu_layout_node_merge_get_type (node))
4784 		{
4785 		case MENU_LAYOUT_MERGE_NONE:
4786 		  break;
4787 
4788 		case MENU_LAYOUT_MERGE_MENUS:
4789 		  merge_subdirs (tree,
4790 				 directory,
4791 				 get_subdirs_from_layout_info (tmp->next));
4792 		  break;
4793 
4794 		case MENU_LAYOUT_MERGE_FILES:
4795 		  merge_entries (tree,
4796 				 directory,
4797 				 get_entries_from_layout_info (tmp->next));
4798 		  break;
4799 
4800 		case MENU_LAYOUT_MERGE_ALL:
4801 		  merge_subdirs_and_entries (tree,
4802 					     directory,
4803 					     get_subdirs_from_layout_info (tmp->next),
4804 					     get_entries_from_layout_info (tmp->next));
4805 		  break;
4806 
4807 		default:
4808 		  g_assert_not_reached ();
4809 		  break;
4810 		}
4811 	      break;
4812 
4813 	    default:
4814 	      g_assert_not_reached ();
4815 	      break;
4816 	    }
4817 
4818 	  tmp = tmp->next;
4819 	}
4820     }
4821 
4822   g_slist_foreach (directory->subdirs,
4823 		   (GFunc) matemenu_tree_item_unref,
4824 		   NULL);
4825   g_slist_free (directory->subdirs);
4826   directory->subdirs = NULL;
4827 
4828   g_slist_foreach (directory->entries,
4829 		   (GFunc) matemenu_tree_item_unref,
4830 		   NULL);
4831   g_slist_free (directory->entries);
4832   directory->entries = NULL;
4833 
4834   g_slist_foreach (directory->default_layout_info,
4835 		   (GFunc) menu_layout_node_unref,
4836 		   NULL);
4837   g_slist_free (directory->default_layout_info);
4838   directory->default_layout_info = NULL;
4839 
4840   g_slist_foreach (directory->layout_info,
4841 		   (GFunc) menu_layout_node_unref,
4842 		   NULL);
4843   g_slist_free (directory->layout_info);
4844   directory->layout_info = NULL;
4845 }
4846 
4847 static void
handle_entries_changed(MenuLayoutNode * layout,MateMenuTree * tree)4848 handle_entries_changed (MenuLayoutNode *layout,
4849                         MateMenuTree       *tree)
4850 {
4851   if (tree->layout == layout)
4852     {
4853       matemenu_tree_force_rebuild (tree);
4854       matemenu_tree_invoke_monitors (tree);
4855     }
4856 }
4857 
4858 static void
update_entry_index(MateMenuTree * tree,MateMenuTreeDirectory * dir)4859 update_entry_index (MateMenuTree           *tree,
4860 		    MateMenuTreeDirectory  *dir)
4861 {
4862   MateMenuTreeIter *iter = matemenu_tree_directory_iter (dir);
4863   MateMenuTreeItemType next_type;
4864 
4865   while ((next_type = matemenu_tree_iter_next (iter)) != MATEMENU_TREE_ITEM_INVALID)
4866     {
4867       gpointer item = NULL;
4868 
4869       switch (next_type)
4870         {
4871         case MATEMENU_TREE_ITEM_ENTRY:
4872           {
4873 	    const char *id;
4874 
4875             item = matemenu_tree_iter_get_entry (iter);
4876             id = matemenu_tree_entry_get_desktop_file_id (item);
4877             if (id != NULL)
4878               g_hash_table_insert (tree->entries_by_id, (char*)id, item);
4879           }
4880           break;
4881         case MATEMENU_TREE_ITEM_DIRECTORY:
4882           {
4883             item = matemenu_tree_iter_get_directory (iter);
4884             update_entry_index (tree, (MateMenuTreeDirectory*)item);
4885           }
4886           break;
4887         default:
4888           break;
4889         }
4890       if (item != NULL)
4891         matemenu_tree_item_unref (item);
4892     }
4893 
4894   matemenu_tree_iter_unref (iter);
4895 }
4896 
4897 static gboolean
matemenu_tree_build_from_layout(MateMenuTree * tree,GError ** error)4898 matemenu_tree_build_from_layout (MateMenuTree  *tree,
4899                               GError    **error)
4900 {
4901   DesktopEntrySet *allocated;
4902 
4903   if (tree->root)
4904     return TRUE;
4905 
4906   if (!matemenu_tree_load_layout (tree, error))
4907     return FALSE;
4908 
4909   menu_verbose ("Building menu tree from layout\n");
4910 
4911   allocated = desktop_entry_set_new ();
4912 
4913   /* create the menu structure */
4914   tree->root = process_layout (tree,
4915                                NULL,
4916                                find_menu_child (tree->layout),
4917                                allocated);
4918   if (tree->root)
4919     {
4920       DesktopEntrySet *unallocated_used;
4921 
4922       unallocated_used = desktop_entry_set_new ();
4923 
4924       process_only_unallocated (tree, tree->root, allocated, unallocated_used);
4925       if (tree->flags & MATEMENU_TREE_FLAGS_INCLUDE_UNALLOCATED)
4926         {
4927           DesktopEntrySet *entry_pool;
4928           DesktopEntrySet *still_unallocated;
4929           GetStillUnallocatedForeachData data;
4930 
4931           entry_pool = _entry_directory_list_get_all_desktops (menu_layout_node_menu_get_app_dirs (find_menu_child (tree->layout)));
4932           still_unallocated = desktop_entry_set_new ();
4933 
4934           data.tree = tree;
4935           data.allocated = allocated;
4936           data.unallocated_used = unallocated_used;
4937           data.still_unallocated = still_unallocated;
4938 
4939           desktop_entry_set_foreach (entry_pool,
4940                                      (DesktopEntrySetForeachFunc) get_still_unallocated_foreach,
4941                                      &data);
4942 
4943           desktop_entry_set_unref (entry_pool);
4944 
4945           desktop_entry_set_foreach (still_unallocated,
4946                                      (DesktopEntrySetForeachFunc) unallocated_entries_listify_foreach,
4947                                      tree->root);
4948 
4949           desktop_entry_set_unref (still_unallocated);
4950         }
4951 
4952       desktop_entry_set_unref (unallocated_used);
4953 
4954       /* process the layout info part that can move/remove items:
4955        * inline, show_empty, etc. */
4956       preprocess_layout_info (tree, tree->root);
4957       /* populate the menu structure that we got with the items, and order it
4958        * according to the layout info */
4959       process_layout_info (tree, tree->root);
4960 
4961       update_entry_index (tree, tree->root);
4962 
4963       menu_layout_node_root_add_entries_monitor (tree->layout,
4964                                                  (MenuLayoutNodeEntriesChangedFunc) handle_entries_changed,
4965                                                  tree);
4966     }
4967 
4968   desktop_entry_set_unref (allocated);
4969 
4970   return TRUE;
4971 }
4972 
4973 static void
matemenu_tree_force_rebuild(MateMenuTree * tree)4974 matemenu_tree_force_rebuild (MateMenuTree *tree)
4975 {
4976   if (tree->root)
4977     {
4978       g_hash_table_remove_all (tree->entries_by_id);
4979       matemenu_tree_item_unref (tree->root);
4980       tree->root = NULL;
4981       tree->loaded = FALSE;
4982 
4983       g_assert (tree->layout != NULL);
4984 
4985       menu_layout_node_root_remove_entries_monitor (tree->layout,
4986                                                     (MenuLayoutNodeEntriesChangedFunc) handle_entries_changed,
4987                                                     tree);
4988     }
4989 }
4990 
4991 GType
matemenu_tree_iter_get_type(void)4992 matemenu_tree_iter_get_type (void)
4993 {
4994   static GType gtype = G_TYPE_INVALID;
4995   if (gtype == G_TYPE_INVALID)
4996     {
4997       gtype = g_boxed_type_register_static ("MateMenuTreeIter",
4998           (GBoxedCopyFunc)matemenu_tree_iter_ref,
4999           (GBoxedFreeFunc)matemenu_tree_iter_unref);
5000     }
5001   return gtype;
5002 }
5003 
5004 GType
matemenu_tree_directory_get_type(void)5005 matemenu_tree_directory_get_type (void)
5006 {
5007   static GType gtype = G_TYPE_INVALID;
5008   if (gtype == G_TYPE_INVALID)
5009     {
5010       gtype = g_boxed_type_register_static ("MateMenuTreeDirectory",
5011           (GBoxedCopyFunc)matemenu_tree_item_ref,
5012           (GBoxedFreeFunc)matemenu_tree_item_unref);
5013     }
5014   return gtype;
5015 }
5016 
5017 GType
matemenu_tree_entry_get_type(void)5018 matemenu_tree_entry_get_type (void)
5019 {
5020   static GType gtype = G_TYPE_INVALID;
5021   if (gtype == G_TYPE_INVALID)
5022     {
5023       gtype = g_boxed_type_register_static ("MateMenuTreeEntry",
5024           (GBoxedCopyFunc)matemenu_tree_item_ref,
5025           (GBoxedFreeFunc)matemenu_tree_item_unref);
5026     }
5027   return gtype;
5028 }
5029 
5030 GType
matemenu_tree_separator_get_type(void)5031 matemenu_tree_separator_get_type (void)
5032 {
5033   static GType gtype = G_TYPE_INVALID;
5034   if (gtype == G_TYPE_INVALID)
5035     {
5036       gtype = g_boxed_type_register_static ("MateMenuTreeSeparator",
5037           (GBoxedCopyFunc)matemenu_tree_item_ref,
5038           (GBoxedFreeFunc)matemenu_tree_item_unref);
5039     }
5040   return gtype;
5041 }
5042 
5043 GType
matemenu_tree_header_get_type(void)5044 matemenu_tree_header_get_type (void)
5045 {
5046   static GType gtype = G_TYPE_INVALID;
5047   if (gtype == G_TYPE_INVALID)
5048     {
5049       gtype = g_boxed_type_register_static ("MateMenuTreeHeader",
5050           (GBoxedCopyFunc)matemenu_tree_item_ref,
5051           (GBoxedFreeFunc)matemenu_tree_item_unref);
5052     }
5053   return gtype;
5054 }
5055 
5056 GType
matemenu_tree_alias_get_type(void)5057 matemenu_tree_alias_get_type (void)
5058 {
5059   static GType gtype = G_TYPE_INVALID;
5060   if (gtype == G_TYPE_INVALID)
5061     {
5062       gtype = g_boxed_type_register_static ("MateMenuTreeAlias",
5063           (GBoxedCopyFunc)matemenu_tree_item_ref,
5064           (GBoxedFreeFunc)matemenu_tree_item_unref);
5065     }
5066   return gtype;
5067 }
5068 
5069 GType
matemenu_tree_flags_get_type(void)5070 matemenu_tree_flags_get_type (void)
5071 {
5072   static GType enum_type_id = 0;
5073   if (G_UNLIKELY (!enum_type_id))
5074     {
5075       static const GFlagsValue values[] = {
5076         { MATEMENU_TREE_FLAGS_NONE, "MATEMENU_TREE_FLAGS_NONE", "none" },
5077         { MATEMENU_TREE_FLAGS_INCLUDE_EXCLUDED, "MATEMENU_TREE_FLAGS_INCLUDE_EXCLUDED", "include-excluded" },
5078         { MATEMENU_TREE_FLAGS_SHOW_EMPTY, "MATEMENU_TREE_FLAGS_SHOW_EMPTY", "show-empty" },
5079         { MATEMENU_TREE_FLAGS_INCLUDE_NODISPLAY, "MATEMENU_TREE_FLAGS_INCLUDE_NODISPLAY", "include-nodisplay" },
5080         { MATEMENU_TREE_FLAGS_SHOW_ALL_SEPARATORS, "MATEMENU_TREE_FLAGS_SHOW_ALL_SEPARATORS", "show-all-separators" },
5081         { MATEMENU_TREE_FLAGS_SORT_DISPLAY_NAME, "MATEMENU_TREE_FLAGS_SORT_DISPLAY_NAME", "sort-display-name" },
5082         { MATEMENU_TREE_FLAGS_INCLUDE_UNALLOCATED, "MATEMENU_TREE_FLAGS_INCLUDE_UNALLOCATED,", "include-unallocated" },
5083         { 0, NULL, NULL }
5084       };
5085       enum_type_id = g_flags_register_static ("MateMenuTreeFlags", values);
5086     }
5087   return enum_type_id;
5088 }
5089