1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
3 apps_menu.c for the Openbox window manager
4 Copyright (c) 2011 Dana Jansens
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.
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.
16 See the COPYING file for a copy of the GNU General Public License.
21 #include "menuframe.h"
23 #include "apps_menu.h"
26 #include "obt/bsearch.h"
27 #include "obt/linkbase.h"
29 #include "obt/paths.h"
33 #define MENU_NAME "apps-menu"
35 typedef struct _ObAppsMenuCategory ObAppsMenuCategory;
37 struct _ObAppsMenuCategory {
38 /*! The quark for the internal name of the category, eg. AudioVideo. These
39 come from the XDG menu specification. */
41 /*! The user-visible (translated) name for the category. */
42 const gchar *friendly;
43 /*! The string identifier of the menu */
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.
49 /*! The menu entry in the apps menu for this. */
55 static ObMenu *apps_menu;
56 static ObtLinkBase *linkbase;
57 static gboolean dirty;
59 static ObAppsMenuCategory *categories;
60 /*! An array of pointers to the categories array, sorted by their friendly
62 static ObAppsMenuCategory **sorted_categories;
63 static guint n_categories;
65 static void self_destroy(ObMenu *menu, gpointer data)
67 menu_clear_entries(menu);
70 static gboolean self_update(ObMenuFrame *frame, gpointer data)
75 if (!dirty) return TRUE; /* nothing has changed to be updated */
78 for (i = 0; i < n_categories; ++i) {
79 ObAppsMenuCategory *cat = &categories[i];
82 gboolean sort_submenu;
84 sort_submenu = FALSE; /* sort the category's submenu? */
86 /* add/remove categories from the main apps menu if they were/are
88 if (cat->links->len == 0) {
90 menu_entry_remove(cat->entry);
94 else if (!cat->entry) {
95 cat->entry = menu_add_submenu(apps_menu, 0, cat->menu_name);
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) {
107 /* get the next link from the menu */
109 m = ((ObMenuEntry*)it->data)->data.normal.data;
112 /* get the next link from our list */
113 l = g_ptr_array_index(cat->links, j);
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);
123 r = obt_link_cmp_by_name(&m, &l);
125 /* we reached the end of the menu, but there's more in
126 the list, so just add it */
130 /* the menu progressed faster than the list, the list has
131 something it doesn't, so add that */
134 cat->menu, 0, obt_link_name(l), NULL, FALSE);
135 e->data.normal.data = l; /* save the link in the entry */
139 /* if r < 0 then we didn't null something that was removed
145 /* if we added something to the menu, then don't move
146 forward in the menu, as we were inserting something before
148 it = g_list_next(it);
153 menu_sort_entries(cat->menu);
157 menu_sort_entries(apps_menu);
161 return TRUE; /* always show the menu */
164 static gboolean add_link_to_category(ObtLink *link, ObAppsMenuCategory *cat)
169 /* check for link in our existing list */
171 for (i = 0; i < cat->links->len; ++i) {
172 ObtLink *l = g_ptr_array_index(cat->links, i);
179 if (add) { /* wasn't found in our list already */
180 g_ptr_array_add(cat->links, link);
183 dirty = TRUE; /* never set dirty to FALSE here.. */
188 static gboolean remove_link_from_category(ObtLink *link,
189 ObAppsMenuCategory *cat)
193 rm = g_ptr_array_remove(cat->links, link);
198 dirty = TRUE; /* never set dirty to FALSE here.. */
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) {
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;
224 static void linkbase_update(ObtLinkBase *lb, ObtLink *removed,
225 ObtLink *added, gpointer data)
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). */
234 #define CAT_TO_INT(c) (c.q)
236 cats = obt_link_app_categories(removed, &n_cats);
237 for (i = 0; i < n_cats; ++i) {
239 BSEARCH_CMP(ObAppsMenuCategory, categories, 0, n_categories,
240 cats[i], CAT_TO_INT);
242 remove_link_from_category(removed, &categories[BSEARCH_AT()]);
247 cats = obt_link_app_categories(added, &n_cats);
248 for (i = 0; i < n_cats; ++i) {
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);
261 static int cat_cmp(const void *a, const void *b)
263 const ObAppsMenuCategory *ca = a, *cb = b;
264 return ca->q - cb->q;
267 static int cat_friendly_cmp(const void *a, const void *b)
269 ObAppsMenuCategory *const *ca = a, *const *cb = b;
270 return strcmp((*ca)->friendly, (*cb)->friendly);
273 void apps_menu_startup(gboolean reconfig)
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);
288 /* From http://standards.freedesktop.org/menu-spec/latest/apa.html */
292 const gchar *friendly;
294 { "AudioVideo", "Sound & Video" },
295 { "Development", "Programming" },
296 { "Education", "Education" },
298 { "Graphics", "Graphics" },
299 { "Network", "Internet" },
300 { "Office", "Office" },
301 { "Settings", "Settings" },
302 { "System", "System" },
303 { "Utility", "Accessories" },
308 for (i = 0; cats[i].name; ++i); /* count them */
311 categories = g_new0(ObAppsMenuCategory, n_categories);
312 sorted_categories = g_new(ObAppsMenuCategory*, n_categories);
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,
322 categories[i].links = g_ptr_array_new_with_free_func(
323 (GDestroyNotify)obt_link_unref);
326 /* Sort the categories by their quark values so we can binary search */
327 qsort(categories, n_categories, sizeof(ObAppsMenuCategory), cat_cmp);
329 for (i = 0; i < n_categories; ++i)
330 sorted_categories[i] = &categories[i];
331 qsort(sorted_categories, n_categories, sizeof(void*),
334 /* add all the existing links */
335 for (i = 0; i < n_categories; ++i) {
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]);
342 g_ptr_array_sort(categories[i].links, obt_link_cmp_by_name);
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);
351 void apps_menu_shutdown(gboolean reconfig)
356 obt_linkbase_unref(linkbase);
359 for (i = 0; i < n_categories; ++i) {
361 menu_free(categories[i].menu);
362 g_free(categories[i].menu_name);
363 g_ptr_array_unref(categories[i].links);
367 g_free(sorted_categories);
368 sorted_categories = NULL;
371 /* freed by the hash table */