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