1 /*
2 * fm-app-menu-view.c
3 *
4 * Copyright 2010 - 2012 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
5 * Copyright 2013-2014 Andriy Grytsenko (LStranger) <andrej@rep.kiev.ua>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
20 * MA 02110-1301, USA.
21 */
22
23 /**
24 * SECTION:fm-app-menu-view
25 * @short_description: Applications tree for application selection dialogs.
26 * @title: Application chooser tree
27 *
28 * @include: libfm/fm-gtk.h
29 *
30 * The widget to represent known applications as a tree.
31 */
32
33 #ifdef HAVE_CONFIG_H
34 #include <config.h>
35 #endif
36
37 #include "../glib-compat.h"
38 #include "fm-app-menu-view.h"
39 #include "fm-icon.h"
40 #include <menu-cache.h>
41 #include <glib/gi18n-lib.h>
42 #include <gio/gdesktopappinfo.h>
43 #include <string.h>
44
45 /* support for libmenu-cache 0.4.x */
46 #ifndef MENU_CACHE_CHECK_VERSION
47 # ifdef HAVE_MENU_CACHE_DIR_LIST_CHILDREN
48 # define MENU_CACHE_CHECK_VERSION(_a,_b,_c) (_a == 0 && _b < 5) /* < 0.5.0 */
49 # else
50 # define MENU_CACHE_CHECK_VERSION(_a,_b,_c) 0 /* not even 0.4.0 */
51 # endif
52 #endif
53
54 enum
55 {
56 COL_ICON,
57 COL_TITLE,
58 COL_ITEM,
59 N_COLS
60 };
61
62 static GtkTreeStore* store = NULL;
63 static MenuCache* menu_cache = NULL;
64 static gpointer menu_cache_reload_notify = NULL;
65
destroy_store(gpointer user_data,GObject * obj)66 static void destroy_store(gpointer user_data, GObject *obj)
67 {
68 menu_cache_remove_reload_notify(menu_cache, menu_cache_reload_notify);
69 menu_cache_reload_notify = NULL;
70 menu_cache_unref(menu_cache);
71 menu_cache = NULL;
72 store = NULL;
73 }
74
75 /* called with lock held */
add_menu_items(GtkTreeIter * parent_it,MenuCacheDir * dir)76 static void add_menu_items(GtkTreeIter* parent_it, MenuCacheDir* dir)
77 {
78 GtkTreeIter it;
79 GSList * l;
80 #if MENU_CACHE_CHECK_VERSION(0, 4, 0)
81 GSList *list;
82 #endif
83 GIcon* gicon;
84 /* Iterate over all menu items in this directory. */
85 #if MENU_CACHE_CHECK_VERSION(0, 4, 0)
86 for (l = list = menu_cache_dir_list_children(dir); l != NULL; l = l->next)
87 #else
88 for (l = menu_cache_dir_get_children(dir); l != NULL; l = l->next)
89 #endif
90 {
91 /* Get the menu item. */
92 MenuCacheItem* item = MENU_CACHE_ITEM(l->data);
93 switch(menu_cache_item_get_type(item))
94 {
95 case MENU_CACHE_TYPE_NONE:
96 case MENU_CACHE_TYPE_SEP:
97 break;
98 case MENU_CACHE_TYPE_APP:
99 case MENU_CACHE_TYPE_DIR:
100 if(menu_cache_item_get_icon(item))
101 gicon = G_ICON(fm_icon_from_name(menu_cache_item_get_icon(item)));
102 else
103 gicon = NULL;
104 gtk_tree_store_append(store, &it, parent_it);
105 gtk_tree_store_set(store, &it,
106 COL_ICON, gicon,
107 COL_TITLE, menu_cache_item_get_name(item),
108 COL_ITEM, item, -1);
109 if(gicon)
110 g_object_unref(gicon);
111
112 if(menu_cache_item_get_type(item) == MENU_CACHE_TYPE_DIR)
113 add_menu_items(&it, MENU_CACHE_DIR(item));
114 break;
115 }
116 }
117 #if MENU_CACHE_CHECK_VERSION(0, 4, 0)
118 g_slist_free_full(list, (GDestroyNotify)menu_cache_item_unref);
119 #endif
120 }
121
122 #if MENU_CACHE_CHECK_VERSION(0, 4, 0)
on_menu_cache_reload(MenuCache * mc,gpointer user_data)123 static void on_menu_cache_reload(MenuCache* mc, gpointer user_data)
124 #else
125 static void on_menu_cache_reload(gpointer mc, gpointer user_data)
126 #endif
127 {
128 g_return_if_fail(store);
129 gtk_tree_store_clear(store);
130 #if MENU_CACHE_CHECK_VERSION(0, 4, 0)
131 MenuCacheDir* dir = menu_cache_dup_root_dir(mc);
132 #else
133 MenuCacheDir* dir = menu_cache_get_root_dir(menu_cache);
134 #endif
135 /* FIXME: preserve original selection */
136 if(dir)
137 {
138 add_menu_items(NULL, dir);
139 #if MENU_CACHE_CHECK_VERSION(0, 4, 0)
140 menu_cache_item_unref(MENU_CACHE_ITEM(dir));
141 #endif
142 }
143 }
144
145 /**
146 * fm_app_menu_view_new
147 *
148 * Creates new application tree widget.
149 *
150 * Returns: (transfer full): a new widget.
151 *
152 * Since: 0.1.0
153 */
fm_app_menu_view_new(void)154 GtkTreeView *fm_app_menu_view_new(void)
155 {
156 GtkTreeView* view;
157 GtkTreeViewColumn* col;
158 GtkCellRenderer* render;
159
160 if(!store)
161 {
162 static GType menu_cache_item_type = 0;
163 char* oldenv;
164 if(G_UNLIKELY(!menu_cache_item_type))
165 menu_cache_item_type = g_boxed_type_register_static("MenuCacheItem",
166 (GBoxedCopyFunc)menu_cache_item_ref,
167 (GBoxedFreeFunc)menu_cache_item_unref);
168 store = gtk_tree_store_new(N_COLS, G_TYPE_ICON, /*GDK_TYPE_PIXBUF, */G_TYPE_STRING, menu_cache_item_type);
169 g_object_weak_ref(G_OBJECT(store), destroy_store, NULL);
170
171 /* ensure that we're using lxmenu-data */
172 oldenv = g_strdup(g_getenv("XDG_MENU_PREFIX"));
173 g_setenv("XDG_MENU_PREFIX", "lxde-", TRUE);
174 menu_cache = menu_cache_lookup("applications.menu");
175 if(oldenv)
176 {
177 g_setenv("XDG_MENU_PREFIX", oldenv, TRUE);
178 g_free(oldenv);
179 }
180 else
181 g_unsetenv("XDG_MENU_PREFIX");
182
183 if(menu_cache)
184 {
185 #if MENU_CACHE_CHECK_VERSION(0, 4, 0)
186 MenuCacheDir* dir = menu_cache_dup_root_dir(menu_cache);
187 #else
188 MenuCacheDir* dir = menu_cache_get_root_dir(menu_cache);
189 #endif
190 menu_cache_reload_notify = menu_cache_add_reload_notify(menu_cache, on_menu_cache_reload, NULL);
191 if(dir) /* content of menu is already loaded */
192 {
193 add_menu_items(NULL, dir);
194 #if MENU_CACHE_CHECK_VERSION(0, 4, 0)
195 menu_cache_item_unref(MENU_CACHE_ITEM(dir));
196 #endif
197 }
198 }
199 }
200 else
201 g_object_ref(store);
202
203 view = (GtkTreeView*)gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
204
205 render = gtk_cell_renderer_pixbuf_new();
206 col = gtk_tree_view_column_new();
207 gtk_tree_view_column_set_title(col, _("Installed Applications"));
208 gtk_tree_view_column_pack_start(col, render, FALSE);
209 gtk_tree_view_column_set_attributes(col, render, "gicon", COL_ICON, NULL);
210
211 render = gtk_cell_renderer_text_new();
212 gtk_tree_view_column_pack_start(col, render, TRUE);
213 gtk_tree_view_column_set_attributes(col, render, "text", COL_TITLE, NULL);
214
215 gtk_tree_view_append_column(view, col);
216
217 g_object_unref(store);
218 return view;
219 }
220
221 /**
222 * fm_app_menu_view_dup_selected_app
223 * @view: a widget
224 *
225 * Retrieves selected application from the widget.
226 * The returned data should be freed with g_object_unref() after usage.
227 *
228 * Before 1.0.0 this call had name fm_app_menu_view_get_selected_app.
229 *
230 * Returns: (transfer full): selected application descriptor.
231 *
232 * Since: 0.1.0
233 */
fm_app_menu_view_dup_selected_app(GtkTreeView * view)234 GAppInfo* fm_app_menu_view_dup_selected_app(GtkTreeView* view)
235 {
236 char* id = fm_app_menu_view_dup_selected_app_desktop_id(view);
237 if(id)
238 {
239 GDesktopAppInfo* app = g_desktop_app_info_new(id);
240 g_free(id);
241 return G_APP_INFO(app);
242 }
243 return NULL;
244 }
245
246 /**
247 * fm_app_menu_view_dup_selected_app_desktop_id
248 * @view: a widget
249 *
250 * Retrieves name of selected application from the widget.
251 * The returned data should be freed with g_free() after usage.
252 *
253 * Before 1.0.0 this call had name fm_app_menu_view_get_selected_app_desktop_id.
254 *
255 * Returns: (transfer full): selected application name.
256 *
257 * Since: 0.1.0
258 */
fm_app_menu_view_dup_selected_app_desktop_id(GtkTreeView * view)259 char* fm_app_menu_view_dup_selected_app_desktop_id(GtkTreeView* view)
260 {
261 GtkTreeIter it;
262 GtkTreeSelection* sel = gtk_tree_view_get_selection(view);
263 /* FIXME: this should be checked if it's exactly app menu tree! */
264 char* id = NULL;
265 if(gtk_tree_selection_get_selected(sel, NULL, &it))
266 {
267 MenuCacheItem* item;
268 gtk_tree_model_get(GTK_TREE_MODEL(store), &it, COL_ITEM, &item, -1);
269 if(item && menu_cache_item_get_type(item) == MENU_CACHE_TYPE_APP)
270 id = g_strdup(menu_cache_item_get_id(item));
271 }
272 return id;
273 }
274
275 /**
276 * fm_app_menu_view_dup_selected_app_desktop_file_path
277 * @view: a widget
278 *
279 * Retrieves file path to selected application from the widget.
280 * The returned data should be freed with g_free() after usage.
281 *
282 * Before 1.0.0 this call had name fm_app_menu_view_get_selected_app_desktop_file.
283 *
284 * Returns: (transfer full): path to selected application file.
285 *
286 * Since: 0.1.0
287 */
fm_app_menu_view_dup_selected_app_desktop_file_path(GtkTreeView * view)288 char* fm_app_menu_view_dup_selected_app_desktop_file_path(GtkTreeView* view)
289 {
290 GtkTreeIter it;
291 GtkTreeSelection* sel = gtk_tree_view_get_selection(view);
292 /* FIXME: this should be checked if it's exactly app menu tree! */
293 if(gtk_tree_selection_get_selected(sel, NULL, &it))
294 {
295 MenuCacheItem* item;
296 gtk_tree_model_get(GTK_TREE_MODEL(store), &it, COL_ITEM, &item, -1);
297 if(item && menu_cache_item_get_type(item) == MENU_CACHE_TYPE_APP)
298 {
299 char* path = menu_cache_item_get_file_path(item);
300 return path;
301 }
302 }
303 return NULL;
304 }
305
306 /**
307 * fm_app_menu_view_dup_selected_app_desktop_path
308 * @view: a widget
309 *
310 * Retrieves #FmPath to selected application from the widget as a child
311 * below fm_path_get_apps_menu() root path. Return %NULL if there is no
312 * application selected.
313 * The returned data should be freed with fm_path_unref() after usage.
314 *
315 * Returns: (transfer full): path to selected application file.
316 *
317 * Since: 1.2.0
318 */
fm_app_menu_view_dup_selected_app_desktop_path(GtkTreeView * view)319 FmPath * fm_app_menu_view_dup_selected_app_desktop_path(GtkTreeView* view)
320 {
321 GtkTreeIter it;
322 GtkTreeSelection* sel = gtk_tree_view_get_selection(view);
323 /* FIXME: this should be checked if it's exactly app menu tree! */
324 if(gtk_tree_selection_get_selected(sel, NULL, &it))
325 {
326 MenuCacheItem* item;
327 gtk_tree_model_get(GTK_TREE_MODEL(store), &it, COL_ITEM, &item, -1);
328 if(item && menu_cache_item_get_type(item) == MENU_CACHE_TYPE_APP)
329 {
330 char *mpath = menu_cache_dir_make_path(MENU_CACHE_DIR(item));
331 FmPath *path = fm_path_new_relative(fm_path_get_apps_menu(),
332 mpath+13 /* skip "/Applications" */);
333 g_free(mpath);
334 return path;
335 }
336 }
337 return NULL;
338 }
339
340 /**
341 * fm_app_menu_view_is_item_app
342 * @view: a widget
343 * @it: tree iterator
344 *
345 * Checks if item at @it is an application.
346 *
347 * Returns: %TRUE if item is an application.
348 *
349 * Since: 0.1.0
350 */
fm_app_menu_view_is_item_app(GtkTreeView * view,GtkTreeIter * it)351 gboolean fm_app_menu_view_is_item_app(GtkTreeView* view, GtkTreeIter* it)
352 {
353 MenuCacheItem* item;
354 /* FIXME: this should be checked if it's exactly app menu tree! */
355 gboolean ret = FALSE;
356 gtk_tree_model_get(GTK_TREE_MODEL(store), it, COL_ITEM, &item, -1);
357 if(item && menu_cache_item_get_type(item) == MENU_CACHE_TYPE_APP)
358 ret = TRUE;
359 return ret;
360 }
361
362 /**
363 * fm_app_menu_view_is_app_selected
364 * @view: a widget
365 *
366 * Checks if there is an application selected in @view.
367 *
368 * Returns: %TRUE if there is an application selected.
369 *
370 * Since: 0.1.0
371 */
fm_app_menu_view_is_app_selected(GtkTreeView * view)372 gboolean fm_app_menu_view_is_app_selected(GtkTreeView* view)
373 {
374 GtkTreeIter it;
375 GtkTreeSelection* sel = gtk_tree_view_get_selection(view);
376 if(gtk_tree_selection_get_selected(sel, NULL, &it))
377 return fm_app_menu_view_is_item_app(view, &it);
378 return FALSE;
379 }
380