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, ObtLinkBaseUpdateType type,
225 ObtLink *link, gpointer data)
230 if (obt_link_type(link) != OBT_LINK_TYPE_APPLICATION)
231 return; /* not in our menu */
233 /* For each category in the link, search our list of categories and
234 if we are showing that category in the menu, add the link to it (or
235 remove the link from it). */
236 cats = obt_link_app_categories(link, &n_cats);
237 for (i = 0; i < n_cats; ++i) {
240 #define CAT_TO_INT(c) (c.q)
241 BSEARCH_CMP(ObAppsMenuCategory, categories, 0, n_categories,
242 cats[i], CAT_TO_INT);
245 if (BSEARCH_FOUND()) {
246 if (type == OBT_LINKBASE_ADDED) {
247 add_link_to_category(link, &categories[BSEARCH_AT()]);
248 g_ptr_array_sort(categories[i].links, obt_link_cmp_by_name);
251 remove_link_from_category(link, &categories[BSEARCH_AT()]);
256 static int cat_cmp(const void *a, const void *b)
258 const ObAppsMenuCategory *ca = a, *cb = b;
259 return ca->q - cb->q;
262 static int cat_friendly_cmp(const void *a, const void *b)
264 ObAppsMenuCategory *const *ca = a, *const *cb = b;
265 return strcmp((*ca)->friendly, (*cb)->friendly);
268 void apps_menu_startup(gboolean reconfig)
274 paths = obt_paths_new();
275 /* XXX allow more environments, like GNOME or KDE, to be included */
276 linkbase = obt_linkbase_new(paths, ob_locale_msg,
277 OBT_LINK_ENV_OPENBOX);
278 obt_paths_unref(paths);
279 obt_linkbase_set_update_func(linkbase, linkbase_update, NULL);
283 /* From http://standards.freedesktop.org/menu-spec/latest/apa.html */
287 const gchar *friendly;
289 { "AudioVideo", "Sound & Video" },
290 { "Development", "Programming" },
291 { "Education", "Education" },
293 { "Graphics", "Graphics" },
294 { "Network", "Internet" },
295 { "Office", "Office" },
296 { "Settings", "Settings" },
297 { "System", "System" },
298 { "Utility", "Accessories" },
303 for (i = 0; cats[i].name; ++i); /* count them */
306 categories = g_new0(ObAppsMenuCategory, n_categories);
307 sorted_categories = g_new(ObAppsMenuCategory*, n_categories);
309 for (i = 0; cats[i].name; ++i) {
310 categories[i].q = g_quark_from_static_string(cats[i].name);
311 categories[i].friendly = _(cats[i].friendly);
312 categories[i].menu_name = g_strdup_printf(
313 "%s-%s", MENU_NAME, cats[i].name);
314 categories[i].menu = menu_new(categories[i].menu_name,
315 categories[i].friendly, FALSE,
317 categories[i].links = g_ptr_array_new_with_free_func(
318 (GDestroyNotify)obt_link_unref);
321 /* Sort the categories by their quark values so we can binary search */
322 qsort(categories, n_categories, sizeof(ObAppsMenuCategory), cat_cmp);
324 for (i = 0; i < n_categories; ++i)
325 sorted_categories[i] = &categories[i];
326 qsort(sorted_categories, n_categories, sizeof(void*),
329 /* add all the existing links */
330 for (i = 0; i < n_categories; ++i) {
333 links = obt_linkbase_category(linkbase, categories[i].q);
334 for (it = links; it; it = g_slist_next(it))
335 add_link_to_category(it->data, &categories[i]);
337 g_ptr_array_sort(categories[i].links, obt_link_cmp_by_name);
341 apps_menu = menu_new(MENU_NAME, _("Applications"), FALSE, NULL);
342 menu_set_update_func(apps_menu, self_update);
343 menu_set_destroy_func(apps_menu, self_destroy);
346 void apps_menu_shutdown(gboolean reconfig)
351 obt_linkbase_unref(linkbase);
354 for (i = 0; i < n_categories; ++i) {
356 menu_free(categories[i].menu);
357 g_free(categories[i].menu_name);
358 g_ptr_array_unref(categories[i].links);
362 g_free(sorted_categories);
363 sorted_categories = NULL;
366 /* freed by the hash table */