EFL 1.7 svn doobies
[profile/ivi/efreet.git] / src / lib / efreet_menu.c
1 #ifdef HAVE_CONFIG_H
2 # include <config.h>
3 #endif
4
5 #undef alloca
6 #ifdef HAVE_ALLOCA_H
7 # include <alloca.h>
8 #elif defined __GNUC__
9 # define alloca __builtin_alloca
10 #elif defined _AIX
11 # define alloca __alloca
12 #elif defined _MSC_VER
13 # include <malloc.h>
14 # define alloca _alloca
15 #else
16 # include <stddef.h>
17 # ifdef  __cplusplus
18 extern "C"
19 # endif
20 void *alloca (size_t);
21 #endif
22
23 #include <Ecore_File.h>
24
25 /* define macros and variable for using the eina logging system  */
26 #define EFREET_MODULE_LOG_DOM _efreet_menu_log_dom
27 static int _efreet_menu_log_dom = -1;
28
29 #include "Efreet.h"
30 #include "efreet_private.h"
31 #include "efreet_xml.h"
32
33 typedef struct Efreet_Menu_Move Efreet_Menu_Move;
34
35 struct Efreet_Menu_Move
36 {
37     const char *old_name;     /**< The menu path to move from */
38     const char *new_name;     /**< The menu path to move too */
39 };
40
41 typedef struct Efreet_Menu_Internal Efreet_Menu_Internal;
42
43 struct Efreet_Menu_Internal
44 {
45     struct
46     {
47         const char *path;         /**< The base file path */
48         const char *name;         /**< The filename for this menu */
49     } file;                 /**< The menu file information */
50
51     struct
52     {
53         const char *internal;     /**< The menu name */
54         const char *name;         /**< Name to use in the menus */
55     } name;                       /**< The names for this menu */
56
57     Efreet_Desktop *directory; /**< The directory */
58     Eina_List *directories;  /**< All the directories set in the menu file */
59
60     Efreet_Menu_Move *current_move; /**< The current move */
61
62     Eina_List *app_dirs;           /**< .desktop application directories */
63
64     Eina_List *app_pool;           /**< application pool */
65     Eina_List *applications;       /**< applications in this menu */
66
67     Eina_List *directory_dirs;    /**< .directory file directories */
68     Eina_Hash *directory_cache;    /**< .directory dirs */
69
70     Eina_List *moves;              /**< List of moves to be handled by the menu */
71     Eina_List *filters;            /**< Include and Exclude filters */
72
73     Efreet_Menu_Internal *parent;   /**< Our parent menu */
74     Eina_List *sub_menus;          /**< Our sub menus */
75
76     Eina_List *layout;             /**< This menus layout */
77     Eina_List *default_layout;     /**< Default layout */
78     signed char show_empty;    /**< Whether to show empty menus */
79     signed char in_line;       /**< Whether this meny can be inlined */
80     signed char inline_limit;  /**< Number of elements which triggers inline */
81     signed char inline_header; /**< Whether we should use the header name when this menu is inlined */
82     signed char inline_alias;  /**< Whether we should use the menu name when inlining */
83
84     unsigned char seen_allocated:1;     /**< have we set the only_unallocated */
85     unsigned char only_unallocated:1;   /**< Show only unallocated .desktops */
86
87     unsigned char seen_deleted:1;       /**< Have we seen the deleted item yet */
88     unsigned char deleted:1;            /**< The menu is deleted */
89 };
90
91 typedef struct Efreet_Menu_App_Dir Efreet_Menu_App_Dir;
92
93 struct Efreet_Menu_App_Dir
94 {
95     const char *path;           /**< directory path */
96     const char *prefix;         /**< If it's legacy it can have a prefix */
97     unsigned int legacy:1;      /**< is this a legacy dir */
98 };
99
100 enum Efreet_Menu_Filter_Op_Type
101 {
102     EFREET_MENU_FILTER_OP_OR,
103     EFREET_MENU_FILTER_OP_AND,
104     EFREET_MENU_FILTER_OP_NOT
105 };
106
107 typedef enum Efreet_Menu_Filter_Op_Type Efreet_Menu_Filter_Op_Type;
108
109 enum Efreet_Menu_Filter_Type
110 {
111     EFREET_MENU_FILTER_INCLUDE,
112     EFREET_MENU_FILTER_EXCLUDE
113 };
114
115 typedef enum Efreet_Menu_Filter_Type Efreet_Menu_Filter_Type;
116
117 typedef struct Efreet_Menu_Filter_Op Efreet_Menu_Filter_Op;
118
119 struct Efreet_Menu_Filter_Op
120 {
121     Efreet_Menu_Filter_Op_Type type; /**< The type of operation */
122     Eina_List *categories;          /**< The categories this op applies too */
123     Eina_List *filenames;           /**< The filenames this op applies too */
124
125     Eina_List *filters;             /**< Child filters */
126
127     unsigned char all:1;             /**< Applies to all .desktop files */
128 };
129
130 typedef struct Efreet_Menu_Filter Efreet_Menu_Filter;
131
132 struct Efreet_Menu_Filter
133 {
134     Efreet_Menu_Filter_Type type;   /**< The type of filter */
135     Efreet_Menu_Filter_Op *op;      /**< The filter operations */
136 };
137
138 enum Efreet_Menu_Layout_Type
139 {
140     EFREET_MENU_LAYOUT_MENUNAME,
141     EFREET_MENU_LAYOUT_FILENAME,
142     EFREET_MENU_LAYOUT_SEPARATOR,
143     EFREET_MENU_LAYOUT_MERGE
144 };
145
146 typedef enum Efreet_Menu_Layout_Type Efreet_Menu_Layout_Type;
147
148 typedef struct Efreet_Menu_Layout Efreet_Menu_Layout;
149
150 struct Efreet_Menu_Layout
151 {
152     Efreet_Menu_Layout_Type  type;   /**< The type of layout */
153     const char *name;                /**< The name of the element */
154
155     /* The items below are for Menuname Layout elements */
156     signed char show_empty;    /**< Whether to show empty menus */
157     signed char in_line;       /**< Whether this meny can be inlined */
158     signed char inline_limit;  /**< Number of elements which triggers inline */
159     signed char inline_header; /**< Whether we should use the header name when this menu is inlined */
160     signed char inline_alias;  /**< Whether we should use the menu name when inlining */
161 };
162
163 typedef struct Efreet_Menu_Desktop Efreet_Menu_Desktop;
164
165 struct Efreet_Menu_Desktop
166 {
167     Efreet_Desktop *desktop;   /**< The desktop we refer too */
168     const char *id;            /**< The desktop file id */
169     unsigned char allocated:1; /**< If this desktop has been allocated */
170 };
171
172 static const char *efreet_menu_prefix = NULL; /**< The $XDG_MENU_PREFIX env var */
173 Eina_List *efreet_menu_kde_legacy_dirs = NULL; /**< The directories to use for KDELegacy entries */
174 static const char *efreet_tag_menu = NULL;
175 static const char *efreet_menu_file = NULL; /**< A menu file set explicityl as default */
176
177 static Eina_Hash *efreet_merged_menus = NULL;
178 static Eina_Hash *efreet_merged_dirs = NULL;
179
180 static Eina_Hash *efreet_menu_handle_cbs = NULL;
181 static Eina_Hash *efreet_menu_filter_cbs = NULL;
182 static Eina_Hash *efreet_menu_move_cbs = NULL;
183 static Eina_Hash *efreet_menu_layout_cbs = NULL;
184
185 static const char *efreet_menu_prefix_get(void);
186
187 static Efreet_Menu_Internal *efreet_menu_by_name_find(Efreet_Menu_Internal *internal,
188                                                     const char *name,
189                                                     Efreet_Menu_Internal **parent);
190 static int efreet_menu_cb_compare_names(Efreet_Menu_Internal *internal, const char *name);
191 static int efreet_menu_cb_md_compare_ids(Efreet_Menu_Desktop *md, const char *name);
192
193 static int efreet_menu_cb_entry_compare_menu(Efreet_Menu *entry, Efreet_Menu_Internal *internal);
194 static int efreet_menu_cb_entry_compare_desktop(Efreet_Menu *entry, Efreet_Desktop *desktop);
195
196 static int efreet_menu_cb_move_compare(Efreet_Menu_Move *move, const char *old);
197
198 static int efreet_menu_process(Efreet_Menu_Internal *internal, unsigned int only_unallocated);
199 static int efreet_menu_process_dirs(Efreet_Menu_Internal *internal);
200 static int efreet_menu_app_dirs_process(Efreet_Menu_Internal *internal);
201 static int efreet_menu_app_dir_scan(Efreet_Menu_Internal *internal,
202                                         const char *path,
203                                         const char *id,
204                                         int legacy);
205 static int efreet_menu_directory_dirs_process(Efreet_Menu_Internal *internal);
206 static int efreet_menu_directory_dir_scan(const char *path,
207                                             const char *relative_path,
208                                             Eina_Hash *cache);
209 static Efreet_Desktop *efreet_menu_directory_get(Efreet_Menu_Internal *internal,
210                                                     const char *path);
211 static void efreet_menu_process_filters(Efreet_Menu_Internal *internal,
212                                             unsigned int only_unallocated);
213 static Eina_List *efreet_menu_process_app_pool(Eina_List *pool,
214                                                Eina_List *applications,
215                                                Eina_Hash *matches,
216                                                Efreet_Menu_Filter *filter,
217                                                unsigned int only_unallocated);
218 static int efreet_menu_filter_matches(Efreet_Menu_Filter_Op *op,
219                                         Efreet_Menu_Desktop *md);
220 static int efreet_menu_filter_or_matches(Efreet_Menu_Filter_Op *op,
221                                             Efreet_Menu_Desktop *md);
222 static int efreet_menu_filter_and_matches(Efreet_Menu_Filter_Op *op,
223                                             Efreet_Menu_Desktop *md);
224 static int efreet_menu_filter_not_matches(Efreet_Menu_Filter_Op *op,
225                                             Efreet_Menu_Desktop *md);
226
227 static Efreet_Menu *efreet_menu_layout_menu(Efreet_Menu_Internal *internal);
228 static Efreet_Menu *efreet_menu_layout_desktop(Efreet_Menu_Desktop *md);
229 static void efreet_menu_layout_entries_get(Efreet_Menu *entry, Efreet_Menu_Internal *internal,
230                                             Efreet_Menu_Layout *layout);
231 static int efreet_menu_layout_is_empty(Efreet_Menu *entry);
232
233 static Efreet_Menu_Internal *efreet_menu_internal_new(void);
234 static void efreet_menu_internal_free(Efreet_Menu_Internal *internal);
235 static void efreet_menu_create_sub_menu_list(Efreet_Menu_Internal *internal);
236 static void efreet_menu_create_app_dirs_list(Efreet_Menu_Internal *internal);
237 static void efreet_menu_create_directory_dirs_list(Efreet_Menu_Internal *internal);
238 static void efreet_menu_create_directories_list(Efreet_Menu_Internal *internal);
239 static void efreet_menu_create_move_list(Efreet_Menu_Internal *internal);
240 static void efreet_menu_create_filter_list(Efreet_Menu_Internal *internal);
241 static void efreet_menu_create_layout_list(Efreet_Menu_Internal *internal);
242 static void efreet_menu_create_default_layout_list(Efreet_Menu_Internal *internal);
243 static const char *efreet_menu_path_get(Efreet_Menu_Internal *internal, const char *suffix);
244
245 static Efreet_Menu_App_Dir *efreet_menu_app_dir_new(void);
246 static void efreet_menu_app_dir_free(Efreet_Menu_App_Dir *dir);
247
248 static Efreet_Menu_Move *efreet_menu_move_new(void);
249 static void efreet_menu_move_free(Efreet_Menu_Move *move);
250
251 static Efreet_Menu_Filter *efreet_menu_filter_new(void);
252 static void efreet_menu_filter_free(Efreet_Menu_Filter *filter);
253
254 static Efreet_Menu_Layout *efreet_menu_layout_new(void);
255 static void efreet_menu_layout_free(Efreet_Menu_Layout *layout);
256
257 static Efreet_Menu_Filter_Op *efreet_menu_filter_op_new(void);
258 static void efreet_menu_filter_op_free(Efreet_Menu_Filter_Op *op);
259
260 static Efreet_Menu_Desktop *efreet_menu_desktop_new(void);
261 static void efreet_menu_desktop_free(Efreet_Menu_Desktop *md);
262
263 static Efreet_Menu *efreet_menu_entry_new(void);
264
265 static int efreet_menu_handle_menu(Efreet_Menu_Internal *internal, Efreet_Xml *xml);
266 static int efreet_menu_handle_name(Efreet_Menu_Internal *parent, Efreet_Xml *xml);
267
268 static int efreet_menu_handle_sub_menu(Efreet_Menu_Internal *parent, Efreet_Xml *xml);
269 static int efreet_menu_handle_app_dir(Efreet_Menu_Internal *parent, Efreet_Xml *xml);
270 static int efreet_menu_handle_default_app_dirs(Efreet_Menu_Internal *parent, Efreet_Xml *xml);
271 static int efreet_menu_handle_directory_dir(Efreet_Menu_Internal *parent, Efreet_Xml *xml);
272 static int efreet_menu_handle_default_directory_dirs(Efreet_Menu_Internal *parent, Efreet_Xml *xml);
273 static int efreet_menu_handle_directory(Efreet_Menu_Internal *parent, Efreet_Xml *xml);
274 static int efreet_menu_handle_only_unallocated(Efreet_Menu_Internal *parent, Efreet_Xml *xml);
275 static int efreet_menu_handle_not_only_unallocated(Efreet_Menu_Internal *parent, Efreet_Xml *xml);
276 static int efreet_menu_handle_deleted(Efreet_Menu_Internal *parent, Efreet_Xml *xml);
277 static int efreet_menu_handle_not_deleted(Efreet_Menu_Internal *parent, Efreet_Xml *xml);
278 static int efreet_menu_handle_include(Efreet_Menu_Internal *parent, Efreet_Xml *xml);
279 static int efreet_menu_handle_exclude(Efreet_Menu_Internal *parent, Efreet_Xml *xml);
280 static int efreet_menu_handle_filename(Efreet_Menu_Filter_Op *op, Efreet_Xml *xml);
281 static int efreet_menu_handle_category(Efreet_Menu_Filter_Op *op, Efreet_Xml *xml);
282 static int efreet_menu_handle_all(Efreet_Menu_Filter_Op *op, Efreet_Xml *xml);
283 static int efreet_menu_handle_and(Efreet_Menu_Filter_Op *op, Efreet_Xml *xml);
284 static int efreet_menu_handle_or(Efreet_Menu_Filter_Op *op, Efreet_Xml *xml);
285 static int efreet_menu_handle_not(Efreet_Menu_Filter_Op *op, Efreet_Xml *xml);
286 static int efreet_menu_handle_merge_file(Efreet_Menu_Internal *parent, Efreet_Xml *xml);
287 static int efreet_menu_handle_merge_dir(Efreet_Menu_Internal *parent, Efreet_Xml *xml);
288 static int efreet_menu_handle_default_merge_dirs(Efreet_Menu_Internal *parent, Efreet_Xml *xml);
289 static int efreet_menu_handle_legacy_dir(Efreet_Menu_Internal *parent, Efreet_Xml *xml);
290 static Efreet_Menu_Internal *efreet_menu_handle_legacy_dir_helper(Efreet_Menu_Internal *root,
291                                                 Efreet_Menu_Internal *parent,
292                                                 const char *legacy_dir,
293                                                 const char *prefix);
294 static int efreet_menu_handle_kde_legacy_dirs(Efreet_Menu_Internal *parent, Efreet_Xml *xml);
295 static int efreet_menu_handle_move(Efreet_Menu_Internal *parent, Efreet_Xml *xml);
296 static int efreet_menu_handle_old(Efreet_Menu_Internal *parent, Efreet_Xml *xml);
297 static int efreet_menu_handle_new(Efreet_Menu_Internal *parent, Efreet_Xml *xml);
298 static int efreet_menu_handle_layout(Efreet_Menu_Internal *parent, Efreet_Xml *xml);
299 static int efreet_menu_handle_default_layout(Efreet_Menu_Internal *parent, Efreet_Xml *xml);
300
301 static int efreet_menu_handle_filter(Efreet_Menu_Internal *parent, Efreet_Xml *xml,
302                                                     Efreet_Menu_Filter_Type type);
303 static int efreet_menu_handle_filter_op(Efreet_Menu_Filter_Op *op, Efreet_Xml *xml);
304 static int efreet_menu_handle_filter_child_op(Efreet_Menu_Filter_Op *op, Efreet_Xml *xml,
305                                                       Efreet_Menu_Filter_Op_Type type);
306
307 static int efreet_menu_handle_layout_menuname(Efreet_Menu_Internal *parent, Efreet_Xml *xml, int def);
308 static int efreet_menu_handle_layout_filename(Efreet_Menu_Internal *parent, Efreet_Xml *xml, int def);
309 static int efreet_menu_handle_layout_separator(Efreet_Menu_Internal *parent, Efreet_Xml *xml, int def);
310 static int efreet_menu_handle_layout_merge(Efreet_Menu_Internal *parent, Efreet_Xml *xml, int def);
311
312 static int efreet_menu_merge(Efreet_Menu_Internal *parent, Efreet_Xml *xml, const char *path);
313 static int efreet_menu_merge_dir(Efreet_Menu_Internal *parent, Efreet_Xml *xml, const char *path);
314
315 static int efreet_menu_cb_app_dirs_compare(Efreet_Menu_App_Dir *a, const char *b);
316
317 static void efreet_menu_resolve_moves(Efreet_Menu_Internal *internal);
318 static void efreet_menu_concatenate(Efreet_Menu_Internal *dest, Efreet_Menu_Internal *src);
319
320 static int efreet_menu_cb_menu_compare(Efreet_Menu_Internal *a, Efreet_Menu_Internal *b);
321 static int efreet_menu_cb_md_compare(const Efreet_Menu_Desktop *a, const Efreet_Menu_Desktop *b);
322
323 static int efreet_menu_save_menu(Efreet_Menu *menu, FILE *f, int indent);
324 static int efreet_menu_save_indent(FILE *f, int indent);
325
326 static void efreet_menu_path_set(Efreet_Menu_Internal *internal, const char *path);
327
328 int
329 efreet_menu_init(void)
330 {
331     int i;
332
333     struct
334     {
335         const char *key;
336         int (*cb)(Efreet_Menu_Internal *parent, Efreet_Xml *xml);
337     } menu_cbs[] = {
338         {"Menu", efreet_menu_handle_sub_menu},
339         {"AppDir", efreet_menu_handle_app_dir},
340         {"DefaultAppDirs", efreet_menu_handle_default_app_dirs},
341         {"DirectoryDir", efreet_menu_handle_directory_dir},
342         {"DefaultDirectoryDirs", efreet_menu_handle_default_directory_dirs},
343         {"Name", efreet_menu_handle_name},
344         {"Directory", efreet_menu_handle_directory},
345         {"OnlyUnallocated", efreet_menu_handle_only_unallocated},
346         {"NotOnlyUnallocated", efreet_menu_handle_not_only_unallocated},
347         {"Deleted", efreet_menu_handle_deleted},
348         {"NotDeleted", efreet_menu_handle_not_deleted},
349         {"Include", efreet_menu_handle_include},
350         {"Exclude", efreet_menu_handle_exclude},
351         {"MergeFile", efreet_menu_handle_merge_file},
352         {"MergeDir", efreet_menu_handle_merge_dir},
353         {"DefaultMergeDirs", efreet_menu_handle_default_merge_dirs},
354         {"LegacyDir", efreet_menu_handle_legacy_dir},
355         {"KDELegacyDirs", efreet_menu_handle_kde_legacy_dirs},
356         {"Move", efreet_menu_handle_move},
357         {"Layout", efreet_menu_handle_layout},
358         {"DefaultLayout", efreet_menu_handle_default_layout},
359         {NULL, NULL}
360     };
361
362     struct
363     {
364         const char *key;
365         int (*cb)(Efreet_Menu_Filter_Op *op, Efreet_Xml *xml);
366     } filter_cbs[] = {
367         {"Filename", efreet_menu_handle_filename},
368         {"Category", efreet_menu_handle_category},
369         {"All", efreet_menu_handle_all},
370         {"And", efreet_menu_handle_and},
371         {"Or", efreet_menu_handle_or},
372         {"Not", efreet_menu_handle_not},
373         {NULL, NULL}
374     };
375
376     struct
377     {
378         const char *key;
379         int (*cb)(Efreet_Menu_Internal *parent, Efreet_Xml *xml);
380     } move_cbs[] = {
381         {"Old", efreet_menu_handle_old},
382         {"New", efreet_menu_handle_new},
383         {NULL, NULL}
384     };
385
386     struct
387     {
388         const char *key;
389         int (*cb)(Efreet_Menu_Internal *parent, Efreet_Xml *xml, int def);
390     } layout_cbs[] = {
391         {"Menuname", efreet_menu_handle_layout_menuname},
392         {"Filename", efreet_menu_handle_layout_filename},
393         {"Separator", efreet_menu_handle_layout_separator},
394         {"Merge", efreet_menu_handle_layout_merge},
395         {NULL, NULL}
396     };
397
398     _efreet_menu_log_dom = eina_log_domain_register
399       ("efreet_menu", EFREET_DEFAULT_LOG_COLOR);
400     if (_efreet_menu_log_dom < 0)
401     {
402         EINA_LOG_ERR("Efreet: Could not create a log domain for efreet_menu");
403         return 0;
404     }
405
406     efreet_menu_handle_cbs = eina_hash_string_superfast_new(NULL);
407     efreet_menu_filter_cbs = eina_hash_string_superfast_new(NULL);
408     efreet_menu_move_cbs = eina_hash_string_superfast_new(NULL);
409     efreet_menu_layout_cbs = eina_hash_string_superfast_new(NULL);
410     if (!efreet_menu_handle_cbs || !efreet_menu_filter_cbs
411             || !efreet_menu_move_cbs || !efreet_menu_layout_cbs)
412     {
413         eina_log_domain_unregister(_efreet_menu_log_dom);
414         _efreet_menu_log_dom = -1;
415         return 0;
416     }
417
418     /* set Menu into it's own so we can check the XML is valid before trying
419      * to handle it */
420     efreet_tag_menu = eina_stringshare_add(menu_cbs[0].key);
421
422     for (i = 0; menu_cbs[i].key; i++)
423     {
424         eina_hash_del(efreet_menu_handle_cbs,
425                         menu_cbs[i].key,
426                         NULL);
427         eina_hash_add(efreet_menu_handle_cbs,
428                         menu_cbs[i].key,
429                         menu_cbs[i].cb);
430     }
431     for (i = 0; filter_cbs[i].key; i++)
432     {
433         eina_hash_del(efreet_menu_filter_cbs,
434                         filter_cbs[i].key,
435                         NULL);
436         eina_hash_add(efreet_menu_filter_cbs,
437                         filter_cbs[i].key,
438                         filter_cbs[i].cb);
439     }
440     for (i = 0; move_cbs[i].key; i++)
441     {
442         eina_hash_del(efreet_menu_move_cbs,
443                         move_cbs[i].key,
444                         NULL);
445         eina_hash_add(efreet_menu_move_cbs,
446                         move_cbs[i].key,
447                         move_cbs[i].cb);
448     }
449     for (i = 0; layout_cbs[i].key; i++)
450     {
451         eina_hash_del(efreet_menu_layout_cbs,
452                         layout_cbs[i].key,
453                         NULL);
454         eina_hash_add(efreet_menu_layout_cbs,
455                         layout_cbs[i].key,
456                         layout_cbs[i].cb);
457     }
458     return 1;
459 }
460
461 EAPI int
462 efreet_menu_kde_legacy_init(void)
463 {
464     FILE *f;
465     char buf[PATH_MAX];
466     char *p, *s;
467
468     IF_FREE_LIST(efreet_menu_kde_legacy_dirs, eina_stringshare_del);
469
470     f = popen("kde-config --path apps", "r");
471     if (!f) return 0;
472
473     /* XXX if the return from kde-config is a line longer than PATH_MAX,
474      * this won't be correct (increase buffer and get the rest...) */
475     if (!fgets(buf, sizeof(buf), f))
476     {
477         ERR("Error initializing KDE legacy information");
478         return 0;
479     }
480     s = buf;
481
482     p = strchr(s, ':');
483     while (p)
484     {
485         *p = '\0';
486         efreet_menu_kde_legacy_dirs = eina_list_append(efreet_menu_kde_legacy_dirs,
487                             (void *)eina_stringshare_add(s));
488         s = p + 1;
489         p = strchr(s, ':');
490     }
491
492     if (*s)
493         efreet_menu_kde_legacy_dirs = eina_list_append(efreet_menu_kde_legacy_dirs,
494                             (void *)eina_stringshare_add(s));
495
496     pclose(f);
497     return 1;
498 }
499
500 void
501 efreet_menu_shutdown(void)
502 {
503     IF_RELEASE(efreet_menu_file);
504
505     IF_FREE_HASH(efreet_menu_handle_cbs);
506     IF_FREE_HASH(efreet_menu_filter_cbs);
507     IF_FREE_HASH(efreet_menu_move_cbs);
508     IF_FREE_HASH(efreet_menu_layout_cbs);
509
510     IF_FREE_LIST(efreet_menu_kde_legacy_dirs, eina_stringshare_del);
511
512     IF_FREE_HASH(efreet_merged_menus);
513     IF_FREE_HASH(efreet_merged_dirs);
514
515     IF_RELEASE(efreet_tag_menu);
516
517     eina_log_domain_unregister(_efreet_menu_log_dom);
518     _efreet_menu_log_dom = -1;
519 }
520
521 EAPI Efreet_Menu *
522 efreet_menu_new(const char *name)
523 {
524     Efreet_Menu *menu;
525
526     EINA_SAFETY_ON_NULL_RETURN_VAL(name, NULL);
527
528     menu = efreet_menu_entry_new();
529     menu->type = EFREET_MENU_ENTRY_MENU;
530     menu->name = eina_stringshare_add(name);
531     return menu;
532 }
533
534 EAPI void
535 efreet_menu_file_set(const char *file)
536 {
537     IF_RELEASE(efreet_menu_file);
538     efreet_menu_file = NULL;
539     if (file) efreet_menu_file = eina_stringshare_add(file);
540 }
541
542 EAPI Efreet_Menu *
543 efreet_menu_get(void)
544 {
545     char menu[PATH_MAX];
546     const char *dir;
547     Eina_List *config_dirs, *l;
548
549 #ifndef STRICT_SPEC
550     /* prefer user set menu */
551     if (efreet_menu_file)
552     {
553         if (ecore_file_exists(efreet_menu_file))
554         return efreet_menu_parse(efreet_menu_file);
555     }
556 #endif
557
558     /* check the users config directory first */
559     snprintf(menu, sizeof(menu), "%s/menus/%sapplications.menu",
560                         efreet_config_home_get(), efreet_menu_prefix_get());
561     if (ecore_file_exists(menu))
562         return efreet_menu_parse(menu);
563
564     /* fallback to the XDG_CONFIG_DIRS */
565     config_dirs = efreet_config_dirs_get();
566     EINA_LIST_FOREACH(config_dirs, l, dir)
567     {
568         snprintf(menu, sizeof(menu), "%s/menus/%sapplications.menu",
569                                     dir, efreet_menu_prefix_get());
570         if (ecore_file_exists(menu))
571             return efreet_menu_parse(menu);
572     }
573
574     return NULL;
575 }
576
577 EAPI Efreet_Menu *
578 efreet_menu_parse(const char *path)
579 {
580     Efreet_Xml *xml;
581     Efreet_Menu_Internal *internal = NULL;
582     Efreet_Menu *entry = NULL;
583
584     EINA_SAFETY_ON_NULL_RETURN_VAL(path, NULL);
585
586     xml = efreet_xml_new(path);
587     if (!xml) return NULL;
588
589     /* make sure we've got a <Menu> to start with */
590     if (xml->tag != efreet_tag_menu)
591     {
592         WRN("Efreet_menu: Menu file didn't start with <Menu> tag.");
593         efreet_xml_del(xml);
594         return NULL;
595     }
596
597     IF_FREE_HASH(efreet_merged_menus);
598     efreet_merged_menus = eina_hash_string_superfast_new(NULL);
599
600     IF_FREE_HASH(efreet_merged_dirs);
601     efreet_merged_dirs = eina_hash_string_superfast_new(NULL);
602
603     /* split apart the filename and the path */
604     internal = efreet_menu_internal_new();
605     if (!internal) return NULL;
606
607     /* Set default values */
608     internal->show_empty = 0;
609     internal->in_line = 0;
610     internal->inline_limit = 4;
611     internal->inline_header = 1;
612     internal->inline_alias = 0;
613
614     efreet_menu_path_set(internal, path);
615     if (!efreet_menu_handle_menu(internal, xml))
616     {
617         efreet_xml_del(xml);
618         efreet_menu_internal_free(internal);
619         return NULL;
620     }
621     efreet_xml_del(xml);
622
623     efreet_menu_resolve_moves(internal);
624
625     if (!efreet_menu_process_dirs(internal))
626     {
627         efreet_menu_internal_free(internal);
628         return NULL;
629     }
630
631     /* handle all .desktops */
632     if (!efreet_menu_process(internal, 0))
633     {
634         efreet_menu_internal_free(internal);
635         return NULL;
636     }
637
638     /* handle menus with only unallocated .desktops */
639     if (!efreet_menu_process(internal, 1))
640     {
641         efreet_menu_internal_free(internal);
642         return NULL;
643     }
644
645     /* layout menu */
646     entry = efreet_menu_layout_menu(internal);
647     efreet_menu_internal_free(internal);
648     return entry;
649 }
650
651 EAPI int
652 efreet_menu_save(Efreet_Menu *menu, const char *path)
653 {
654     FILE *f;
655     int ret;
656
657     EINA_SAFETY_ON_NULL_RETURN_VAL(menu, 0);
658     EINA_SAFETY_ON_NULL_RETURN_VAL(path, 0);
659
660     f = fopen(path, "w");
661     if (!f) return 0;
662     fprintf(f, "<?xml version=\"1.0\"?>\n");
663     fprintf(f, "<!DOCTYPE Menu PUBLIC \"-//freedesktop//DTD Menu 1.0//EN\" "
664                 "\"http://standards.freedesktop.org/menu-spec/menu-1.0.dtd\">\n");
665     ret = efreet_menu_save_menu(menu, f, 0);
666     fclose(f);
667     return ret;
668 }
669
670 static int
671 efreet_menu_save_menu(Efreet_Menu *menu, FILE *f, int indent)
672 {
673     Eina_List *l;
674
675     efreet_menu_save_indent(f, indent);
676     fprintf(f, "<Menu>\n");
677     if (menu->name)
678     {
679         efreet_menu_save_indent(f, indent + 1);
680         fprintf(f, "<Name>%s</Name>\n", menu->name);
681     }
682
683     if (indent == 0)
684     {
685         /* Only save these for the root element */
686         efreet_menu_save_indent(f, indent + 1);
687         fprintf(f, "<DefaultAppDirs/>\n");
688         efreet_menu_save_indent(f, indent + 1);
689         fprintf(f, "<DefaultDirectoryDirs/>\n");
690     }
691
692     if (menu->desktop)
693     {
694         efreet_menu_save_indent(f, indent + 1);
695         fprintf(f, "<Directory>%s</Directory>\n", menu->desktop->orig_path);
696     }
697
698     if (menu->entries)
699     {
700         Efreet_Menu *entry;
701         int has_desktop = 0, has_menu = 0;
702
703         efreet_menu_save_indent(f, indent + 1);
704         fprintf(f, "<Layout>\n");
705         EINA_LIST_FOREACH(menu->entries, l, entry)
706         {
707             if (entry->type == EFREET_MENU_ENTRY_MENU)
708             {
709                 efreet_menu_save_indent(f, indent + 2);
710                 fprintf(f, "<Menuname>%s</Menuname>\n", entry->id);
711                 has_menu = 1;
712             }
713             else if (entry->type == EFREET_MENU_ENTRY_DESKTOP)
714             {
715                 efreet_menu_save_indent(f, indent + 2);
716                 fprintf(f, "<Filename>%s</Filename>\n", entry->id);
717                 has_desktop = 1;
718             }
719             else if (entry->type == EFREET_MENU_ENTRY_SEPARATOR)
720             {
721                 efreet_menu_save_indent(f, indent + 2);
722                 fprintf(f, "<Separator/>\n");
723             }
724         }
725         efreet_menu_save_indent(f, indent + 1);
726         fprintf(f, "</Layout>\n");
727
728         if (has_desktop)
729         {
730             efreet_menu_save_indent(f, indent + 1);
731             fprintf(f, "<Include>\n");
732             EINA_LIST_FOREACH(menu->entries, l, entry)
733             {
734                 if (entry->type == EFREET_MENU_ENTRY_DESKTOP)
735                 {
736                     efreet_menu_save_indent(f, indent + 2);
737                     fprintf(f, "<Filename>%s</Filename>\n", entry->id);
738                 }
739             }
740             efreet_menu_save_indent(f, indent + 1);
741             fprintf(f, "</Include>\n");
742         }
743
744         if (has_menu)
745         {
746             EINA_LIST_FOREACH(menu->entries, l, entry)
747             {
748                 if (entry->type == EFREET_MENU_ENTRY_MENU)
749                     efreet_menu_save_menu(entry, f, indent + 1);
750             }
751         }
752     }
753     efreet_menu_save_indent(f, indent);
754     fprintf(f, "</Menu>\n");
755     return 1;
756 }
757
758 static int
759 efreet_menu_save_indent(FILE *f, int indent)
760 {
761     int i;
762
763     for (i = 0; i < indent; i++)
764         fprintf(f, "  ");
765     return 1;
766 }
767
768 EAPI int
769 efreet_menu_desktop_insert(Efreet_Menu *menu, Efreet_Desktop *desktop, int pos)
770 {
771     Efreet_Menu *entry;
772     const char *id;
773
774     EINA_SAFETY_ON_NULL_RETURN_VAL(menu, 0);
775     EINA_SAFETY_ON_NULL_RETURN_VAL(desktop, 0);
776
777     id = efreet_util_path_to_file_id(desktop->orig_path);
778     if (!id) return 0;
779
780     entry = efreet_menu_entry_new();
781     entry->type = EFREET_MENU_ENTRY_DESKTOP;
782     entry->id = eina_stringshare_add(id);
783     entry->name = eina_stringshare_add(desktop->name);
784     if (desktop->icon) entry->icon = eina_stringshare_add(desktop->icon);
785     efreet_desktop_ref(desktop);
786     entry->desktop = desktop;
787
788     if (pos < 0 || (unsigned int)pos >= eina_list_count(menu->entries))
789         menu->entries = eina_list_append(menu->entries, entry);
790     else
791     {
792         menu->entries = eina_list_append_relative(menu->entries, entry,
793                                                   eina_list_nth(menu->entries, pos));
794     }
795     return 1;
796 }
797
798 EAPI int
799 efreet_menu_desktop_remove(Efreet_Menu *menu, Efreet_Desktop *desktop)
800 {
801     Efreet_Menu *entry;
802
803     EINA_SAFETY_ON_NULL_RETURN_VAL(menu, 0);
804     EINA_SAFETY_ON_NULL_RETURN_VAL(desktop, 0);
805
806     entry = eina_list_search_unsorted(menu->entries,
807                                       EINA_COMPARE_CB(efreet_menu_cb_entry_compare_desktop),
808                             desktop);
809     if (entry)
810     {
811         menu->entries = eina_list_remove(menu->entries, entry);
812         efreet_menu_free(entry);
813         return 1;
814     }
815     return 0;
816 }
817
818 EAPI void
819 efreet_menu_dump(Efreet_Menu *menu, const char *indent)
820 {
821     Eina_List *l;
822
823     EINA_SAFETY_ON_NULL_RETURN(menu);
824     EINA_SAFETY_ON_NULL_RETURN(indent);
825
826     INF("%s%s: ", indent, menu->name);
827     INF("%s", (menu->icon ? menu->icon : "No icon"));
828
829     /* XXX dump the rest of the menu info */
830
831     if (menu->entries)
832     {
833         Efreet_Menu *entry;
834         char *new_indent;
835         size_t len;
836
837         len = strlen(indent) + 3;
838         new_indent = alloca(len);
839         snprintf(new_indent, len, "%s  ", indent);
840
841         EINA_LIST_FOREACH(menu->entries, l, entry)
842         {
843             if (entry->type == EFREET_MENU_ENTRY_SEPARATOR)
844                 INF("%s|---", new_indent);
845             else if (entry->type == EFREET_MENU_ENTRY_DESKTOP)
846                 INF("%s|-%s", new_indent, entry->name);
847             else if (entry->type == EFREET_MENU_ENTRY_MENU)
848                 efreet_menu_dump(entry, new_indent);
849             else if (entry->type == EFREET_MENU_ENTRY_HEADER)
850                 INF("%s|---%s", new_indent, entry->name);
851         }
852     }
853 }
854
855 /**
856  * @internal
857  * @param user_dir The user directory to work with
858  * @param system_dirs The system directories to work with
859  * @param suffix The path suffix to add
860  * @return Returns the list of directories
861  * @brief Creates the list of directories based on the user
862  * dir, system dirs and given suffix.
863  *
864  * Needs EAPI because of helper binaries
865  */
866 EAPI Eina_List *
867 efreet_default_dirs_get(const char *user_dir, Eina_List *system_dirs,
868                                                     const char *suffix)
869 {
870     const char *xdg_dir;
871     char dir[PATH_MAX];
872     Eina_List *list = NULL;
873     Eina_List *l;
874
875     EINA_SAFETY_ON_NULL_RETURN_VAL(user_dir, NULL);
876     EINA_SAFETY_ON_NULL_RETURN_VAL(suffix, NULL);
877
878     snprintf(dir, sizeof(dir), "%s/%s", user_dir, suffix);
879     list = eina_list_append(list, eina_stringshare_add(dir));
880
881     EINA_LIST_FOREACH(system_dirs, l, xdg_dir)
882     {
883         snprintf(dir, sizeof(dir), "%s/%s", xdg_dir, suffix);
884         list = eina_list_append(list, eina_stringshare_add(dir));
885     }
886
887     return list;
888 }
889
890 /**
891  * @internal
892  * @return Returns a new Efreet_Menu_Internal struct
893  * @brief Allocates and initializes a new Efreet_Menu_Internal structure
894  */
895 static Efreet_Menu_Internal *
896 efreet_menu_internal_new(void)
897 {
898     Efreet_Menu_Internal *internal;
899
900     internal = NEW(Efreet_Menu_Internal, 1);
901     if (!internal) return NULL;
902     internal->show_empty = -1;
903     internal->in_line = -1;
904     internal->inline_limit = -1;
905     internal->inline_header = -1;
906     internal->inline_alias = -1;
907
908     return internal;
909 }
910
911 /**
912  * @param menu The menu to free
913  * @return Returns no value
914  * @brief Frees up the given menu structure
915  */
916 void
917 efreet_menu_internal_free(Efreet_Menu_Internal *internal)
918 {
919     if (!internal) return;
920
921     IF_RELEASE(internal->file.path);
922     IF_RELEASE(internal->file.name);
923
924     IF_RELEASE(internal->name.internal);
925     internal->name.name = NULL;
926
927     internal->applications = eina_list_free(internal->applications);
928
929     IF_FREE_LIST(internal->directories, eina_stringshare_del);
930     IF_FREE_LIST(internal->app_dirs, efreet_menu_app_dir_free);
931     IF_FREE_LIST(internal->app_pool, efreet_menu_desktop_free);
932     IF_FREE_LIST(internal->directory_dirs, eina_stringshare_del);
933     IF_FREE_HASH(internal->directory_cache);
934
935     IF_FREE_LIST(internal->moves, efreet_menu_move_free);
936     IF_FREE_LIST(internal->filters, efreet_menu_filter_free);
937
938     IF_FREE_LIST(internal->sub_menus, efreet_menu_internal_free);
939
940     IF_FREE_LIST(internal->layout, efreet_menu_layout_free);
941     IF_FREE_LIST(internal->default_layout, efreet_menu_layout_free);
942
943     FREE(internal);
944 }
945
946 /**
947  * @internal
948  * @return Returns the XDG_MENU_PREFIX env variable or "" if none set
949  * @brief Retrieves the XDG_MENU_PREFIX or "" if not set.
950  */
951 static const char *
952 efreet_menu_prefix_get(void)
953 {
954     if (efreet_menu_prefix) return efreet_menu_prefix;
955
956     efreet_menu_prefix = getenv("XDG_MENU_PREFIX");
957     if (!efreet_menu_prefix) efreet_menu_prefix = "";
958
959     return efreet_menu_prefix;
960 }
961
962 /**
963  * @internal
964  * @param menu The menu to populate
965  * @param xml The xml dom tree to populate from
966  * @return Returns 1 if this XML tree is valid, 0 otherwise
967  * @brief Populates the given menu from the given xml structure
968  *
969  * We walk the Menu children backwards. The reason for this is so that we
970  * can deal with all the things that make us select the 'last' element
971  * (MergeFile, Directory, etc). We'll see the last one first and can deal
972  * with it right away.
973  */
974 static int
975 efreet_menu_handle_menu(Efreet_Menu_Internal *internal, Efreet_Xml *xml)
976 {
977     Efreet_Xml *child;
978     Eina_List *l;
979     int (*cb)(Efreet_Menu_Internal *parent, Efreet_Xml *xml);
980
981     EINA_LIST_REVERSE_FOREACH(xml->children, l, child)
982     {
983         cb = eina_hash_find(efreet_menu_handle_cbs, child->tag);
984         if (cb)
985         {
986             if (!cb(internal, child))
987                 return 0;
988         }
989         else
990         {
991             WRN("Unknown XML tag: %s", child->tag);
992             return 0;
993         }
994     }
995     return 1;
996 }
997
998 /**
999  * @internal
1000  * @param parent The parent Menu
1001  * @param xml The xml that defines the menu
1002  * @return Returns 1 on success or 0 on failure
1003  * @brief Handles the sub-menu nodes of the XML file
1004  */
1005 static int
1006 efreet_menu_handle_sub_menu(Efreet_Menu_Internal *parent, Efreet_Xml *xml)
1007 {
1008     Efreet_Menu_Internal *internal, *match;
1009
1010     efreet_menu_create_sub_menu_list(parent);
1011
1012     internal = efreet_menu_internal_new();
1013     if (!internal) return 0;
1014     internal->file.path = eina_stringshare_add(parent->file.path);
1015     if (!efreet_menu_handle_menu(internal, xml))
1016     {
1017         efreet_menu_internal_free(internal);
1018         return 0;
1019     }
1020
1021     /* if this menu already exists we just take this one and stick it on the
1022      * start of the existing one */
1023     if ((match = eina_list_search_unsorted(parent->sub_menus,
1024                                            EINA_COMPARE_CB(efreet_menu_cb_menu_compare),
1025                                            internal)))
1026     {
1027
1028         efreet_menu_concatenate(match, internal);
1029         efreet_menu_internal_free(internal);
1030     }
1031     else
1032         parent->sub_menus = eina_list_prepend(parent->sub_menus, internal);
1033
1034     return 1;
1035 }
1036
1037 /**
1038  * @internal
1039  * @param parent The parent menu
1040  * @param xml The xml tree
1041  * @return Returns 1 on success or 0 on failure
1042  * @brief Handles the AppDir tag
1043  */
1044 static int
1045 efreet_menu_handle_app_dir(Efreet_Menu_Internal *parent, Efreet_Xml *xml)
1046 {
1047     const char *path;
1048     Efreet_Menu_App_Dir *app_dir;
1049
1050     if (!parent || !xml) return 0;
1051
1052     efreet_menu_create_app_dirs_list(parent);
1053     path = efreet_menu_path_get(parent, xml->text);
1054     if (!path) return 0;
1055
1056     /* we've already got this guy in our list we can skip it */
1057     if (eina_list_search_unsorted(parent->app_dirs,
1058                                   EINA_COMPARE_CB(efreet_menu_cb_app_dirs_compare),
1059                                   path))
1060     {
1061         eina_stringshare_del(path);
1062         return 1;
1063     }
1064
1065     app_dir = efreet_menu_app_dir_new();
1066     app_dir->path = path;
1067
1068     parent->app_dirs = eina_list_prepend(parent->app_dirs, app_dir);
1069
1070     return 1;
1071 }
1072
1073 /**
1074  * @internal
1075  * @param parent The parent menu
1076  * @param xml UNUSED
1077  * @return Returns 1 on success or 0 on failure
1078  * @brief Handles the DefaultAppDirs
1079  */
1080 static int
1081 efreet_menu_handle_default_app_dirs(Efreet_Menu_Internal *parent, Efreet_Xml *xml __UNUSED__)
1082 {
1083     Eina_List *prepend = NULL;
1084     Eina_List *dirs;
1085     char *dir;
1086
1087     if (!parent) return 0;
1088
1089     efreet_menu_create_app_dirs_list(parent);
1090     dirs = efreet_default_dirs_get(efreet_data_home_get(), efreet_data_dirs_get(),
1091                                                                     "applications");
1092     EINA_LIST_FREE(dirs, dir)
1093     {
1094         if (!eina_list_search_unsorted(parent->app_dirs,
1095                                        EINA_COMPARE_CB(efreet_menu_cb_app_dirs_compare),
1096                                        dir))
1097         {
1098             Efreet_Menu_App_Dir *app_dir;
1099
1100             app_dir = efreet_menu_app_dir_new();
1101             app_dir->path = eina_stringshare_ref(dir);
1102
1103             prepend = eina_list_append(prepend, app_dir);
1104         }
1105
1106         eina_stringshare_del(dir);
1107     }
1108     parent->app_dirs = eina_list_merge(prepend, parent->app_dirs);
1109
1110     return 1;
1111 }
1112
1113 /**
1114  * @internal
1115  * @param parent The parent menu
1116  * @param xml The xml tree
1117  * @return Returns 1 on success or 0 on failure
1118  * @brief Handles the DirectoryDir tag
1119  */
1120 static int
1121 efreet_menu_handle_directory_dir(Efreet_Menu_Internal *parent, Efreet_Xml *xml)
1122 {
1123     const char *path;
1124
1125     if (!parent || !xml) return 0;
1126
1127     efreet_menu_create_directory_dirs_list(parent);
1128     path = efreet_menu_path_get(parent, xml->text);
1129     if (!path) return 0;
1130
1131     /* we've already got this guy in our list we can skip it */
1132     if (eina_list_search_unsorted(parent->directory_dirs, EINA_COMPARE_CB(strcmp), path))
1133     {
1134         eina_stringshare_del(path);
1135         return 1;
1136     }
1137
1138     parent->directory_dirs = eina_list_prepend(parent->directory_dirs, path);
1139
1140     return 1;
1141 }
1142
1143 /**
1144  * @internal
1145  * @param parent The parent menu
1146  * @param xml UNUSED
1147  * @return Returns 1 on success or 0 on failure
1148  * @brief Handles the DefaultDirectoryDirs tag
1149  */
1150 static int
1151 efreet_menu_handle_default_directory_dirs(Efreet_Menu_Internal *parent, Efreet_Xml *xml __UNUSED__)
1152 {
1153     Eina_List *dirs;
1154     char *dir;
1155
1156     if (!parent) return 0;
1157
1158     efreet_menu_create_directory_dirs_list(parent);
1159     dirs = efreet_default_dirs_get(efreet_data_home_get(), efreet_data_dirs_get(),
1160                                                             "desktop-directories");
1161     EINA_LIST_FREE(dirs, dir)
1162     {
1163         if (!eina_list_search_unsorted(parent->directory_dirs, EINA_COMPARE_CB(strcmp), dir))
1164             parent->directory_dirs = eina_list_prepend(parent->directory_dirs, eina_stringshare_ref(dir));
1165         eina_stringshare_del(dir);
1166     }
1167
1168     return 1;
1169 }
1170
1171 /**
1172  * @internal
1173  * @param parent The parent Menu
1174  * @param xml The xml to work with
1175  * @return Returns 1 on success or 0 on failure
1176  * @brief Sets the menu name from the given XML fragment.
1177  */
1178 static int
1179 efreet_menu_handle_name(Efreet_Menu_Internal *parent, Efreet_Xml *xml)
1180 {
1181     /* not allowed to have two Name settings in a menu */
1182     if (parent->name.internal)
1183     {
1184         INF("efreet_menu_handle_name() setting second name into menu");
1185         return 0;
1186     }
1187     /* ignore the name if it is empty */
1188     if (!xml->text) return 1;
1189
1190     /* ignore the name if it contains a / */
1191     if (strchr(xml->text, '/')) return 1;
1192
1193     parent->name.internal = eina_stringshare_add(xml->text);
1194
1195     return 1;
1196 }
1197
1198 /**
1199  * @internal
1200  * @param parent The parent menu
1201  * @param xml The xml tree
1202  * @return Returns 1 on success or 0 on failure
1203  * @brief Handles the Directory tag
1204  *
1205  * This just adds the given directory path to a list which we'll walk once
1206  * we've traversed the entire menu into memory.
1207  */
1208 static int
1209 efreet_menu_handle_directory(Efreet_Menu_Internal *parent, Efreet_Xml *xml)
1210 {
1211     if (!parent || !xml) return 0;
1212
1213     efreet_menu_create_directories_list(parent);
1214     parent->directories = eina_list_prepend(parent->directories, eina_stringshare_add(xml->text));
1215
1216     return 1;
1217 }
1218
1219 /**
1220  * @internal
1221  * @param parent The parent menu
1222  * @param xml The xml tree
1223  * @return Returns 1 on success or 0 on failure
1224  * @brief Handles the OnlyUnallocated tag
1225  */
1226 static int
1227 efreet_menu_handle_only_unallocated(Efreet_Menu_Internal *parent, Efreet_Xml *xml)
1228 {
1229     if (!parent || !xml) return 0;
1230
1231     /* a later instance has been seen so we can ignore this one */
1232     if (parent->seen_allocated) return 1;
1233
1234     parent->seen_allocated = 1;
1235     parent->only_unallocated = 1;
1236
1237     return 1;
1238 }
1239
1240 /**
1241  * @internal
1242  * @param parent The parent menu
1243  * @param xml The xml tree
1244  * @return Returns 1 on success or 0 on failure
1245  * @brief Handles the NotOnlyUnallocated tag
1246  */
1247 static int
1248 efreet_menu_handle_not_only_unallocated(Efreet_Menu_Internal *parent, Efreet_Xml *xml)
1249 {
1250     if (!parent || !xml) return 0;
1251
1252     /* a later instance has been seen so we can ignore this one */
1253     if (parent->seen_allocated) return 1;
1254
1255     parent->seen_allocated = 1;
1256     parent->only_unallocated = 0;
1257
1258     return 1;
1259 }
1260
1261 /**
1262  * @internal
1263  * @param parent The parent menu
1264  * @param xml The xml tree
1265  * @return Returns 1 on success or 0 on failure
1266  * @brief Handles the Deleted tag
1267  */
1268 static int
1269 efreet_menu_handle_deleted(Efreet_Menu_Internal *parent, Efreet_Xml *xml)
1270 {
1271     if (!parent || !xml) return 0;
1272
1273     /* a later instance has been seen so we can ignore this one */
1274     if (parent->seen_deleted) return 1;
1275
1276     parent->seen_deleted = 1;
1277     parent->deleted = 1;
1278
1279     return 1;
1280 }
1281
1282 /**
1283  * @internal
1284  * @param parent The parent menu
1285  * @param xml The xml tree
1286  * @return Returns 1 on success or 0 on failure
1287  * @brief Handles the NotDeleted tag
1288  */
1289 static int
1290 efreet_menu_handle_not_deleted(Efreet_Menu_Internal *parent, Efreet_Xml *xml)
1291 {
1292     if (!parent || !xml) return 0;
1293
1294     /* a later instance has been seen so we can ignore this one */
1295     if (parent->seen_deleted) return 1;
1296
1297     parent->seen_deleted = 1;
1298     parent->deleted = 0;
1299
1300     return 1;
1301 }
1302
1303 /**
1304  * @internal
1305  * @param parent The parent menu
1306  * @param xml The XML tree to work with
1307  * @return Returns 1 on success or 0 on failure
1308  * @brief Handles parsing the Include tag and all subtags
1309  */
1310 static int
1311 efreet_menu_handle_include(Efreet_Menu_Internal *parent, Efreet_Xml *xml)
1312 {
1313     return efreet_menu_handle_filter(parent, xml,
1314                                 EFREET_MENU_FILTER_INCLUDE);
1315 }
1316
1317 /**
1318  * @internal
1319  * @param parent The parent menu
1320  * @param xml The xml tree
1321  * @return Returns 1 on success or 0 on failure
1322  * @brief Handles the Exclude tag and all subtags
1323  */
1324 static int
1325 efreet_menu_handle_exclude(Efreet_Menu_Internal *parent, Efreet_Xml *xml)
1326 {
1327     return efreet_menu_handle_filter(parent, xml,
1328                                 EFREET_MENU_FILTER_EXCLUDE);
1329 }
1330
1331 /**
1332  * @internal
1333  * @param op The filter operation
1334  * @param xml The xml tree
1335  * @return Returns 1 on success or 0 on failure
1336  * @brief Handles the Filename tag
1337  */
1338 static int
1339 efreet_menu_handle_filename(Efreet_Menu_Filter_Op *op, Efreet_Xml *xml)
1340 {
1341     if (!op || !xml) return 0;
1342
1343     op->filenames = eina_list_append(op->filenames, eina_stringshare_add(xml->text));
1344
1345     return 1;
1346 }
1347
1348 /**
1349  * @internal
1350  * @param op The filter operation
1351  * @param xml The xml tree
1352  * @return Returns 1 on success or 0 on failure
1353  * @brief Handles the Category tag
1354  */
1355 static int
1356 efreet_menu_handle_category(Efreet_Menu_Filter_Op *op, Efreet_Xml *xml)
1357 {
1358     if (!op || !xml) return 0;
1359
1360
1361     op->categories = eina_list_append(op->categories, eina_stringshare_add(xml->text));
1362
1363     return 1;
1364 }
1365
1366 /**
1367  * @internal
1368  * @param op The filter operation
1369  * @param xml The xml tree
1370  * @return Returns 1 on success or 0 on failure
1371  * @brief Handles the All tag and all subtags
1372  */
1373 static int
1374 efreet_menu_handle_all(Efreet_Menu_Filter_Op *op, Efreet_Xml *xml)
1375 {
1376     if (!op || !xml) return 0;
1377
1378     op->all = 1;
1379
1380     return 1;
1381 }
1382
1383 /**
1384  * @internal
1385  * @param op The filter operation
1386  * @param xml The xml tree
1387  * @return Returns 1 on success or 0 on failure
1388  * @brief Handles the And tag and all subtags
1389  */
1390 static int
1391 efreet_menu_handle_and(Efreet_Menu_Filter_Op *op, Efreet_Xml *xml)
1392 {
1393     if (!op || !xml) return 0;
1394
1395     return efreet_menu_handle_filter_child_op(op, xml,
1396                             EFREET_MENU_FILTER_OP_AND);
1397 }
1398
1399 /**
1400  * @internal
1401  * @param op The filter operation
1402  * @param xml The xml tree
1403  * @return Returns 1 on success or 0 on failure
1404  * @brief Handles the Or tag and all subtags
1405  */
1406 static int
1407 efreet_menu_handle_or(Efreet_Menu_Filter_Op *op, Efreet_Xml *xml)
1408 {
1409     if (!op || !xml) return 0;
1410
1411     return efreet_menu_handle_filter_child_op(op, xml,
1412                             EFREET_MENU_FILTER_OP_OR);
1413 }
1414
1415 /**
1416  * @internal
1417  * @param op The filter operation
1418  * @param xml The xml tree
1419  * @return Returns 1 on success or 0 on failure
1420  * @brief Handles the Not tag and all subtags
1421  */
1422 static int
1423 efreet_menu_handle_not(Efreet_Menu_Filter_Op *op, Efreet_Xml *xml)
1424 {
1425     if (!op || !xml) return 0;
1426
1427     return efreet_menu_handle_filter_child_op(op, xml,
1428                             EFREET_MENU_FILTER_OP_NOT);
1429 }
1430
1431 /**
1432  * @internal
1433  * @param parent The parent menu
1434  * @param xml The xml tree
1435  * @return Returns 1 on success or 0 on failure
1436  * @brief Handles the MergeFile tag
1437  */
1438 static int
1439 efreet_menu_handle_merge_file(Efreet_Menu_Internal *parent, Efreet_Xml *xml)
1440 {
1441     Eina_List *l;
1442     const char *path = NULL;
1443     const char *attr = NULL;
1444     int is_path = 1;
1445     int ret = 1;
1446
1447     if (!parent || !xml) return 0;
1448
1449     /* check to see if this is a path or parent type */
1450     attr = efreet_xml_attribute_get(xml, "type");
1451     if (attr && !strcmp(attr, "parent"))
1452         is_path = 0;
1453
1454     /* we're given a path */
1455     if (is_path)
1456         path = efreet_menu_path_get(parent, xml->text);
1457
1458     /* need to find the next menu with the same name as ours in the config
1459      * dir after ours (if we're in a config dir) */
1460     else
1461     {
1462         Eina_List *search_dirs;
1463         const char *dir, *p;
1464
1465         if (!parent->file.path)
1466         {
1467             INF("efreet_menu_handle_merge_file() missing menu path ...");
1468             return 0;
1469         }
1470
1471         search_dirs = efreet_config_dirs_get();
1472
1473         /* we need to find the next menu with the same name in the directory
1474          * after the on the the menu was found in. to do that we first check
1475          * if it's in the config_home_directory() if so we need to search
1476          * all of the dirs. If it isn't in the config home directory then we
1477          * scan the search dirs and look for it. The search_dirs list will
1478          * be left at the next pointer so we can start looking for the menu
1479          * from that point */
1480
1481         dir = efreet_config_home_get();
1482         if (strncmp(dir, parent->file.path, eina_stringshare_strlen(dir)))
1483         {
1484             EINA_LIST_FOREACH(search_dirs, l, dir)
1485             {
1486                 if (!strncmp(dir, parent->file.path, eina_stringshare_strlen(dir)))
1487                     break;
1488             }
1489         }
1490
1491         if (!dir)
1492         {
1493             INF("efreet_menu_handle_merge_file() failed to find "
1494                     "menu parent directory");
1495             return 0;
1496         }
1497
1498         /* the parent file path may have more path then just the base
1499          * directory so we need to append that as well */
1500         p = parent->file.path + eina_stringshare_strlen(dir);
1501
1502         /* whatever dirs are left in the search dir we need to look for the
1503          * menu with the same relative filename */
1504         EINA_LIST_FOREACH(search_dirs, l, dir)
1505         {
1506             char file[PATH_MAX];
1507
1508             snprintf(file, sizeof(file), "%s/%s/%s", dir, (p ? p : ""),
1509                                                         parent->file.name);
1510             if (ecore_file_exists(file))
1511             {
1512                 path = eina_stringshare_add(file);
1513                 break;
1514             }
1515         }
1516     }
1517
1518     /* nothing to do if no file found */
1519     if (!path) return 1;
1520
1521     if (!efreet_menu_merge(parent, xml, path))
1522         ret = 0;
1523
1524     eina_stringshare_del(path);
1525
1526     return ret;
1527 }
1528
1529 /**
1530  * @internal
1531  * @param parent The parent menu to merge into
1532  * @param xml The XML to be merged
1533  * @param path The path to the .menu file to merge
1534  */
1535 static int
1536 efreet_menu_merge(Efreet_Menu_Internal *parent, Efreet_Xml *xml, const char *path)
1537 {
1538     Efreet_Xml *merge_xml;
1539     Efreet_Menu_Internal *internal;
1540     char rp[PATH_MAX];
1541
1542     if (!parent || !xml || !path) return 0;
1543
1544     /* do nothing if the file doesn't exist */
1545     if (!ecore_file_exists(path)) return 1;
1546
1547     if (!realpath(path, rp))
1548     {
1549         INF("efreet_menu_merge() unable to get real path for %s", path);
1550         return 0;
1551     }
1552
1553     /* don't merge the same path twice */
1554     if (eina_hash_find(efreet_merged_menus, rp))
1555     {
1556         return 1;
1557     }
1558
1559     eina_hash_add(efreet_merged_menus, rp, (void *)1);
1560
1561     merge_xml = efreet_xml_new(rp);
1562
1563     if (!merge_xml)
1564     {
1565         INF("efreet_menu_merge() failed to read in the "
1566                 "merge file (%s)", rp);
1567         return 0;
1568     }
1569
1570     internal = efreet_menu_internal_new();
1571     if (!internal) return 0;
1572     efreet_menu_path_set(internal, path);
1573     efreet_menu_handle_menu(internal, merge_xml);
1574     efreet_menu_concatenate(parent, internal);
1575     efreet_menu_internal_free(internal);
1576
1577     efreet_xml_del(merge_xml);
1578
1579     return 1;
1580 }
1581
1582 /**
1583  * @internal
1584  * @param parent The parent menu
1585  * @param xml The xml tree
1586  * @return Returns 1 on success or 0 on failure
1587  * @brief Handles the MergeDir tag
1588  */
1589 static int
1590 efreet_menu_handle_merge_dir(Efreet_Menu_Internal *parent, Efreet_Xml *xml)
1591 {
1592     const char *path;
1593     int ret;
1594
1595     if (!parent || !xml || !xml->text) return 0;
1596
1597     path = efreet_menu_path_get(parent, xml->text);
1598     if (!path) return 1;
1599     if (!ecore_file_exists(path))
1600     {
1601         eina_stringshare_del(path);
1602         return 1;
1603     }
1604
1605     ret = efreet_menu_merge_dir(parent, xml, path);
1606     eina_stringshare_del(path);
1607
1608     return ret;
1609 }
1610
1611 /**
1612  * @internal
1613  * @param parent the parent menu of the merge
1614  * @param xml The xml tree
1615  * @param path The path to the merge directory
1616  * @return Returns 1 on success or 0 on failure
1617  * @brief Find all of the .menu files in the given directory and merge them
1618  * into the @a parent menu.
1619  */
1620 static int
1621 efreet_menu_merge_dir(Efreet_Menu_Internal *parent, Efreet_Xml *xml, const char *path)
1622 {
1623     Eina_Iterator *it;
1624     Eina_File_Direct_Info *info;
1625
1626     if (!parent || !xml || !path) return 0;
1627
1628     /* check to see if we've merged this directory already */
1629     if (eina_hash_find(efreet_merged_dirs, path)) return 1;
1630     eina_hash_add(efreet_merged_dirs, path, (void *)1);
1631
1632     it = eina_file_direct_ls(path);
1633     if (!it) return 1;
1634
1635     EINA_ITERATOR_FOREACH(it, info)
1636     {
1637         char *p;
1638
1639         p = strrchr(info->path + info->name_start, '.');
1640         if (!p) continue;
1641         if (strcmp(p, ".menu")) continue;
1642
1643         if (!efreet_menu_merge(parent, xml, info->path))
1644         {
1645             eina_iterator_free(it);
1646             return 0;
1647         }
1648     }
1649     eina_iterator_free(it);
1650
1651     return 1;
1652 }
1653
1654 /**
1655  * @internal
1656  * @param parent The parent menu
1657  * @param xml The xml tree
1658  * @return Returns 1 on success or 0 on failure
1659  * @brief Handles the DefaultMergeDirs tag
1660  */
1661 static int
1662 efreet_menu_handle_default_merge_dirs(Efreet_Menu_Internal *parent, Efreet_Xml *xml)
1663 {
1664     Eina_List *dirs;
1665     char path[PATH_MAX], *p, *pp;
1666 #ifndef STRICT_SPEC
1667     char parent_path[PATH_MAX];
1668 #endif
1669     const char *prefix;
1670
1671     if (!parent || !xml) return 0;
1672
1673     prefix = efreet_menu_prefix_get();
1674     if (!strcmp(prefix, "gnome-") &&
1675             (!strcmp(parent->file.name, "gnome-applications.menu")))
1676     {
1677         p = alloca(sizeof("applications"));
1678         memcpy(p, "applications", sizeof("applications"));
1679     }
1680     else if ((!strcmp(prefix, "kde-") &&
1681             (!strcmp(parent->file.name, "kde-applications.menu"))))
1682     {
1683         p = alloca(sizeof("applications"));
1684         memcpy(p, "applications", sizeof("applications"));
1685     }
1686     else
1687     {
1688         char *s;
1689         size_t len;
1690
1691         len = strlen(parent->file.name) + 1;
1692         p = alloca(len);
1693         memcpy(p, parent->file.name, len);
1694         s = strrchr(p, '.');
1695         if (s) *s = '\0';
1696     }
1697     snprintf(path, sizeof(path), "menus/%s-merged", p);
1698
1699     dirs = efreet_default_dirs_get(efreet_config_home_get(),
1700                                     efreet_config_dirs_get(), path);
1701
1702     EINA_LIST_FREE(dirs, pp)
1703     {
1704         efreet_menu_merge_dir(parent, xml, pp);
1705         eina_stringshare_del(pp);
1706     }
1707 #ifndef STRICT_SPEC
1708     /* Also check the path of the parent file */
1709     snprintf(parent_path, sizeof(parent_path), "%s/%s", parent->file.path, path);
1710     efreet_menu_merge_dir(parent, xml, parent_path);
1711 #endif
1712
1713     return 1;
1714 }
1715
1716 /**
1717  * @internal
1718  * @param parent The parent menu
1719  * @param xml The xml tree
1720  * @return Returns 1 on success or 0 on failure
1721  * @brief Handles the LegacyDir tag
1722  */
1723 static int
1724 efreet_menu_handle_legacy_dir(Efreet_Menu_Internal *parent, Efreet_Xml *xml)
1725 {
1726     Efreet_Menu_Internal *legacy;
1727
1728     if (!parent || !xml) return 0;
1729
1730     legacy = efreet_menu_handle_legacy_dir_helper(NULL, parent, xml->text,
1731                                 efreet_xml_attribute_get(xml, "prefix"));
1732     if (legacy)
1733     {
1734         efreet_menu_concatenate(parent, legacy);
1735         efreet_menu_internal_free(legacy);
1736     }
1737
1738     return 1;
1739
1740 }
1741
1742 /**
1743  * @internal
1744  * @param parent The parent menu
1745  * @param legacy_dir The legacy directory path
1746  * @param prefix The legacy directory prefix if one set
1747  * @return Returns the Efreet_Menu_Internal representing the legacy hierarchy
1748  * @brief Handles the process of merging @a legacy_dir into @a parent menu
1749  */
1750 static Efreet_Menu_Internal *
1751 efreet_menu_handle_legacy_dir_helper(Efreet_Menu_Internal *root,
1752                                         Efreet_Menu_Internal *parent,
1753                                         const char *legacy_dir,
1754                                         const char *prefix)
1755 {
1756     const char *path;
1757     Efreet_Menu_Internal *legacy_internal;
1758     Efreet_Menu_Filter *filter;
1759     Efreet_Menu_App_Dir *app_dir;
1760     int count = 0;
1761     Eina_Iterator *it;
1762
1763     if (!parent || !legacy_dir) return 0;
1764
1765     path = efreet_menu_path_get(parent, legacy_dir);
1766
1767     /* nothing to do if the legacy path doesn't exist */
1768     if (!path || !ecore_file_exists(path))
1769     {
1770         eina_stringshare_del(path);
1771         return NULL;
1772     }
1773
1774     legacy_internal = efreet_menu_internal_new();
1775     if (!legacy_internal)
1776         return NULL;
1777     legacy_internal->name.internal = eina_stringshare_add(ecore_file_file_get(path));
1778
1779     /* add the legacy dir as an app dir */
1780     app_dir = efreet_menu_app_dir_new();
1781     app_dir->path = eina_stringshare_add(path);
1782     app_dir->legacy = 1;
1783     if (prefix && !strchr(prefix, '/')) app_dir->prefix = eina_stringshare_add(prefix);
1784
1785     efreet_menu_create_app_dirs_list(legacy_internal);
1786     legacy_internal->app_dirs = eina_list_append(legacy_internal->app_dirs, app_dir);
1787 #ifndef STRICT_SPEC
1788     if (root)
1789     {
1790         /* XXX This seems wrong, but it makes efreet pass the fdo tests */
1791         app_dir = efreet_menu_app_dir_new();
1792         app_dir->path = eina_stringshare_add(path);
1793         app_dir->legacy = 1;
1794         if (prefix && !strchr(prefix, '/')) app_dir->prefix = eina_stringshare_add(prefix);
1795         root->app_dirs = eina_list_append(root->app_dirs, app_dir);
1796     }
1797 #endif
1798
1799     /* add the legacy dir as a directory dir */
1800     efreet_menu_create_directory_dirs_list(legacy_internal);
1801     legacy_internal->directory_dirs = eina_list_append(legacy_internal->directory_dirs, eina_stringshare_add(path));
1802
1803     /* setup a filter for all the conforming .desktop files in the legacy
1804      * dir */
1805     filter = efreet_menu_filter_new();
1806     if (!filter)
1807     {
1808         efreet_menu_internal_free(legacy_internal);
1809         return NULL;
1810     }
1811     filter->type = EFREET_MENU_FILTER_INCLUDE;
1812
1813     filter->op->type = EFREET_MENU_FILTER_OP_OR;
1814
1815     efreet_menu_create_filter_list(legacy_internal);
1816     legacy_internal->filters = eina_list_append(legacy_internal->filters, filter);
1817
1818     it = eina_file_direct_ls(path);
1819     if (it)
1820     {
1821         Eina_File_Direct_Info *info;
1822
1823         EINA_ITERATOR_FOREACH(it, info)
1824         {
1825             Efreet_Desktop *desktop = NULL;
1826             char buf[PATH_MAX];
1827             char *exten;
1828             const char *fname;
1829
1830             fname = info->path + info->name_start;
1831             /* recurse into sub directories */
1832             if (ecore_file_is_dir(info->path))
1833             {
1834                 Efreet_Menu_Internal *ret;
1835
1836                 ret = efreet_menu_handle_legacy_dir_helper(root ? root : legacy_internal,
1837                         legacy_internal, info->path, prefix);
1838                 if (!ret)
1839                 {
1840                     efreet_menu_internal_free(legacy_internal);
1841                     eina_stringshare_del(path);
1842                     eina_iterator_free(it);
1843                     return NULL;
1844                 }
1845
1846                 efreet_menu_create_sub_menu_list(legacy_internal);
1847                 legacy_internal->sub_menus = eina_list_prepend(legacy_internal->sub_menus, ret);
1848
1849                 continue;
1850             }
1851
1852             if (!strcmp(fname, ".directory"))
1853             {
1854                 legacy_internal->directory = efreet_desktop_get(info->path);
1855                 if (legacy_internal->directory
1856                         && legacy_internal->directory->type != EFREET_DESKTOP_TYPE_DIRECTORY)
1857                 {
1858                     efreet_desktop_free(legacy_internal->directory);
1859                     legacy_internal->directory = NULL;
1860                 }
1861                 continue;
1862             }
1863
1864             exten = strrchr(fname, '.');
1865
1866             if (exten && !strcmp(exten, ".desktop"))
1867                 desktop = efreet_desktop_get(info->path);
1868
1869             if (!desktop) continue;
1870
1871             /* if the .desktop has categories it isn't legacy */
1872             if (efreet_desktop_category_count_get(desktop) != 0)
1873             {
1874                 efreet_desktop_free(desktop);
1875                 continue;
1876             }
1877
1878             /* XXX: This will disappear when the .desktop is free'd */
1879             efreet_desktop_category_add(desktop, "Legacy");
1880
1881             if (prefix)
1882             {
1883                 snprintf(buf, sizeof(buf), "%s%s", prefix, fname);
1884                 filter->op->filenames = eina_list_append(filter->op->filenames, eina_stringshare_add(buf));
1885             }
1886             else
1887                 filter->op->filenames = eina_list_append(filter->op->filenames, eina_stringshare_add(fname));
1888
1889             count++;
1890             efreet_desktop_free(desktop);
1891         }
1892         eina_iterator_free(it);
1893     }
1894
1895     eina_stringshare_del(path);
1896     return legacy_internal;
1897 }
1898
1899 /**
1900  * @internal
1901  * @param parent The parent menu
1902  * @param xml UNUSED
1903  * @return Returns 1 on success or 0 on failure
1904  * @brief Handles the KDELegacyDirs tag
1905  */
1906 static int
1907 efreet_menu_handle_kde_legacy_dirs(Efreet_Menu_Internal *parent, Efreet_Xml *xml __UNUSED__)
1908 {
1909     Eina_List *l;
1910     const char *dir;
1911
1912     if (!parent) return 0;
1913
1914     if (!efreet_menu_kde_legacy_dirs) return 1;
1915
1916     /* XXX if one _helper() call succeeds, we return success. should this be flipped?
1917      * (return fail if on of them failed) */
1918     EINA_LIST_FOREACH(efreet_menu_kde_legacy_dirs, l, dir)
1919     {
1920         Efreet_Menu_Internal *kde;
1921
1922         kde = efreet_menu_handle_legacy_dir_helper(NULL, parent, dir, "kde");
1923         if (kde)
1924         {
1925             efreet_menu_concatenate(parent, kde);
1926             efreet_menu_internal_free(kde);
1927             return 1;
1928         }
1929     }
1930
1931     return 0;
1932 }
1933
1934 /**
1935  * @internal
1936  * @param parent The parent menu
1937  * @param xml The xml tree
1938  * @return Returns 1 on success or 0 on failure
1939  * @brief Handles the Move tag and all subtags
1940  */
1941 static int
1942 efreet_menu_handle_move(Efreet_Menu_Internal *parent, Efreet_Xml *xml)
1943 {
1944     Efreet_Xml *child;
1945     Eina_List *l;
1946
1947     if (!parent || !xml) return 0;
1948
1949     efreet_menu_create_move_list(parent);
1950
1951     EINA_LIST_FOREACH(xml->children, l, child)
1952     {
1953         int (*cb)(Efreet_Menu_Internal *parent, Efreet_Xml *xml);
1954
1955         cb = eina_hash_find(efreet_menu_move_cbs, child->tag);
1956         if (cb)
1957         {
1958             if (!cb(parent, child))
1959                 return 0;
1960         }
1961         else
1962         {
1963             INF("efreet_menu_handle_move() unknown tag found "
1964                     "in Move (%s)", child->tag);
1965             return 0;
1966         }
1967     }
1968
1969     parent->current_move = NULL;
1970
1971     return 1;
1972 }
1973
1974 /**
1975  * @internal
1976  * @param parent The parent menu
1977  * @param xml The xml tree
1978  * @return Returns 1 on success or 0 on failure
1979  * @brief Handles the Old tag
1980  */
1981 static int
1982 efreet_menu_handle_old(Efreet_Menu_Internal *parent, Efreet_Xml *xml)
1983 {
1984     Efreet_Menu_Move *move;
1985
1986     if (!parent || !xml || !xml->text) return 0;
1987
1988     if (parent->current_move)
1989     {
1990         INF("efreet_menu_handle_old() saw second <Old> "
1991                 "before seeing <New>");
1992         return 0;
1993     }
1994
1995     /* If we already moved this menu, remove the old move */
1996     /* XXX This seems wrong, but it makes efreet pass the fdo tests */
1997 #ifndef STRICT_SPEC
1998     move = eina_list_search_unsorted(parent->moves,
1999                                      EINA_COMPARE_CB(efreet_menu_cb_move_compare),
2000                                      xml->text);
2001     if (move)
2002     {
2003         efreet_menu_move_free(move);
2004         parent->moves = eina_list_remove(parent->moves, move);
2005     }
2006 #endif
2007
2008     move = efreet_menu_move_new();
2009     move->old_name = eina_stringshare_add(xml->text);
2010
2011     parent->current_move = move;
2012     parent->moves = eina_list_append(parent->moves, move);
2013
2014     return 1;
2015 }
2016
2017 /**
2018  * @internal
2019  * @param parent The parent menu
2020  * @param xml The xml tree
2021  * @return Returns 1 on success or 0 on failure
2022  * @brief Handles the New tag
2023  */
2024 static int
2025 efreet_menu_handle_new(Efreet_Menu_Internal *parent, Efreet_Xml *xml)
2026 {
2027     if (!parent || !xml || !xml->text) return 0;
2028
2029     if (!parent->current_move)
2030     {
2031         INF("efreet_menu_handle_new() saw New before seeing Old");
2032         return 0;
2033     }
2034
2035     parent->current_move->new_name = eina_stringshare_add(xml->text);
2036     parent->current_move = NULL;
2037
2038     return 1;
2039 }
2040
2041 /**
2042  * @internal
2043  * @param parent The parent menu
2044  * @param xml The xml tree
2045  * @return Returns 1 on success or 0 on failure
2046  * @brief Handles the Layout tag and all subtags
2047  */
2048 static int
2049 efreet_menu_handle_layout(Efreet_Menu_Internal *parent, Efreet_Xml *xml)
2050 {
2051     Efreet_Xml *child;
2052     Eina_List *l;
2053
2054     if (!parent || !xml) return 0;
2055
2056     /* We use the last existing layout */
2057     if (parent->layout) return 1;
2058
2059     efreet_menu_create_layout_list(parent);
2060
2061     EINA_LIST_FOREACH(xml->children, l, child)
2062     {
2063         int (*cb)(Efreet_Menu_Internal *parent, Efreet_Xml *xml, int def);
2064
2065         cb = eina_hash_find(efreet_menu_layout_cbs, child->tag);
2066         if (cb)
2067         {
2068             if (!cb(parent, child, 0))
2069                 return 0;
2070         }
2071         else
2072         {
2073             INF("efreet_menu_handle_move() unknown tag found "
2074                     "in Layout (%s)", child->tag);
2075             return 0;
2076         }
2077     }
2078
2079     return 1;
2080 }
2081
2082 /**
2083  * @internal
2084  * @param parent The parent menu
2085  * @param xml The xml tree
2086  * @return Returns 1 on success or 0 on failure
2087  * @brief Handles the DefaultLayout tag
2088  */
2089 static int
2090 efreet_menu_handle_default_layout(Efreet_Menu_Internal *parent, Efreet_Xml *xml)
2091 {
2092     const char *val;
2093     Efreet_Xml *child;
2094     Eina_List *l;
2095
2096     if (!parent || !xml) return 0;
2097
2098     /* We use the last existing layout */
2099     if (parent->default_layout) return 1;
2100
2101     val = efreet_xml_attribute_get(xml, "show_empty");
2102     if (val) parent->show_empty = !strcmp(val, "true");
2103
2104     val = efreet_xml_attribute_get(xml, "inline");
2105     if (val) parent->in_line = !strcmp(val, "true");
2106
2107     val = efreet_xml_attribute_get(xml, "inline_limit");
2108     if (val) parent->inline_limit = atoi(val);
2109
2110     val = efreet_xml_attribute_get(xml, "inline_header");
2111     if (val) parent->inline_header = !strcmp(val, "true");
2112
2113     val = efreet_xml_attribute_get(xml, "inline_alias");
2114     if (val) parent->inline_alias = !strcmp(val, "true");
2115
2116     efreet_menu_create_default_layout_list(parent);
2117
2118     EINA_LIST_FOREACH(xml->children, l, child)
2119     {
2120         int (*cb)(Efreet_Menu_Internal *parent, Efreet_Xml *xml, int def);
2121
2122         cb = eina_hash_find(efreet_menu_layout_cbs, child->tag);
2123         if (cb)
2124         {
2125             if (!cb(parent, child, 1))
2126                 return 0;
2127         }
2128         else
2129         {
2130             INF("efreet_menu_handle_move() unknown tag found in "
2131                     "DefaultLayout (%s)", child->tag);
2132             return 0;
2133         }
2134     }
2135
2136     return 1;
2137 }
2138
2139 static int
2140 efreet_menu_handle_layout_menuname(Efreet_Menu_Internal *parent, Efreet_Xml *xml, int def)
2141 {
2142     Efreet_Menu_Layout *layout;
2143     const char *val;
2144
2145     if (!parent || !xml) return 0;
2146
2147     if (!xml->text)
2148     {
2149         INF("efreet_menu_handle_layout_menuname() The Menuname tag in "
2150                 "layout needs a filename.");
2151         return 0;
2152     }
2153
2154     layout = efreet_menu_layout_new();
2155     layout->type = EFREET_MENU_LAYOUT_MENUNAME;
2156     layout->name = eina_stringshare_add(xml->text);
2157
2158     val = efreet_xml_attribute_get(xml, "show_empty");
2159     if (val) layout->show_empty = !strcmp(val, "true");
2160
2161     val = efreet_xml_attribute_get(xml, "inline");
2162     if (val) layout->in_line = !strcmp(val, "true");
2163
2164     val = efreet_xml_attribute_get(xml, "inline_limit");
2165     if (val) layout->inline_limit = atoi(val);
2166
2167     val = efreet_xml_attribute_get(xml, "inline_header");
2168     if (val) layout->inline_header = !strcmp(val, "true");
2169
2170     val = efreet_xml_attribute_get(xml, "inline_alias");
2171     if (val) layout->inline_alias = !strcmp(val, "true");
2172
2173     if (def) parent->default_layout = eina_list_append(parent->default_layout, layout);
2174     else parent->layout = eina_list_append(parent->layout, layout);
2175
2176     return 1;
2177 }
2178
2179 static int
2180 efreet_menu_handle_layout_filename(Efreet_Menu_Internal *parent, Efreet_Xml *xml, int def)
2181 {
2182     Efreet_Menu_Layout *layout;
2183
2184     if (!parent || !xml) return 0;
2185
2186     if (!xml->text)
2187     {
2188         INF("efreet_menu_handle_layout_filename() The Filename tag in "
2189                 "layout needs a filename.");
2190         return 0;
2191     }
2192
2193     layout = efreet_menu_layout_new();
2194     layout->type = EFREET_MENU_LAYOUT_FILENAME;
2195     layout->name = eina_stringshare_add(xml->text);
2196
2197     if (def) parent->default_layout = eina_list_append(parent->default_layout, layout);
2198     else parent->layout = eina_list_append(parent->layout, layout);
2199
2200     return 1;
2201 }
2202
2203 static int
2204 efreet_menu_handle_layout_separator(Efreet_Menu_Internal *parent, Efreet_Xml *xml, int def)
2205 {
2206     Efreet_Menu_Layout *layout;
2207
2208     if (!parent || !xml) return 0;
2209
2210     layout = efreet_menu_layout_new();
2211     layout->type = EFREET_MENU_LAYOUT_SEPARATOR;
2212     if (def)
2213         parent->default_layout = eina_list_append(parent->default_layout, layout);
2214     else
2215         parent->layout = eina_list_append(parent->layout, layout);
2216     return 1;
2217 }
2218
2219 static int
2220 efreet_menu_handle_layout_merge(Efreet_Menu_Internal *parent, Efreet_Xml *xml, int def)
2221 {
2222     Efreet_Menu_Layout *layout;
2223     const char *attr;
2224
2225     if (!parent || !xml) return 0;
2226
2227     attr = efreet_xml_attribute_get(xml, "type");
2228     if (!attr)
2229     {
2230         INF("efreet_menu_handle_layout_merge() The Merge tag in layout "
2231                 "needs a type attribute.");
2232         return 0;
2233     }
2234
2235     if (strcmp(attr, "files") && strcmp(attr, "menus") && strcmp(attr, "all"))
2236     {
2237         INF("efreet_menu_handle_layout_merge() The type attribute for "
2238                 "the Merge tag contains an unknown value (%s).", attr);
2239         return 0;
2240     }
2241
2242     layout = efreet_menu_layout_new();
2243     layout->type = EFREET_MENU_LAYOUT_MERGE;
2244     layout->name = eina_stringshare_add(attr);
2245
2246     if (def) parent->default_layout = eina_list_append(parent->default_layout, layout);
2247     else parent->layout = eina_list_append(parent->layout, layout);
2248
2249     return 1;
2250 }
2251
2252 /**
2253  * @internal
2254  * @param parent The parent menu
2255  * @param xml The XML tree to parse
2256  * @param type The type of filter
2257  * @return Returns 1 on success or 0 on failure
2258  * @brief Parses the given XML tree and adds the filter to the parent menu
2259  */
2260 static int
2261 efreet_menu_handle_filter(Efreet_Menu_Internal *parent, Efreet_Xml *xml,
2262                                         Efreet_Menu_Filter_Type type)
2263 {
2264     Efreet_Menu_Filter *filter;
2265
2266     efreet_menu_create_filter_list(parent);
2267
2268     /* filters have a default or relationship */
2269     filter = efreet_menu_filter_new();
2270     if (!filter) return 0;
2271     filter->type = type;
2272     filter->op->type = EFREET_MENU_FILTER_OP_OR;
2273
2274     if (!efreet_menu_handle_filter_op(filter->op, xml))
2275     {
2276         efreet_menu_filter_free(filter);
2277         return 0;
2278     }
2279
2280     parent->filters = eina_list_prepend(parent->filters, filter);
2281
2282     return 1;
2283 }
2284
2285 /**
2286  * @internal
2287  * @param op The operation to work with
2288  * @param xml The XML tree representing this operation
2289  * @return Returns 1 on success or 0 on failure
2290  * @brief Parses the given XML tree and populates the operation
2291  */
2292 static int
2293 efreet_menu_handle_filter_op(Efreet_Menu_Filter_Op *op, Efreet_Xml *xml)
2294 {
2295     Efreet_Xml *child;
2296     Eina_List *l;
2297
2298     EINA_LIST_FOREACH(xml->children, l, child)
2299     {
2300         int (*cb)(Efreet_Menu_Filter_Op *op, Efreet_Xml *xml);
2301
2302         cb = eina_hash_find(efreet_menu_filter_cbs, child->tag);
2303         if (cb)
2304         {
2305             if (!cb(op, child))
2306                 return 0;
2307         }
2308         else
2309         {
2310             INF("efreet_menu_handle_filter_op() unknown tag in filter (%s)", child->tag);
2311             return 0;
2312         }
2313     }
2314     return 1;
2315 }
2316
2317 /**
2318  * @internal
2319  * @return Returns a new Efreet_Menu_Filter on success or NULL on failure
2320  * @brief Creates and initializes an Efreet_Menu_Filter object
2321  */
2322 static Efreet_Menu_Filter *
2323 efreet_menu_filter_new(void)
2324 {
2325     Efreet_Menu_Filter *filter;
2326
2327     filter = NEW(Efreet_Menu_Filter, 1);
2328     if (!filter) return NULL;
2329     filter->op = efreet_menu_filter_op_new();
2330     if (!filter->op)
2331     {
2332         FREE(filter);
2333         return NULL;
2334     }
2335
2336     return filter;
2337 }
2338
2339 /**
2340  * @internal
2341  * @param filter The filter to work with
2342  * @return Returns no data
2343  * @brief Frees the given filter and all data
2344  */
2345 static void
2346 efreet_menu_filter_free(Efreet_Menu_Filter *filter)
2347 {
2348     if (!filter) return;
2349
2350     if (filter->op) efreet_menu_filter_op_free(filter->op);
2351     filter->op = NULL;
2352
2353     FREE(filter);
2354 }
2355
2356 /**
2357  * @internal
2358  * @return Returns a new Efreet_Menu_Layout on success or NULL on failure
2359  * @brief Creates and initializes an Efreet_Menu_Layout object
2360  */
2361 static Efreet_Menu_Layout *
2362 efreet_menu_layout_new(void)
2363 {
2364     Efreet_Menu_Layout *layout;
2365
2366     layout = NEW(Efreet_Menu_Layout, 1);
2367     layout->show_empty = -1;
2368     layout->in_line = -1;
2369     layout->inline_limit = -1;
2370     layout->inline_header = -1;
2371     layout->inline_alias = -1;
2372
2373     return layout;
2374 }
2375
2376 /**
2377  * @internal
2378  * @param filter The filter to work with
2379  * @return Returns no data
2380  * @brief Frees the given filter and all data
2381  */
2382 static void
2383 efreet_menu_layout_free(Efreet_Menu_Layout *layout)
2384 {
2385     if (!layout) return;
2386
2387     IF_RELEASE(layout->name);
2388     FREE(layout);
2389 }
2390
2391 /**
2392  * @internal
2393  * @return Returns a new Efreet_Menu_Filter_Op on success or NULL on failure
2394  * @brief Creates and initializes an Efreet_Menu_Filter_Op structure
2395  */
2396 static Efreet_Menu_Filter_Op *
2397 efreet_menu_filter_op_new(void)
2398 {
2399     Efreet_Menu_Filter_Op *op;
2400
2401     op = NEW(Efreet_Menu_Filter_Op, 1);
2402
2403     return op;
2404 }
2405
2406 /**
2407  * @internal
2408  * @param op The operation to work with
2409  * @return Returns no value.
2410  * @brief Frees the given operation and all sub data
2411  */
2412 static void
2413 efreet_menu_filter_op_free(Efreet_Menu_Filter_Op *op)
2414 {
2415     if (!op) return;
2416
2417     IF_FREE_LIST(op->categories, eina_stringshare_del);
2418     IF_FREE_LIST(op->filenames, eina_stringshare_del);
2419     IF_FREE_LIST(op->filters, efreet_menu_filter_op_free);
2420
2421     FREE(op);
2422 }
2423
2424 /**
2425  * @internal
2426  * @return Returns a new Efreet_Menu_Desktop on success or NULL on failure
2427  * @brief Creates and returns an Efreet_Menu_Desktop
2428  */
2429 static Efreet_Menu_Desktop *
2430 efreet_menu_desktop_new(void)
2431 {
2432     Efreet_Menu_Desktop *md;
2433
2434     md = NEW(Efreet_Menu_Desktop, 1);
2435
2436     return md;
2437 }
2438
2439 /**
2440  * @internal
2441  * @param md The Efreet_Menu_Desktop to free
2442  * @return Returns no value
2443  * @brief Frees the given structure
2444  */
2445 static void
2446 efreet_menu_desktop_free(Efreet_Menu_Desktop *md)
2447 {
2448     IF_RELEASE(md->id);
2449     if (md->desktop) efreet_desktop_free(md->desktop);
2450     FREE(md);
2451 }
2452
2453 /**
2454  * @internal
2455  * @return Returns a new Efreet_Menu on success or NULL on failure
2456  * @brief Creates and returns an Efreet_Menu
2457  */
2458 static Efreet_Menu *
2459 efreet_menu_entry_new(void)
2460 {
2461     Efreet_Menu *entry;
2462
2463     entry = NEW(Efreet_Menu, 1);
2464
2465     return entry;
2466 }
2467
2468 EAPI void
2469 efreet_menu_free(Efreet_Menu *entry)
2470 {
2471     Efreet_Menu *sub;
2472
2473     if (!entry) return;
2474
2475     IF_RELEASE(entry->name);
2476     IF_RELEASE(entry->icon);
2477     EINA_LIST_FREE(entry->entries, sub)
2478         efreet_menu_free(sub);
2479     IF_RELEASE(entry->id);
2480     if (entry->desktop) efreet_desktop_free(entry->desktop);
2481     FREE(entry);
2482 }
2483
2484 /**
2485  * @internal
2486  * @param op The op to add a child too
2487  * @param xml The XML tree of the child
2488  * @param type The type of child to add
2489  * @return Returns 1 on success or 0 on failure
2490  * @brief Parses the given XML tree and populates a new child operation.
2491  */
2492 static int
2493 efreet_menu_handle_filter_child_op(Efreet_Menu_Filter_Op *op, Efreet_Xml *xml,
2494                                                 Efreet_Menu_Filter_Op_Type type)
2495 {
2496     Efreet_Menu_Filter_Op *child_op;
2497
2498     child_op = efreet_menu_filter_op_new();
2499     child_op->type = type;
2500
2501     if (!efreet_menu_handle_filter_op(child_op, xml))
2502     {
2503         efreet_menu_filter_op_free(child_op);
2504         return 0;
2505     }
2506
2507     op->filters = eina_list_append(op->filters, child_op);
2508
2509     return 1;
2510 }
2511
2512 /**
2513  * @internal
2514  * @param menu The menu to work with
2515  * @param only_unallocated Do we only look for unallocated items?
2516  * @return Returns 1 if we've successfully processed the menu, 0 otherwise
2517  * @brief Handles the processing of the menu data to retrieve the .desktop
2518  * files for the menu
2519  */
2520 static int
2521 efreet_menu_process(Efreet_Menu_Internal *internal, unsigned int only_unallocated)
2522 {
2523     Eina_List *l;
2524
2525     /* a menu _MUST_ have a name */
2526     if (!internal->name.internal || (internal->name.internal[0] == '\0'))
2527         return 0;
2528
2529     /* handle filtering out .desktop files as needed. This deals with all
2530      * .desktop files */
2531     efreet_menu_process_filters(internal, only_unallocated);
2532
2533     if (internal->sub_menus)
2534     {
2535         Efreet_Menu_Internal *sub_internal;
2536
2537         EINA_LIST_FOREACH(internal->sub_menus, l, sub_internal)
2538         {
2539             sub_internal->parent = internal;
2540             efreet_menu_process(sub_internal, only_unallocated);
2541         }
2542     }
2543
2544     return 1;
2545 }
2546
2547 /* This will walk through all of the app dirs and load all the .desktop
2548  * files into the cache for the menu. The .desktop files will have their
2549  * allocated flag set to 0 */
2550 static int
2551 efreet_menu_process_dirs(Efreet_Menu_Internal *internal)
2552 {
2553     Eina_List *l;
2554
2555     /* Scan application directories for .desktop files */
2556     if (!efreet_menu_app_dirs_process(internal))
2557         return 0;
2558
2559     /* Scan directory directories for .directory file */
2560     if (!efreet_menu_directory_dirs_process(internal))
2561         return 0;
2562
2563     if (internal->sub_menus)
2564     {
2565         Efreet_Menu_Internal *sub_internal;
2566
2567         EINA_LIST_FOREACH(internal->sub_menus, l, sub_internal)
2568         {
2569             sub_internal->parent = internal;
2570             efreet_menu_process_dirs(sub_internal);
2571         }
2572     }
2573
2574     return 1;
2575 }
2576
2577 /**
2578  * @internal
2579  * @param menu the menu to process
2580  * @param only_unallocated Only handle menus that deal with unallocated items
2581  * @return Returns no value
2582  * @brief Handles the processing of the filters attached to the given menu.
2583  *
2584  * For each include filter we'll add the items to our applications array. Each
2585  * exclude filter will remove items from the applications array
2586  */
2587 static void
2588 efreet_menu_process_filters(Efreet_Menu_Internal *internal, unsigned int only_unallocated)
2589 {
2590     Efreet_Menu_Filter *filter;
2591     Efreet_Menu_Desktop *md;
2592     Eina_List *l, *ll;
2593
2594     int included = 0;
2595
2596     /* nothing to do if we're checking the other option */
2597     if (only_unallocated != internal->only_unallocated) return;
2598
2599     internal->applications = eina_list_free(internal->applications);
2600
2601     if (!internal->filters) return;
2602
2603     EINA_LIST_FOREACH(internal->filters, l, filter)
2604     {
2605         /* skip excludes until we get an include */
2606         if (!included && (filter->type == EFREET_MENU_FILTER_EXCLUDE))
2607             continue;
2608         included = 1;
2609
2610         if (filter->type == EFREET_MENU_FILTER_INCLUDE)
2611         {
2612             Eina_Hash *matches;
2613
2614             matches = eina_hash_string_superfast_new(NULL);
2615             internal->applications = efreet_menu_process_app_pool(internal->app_pool, internal->applications,
2616                                         matches, filter, internal->only_unallocated);
2617             if (internal->parent)
2618             {
2619                 Efreet_Menu_Internal *parent;
2620
2621                 parent = internal->parent;
2622                 do {
2623                     internal->applications = efreet_menu_process_app_pool(parent->app_pool,
2624                                                 internal->applications, matches, filter,
2625                                                 internal->only_unallocated);
2626                 } while ((parent = parent->parent));
2627             }
2628             eina_hash_free(matches);
2629         }
2630         else
2631         {
2632             /* check each item in our menu so far and see if it's excluded */
2633             l = internal->applications;
2634             while ((md = eina_list_data_get(l)))
2635             {
2636                 ll = eina_list_next(l);
2637                 if (efreet_menu_filter_matches(filter->op, md))
2638                     internal->applications = eina_list_remove_list(internal->applications, l);
2639                 l = ll;
2640             }
2641         }
2642     }
2643
2644     /* sort the menu applications. we do this in process filters so it will only
2645      * be done once per menu.*/
2646     if (eina_list_count(internal->applications))
2647     {
2648         Eina_List           *l2;
2649
2650         EINA_LIST_FOREACH_SAFE(internal->applications, l, l2, md)
2651         {
2652             if (md->desktop->no_display)
2653                 internal->applications = eina_list_remove_list(internal->applications, l);
2654         }
2655         internal->applications = eina_list_sort(internal->applications,
2656                                                 eina_list_count(internal->applications),
2657                                                 EINA_COMPARE_CB(efreet_menu_cb_md_compare));
2658     }
2659 }
2660
2661 /**
2662  * @internal
2663  * @param pool The app pool to iterate
2664  * @param applications The list of applications to append too
2665  * @param matches The hash of previously matched ids
2666  * @param filter The menu filter to run on the pool items
2667  * @param only_unallocated Do we check only unallocated pool items?
2668  * @return Returns no value.
2669  * @brief This will iterate the items in @a pool and append them to @a
2670  * applications if they match the @a filter given and aren't previoulsy entered
2671  * in @a matches. If @a only_unallocated is set we'll only only at the
2672  * .desktop files that haven't been previoulsy matched
2673  */
2674 static Eina_List *
2675 efreet_menu_process_app_pool(Eina_List *pool, Eina_List *applications,
2676                                 Eina_Hash *matches, Efreet_Menu_Filter *filter,
2677                                 unsigned int only_unallocated)
2678 {
2679     Efreet_Menu_Desktop *md;
2680     Eina_List *l;
2681
2682     EINA_LIST_FOREACH(pool, l, md)
2683     {
2684         if (eina_hash_find(matches, md->id)) continue;
2685         if (only_unallocated && md->allocated) continue;
2686         if (efreet_menu_filter_matches(filter->op, md))
2687         {
2688             applications = eina_list_append(applications, md);
2689             eina_hash_direct_add(matches, (void *)md->id, md);
2690             md->allocated = 1;
2691         }
2692     }
2693     return applications;
2694 }
2695
2696 /**
2697  * @internal
2698  * @param op The filter operation to execute
2699  * @param md The desktop to run the filter on
2700  * @return Returns 1 if this desktop matches the given filter, 0 otherwise
2701  * @brief This will execute the given @a filter on the given desktop
2702  */
2703 static int
2704 efreet_menu_filter_matches(Efreet_Menu_Filter_Op *op, Efreet_Menu_Desktop *md)
2705 {
2706     if (op->type == EFREET_MENU_FILTER_OP_OR)
2707         return efreet_menu_filter_or_matches(op, md);
2708
2709     if (op->type == EFREET_MENU_FILTER_OP_AND)
2710         return efreet_menu_filter_and_matches(op, md);
2711
2712     if (op->type == EFREET_MENU_FILTER_OP_NOT)
2713         return efreet_menu_filter_not_matches(op, md);
2714
2715     return 0;
2716 }
2717
2718 /**
2719  * @internal
2720  * @param op The filter operation to execute
2721  * @param md The desktop to execute on
2722  * @return Returns 1 if the desktop matches, 0 otherwise
2723  * @brief Executes the OR operation, @a op, on the desktop, @a md.
2724  */
2725 static int
2726 efreet_menu_filter_or_matches(Efreet_Menu_Filter_Op *op, Efreet_Menu_Desktop *md)
2727 {
2728     Efreet_Menu_Filter_Op *child;
2729     Eina_List *l;
2730     char *t;
2731
2732     if (op->all) return 1;
2733
2734     if (op->categories && md->desktop->categories)
2735     {
2736         EINA_LIST_FOREACH(op->categories, l, t)
2737         {
2738             if (eina_list_search_unsorted(md->desktop->categories,
2739                                           EINA_COMPARE_CB(strcmp), t))
2740                 return 1;
2741         }
2742     }
2743
2744     if (op->filenames)
2745     {
2746         EINA_LIST_FOREACH(op->filenames, l, t)
2747             if (t == md->id) return 1;
2748     }
2749
2750     if (op->filters)
2751     {
2752         EINA_LIST_FOREACH(op->filters, l, child)
2753         {
2754             if (efreet_menu_filter_matches(child, md))
2755                 return 1;
2756         }
2757     }
2758
2759     return 0;
2760 }
2761
2762 /**
2763  * @internal
2764  * @param op The filter operation to execute
2765  * @param md The desktop to execute on
2766  * @return Returns 1 if the desktop matches, 0 otherwise
2767  * @brief Executes the AND operation, @a op, on the desktop, @a md.
2768  */
2769 static int
2770 efreet_menu_filter_and_matches(Efreet_Menu_Filter_Op *op, Efreet_Menu_Desktop *md)
2771 {
2772     Efreet_Menu_Filter_Op *child;
2773     Eina_List *l;
2774     char *t;
2775
2776     if (op->categories)
2777     {
2778         if ((eina_list_count(op->categories) > 0) && !md->desktop->categories)
2779             return 0;
2780
2781         EINA_LIST_FOREACH(op->categories, l, t)
2782         {
2783             if (!eina_list_search_unsorted(md->desktop->categories,
2784                                            EINA_COMPARE_CB(strcmp), t))
2785                 return 0;
2786         }
2787     }
2788
2789     if (op->filenames)
2790     {
2791         EINA_LIST_FOREACH(op->filenames, l, t)
2792         {
2793             if (t != md->id) return 0;
2794         }
2795     }
2796
2797     if (op->filters)
2798     {
2799         EINA_LIST_FOREACH(op->filters, l, child)
2800         {
2801             if (!efreet_menu_filter_matches(child, md))
2802                 return 0;
2803         }
2804     }
2805
2806     return 1;
2807 }
2808
2809 /**
2810  * @internal
2811  * @param op The filter operation to execute
2812  * @param md The desktop to execute on
2813  * @return Returns 1 if the desktop matches, 0 otherwise
2814  * @brief Executes the NOT operation, @a op, on the desktop, @a md.
2815  */
2816 static int
2817 efreet_menu_filter_not_matches(Efreet_Menu_Filter_Op *op, Efreet_Menu_Desktop *md)
2818 {
2819     Efreet_Menu_Filter_Op *child;
2820     Eina_List *l;
2821     char *t;
2822
2823     /* !all means no desktops match */
2824     if (op->all) return 0;
2825
2826     if (op->categories)
2827     {
2828         if ((eina_list_count(op->categories) > 0) && !md->desktop->categories)
2829             return 1;
2830
2831         EINA_LIST_FOREACH(op->categories, l, t)
2832         {
2833             if (eina_list_search_unsorted(md->desktop->categories,
2834                                           EINA_COMPARE_CB(strcmp), t))
2835                 return 0;
2836         }
2837     }
2838
2839     if (op->filenames)
2840     {
2841         EINA_LIST_FOREACH(op->filenames, l, t)
2842         {
2843             if (t == md->id) return 0;
2844         }
2845     }
2846
2847     if (op->filters)
2848     {
2849         EINA_LIST_FOREACH(op->filters, l, child)
2850         {
2851             if (efreet_menu_filter_matches(child, md))
2852                 return 0;
2853         }
2854     }
2855
2856     return 1;
2857 }
2858
2859 /**
2860  * @internal
2861  * @param dest The destination menu
2862  * @param src The source menu
2863  * @return Returns no value
2864  * @brief Takes the child elements of the menu @a src and puts then on the
2865  * _start_ of the menu @a dest.
2866  */
2867 static void
2868 efreet_menu_concatenate(Efreet_Menu_Internal *dest, Efreet_Menu_Internal *src)
2869 {
2870     Efreet_Menu_Internal *submenu;
2871
2872     if (!dest || !src) return;
2873
2874     if (!dest->directory && src->directory)
2875     {
2876         dest->directory = src->directory;
2877         src->directory = NULL;
2878     }
2879
2880     if (!dest->seen_allocated && src->seen_allocated)
2881     {
2882         dest->only_unallocated = src->only_unallocated;
2883         dest->seen_allocated = 1;
2884     }
2885
2886     if (!dest->seen_deleted && src->seen_deleted)
2887     {
2888         dest->deleted = src->deleted;
2889         dest->seen_deleted = 1;
2890     }
2891
2892     if (src->directories)
2893     {
2894         efreet_menu_create_directories_list(dest);
2895         dest->directories = eina_list_merge(src->directories, dest->directories);
2896         src->directories = NULL;
2897     }
2898
2899     if (src->app_dirs)
2900     {
2901         efreet_menu_create_app_dirs_list(dest);
2902         dest->app_dirs = eina_list_merge(src->app_dirs, dest->app_dirs);
2903         src->app_dirs = NULL;
2904     }
2905
2906     if (src->directory_dirs)
2907     {
2908         efreet_menu_create_directory_dirs_list(dest);
2909         dest->directory_dirs = eina_list_merge(src->directory_dirs, dest->directory_dirs);
2910         src->directory_dirs = NULL;
2911     }
2912
2913     if (src->moves)
2914     {
2915         efreet_menu_create_move_list(dest);
2916         dest->moves = eina_list_merge(src->moves, dest->moves);
2917         src->moves = NULL;
2918     }
2919
2920     if (src->filters)
2921     {
2922         efreet_menu_create_filter_list(dest);
2923         dest->filters = eina_list_merge(src->filters, dest->filters);
2924         src->filters = NULL;
2925     }
2926
2927     if (src->sub_menus)
2928     {
2929         efreet_menu_create_sub_menu_list(dest);
2930
2931         while ((submenu = eina_list_data_get(eina_list_last(src->sub_menus))))
2932         {
2933             Efreet_Menu_Internal *match;
2934
2935             src->sub_menus = eina_list_remove(src->sub_menus, submenu);
2936             /* if this menu is in the list already we just add to that */
2937             if ((match = eina_list_search_unsorted(dest->sub_menus,
2938                                                    EINA_COMPARE_CB(efreet_menu_cb_menu_compare),
2939                                                    submenu)))
2940             {
2941                 efreet_menu_concatenate(match, submenu);
2942                 efreet_menu_internal_free(submenu);
2943             }
2944             else
2945                 dest->sub_menus = eina_list_prepend(dest->sub_menus, submenu);
2946         }
2947     }
2948 }
2949
2950 /**
2951  * @internal
2952  * @param menu The menu to work with
2953  * @return Returns no value
2954  * @brief Handles any \<Move\> commands in the menus
2955  */
2956 static void
2957 efreet_menu_resolve_moves(Efreet_Menu_Internal *internal)
2958 {
2959     Efreet_Menu_Internal *child;
2960     Efreet_Menu_Move *move;
2961     Eina_List *l;
2962
2963     /* child moves are handled before parent moves */
2964     if (internal->sub_menus)
2965     {
2966         EINA_LIST_FOREACH(internal->sub_menus, l, child)
2967             efreet_menu_resolve_moves(child);
2968     }
2969
2970     /* nothing to do if this menu has no moves */
2971     if (!internal->moves) return;
2972
2973     EINA_LIST_FOREACH(internal->moves, l, move)
2974     {
2975         Efreet_Menu_Internal *origin, *dest, *parent;
2976
2977         /* if the origin path doesn't exist we do nothing */
2978         origin = efreet_menu_by_name_find(internal, move->old_name, &parent);
2979         if (!origin) continue;
2980
2981         /* remove the origin menu from the parent */
2982         parent->sub_menus = eina_list_remove(parent->sub_menus, origin);
2983
2984         /* if the destination path doesn't exist we just rename the origin
2985          * menu and append to the parents list of children */
2986         dest = efreet_menu_by_name_find(internal, move->new_name, &parent);
2987         if (!dest)
2988         {
2989             char *path, *tmp, *t;
2990             size_t len;
2991
2992             /* if the dest path has /'s in it then we need to add menus to
2993              * fill out the paths */
2994             len = strlen(move->new_name) + 1;
2995             t = alloca(len);
2996             memcpy(t, move->new_name, len);
2997             tmp = t;
2998             path = strchr(tmp, '/');
2999             while (path)
3000             {
3001                 Efreet_Menu_Internal *ancestor;
3002
3003                 *path = '\0';
3004
3005                 ancestor = efreet_menu_internal_new();
3006                 if (!ancestor) goto error;
3007                 ancestor->name.internal = eina_stringshare_add(tmp);
3008
3009                 efreet_menu_create_sub_menu_list(parent);
3010                 parent->sub_menus = eina_list_append(parent->sub_menus, ancestor);
3011
3012                 parent = ancestor;
3013                 tmp = ++path;
3014                 path = strchr(tmp, '/');
3015             }
3016
3017             IF_RELEASE(origin->name.internal);
3018             origin->name.internal = eina_stringshare_add(tmp);
3019
3020             efreet_menu_create_sub_menu_list(parent);
3021             parent->sub_menus = eina_list_append(parent->sub_menus, origin);
3022         }
3023         else
3024         {
3025             efreet_menu_concatenate(dest, origin);
3026             efreet_menu_internal_free(origin);
3027         }
3028     }
3029 error:
3030     IF_FREE_LIST(internal->moves, efreet_menu_move_free);
3031 }
3032
3033 /**
3034  * @internal
3035  * @param menu The menu to start searching from
3036  * @param name The menu name to find
3037  * @param parent The parent of the found menu
3038  * @return Returns the menu with the given @a name or NULL if none found
3039  * @brief Searches the menu tree starting at @a menu looking for a menu with
3040  * @a name.
3041  */
3042 static Efreet_Menu_Internal *
3043 efreet_menu_by_name_find(Efreet_Menu_Internal *internal, const char *name, Efreet_Menu_Internal **parent)
3044 {
3045     char *part, *tmp, *ptr;
3046     size_t len;
3047
3048     if (parent) *parent = internal;
3049
3050     /* find the correct parent menu */
3051     len = strlen(name) + 1;
3052     tmp = alloca(len);
3053     memcpy(tmp, name, len);
3054     ptr = tmp;
3055     part = strchr(ptr, '/');
3056     while (part)
3057     {
3058         *part = '\0';
3059
3060         if (!(internal = eina_list_search_unsorted(internal->sub_menus,
3061                                                    EINA_COMPARE_CB(efreet_menu_cb_compare_names),
3062                                                    ptr)))
3063         {
3064             return NULL;
3065         }
3066
3067         ptr = ++part;
3068         part = strchr(ptr, '/');
3069     }
3070
3071     if (parent) *parent = internal;
3072
3073     /* find the menu in the parent list */
3074     if (!(internal = eina_list_search_unsorted(internal->sub_menus,
3075                                                EINA_COMPARE_CB(efreet_menu_cb_compare_names),
3076                                                ptr)))
3077     {
3078         return NULL;
3079     }
3080
3081     return internal;
3082 }
3083
3084 static void
3085 efreet_menu_path_set(Efreet_Menu_Internal *internal, const char *path)
3086 {
3087     char *tmp, *p;
3088     size_t len;
3089
3090     len = strlen(path) + 1;
3091     tmp = alloca(len);
3092     memcpy(tmp, path, len);
3093     p = strrchr(tmp, '/');
3094     if (p)
3095     {
3096         *p = '\0';
3097         p++;
3098
3099         internal->file.path = eina_stringshare_add(tmp);
3100         internal->file.name = eina_stringshare_add(p);
3101     }
3102 }
3103
3104 /**
3105  * @internal
3106  * @return Returns a new Efreet_Menu_Move struct on success or NULL on failure
3107  * @brief Creates an returns a new Efreet_Menu_Move struct or NULL on failure
3108  */
3109 static Efreet_Menu_Move *
3110 efreet_menu_move_new(void)
3111 {
3112     Efreet_Menu_Move *move;
3113
3114     move = NEW(Efreet_Menu_Move, 1);
3115
3116     return move;
3117 }
3118
3119 /**
3120  * @internal
3121  * @param move The Efreet_Menu_Move to free
3122  * @return Returns no value.
3123  * @brief Frees the given move structure
3124  */
3125 static void
3126 efreet_menu_move_free(Efreet_Menu_Move *move)
3127 {
3128     if (!move) return;
3129
3130     IF_RELEASE(move->old_name);
3131     IF_RELEASE(move->new_name);
3132
3133     FREE(move);
3134 }
3135
3136 /**
3137  * @internal
3138  * @return Returns a new Efreet_Menu_App_Dir on success or NULL on failure
3139  * @brief Creates and initializes a new Efreet_Menu_App_Dir structure
3140  */
3141 static Efreet_Menu_App_Dir *
3142 efreet_menu_app_dir_new(void)
3143 {
3144     Efreet_Menu_App_Dir *dir;
3145
3146     dir = NEW(Efreet_Menu_App_Dir, 1);
3147
3148     return dir;
3149 }
3150
3151 /**
3152  * @internal
3153  * @param dir The Efreet_Menu_App_Dir to free
3154  * @return Returns no value.
3155  * @brief Frees the given dir structure
3156  */
3157 static void
3158 efreet_menu_app_dir_free(Efreet_Menu_App_Dir *dir)
3159 {
3160     if (!dir) return;
3161
3162     IF_RELEASE(dir->path);
3163     IF_RELEASE(dir->prefix);
3164     FREE(dir);
3165 }
3166
3167 /**
3168  * @internal
3169  * @param a The app dir to compare too
3170  * @param b The path to compare too
3171  * @return Returns 0 if the strings are equals, != 0 otherwise
3172  * @brief Compares the too strings
3173  */
3174 static int
3175 efreet_menu_cb_app_dirs_compare(Efreet_Menu_App_Dir *a, const char *b)
3176 {
3177     if (!a->path || !b) return 1;
3178     if (a->path == b) return 0;
3179     return strcmp(a->path, b);
3180 }
3181
3182 static void
3183 efreet_menu_create_sub_menu_list(Efreet_Menu_Internal *internal)
3184 {
3185     if (!internal || internal->sub_menus) return;
3186
3187     internal->sub_menus = NULL;
3188 }
3189
3190 static void
3191 efreet_menu_create_app_dirs_list(Efreet_Menu_Internal *internal)
3192 {
3193     if (!internal || internal->app_dirs) return;
3194
3195     internal->app_dirs = NULL;
3196 }
3197
3198 static void
3199 efreet_menu_create_directory_dirs_list(Efreet_Menu_Internal *internal)
3200 {
3201     if (!internal || internal->directory_dirs) return;
3202
3203     internal->directory_dirs = NULL;
3204 }
3205
3206 static void
3207 efreet_menu_create_move_list(Efreet_Menu_Internal *internal)
3208 {
3209     if (!internal || internal->moves) return;
3210
3211     internal->moves = NULL;
3212 }
3213
3214 static void
3215 efreet_menu_create_filter_list(Efreet_Menu_Internal *internal)
3216 {
3217     if (!internal || internal->filters) return;
3218
3219     internal->filters = NULL;
3220 }
3221
3222 static void
3223 efreet_menu_create_layout_list(Efreet_Menu_Internal *internal)
3224 {
3225     if (!internal || internal->layout) return;
3226
3227     internal->layout = NULL;
3228 }
3229
3230 static void
3231 efreet_menu_create_default_layout_list(Efreet_Menu_Internal *internal)
3232 {
3233     if (!internal || internal->default_layout) return;
3234
3235     internal->default_layout = NULL;
3236 }
3237
3238 static void
3239 efreet_menu_create_directories_list(Efreet_Menu_Internal *internal)
3240 {
3241     if (!internal || internal->directories) return;
3242
3243     internal->directories = NULL;
3244 }
3245
3246 static const char *
3247 efreet_menu_path_get(Efreet_Menu_Internal *internal, const char *suffix)
3248 {
3249     char path[PATH_MAX];
3250     size_t len;
3251
3252     /* see if we've got an absolute or relative path */
3253     if (suffix[0] == '/')
3254         snprintf(path, sizeof(path), "%s", suffix);
3255
3256     else
3257     {
3258         if (!internal->file.path)
3259         {
3260             INF("efreet_menu_handle_app_dir() missing menu path ...");
3261             return NULL;
3262         }
3263         snprintf(path, sizeof(path), "%s/%s", internal->file.path, suffix);
3264     }
3265
3266     len = strlen(path);
3267     while (path[len] == '/') path[len--] = '\0';
3268
3269     return eina_stringshare_add(path);
3270 }
3271
3272 static int
3273 efreet_menu_cb_menu_compare(Efreet_Menu_Internal *a, Efreet_Menu_Internal *b)
3274 {
3275     if (!a->name.internal || !b->name.internal) return 1;
3276     if (a->name.internal == b->name.internal) return 0;
3277     return strcmp(a->name.internal, b->name.internal);
3278 }
3279
3280 static int
3281 efreet_menu_app_dirs_process(Efreet_Menu_Internal *internal)
3282 {
3283     Efreet_Menu_App_Dir *app_dir;
3284     Efreet_Menu_Desktop *md;
3285     Eina_List *l;
3286
3287     EINA_LIST_FREE(internal->app_pool, md)
3288         efreet_menu_desktop_free(md);
3289
3290     EINA_LIST_FOREACH(internal->app_dirs, l, app_dir)
3291         efreet_menu_app_dir_scan(internal, app_dir->path, app_dir->prefix, app_dir->legacy);
3292
3293     return 1;
3294 }
3295
3296 static int
3297 efreet_menu_app_dir_scan(Efreet_Menu_Internal *internal, const char *path, const char *id, int legacy)
3298 {
3299     Efreet_Desktop *desktop;
3300     Efreet_Menu_Desktop *menu_desktop;
3301     char buf2[PATH_MAX];
3302     Eina_Iterator *it;
3303     Eina_File_Direct_Info *info;
3304
3305     it = eina_file_direct_ls(path);
3306     if (!it) return 1;
3307
3308     EINA_ITERATOR_FOREACH(it, info)
3309     {
3310         const char *fname;
3311
3312         fname = info->path + info->name_start;
3313         if (id)
3314             snprintf(buf2, sizeof(buf2), "%s-%s", id, fname);
3315         else
3316             strcpy(buf2, fname);
3317
3318         if (ecore_file_is_dir(info->path))
3319         {
3320             if (!legacy)
3321                 efreet_menu_app_dir_scan(internal, info->path, buf2, legacy);
3322         }
3323         else
3324         {
3325             const char *ext;
3326
3327             ext = strrchr(fname, '.');
3328
3329             if (!ext || strcmp(ext, ".desktop")) continue;
3330             desktop = efreet_desktop_get(info->path);
3331
3332             if (!desktop || desktop->type != EFREET_DESKTOP_TYPE_APPLICATION)
3333             {
3334                 if (desktop) efreet_desktop_free(desktop);
3335                 continue;
3336             }
3337             /* Don't add two files with the same id in the app pool */
3338             if (eina_list_search_unsorted(internal->app_pool,
3339                                           EINA_COMPARE_CB(efreet_menu_cb_md_compare_ids),
3340                                           buf2))
3341             {
3342                 if (desktop) efreet_desktop_free(desktop);
3343                 continue;
3344             }
3345
3346             menu_desktop = efreet_menu_desktop_new();
3347             menu_desktop->desktop = desktop;
3348             menu_desktop->id = eina_stringshare_add(buf2);
3349             internal->app_pool = eina_list_prepend(internal->app_pool, menu_desktop);
3350         }
3351     }
3352     eina_iterator_free(it);
3353
3354     return 1;
3355 }
3356
3357 /**
3358  * @internal
3359  * @param menu The menu to work with
3360  * @return Returns 1 on success or 0 on failure
3361  * @brief Process the directory dirs in @a menu
3362  */
3363 static int
3364 efreet_menu_directory_dirs_process(Efreet_Menu_Internal *internal)
3365 {
3366     const char *path;
3367     Eina_List *l;
3368
3369     if (internal->directory_dirs)
3370     {
3371         internal->directory_cache =
3372             eina_hash_string_superfast_new(EINA_FREE_CB(efreet_desktop_free));
3373
3374         EINA_LIST_REVERSE_FOREACH(internal->directory_dirs, l, path)
3375             efreet_menu_directory_dir_scan(path, NULL, internal->directory_cache);
3376     }
3377
3378     if (internal->directories)
3379     {
3380         EINA_LIST_REVERSE_FOREACH(internal->directories, l, path)
3381         {
3382             internal->directory = efreet_menu_directory_get(internal, path);
3383             if (internal->directory) break;
3384         }
3385     }
3386     if (!internal->directory)
3387         internal->name.name = internal->name.internal;
3388     else
3389         internal->name.name = internal->directory->name;
3390
3391     return 1;
3392 }
3393
3394 /**
3395  * @internal
3396  * @param path The path to scan
3397  * @param relative_path The relative portion of the path
3398  * @param cache The cache to populate
3399  * @return Returns 1 on success or 0 on failure
3400  * @brief Scans the given directory dir for .directory files and adds the
3401  * applications to the cache
3402  */
3403 static int
3404 efreet_menu_directory_dir_scan(const char *path, const char *relative_path,
3405                                 Eina_Hash *cache)
3406 {
3407     Efreet_Desktop *desktop;
3408     char buf2[PATH_MAX];
3409     Eina_Iterator *it;
3410     Eina_File_Direct_Info *info;
3411     char *ext;
3412
3413     it = eina_file_direct_ls(path);
3414     if (!it) return 1;
3415
3416     EINA_ITERATOR_FOREACH(it, info)
3417     {
3418         const char *fname;
3419
3420         fname = info->path + info->name_start;
3421         if (relative_path)
3422             snprintf(buf2, sizeof(buf2), "%s/%s", relative_path, fname);
3423         else
3424             strcpy(buf2, fname);
3425
3426         if (ecore_file_is_dir(info->path))
3427             efreet_menu_directory_dir_scan(info->path, buf2, cache);
3428
3429         else
3430         {
3431             ext = strrchr(fname, '.');
3432             if (!ext || strcmp(ext, ".directory")) continue;
3433
3434             desktop = efreet_desktop_get(info->path);
3435             if (!desktop || desktop->type != EFREET_DESKTOP_TYPE_DIRECTORY)
3436             {
3437                 efreet_desktop_free(desktop);
3438                 continue;
3439             }
3440
3441             eina_hash_del(cache, buf2, NULL);
3442             eina_hash_add(cache, buf2, desktop);
3443         }
3444     }
3445     eina_iterator_free(it);
3446
3447     return 1;
3448 }
3449
3450 /**
3451  * @internal
3452  * @param menu The menu to work with
3453  * @param path The path to work with
3454  * @return Returns the desktop file for this path or NULL if none exists
3455  * @brief Finds the desktop file for the given path.
3456  */
3457 static Efreet_Desktop *
3458 efreet_menu_directory_get(Efreet_Menu_Internal *internal, const char *path)
3459 {
3460     Efreet_Desktop *dir;
3461
3462     if (internal->directory_cache)
3463     {
3464         dir = eina_hash_find(internal->directory_cache, path);
3465         if (dir) return dir;
3466     }
3467
3468     if (internal->parent)
3469         return efreet_menu_directory_get(internal->parent, path);
3470
3471     return NULL;
3472 }
3473
3474 /**
3475  * @internal
3476  * @param a The first desktop
3477  * @param b The second desktop
3478  * @return Returns the comparison of the desktop files
3479  * @brief Compares the desktop files.
3480  */
3481 static int
3482 efreet_menu_cb_md_compare(const Efreet_Menu_Desktop *a, const Efreet_Menu_Desktop *b)
3483 {
3484 #ifdef STRICT_SPEC
3485     return strcmp(ecore_file_file_get(a->desktop->orig_path), ecore_file_file_get(b->desktop->orig_path));
3486 #else
3487     if (a->desktop->name == b->desktop->name) return 0;
3488     return strcasecmp(a->desktop->name, b->desktop->name);
3489 #endif
3490 }
3491
3492 static int
3493 efreet_menu_cb_compare_names(Efreet_Menu_Internal *internal, const char *name)
3494 {
3495     if (internal->name.internal == name) return 0;
3496     return strcmp(internal->name.internal, name);
3497 }
3498
3499 static int
3500 efreet_menu_cb_md_compare_ids(Efreet_Menu_Desktop *md, const char *name)
3501 {
3502     if (md->id == name) return 0;
3503     return strcmp(md->id, name);
3504 }
3505
3506 static Efreet_Menu *
3507 efreet_menu_layout_menu(Efreet_Menu_Internal *internal)
3508 {
3509     Efreet_Menu *entry;
3510     Eina_List *layout = NULL;
3511     Eina_List *l;
3512
3513     if (internal->parent)
3514     {
3515         /* Copy default layout rules */
3516         if (internal->show_empty == -1)    internal->show_empty = internal->parent->show_empty;
3517         if (internal->in_line == -1)       internal->in_line = internal->parent->in_line;
3518         if (internal->inline_limit == -1)  internal->inline_limit = internal->parent->inline_limit;
3519         if (internal->inline_header == -1) internal->inline_header = internal->parent->inline_header;
3520         if (internal->inline_alias == -1)  internal->inline_alias = internal->parent->inline_alias;
3521     }
3522
3523     if (internal->layout)
3524         layout = internal->layout;
3525
3526     else if (internal->parent)
3527     {
3528         Efreet_Menu_Internal *parent;
3529         parent = internal->parent;
3530         do
3531         {
3532             layout = parent->default_layout;
3533             parent = parent->parent;
3534         } while (!layout && parent);
3535     }
3536
3537     /* init entry */
3538     entry = efreet_menu_entry_new();
3539     entry->type = EFREET_MENU_ENTRY_MENU;
3540     entry->id = eina_stringshare_add(internal->name.internal);
3541     entry->name = eina_stringshare_add(internal->name.name);
3542     if (internal->directory)
3543     {
3544         entry->icon = eina_stringshare_add(internal->directory->icon);
3545         efreet_desktop_ref(internal->directory);
3546         entry->desktop = internal->directory;
3547     }
3548     entry->entries = NULL;
3549
3550 #if 1 //STRICT_SPEC
3551     if (internal->sub_menus)
3552     {
3553         internal->sub_menus = eina_list_sort(internal->sub_menus,
3554                                              0,
3555                                              EINA_COMPARE_CB(efreet_menu_cb_menu_compare));
3556     }
3557 #endif
3558
3559     if (layout)
3560     {
3561         Efreet_Menu_Layout *lay;
3562
3563         EINA_LIST_FOREACH(layout, l, lay)
3564             efreet_menu_layout_entries_get(entry, internal, lay);
3565     }
3566     else
3567     {
3568         /* Default layout, first menus, then desktop */
3569         if (internal->sub_menus)
3570         {
3571             Efreet_Menu_Internal *sub;
3572
3573             EINA_LIST_FOREACH(internal->sub_menus, l, sub)
3574             {
3575                 Efreet_Menu *sub_entry;
3576                 if ((sub->directory && sub->directory->no_display) || sub->deleted) continue;
3577                 sub_entry = efreet_menu_layout_menu(sub);
3578                 /* Don't show empty menus */
3579                 if (!sub_entry->entries)
3580                 {
3581                     efreet_menu_free(sub_entry);
3582                     continue;
3583                 }
3584                 entry->entries = eina_list_append(entry->entries, sub_entry);
3585             }
3586         }
3587
3588         if (internal->applications)
3589         {
3590             Efreet_Menu_Desktop *md;
3591
3592             EINA_LIST_FOREACH(internal->applications, l, md)
3593             {
3594                 Efreet_Menu *sub_entry;
3595                 sub_entry = efreet_menu_layout_desktop(md);
3596                 entry->entries = eina_list_append(entry->entries, sub_entry);
3597             }
3598         }
3599     }
3600
3601     /* Don't keep this list around if it is empty */
3602
3603     return entry;
3604 }
3605
3606 static Efreet_Menu *
3607 efreet_menu_layout_desktop(Efreet_Menu_Desktop *md)
3608 {
3609     Efreet_Menu *entry;
3610
3611     /* init entry */
3612     entry = efreet_menu_entry_new();
3613     entry->type = EFREET_MENU_ENTRY_DESKTOP;
3614     entry->id = eina_stringshare_add(md->id);
3615     entry->name = eina_stringshare_add(md->desktop->name);
3616     if (md->desktop->icon) entry->icon = eina_stringshare_add(md->desktop->icon);
3617     efreet_desktop_ref(md->desktop);
3618     entry->desktop = md->desktop;
3619
3620     return entry;
3621 }
3622
3623 static void
3624 efreet_menu_layout_entries_get(Efreet_Menu *entry, Efreet_Menu_Internal *internal,
3625         Efreet_Menu_Layout *layout)
3626 {
3627     Efreet_Menu *sub_entry;
3628
3629     if (internal->sub_menus && layout->type == EFREET_MENU_LAYOUT_MENUNAME)
3630     {
3631         Efreet_Menu_Internal *sub;
3632
3633         /* Efreet_Menu_Layout might be from DefaultLayout, so we need a local copy */
3634         int show_empty, in_line, inline_limit, inline_header, inline_alias;
3635
3636         if (layout->show_empty == -1) show_empty = internal->show_empty;
3637         else show_empty = layout->show_empty;
3638
3639         if (layout->in_line == -1) in_line = internal->in_line;
3640         else in_line = layout->in_line;
3641
3642         if (layout->inline_limit == -1) inline_limit = internal->inline_limit;
3643         else inline_limit = layout->inline_limit;
3644
3645         if (layout->inline_header == -1) inline_header = internal->inline_header;
3646         else inline_header = layout->inline_header;
3647
3648         if (layout->inline_alias == -1) inline_alias = internal->inline_alias;
3649         else inline_alias = layout->inline_alias;
3650
3651         sub = eina_list_search_unsorted(internal->sub_menus,
3652                                         EINA_COMPARE_CB(efreet_menu_cb_compare_names), layout->name);
3653         if (sub)
3654         {
3655             if (!(sub->directory && sub->directory->no_display) && !sub->deleted)
3656             {
3657                 sub_entry = efreet_menu_layout_menu(sub);
3658                 if (!show_empty && efreet_menu_layout_is_empty(sub_entry))
3659                     efreet_menu_free(sub_entry);
3660                 else if (in_line &&
3661                         ((inline_limit == 0) ||
3662                          (!sub_entry->entries ||
3663                           (inline_limit > 0 && eina_list_count(sub_entry->entries) <= (unsigned int)inline_limit))))
3664                 {
3665                     /* Inline */
3666                     if (!sub_entry->entries)
3667                     {
3668                         /* Can't inline an empty submenu */
3669                         entry->entries = eina_list_append(entry->entries, sub_entry);
3670                     }
3671                     else if (inline_alias && (eina_list_count(sub_entry->entries) == 1))
3672                     {
3673                         Efreet_Menu *tmp;
3674
3675                         tmp = eina_list_data_get(sub_entry->entries);
3676                         sub_entry->entries = eina_list_remove_list(sub_entry->entries, sub_entry->entries);
3677                         IF_RELEASE(tmp->name);
3678                         tmp->name = sub_entry->name;
3679                         sub_entry->name = NULL;
3680                         IF_RELEASE(tmp->icon);
3681                         tmp->icon = sub_entry->icon;
3682                         sub_entry->icon = NULL;
3683                         entry->entries = eina_list_append(entry->entries, tmp);
3684                         efreet_menu_free(sub_entry);
3685                     }
3686                     else
3687                     {
3688                         Efreet_Menu *tmp;
3689
3690                         if (inline_header)
3691                         {
3692                             tmp = efreet_menu_entry_new();
3693                             tmp->type = EFREET_MENU_ENTRY_HEADER;
3694                             tmp->name = sub_entry->name;
3695                             sub_entry->name = NULL;
3696                             tmp->icon = sub_entry->icon;
3697                             sub_entry->icon = NULL;
3698                             entry->entries = eina_list_append(entry->entries, tmp);
3699                         }
3700                         while ((tmp = eina_list_data_get(sub_entry->entries)))
3701                         {
3702                             sub_entry->entries = eina_list_remove_list(sub_entry->entries, sub_entry->entries);
3703                             entry->entries = eina_list_append(entry->entries, tmp);
3704                         }
3705                         efreet_menu_free(sub_entry);
3706                     }
3707                 }
3708                 else
3709                     entry->entries = eina_list_append(entry->entries, sub_entry);
3710             }
3711             internal->sub_menus = eina_list_remove(internal->sub_menus, sub);
3712             efreet_menu_internal_free(sub);
3713         }
3714     }
3715     else if (internal->applications && layout->type == EFREET_MENU_LAYOUT_FILENAME)
3716     {
3717         Efreet_Menu_Desktop *md;
3718         md = eina_list_search_unsorted(internal->applications,
3719                                        EINA_COMPARE_CB(efreet_menu_cb_md_compare_ids), layout->name);
3720         if (md)
3721         {
3722             sub_entry = efreet_menu_layout_desktop(md);
3723             entry->entries = eina_list_append(entry->entries, sub_entry);
3724             internal->applications = eina_list_remove(internal->applications, md);
3725         }
3726     }
3727     else if (layout->type == EFREET_MENU_LAYOUT_MERGE)
3728     {
3729         if (internal->applications && !strcmp(layout->name, "files"))
3730         {
3731             Efreet_Menu_Desktop *md;
3732
3733             while ((md = eina_list_data_get(internal->applications)))
3734             {
3735                 internal->applications = eina_list_remove_list(internal->applications,
3736                                                                internal->applications);
3737                 sub_entry = eina_list_search_unsorted(entry->entries,
3738                                                       EINA_COMPARE_CB(efreet_menu_cb_entry_compare_desktop),
3739                                                       md->desktop);
3740                 if (!sub_entry)
3741                 {
3742                     sub_entry = efreet_menu_layout_desktop(md);
3743                     entry->entries = eina_list_append(entry->entries, sub_entry);
3744                 }
3745             }
3746             internal->applications = eina_list_free(internal->applications);
3747         }
3748         else if (internal->sub_menus && !strcmp(layout->name, "menus"))
3749         {
3750             Efreet_Menu_Internal *sub;
3751
3752             while ((sub = eina_list_data_get(internal->sub_menus)))
3753             {
3754                 internal->sub_menus = eina_list_remove_list(internal->sub_menus, internal->sub_menus);
3755                 if ((sub->directory && sub->directory->no_display) || sub->deleted)
3756                 {
3757                     efreet_menu_internal_free(sub);
3758                     continue;
3759                 }
3760                 sub_entry = eina_list_search_unsorted(entry->entries,
3761                                                       EINA_COMPARE_CB(efreet_menu_cb_entry_compare_menu),
3762                                                       sub);
3763                 if (!sub_entry)
3764                 {
3765                     sub_entry = efreet_menu_layout_menu(sub);
3766                     if (!internal->show_empty && efreet_menu_layout_is_empty(sub_entry))
3767                         efreet_menu_free(sub_entry);
3768                     else if (internal->in_line &&
3769                             ((internal->inline_limit == 0) ||
3770                              (!sub_entry->entries ||
3771                               (internal->inline_limit > 0 && eina_list_count(sub_entry->entries) <= (unsigned int)internal->inline_limit))))
3772                     {
3773                         /* Inline */
3774                         if (!sub_entry->entries)
3775                         {
3776                             /* Can't inline an empty submenu */
3777                             entry->entries = eina_list_append(entry->entries, sub_entry);
3778                         }
3779                         else if (internal->inline_alias && (eina_list_count(sub_entry->entries) == 1))
3780                         {
3781                             Efreet_Menu *tmp;
3782
3783                             tmp = eina_list_data_get(sub_entry->entries);
3784                             sub_entry->entries = eina_list_remove_list(sub_entry->entries, sub_entry->entries);
3785                             eina_stringshare_del(tmp->name);
3786                             tmp->name = sub_entry->name;
3787                             sub_entry->name = NULL;
3788                             IF_RELEASE(tmp->icon);
3789                             if (sub_entry->icon)
3790                             {
3791                                 tmp->icon = sub_entry->icon;
3792                                 sub_entry->icon = NULL;
3793                             }
3794                             entry->entries = eina_list_append(entry->entries, tmp);
3795                             efreet_menu_free(sub_entry);
3796                         }
3797                         else
3798                         {
3799                             Efreet_Menu *tmp;
3800
3801                             if (internal->inline_header)
3802                             {
3803                                 tmp = efreet_menu_entry_new();
3804                                 tmp->type = EFREET_MENU_ENTRY_HEADER;
3805                                 tmp->name = sub_entry->name;
3806                                 sub_entry->name = NULL;
3807                                 if (sub_entry->icon) tmp->icon = sub_entry->icon;
3808                                 sub_entry->icon = NULL;
3809                                 entry->entries = eina_list_append(entry->entries, tmp);
3810                             }
3811                             while ((tmp = eina_list_data_get(sub_entry->entries)))
3812                             {
3813                                 sub_entry->entries = eina_list_remove_list(sub_entry->entries,
3814                                                                            sub_entry->entries);
3815                                 entry->entries = eina_list_append(entry->entries, tmp);
3816                             }
3817                             efreet_menu_free(sub_entry);
3818                         }
3819                     }
3820                     else
3821                         entry->entries = eina_list_append(entry->entries, sub_entry);
3822                 }
3823                 efreet_menu_internal_free(sub);
3824             }
3825             IF_FREE_LIST(internal->sub_menus, efreet_menu_internal_free);
3826         }
3827         else if (internal->sub_menus && !strcmp(layout->name, "all"))
3828         {
3829             const char *orig;
3830
3831             orig = layout->name;
3832             layout->name = "menus";
3833             efreet_menu_layout_entries_get(entry, internal, layout);
3834             layout->name = "files";
3835             efreet_menu_layout_entries_get(entry, internal, layout);
3836             layout->name = orig;
3837         }
3838     }
3839     else if (layout->type == EFREET_MENU_LAYOUT_SEPARATOR)
3840     {
3841         sub_entry = efreet_menu_entry_new();
3842         sub_entry->type = EFREET_MENU_ENTRY_SEPARATOR;
3843         entry->entries = eina_list_append(entry->entries, sub_entry);
3844     }
3845 }
3846
3847 static int
3848 efreet_menu_cb_entry_compare_menu(Efreet_Menu *entry, Efreet_Menu_Internal *internal)
3849 {
3850     if (entry->type != EFREET_MENU_ENTRY_MENU) return 1;
3851     if (!entry->name || !internal->name.name) return 1;
3852     if (entry->name == internal->name.name) return 0;
3853     return strcmp(entry->name, internal->name.name);
3854 }
3855
3856 static int
3857 efreet_menu_cb_entry_compare_desktop(Efreet_Menu *entry, Efreet_Desktop *desktop)
3858 {
3859     if (entry->type != EFREET_MENU_ENTRY_DESKTOP) return -1;
3860     if (!entry->name || !desktop->name) return -1;
3861     if (entry->name == desktop->name) return 0;
3862     return strcmp(entry->name, desktop->name);
3863 }
3864
3865 static int
3866 efreet_menu_cb_move_compare(Efreet_Menu_Move *move, const char *old)
3867 {
3868     if (!move->old_name || !old) return 1;
3869     if (move->old_name == old) return 0;
3870     return 1;
3871 }
3872
3873 static int
3874 efreet_menu_layout_is_empty(Efreet_Menu *entry)
3875 {
3876     Efreet_Menu *sub_entry;
3877     Eina_List *l;
3878
3879     if (!entry->entries) return 1;
3880
3881     EINA_LIST_FOREACH(entry->entries, l, sub_entry)
3882     {
3883         if (sub_entry->type == EFREET_MENU_ENTRY_MENU) return 0;
3884         if (sub_entry->type == EFREET_MENU_ENTRY_DESKTOP) return 0;
3885     }
3886     return 1;
3887 }