]> icculus.org git repositories - dana/openbox.git/blob - openbox/apps_menu.c
keep only one version of each .desktop file "id" in the linkbase category lists.
[dana/openbox.git] / openbox / apps_menu.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    apps_menu.c for the Openbox window manager
4    Copyright (c) 2011        Dana Jansens
5
6    This program is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 2 of the License, or
9    (at your option) any later version.
10
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15
16    See the COPYING file for a copy of the GNU General Public License.
17 */
18
19 #include "openbox.h"
20 #include "menu.h"
21 #include "menuframe.h"
22 #include "screen.h"
23 #include "apps_menu.h"
24 #include "config.h"
25 #include "gettext.h"
26 #include "obt/bsearch.h"
27 #include "obt/linkbase.h"
28 #include "obt/link.h"
29 #include "obt/paths.h"
30
31 #include <glib.h>
32
33 #define MENU_NAME "apps-menu"
34
35 typedef struct _ObAppsMenuCategory ObAppsMenuCategory;
36
37 struct _ObAppsMenuCategory {
38     /*! The quark for the internal name of the category, eg. AudioVideo. These
39       come from the XDG menu specification. */
40     GQuark q;
41     /*! The user-visible (translated) name for the category. */
42     const gchar *friendly;
43     /*! The string identifier of the menu */
44     gchar *menu_name;
45     /*! The submenu for this category.  Its label will be "apps-menu-<category>
46       where <category> is the internal name of the category referred to by q.
47     */
48     ObMenu *menu;
49     /*! The menu entry in the apps menu for this. */
50     ObMenuEntry *entry;
51     /*! */
52     GPtrArray *links;
53 };
54
55 static ObMenu *apps_menu;
56 static ObtLinkBase *linkbase;
57 static gboolean dirty;
58
59 static ObAppsMenuCategory *categories;
60 /*! An array of pointers to the categories array, sorted by their friendly
61   names. */
62 static ObAppsMenuCategory **sorted_categories;
63 static guint n_categories;
64
65 static void self_destroy(ObMenu *menu, gpointer data)
66 {
67     menu_clear_entries(menu);
68 }
69
70 static gboolean self_update(ObMenuFrame *frame, gpointer data)
71 {
72     guint i;
73     gboolean sort;
74
75     if (!dirty) return TRUE;  /* nothing has changed to be updated */
76
77     sort = FALSE;
78     for (i = 0; i < n_categories; ++i) {
79         ObAppsMenuCategory *cat = &categories[i];
80         GList *it, *entries;
81         guint j;
82         gboolean sort_submenu;
83
84         sort_submenu = FALSE; /* sort the category's submenu? */
85
86         /* add/remove categories from the main apps menu if they were/are
87            empty. */
88         if (cat->links->len == 0) {
89             if (cat->entry) {
90                 menu_entry_remove(cat->entry);
91                 cat->entry = NULL;
92             }
93         }
94         else if (!cat->entry) {
95             cat->entry = menu_add_submenu(apps_menu, 0, cat->menu_name);
96             sort = TRUE;
97         }
98
99         /* add/remove links from this category. */
100         entries = g_list_copy(cat->menu->entries);
101         it = entries; /* iterates through the entries list */
102         j = 0; /* iterates through the valid ObtLinks list */
103         while (j < cat->links->len) {
104             ObtLink *m, *l;
105             int r;
106
107             /* get the next link from the menu */
108             if (it)
109                 m = ((ObMenuEntry*)it->data)->data.normal.data;
110             else
111                 m = NULL;
112             /* get the next link from our list */
113             l = g_ptr_array_index(cat->links, j);
114
115             if (it && m == NULL) {
116                 /* the entry was removed from the linkbase and nulled here, so
117                    drop it from the menu */
118                 menu_entry_remove(it->data);
119                 r = 0;
120             }
121             else {
122                 if (it)
123                     r = obt_link_cmp_by_name(&m, &l);
124                 else
125                     /* we reached the end of the menu, but there's more in
126                        the list, so just add it */
127                     r = 1;
128
129                 if (r > 0) {
130                     /* the menu progressed faster than the list, the list has
131                        something it doesn't, so add that */
132                     ObMenuEntry *e;
133                     e = menu_add_normal(
134                         cat->menu, 0, obt_link_name(l), NULL, FALSE);
135                     e->data.normal.data = l; /* save the link in the entry */
136                     sort_submenu = TRUE;
137                 }
138                 else
139                     /* if r < 0 then we didn't null something that was removed
140                        from the list */
141                     g_assert(r == 0);
142             }
143
144             if (r == 0 && it)
145                 /* if we added something to the menu, then don't move
146                    forward in the menu, as we were inserting something before
147                    this entry */
148                 it = g_list_next(it);
149             ++j;
150         }
151
152         if (sort_submenu)
153             menu_sort_entries(cat->menu);
154     }
155
156     if (sort) {
157         menu_sort_entries(apps_menu);
158     }
159
160     dirty = FALSE;
161     return TRUE; /* always show the menu */
162 }
163
164 static gboolean add_link_to_category(ObtLink *link, ObAppsMenuCategory *cat)
165 {
166     guint i;
167     gboolean add;
168
169     /* check for link in our existing list */
170     add = TRUE;
171     for (i = 0; i < cat->links->len; ++i) {
172         ObtLink *l = g_ptr_array_index(cat->links, i);
173         if (l == link) {
174             add = FALSE;
175             break;
176         }
177     }
178
179     if (add) { /* wasn't found in our list already */
180         g_ptr_array_add(cat->links, link);
181         obt_link_ref(link);
182
183         dirty = TRUE; /* never set dirty to FALSE here.. */
184     }
185     return add;
186 }
187
188 static gboolean remove_link_from_category(ObtLink *link,
189                                           ObAppsMenuCategory *cat)
190 {
191     gboolean rm;
192
193     rm = g_ptr_array_remove(cat->links, link);
194
195     if (rm) {
196         GList *it;
197
198         dirty = TRUE; /* never set dirty to FALSE here.. */
199
200         /* set the data inside any visible menus for this link to NULL since it
201            stops existing now */
202         for (it = menu_frame_visible; it; it = g_list_next(it)) {
203             ObMenuFrame *frame = it->data;
204             if (frame->menu == cat->menu) {
205                 GList *eit;
206
207                 for (eit = frame->entries; eit; eit = g_list_next(eit)) {
208                     ObMenuEntryFrame *e = it->data;
209                     /* our submenus only contain normal entries */
210                     if (e->entry->data.normal.data == link) {
211                         /* this menu entry points to this link, but the
212                            link is going away, so point it at NULL. */
213                         e->entry->data.normal.data = NULL;
214                         break;
215                     }
216                 }
217                 break;
218             }
219         }
220     }
221     return rm;
222 }
223
224 static void linkbase_update(ObtLinkBase *lb, ObtLink *removed,
225                             ObtLink *added, gpointer data)
226 {
227     const GQuark *cats;
228     gulong n_cats, i;
229
230     /* For each category in the link, search our list of categories and
231        if we are showing that category in the menu, add the link to it (or
232        remove the link from it). */
233
234 #define CAT_TO_INT(c) (c.q)
235     if (removed) {
236         cats = obt_link_app_categories(removed, &n_cats);
237         for (i = 0; i < n_cats; ++i) {
238             BSEARCH_SETUP();
239             BSEARCH_CMP(ObAppsMenuCategory, categories, 0, n_categories,
240                     cats[i], CAT_TO_INT);
241             if (BSEARCH_FOUND())
242                 remove_link_from_category(removed, &categories[BSEARCH_AT()]);
243         }
244     }
245
246     if (added) {
247         cats = obt_link_app_categories(added, &n_cats);
248         for (i = 0; i < n_cats; ++i) {
249             BSEARCH_SETUP();
250             BSEARCH_CMP(ObAppsMenuCategory, categories, 0, n_categories,
251                         cats[i], CAT_TO_INT);
252             if (BSEARCH_FOUND()) {
253                 add_link_to_category(added, &categories[BSEARCH_AT()]);
254                 g_ptr_array_sort(categories[i].links, obt_link_cmp_by_name);
255             }
256         }
257     }
258 #undef  CAT_TO_INT
259 }
260
261 static int cat_cmp(const void *a, const void *b)
262 {
263     const ObAppsMenuCategory *ca = a, *cb = b;
264     return ca->q - cb->q;
265 }
266
267 static int cat_friendly_cmp(const void *a, const void *b)
268 {
269     ObAppsMenuCategory *const *ca = a, *const *cb = b;
270     return strcmp((*ca)->friendly, (*cb)->friendly);
271 }
272
273 void apps_menu_startup(gboolean reconfig)
274 {
275     if (!reconfig) {
276         ObtPaths *paths;
277         guint i;
278
279         paths = obt_paths_new();
280         /* XXX allow more environments, like GNOME or KDE, to be included */
281         linkbase = obt_linkbase_new(paths, ob_locale_msg,
282                                     OBT_LINK_ENV_OPENBOX);
283         obt_paths_unref(paths);
284         obt_linkbase_set_update_func(linkbase, linkbase_update, NULL);
285
286         dirty = FALSE;
287
288         /* From http://standards.freedesktop.org/menu-spec/latest/apa.html */
289         {
290             struct {
291                 const gchar *name;
292                 const gchar *friendly;
293             } const cats[] = {
294                 { "AudioVideo", "Sound & Video" },
295                 { "Development", "Programming" },
296                 { "Education", "Education" },
297                 { "Game", "Games" },
298                 { "Graphics", "Graphics" },
299                 { "Network", "Internet" },
300                 { "Office", "Office" },
301                 { "Settings", "Settings" },
302                 { "System", "System" },
303                 { "Utility", "Accessories" },
304                 { NULL, NULL }
305             };
306             guint i;
307
308             for (i = 0; cats[i].name; ++i); /* count them */
309             n_categories = i;
310
311             categories = g_new0(ObAppsMenuCategory, n_categories);
312             sorted_categories = g_new(ObAppsMenuCategory*, n_categories);
313
314             for (i = 0; cats[i].name; ++i) {
315                 categories[i].q = g_quark_from_static_string(cats[i].name);
316                 categories[i].friendly = _(cats[i].friendly);
317                 categories[i].menu_name = g_strdup_printf(
318                     "%s-%s", MENU_NAME, cats[i].name);
319                 categories[i].menu = menu_new(categories[i].menu_name,
320                                               categories[i].friendly, FALSE,
321                                               &categories[i]);
322                 categories[i].links = g_ptr_array_new_with_free_func(
323                     (GDestroyNotify)obt_link_unref);
324             }
325         }
326         /* Sort the categories by their quark values so we can binary search */
327         qsort(categories, n_categories, sizeof(ObAppsMenuCategory), cat_cmp);
328
329         for (i = 0; i < n_categories; ++i)
330             sorted_categories[i] = &categories[i];
331         qsort(sorted_categories, n_categories, sizeof(void*),
332               cat_friendly_cmp);
333
334         /* add all the existing links */
335         for (i = 0; i < n_categories; ++i) {
336             GSList *links, *it;
337
338             links = obt_linkbase_category(linkbase, categories[i].q);
339             for (it = links; it; it = g_slist_next(it))
340                 add_link_to_category(it->data, &categories[i]);
341
342             g_ptr_array_sort(categories[i].links, obt_link_cmp_by_name);
343         }
344     }
345
346     apps_menu = menu_new(MENU_NAME, _("Applications"), FALSE, NULL);
347     menu_set_update_func(apps_menu, self_update);
348     menu_set_destroy_func(apps_menu, self_destroy);
349 }
350
351 void apps_menu_shutdown(gboolean reconfig)
352 {
353     if (!reconfig) {
354         guint i;
355
356         obt_linkbase_unref(linkbase);
357         linkbase = NULL;
358
359         for (i = 0; i < n_categories; ++i) {
360
361             menu_free(categories[i].menu);
362             g_free(categories[i].menu_name);
363             g_ptr_array_unref(categories[i].links);
364         }
365         g_free(categories);
366         categories = NULL;
367         g_free(sorted_categories);
368         sorted_categories = NULL;
369     }
370
371     /* freed by the hash table */
372 }