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