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 GList *next = it->next;
118 /* the entry was removed from the linkbase and nulled here, so
119 drop it from the menu */
120 menu_entry_remove(it->data);
123 /* don't move forward in the category list, as we're removing
124 something from the menu here, but do continue moving
125 in the menu in a safe way. */
130 r = obt_link_cmp_by_name(&m, &l);
132 /* we reached the end of the menu, but there's more in
133 the list, so just add it */
137 /* the menu progressed faster than the list, the list has
138 something it doesn't, so add that */
141 cat->menu, 0, obt_link_name(l), NULL, FALSE);
142 e->data.normal.data = l; /* save the link in the entry */
146 /* if r < 0 then we didn't null something that was removed
151 /* if we did not add anything ot the menu, then move
152 forward in the menu. otherwise, stay put as we inserted
153 something just before this item, and want to compare
155 it = g_list_next(it);
156 /* the category link was either found in the menu or added to
157 it, so move to the next */
163 menu_sort_entries(cat->menu);
167 menu_sort_entries(apps_menu);
171 return TRUE; /* always show the menu */
174 static gboolean add_link_to_category(ObtLink *link, ObAppsMenuCategory *cat)
179 /* check for link in our existing list */
181 for (i = 0; i < cat->links->len; ++i) {
182 ObtLink *l = g_ptr_array_index(cat->links, i);
189 if (add) { /* wasn't found in our list already */
190 g_ptr_array_add(cat->links, link);
193 dirty = TRUE; /* never set dirty to FALSE here.. */
198 static gboolean remove_link_from_category(ObtLink *link,
199 ObAppsMenuCategory *cat)
203 rm = g_ptr_array_remove(cat->links, link);
208 dirty = TRUE; /* never set dirty to FALSE here.. */
210 /* set the data inside the category's menu for this link to NULL
211 since it stops existing now */
213 for (it = cat->menu->entries; it; it = g_list_next(it)) {
214 ObMenuEntry *e = it->data;
215 /* our submenus only contain normal entries */
216 if (e->data.normal.data == link) {
217 /* this menu entry points to this link, but the
218 link is going away, so point it at NULL. */
219 e->data.normal.data = NULL;
228 static void linkbase_update(ObtLinkBase *lb, ObtLink *removed,
229 ObtLink *added, gpointer data)
234 /* For each category in the link, search our list of categories and
235 if we are showing that category in the menu, add the link to it (or
236 remove the link from it). */
238 #define CAT_TO_INT(c) (c.q)
240 cats = obt_link_app_categories(removed, &n_cats);
241 for (i = 0; i < n_cats; ++i) {
243 BSEARCH_CMP(ObAppsMenuCategory, categories, 0, n_categories,
244 cats[i], CAT_TO_INT);
246 remove_link_from_category(removed, &categories[BSEARCH_AT()]);
251 cats = obt_link_app_categories(added, &n_cats);
252 for (i = 0; i < n_cats; ++i) {
254 BSEARCH_CMP(ObAppsMenuCategory, categories, 0, n_categories,
255 cats[i], CAT_TO_INT);
256 if (BSEARCH_FOUND()) {
257 add_link_to_category(added, &categories[BSEARCH_AT()]);
258 g_ptr_array_sort(categories[BSEARCH_AT()].links,
259 obt_link_cmp_by_name);
266 static int cat_cmp(const void *a, const void *b)
268 const ObAppsMenuCategory *ca = a, *cb = b;
269 return ca->q - cb->q;
272 static int cat_friendly_cmp(const void *a, const void *b)
274 ObAppsMenuCategory *const *ca = a, *const *cb = b;
275 return strcmp((*ca)->friendly, (*cb)->friendly);
278 void apps_menu_startup(gboolean reconfig)
284 paths = obt_paths_new();
285 /* XXX allow more environments, like GNOME or KDE, to be included */
286 linkbase = obt_linkbase_new(paths, ob_locale_msg,
287 OBT_LINK_ENV_OPENBOX);
288 obt_paths_unref(paths);
289 obt_linkbase_set_update_func(linkbase, linkbase_update, NULL);
293 /* From http://standards.freedesktop.org/menu-spec/latest/apa.html */
297 const gchar *friendly;
299 { "AudioVideo", "Sound & Video" },
300 { "Development", "Programming" },
301 { "Education", "Education" },
303 { "Graphics", "Graphics" },
304 { "Network", "Internet" },
305 { "Office", "Office" },
306 { "Settings", "Settings" },
307 { "System", "System" },
308 { "Utility", "Accessories" },
313 for (i = 0; cats[i].name; ++i); /* count them */
316 categories = g_new0(ObAppsMenuCategory, n_categories);
317 sorted_categories = g_new(ObAppsMenuCategory*, n_categories);
319 for (i = 0; cats[i].name; ++i) {
320 categories[i].q = g_quark_from_static_string(cats[i].name);
321 categories[i].friendly = _(cats[i].friendly);
322 categories[i].menu_name = g_strdup_printf(
323 "%s-%s", MENU_NAME, cats[i].name);
324 categories[i].menu = menu_new(categories[i].menu_name,
325 categories[i].friendly, FALSE,
327 categories[i].links = g_ptr_array_new_with_free_func(
328 (GDestroyNotify)obt_link_unref);
331 /* Sort the categories by their quark values so we can binary search */
332 qsort(categories, n_categories, sizeof(ObAppsMenuCategory), cat_cmp);
334 for (i = 0; i < n_categories; ++i)
335 sorted_categories[i] = &categories[i];
336 qsort(sorted_categories, n_categories, sizeof(void*),
339 /* add all the existing links */
340 for (i = 0; i < n_categories; ++i) {
343 links = obt_linkbase_category(linkbase, categories[i].q);
344 for (it = links; it; it = g_slist_next(it))
345 add_link_to_category(it->data, &categories[i]);
347 g_ptr_array_sort(categories[i].links, obt_link_cmp_by_name);
351 apps_menu = menu_new(MENU_NAME, _("Applications"), FALSE, NULL);
352 menu_set_update_func(apps_menu, self_update);
353 menu_set_destroy_func(apps_menu, self_destroy);
356 void apps_menu_shutdown(gboolean reconfig)
361 obt_linkbase_unref(linkbase);
364 for (i = 0; i < n_categories; ++i) {
366 menu_free(categories[i].menu);
367 g_free(categories[i].menu_name);
368 g_ptr_array_unref(categories[i].links);
372 g_free(sorted_categories);
373 sorted_categories = NULL;
376 /* freed by the hash table */