debian: build from GIT requires gtk-doc-tools so adding it to debian/control.
[lxde/lxpanel.git] / plugins / menu.c
CommitLineData
239cb032 1/**
dd731fde 2 * Copyright (c) 2006-2014 LxDE Developers, see the file AUTHORS for details.
a99ee9e1
JH
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software Foundation,
16 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 */
18
bed635b7
AG
19#ifdef HAVE_CONFIG_H
20#include <config.h>
21#endif
22
a52c2257
HJYP
23#include <stdlib.h>
24#include <string.h>
25
26#include <gdk-pixbuf/gdk-pixbuf.h>
27#include <glib.h>
08ea5341 28#include <glib/gi18n.h>
a52c2257 29
28debdd2 30#include <menu-cache.h>
dd731fde 31#include <libfm/fm-gtk.h>
70684259 32
f1286efa
HJYP
33#include <sys/types.h>
34#include <sys/stat.h>
18ecfe2a 35#include <unistd.h>
f1286efa
HJYP
36#include <fcntl.h>
37
a52c2257 38#include "misc.h"
dd731fde 39#include "plugin.h"
2918994e 40#include "menu-policy.h"
a52c2257 41
a52c2257
HJYP
42#include "dbg.h"
43
bed635b7
AG
44/* support for libmenu-cache 0.4.x */
45#ifndef MENU_CACHE_CHECK_VERSION
46# ifdef HAVE_MENU_CACHE_DIR_LIST_CHILDREN
47# define MENU_CACHE_CHECK_VERSION(_a,_b,_c) (_a == 0 && _b < 5) /* < 0.5.0 */
48# else
49# define MENU_CACHE_CHECK_VERSION(_a,_b,_c) 0 /* not even 0.4.0 */
50# endif
51#endif
52
0b806437 53#define DEFAULT_MENU_ICON PACKAGE_DATA_DIR "/images/my-computer.png"
a52c2257
HJYP
54/*
55 * SuxPanel version 0.1
56 * Copyright (c) 2003 Leandro Pereira <leandro@linuxmag.com.br>
57 */
58
59/*
60 * menu style code was taken from suxpanel
61 */
62
a52c2257 63typedef struct {
e2957bd2 64 GtkWidget *menu, *box, *img, *label;
f49fdaeb 65 char *fname, *caption;
a52c2257 66 gulong handler_id;
dd731fde 67 int iconsize;
a52c2257 68 gboolean has_system_menu;
dd731fde 69 guint show_system_menu_idle;
a7bd16a4 70 LXPanel *panel;
dd731fde 71 config_setting_t *settings;
70684259
HJYP
72
73 MenuCache* menu_cache;
9df6826f 74 guint visibility_flags;
727f696e 75 gpointer reload_notify;
d0d4d6f3 76 FmDndSrc *ds;
a52c2257
HJYP
77} menup;
78
871c8f42
HJYP
79static guint idle_loader = 0;
80
70684259
HJYP
81GQuark SYS_MENU_ITEM_ID = 0;
82
dd731fde
AG
83/* FIXME: this is defined in misc.c and should be replaced later */
84GtkWidget *_gtk_image_new_from_file_scaled(const gchar *file, gint width,
85 gint height, gboolean keep_ratio);
86/* FIXME: those are defined on panel main code */
87void restart(void);
88void gtk_run(void);
89void logout(void);
70684259 90
d0d4d6f3
AG
91static void on_data_get(FmDndSrc *ds, GtkWidget *mi)
92{
93 FmFileInfo *fi = g_object_get_qdata(G_OBJECT(mi), SYS_MENU_ITEM_ID);
94
13cf6185 95 /* g_debug("on_data_get(...)"); */
d0d4d6f3
AG
96 fm_dnd_src_set_file(ds, fi);
97}
98
a52c2257 99static void
dd731fde 100menu_destructor(gpointer user_data)
a52c2257 101{
dd731fde 102 menup *m = (menup *)user_data;
a52c2257 103
871c8f42
HJYP
104 if( G_UNLIKELY( idle_loader ) )
105 {
106 g_source_remove( idle_loader );
107 idle_loader = 0;
108 }
87cbb654 109
dd731fde
AG
110 if (m->show_system_menu_idle)
111 g_source_remove(m->show_system_menu_idle);
2918994e 112
0d2ee633 113 /* g_signal_handler_disconnect(G_OBJECT(m->img), m->handler_id); */
d0d4d6f3
AG
114 g_signal_handlers_disconnect_matched(m->ds, G_SIGNAL_MATCH_FUNC, 0, 0, NULL,
115 on_data_get, NULL);
116 g_object_unref(G_OBJECT(m->ds));
a52c2257 117 gtk_widget_destroy(m->menu);
70684259 118
727f696e
HJYP
119 if( m->menu_cache )
120 {
121 menu_cache_remove_reload_notify(m->menu_cache, m->reload_notify);
122 menu_cache_unref( m->menu_cache );
123 }
70684259 124
f49fdaeb
FC
125 g_free(m->fname);
126 g_free(m->caption);
a52c2257
HJYP
127 g_free(m);
128 RET();
129}
130
131static void
132spawn_app(GtkWidget *widget, gpointer data)
133{
a52c2257
HJYP
134 ENTER;
135 if (data) {
dd731fde 136 fm_launch_command_simple(NULL, NULL, 0, data, NULL);
a52c2257
HJYP
137 }
138 RET();
139}
140
141
142static void
143run_command(GtkWidget *widget, void (*cmd)(void))
144{
145 ENTER;
146 cmd();
147 RET();
148}
149
150static void
151menu_pos(GtkMenu *menu, gint *x, gint *y, gboolean *push_in, GtkWidget *widget)
152{
153 int ox, oy, w, h;
dd731fde 154 menup *m;
09fa171b
AG
155 GtkAllocation allocation;
156
157 gtk_widget_get_allocation(GTK_WIDGET(widget), &allocation);
a52c2257 158 ENTER;
dd731fde 159 m = g_object_get_data(G_OBJECT(widget), "plugin");
d5c46ffc 160 gdk_window_get_origin(gtk_widget_get_window(widget), &ox, &oy);
d5c46ffc 161#if GTK_CHECK_VERSION(2,20,0)
200121f9
JL
162 GtkRequisition requisition;
163 gtk_widget_get_requisition(GTK_WIDGET(menu), &requisition);
164 w = requisition.width;
165 h = requisition.height;
d5c46ffc
JL
166
167#else
a52c2257
HJYP
168 w = GTK_WIDGET(menu)->requisition.width;
169 h = GTK_WIDGET(menu)->requisition.height;
d5c46ffc 170#endif
dd731fde 171 if (panel_get_orientation(m->panel) == GTK_ORIENTATION_HORIZONTAL) {
a52c2257
HJYP
172 *x = ox;
173 if (*x + w > gdk_screen_width())
09fa171b 174 *x = ox + allocation.width - w;
a52c2257
HJYP
175 *y = oy - h;
176 if (*y < 0)
09fa171b 177 *y = oy + allocation.height;
a52c2257 178 } else {
09fa171b 179 *x = ox + allocation.width;
a52c2257
HJYP
180 if (*x > gdk_screen_width())
181 *x = ox - w;
182 *y = oy;
183 if (*y + h > gdk_screen_height())
09fa171b 184 *y = oy + allocation.height - h;
a52c2257
HJYP
185 }
186 DBG("widget: x,y=%d,%d w,h=%d,%d\n", ox, oy,
09fa171b 187 allocation.width, allocation.height );
a52c2257
HJYP
188 DBG("w-h %d %d\n", w, h);
189 *push_in = TRUE;
190 RET();
191}
192
dd731fde 193static void on_menu_item( GtkMenuItem* mi, menup* m )
70684259 194{
dd731fde
AG
195 FmFileInfo *fi = g_object_get_qdata(G_OBJECT(mi), SYS_MENU_ITEM_ID);
196
197 lxpanel_launch_path(m->panel, fm_file_info_get_path(fi));
727f696e
HJYP
198}
199
7df0f080 200/* load icon when mapping the menu item to speed up */
dd731fde 201static void on_menu_item_map(GtkWidget *mi, menup *m)
727f696e 202{
cdf8468b 203 GtkImage* img = GTK_IMAGE(gtk_image_menu_item_get_image(GTK_IMAGE_MENU_ITEM(mi)));
727f696e
HJYP
204 if( img )
205 {
dd731fde
AG
206 FmFileInfo *fi;
207 if (gtk_image_get_storage_type(img) == GTK_IMAGE_EMPTY &&
208 (fi = g_object_get_qdata(G_OBJECT(mi), SYS_MENU_ITEM_ID)) != NULL &&
209 fi != (gpointer)1 /* ignore placeholder or separator */)
727f696e 210 {
dd731fde
AG
211 FmIcon *fm_icon = fm_file_info_get_icon(fi);
212 FmIcon *_fm_icon = NULL;
213 GdkPixbuf *icon = NULL;
214
215 if (fm_icon == NULL)
216 fm_icon = _fm_icon = fm_icon_from_name("application-x-executable");
217 icon = fm_pixbuf_from_icon_with_fallback(fm_icon, m->iconsize,
218 "application-x-executable");
219 if (_fm_icon)
220 g_object_unref(_fm_icon);
e7a42ecf 221 if (icon)
65a36853 222 {
e7a42ecf 223 gtk_image_set_from_pixbuf(img, icon);
224 g_object_unref(icon);
65a36853 225 }
727f696e
HJYP
226 }
227 }
7df0f080
HJYP
228}
229
dd731fde 230static void on_menu_item_style_set(GtkWidget* mi, GtkStyle* prev, menup *m)
7df0f080
HJYP
231{
232 /* reload icon */
dd731fde 233 on_menu_item_map(mi, m);
727f696e
HJYP
234}
235
dd731fde
AG
236/* FIXME: this is very dirty, especially if desktop is set not to ~/Desktop
237 therefore it should be removed and drag & drop gestures used instead */
238static void on_add_menu_item_to_desktop(GtkMenuItem* item, GtkWidget* mi)
f1286efa 239{
dd731fde 240 FmFileInfo *fi = g_object_get_qdata(G_OBJECT(mi), SYS_MENU_ITEM_ID);
72e2e990 241 FmPathList *files = fm_path_list_new();
f1286efa 242
72e2e990
AG
243 fm_path_list_push_tail(files, fm_file_info_get_path(fi));
244 fm_link_files(NULL, files, fm_path_get_desktop());
245 fm_path_list_unref(files);
f1286efa
HJYP
246}
247
4f4f4ecb 248/* TODO: add menu item to panel */
18ecfe2a 249#if 0
f1286efa
HJYP
250static void on_add_menu_item_to_panel(GtkMenuItem* item, MenuCacheApp* app)
251{
252 /* Find a penel containing launchbar applet.
253 * The launchbar with most buttons will be choosen if
254 * there are several launchbar applets loaded.
255 */
256 GSList* l;
257 Plugin* lb = NULL;
258 int n_btns = -1;
259
260 for(l = all_panels; !lb && l; l = l->next)
261 {
262 Panel* panel = (Panel*)l->data;
263 GList* pl;
264 for(pl=panel->plugins; pl; pl = pl->next)
265 {
266 Plugin* plugin = (Plugin*)pl;
267 if( strcmp(plugin->class->type, "launchbar") == 0 )
268 {
269 /* FIXME: should we let the users choose which launcherbar to add the btn? */
270 break;
271#if 0
272 int n = launchbar_get_n_btns(plugin);
273 if( n > n_btns )
274 {
275 lb = plugin;
276 n_btns = n;
277 }
278#endif
279 }
280 }
281 }
282
283 if( ! lb ) /* launchbar is not currently in use */
284 {
285 /* FIXME: add a launchbar plugin to the panel which has a menu, too. */
286 }
287
288 if( lb )
289 {
290
291 }
292}
18ecfe2a 293#endif
f1286efa 294
dd731fde 295static void on_menu_item_properties(GtkMenuItem* item, GtkWidget* mi)
f1286efa 296{
dd731fde
AG
297 FmFileInfo *fi = g_object_get_qdata(G_OBJECT(mi), SYS_MENU_ITEM_ID);
298 FmFileInfoList *files = fm_file_info_list_new();
299
300 fm_file_info_list_push_tail(files, fi);
301 fm_show_file_properties(NULL, files);
302 fm_file_info_list_unref(files);
f1286efa
HJYP
303}
304
4f47af9e 305#if 0
f1286efa
HJYP
306/* This following function restore_grabs is taken from menu.c of
307 * gnome-panel.
308 */
309/*most of this function stolen from the real gtk_menu_popup*/
310static void restore_grabs(GtkWidget *w, gpointer data)
727f696e 311{
f1286efa 312 GtkWidget *menu_item = data;
d5c46ffc 313 GtkMenu *menu = GTK_MENU(gtk_widget_get_parent(menu_item));
f1286efa
HJYP
314 GtkWidget *xgrab_shell;
315 GtkWidget *parent;
316
317 /* Find the last viewable ancestor, and make an X grab on it
318 */
319 parent = GTK_WIDGET (menu);
320 xgrab_shell = NULL;
321 while (parent)
322 {
323 gboolean viewable = TRUE;
324 GtkWidget *tmp = parent;
325
326 while (tmp)
327 {
09fa171b
AG
328#if GTK_CHECK_VERSION(2, 24, 0)
329 if (!gtk_widget_get_mapped(tmp))
330#else
f1286efa 331 if (!GTK_WIDGET_MAPPED (tmp))
09fa171b 332#endif
f1286efa
HJYP
333 {
334 viewable = FALSE;
335 break;
336 }
d5c46ffc 337 tmp = gtk_widget_get_parent(tmp);
f1286efa
HJYP
338 }
339
340 if (viewable)
341 xgrab_shell = parent;
342
d5c46ffc 343 parent = gtk_widget_get_parent(parent);
f1286efa
HJYP
344 }
345
346 /*only grab if this HAD a grab before*/
d5c46ffc 347 if (xgrab_shell && (gtk_widget_has_focus(xgrab_shell)))
d5c46ffc 348 {
d5c46ffc 349 if (gdk_pointer_grab (gtk_widget_get_window(xgrab_shell), TRUE,
f1286efa
HJYP
350 GDK_BUTTON_PRESS_MASK |
351 GDK_BUTTON_RELEASE_MASK |
352 GDK_ENTER_NOTIFY_MASK |
353 GDK_LEAVE_NOTIFY_MASK,
354 NULL, NULL, 0) == 0)
355 {
d5c46ffc 356 if (gdk_keyboard_grab (gtk_widget_get_window(xgrab_shell), TRUE,
f1286efa 357 GDK_CURRENT_TIME) == 0)
d5c46ffc 358 gtk_widget_grab_focus (xgrab_shell);
f1286efa
HJYP
359 else
360 gdk_pointer_ungrab (GDK_CURRENT_TIME);
361 }
362 }
363 gtk_grab_add (GTK_WIDGET (menu));
364}
4f47af9e
AG
365#endif
366
367static void restore_submenu(GtkMenuItem *mi, GtkWidget *submenu)
368{
369 g_signal_handlers_disconnect_by_func(mi, restore_submenu, submenu);
370 gtk_menu_item_set_submenu(mi, submenu);
371 g_object_set_data(G_OBJECT(mi), "PanelMenuItemSubmenu", NULL);
372}
f1286efa 373
dd731fde 374static gboolean on_menu_button_press(GtkWidget* mi, GdkEventButton* evt, menup* m)
f1286efa
HJYP
375{
376 if( evt->button == 3 ) /* right */
377 {
87cbb654 378 GtkWidget* item;
4f47af9e
AG
379 GtkMenu* p;
380
381 /* don't make duplicates */
382 if (g_signal_handler_find(mi, G_SIGNAL_MATCH_FUNC, 0, 0, NULL,
383 restore_submenu, NULL))
384 {
385 return FALSE;
386 }
387
388 p = GTK_MENU(gtk_menu_new());
e7a42ecf 389
87cbb654 390 item = gtk_menu_item_new_with_label(_("Add to desktop"));
dd731fde 391 g_signal_connect(item, "activate", G_CALLBACK(on_add_menu_item_to_desktop), mi);
cdf8468b 392 gtk_menu_shell_append(GTK_MENU_SHELL(p), item);
f1286efa 393
dd731fde
AG
394 item = gtk_separator_menu_item_new();
395 gtk_menu_shell_append(GTK_MENU_SHELL(p), item);
396
397 item = gtk_menu_item_new_with_label(_("Properties"));
398 g_signal_connect(item, "activate", G_CALLBACK(on_menu_item_properties), mi);
399 gtk_menu_shell_append(GTK_MENU_SHELL(p), item);
f1286efa 400
4f47af9e
AG
401 item = gtk_menu_item_get_submenu(GTK_MENU_ITEM(mi)); /* reuse it */
402 if (item)
403 {
404 /* set object data to keep reference on the submenu we preserve */
405 g_object_set_data_full(G_OBJECT(mi), "PanelMenuItemSubmenu",
406 g_object_ref(item), g_object_unref);
407 gtk_menu_popdown(GTK_MENU(item));
408 }
409 gtk_menu_item_set_submenu(GTK_MENU_ITEM(mi), GTK_WIDGET(p));
410 g_signal_connect(mi, "deselect", G_CALLBACK(restore_submenu), item);
cdf8468b 411 gtk_widget_show_all(GTK_WIDGET(p));
727f696e 412 }
d0d4d6f3
AG
413 else if (evt->button == 1) /* allow drag on clicked item */
414 {
415 /* disconnect previous menu item */
416 g_signal_handlers_disconnect_matched(m->ds, G_SIGNAL_MATCH_FUNC, 0, 0,
417 NULL, on_data_get, NULL);
418 /* remap FmDndSrc onto current item */
419 fm_dnd_src_set_widget(m->ds, mi);
420 g_signal_connect(m->ds, "data-get", G_CALLBACK(on_data_get), mi);
421 }
727f696e 422 return FALSE;
70684259
HJYP
423}
424
dd731fde 425static GtkWidget* create_item(MenuCacheItem *item, menup *m)
70684259 426{
727f696e
HJYP
427 GtkWidget* mi;
428 if( menu_cache_item_get_type(item) == MENU_CACHE_TYPE_SEP )
dd731fde 429 {
727f696e 430 mi = gtk_separator_menu_item_new();
dd731fde
AG
431 g_object_set_qdata(G_OBJECT(mi), SYS_MENU_ITEM_ID, (gpointer)1);
432 }
727f696e
HJYP
433 else
434 {
435 GtkWidget* img;
dd731fde
AG
436 /* create FmFileInfo for the item, it will be used in callbacks */
437 char *mpath = menu_cache_dir_make_path(MENU_CACHE_DIR(item));
438 FmPath *path = fm_path_new_relative(fm_path_get_apps_menu(), mpath+13);
439 /* skip "/Applications" */
440 FmFileInfo *fi = fm_file_info_new_from_menu_cache_item(path, item);
441
442 g_free(mpath);
443 fm_path_unref(path);
3e84388d 444 mi = gtk_image_menu_item_new_with_mnemonic( menu_cache_item_get_name(item) );
dd731fde
AG
445 g_object_set_qdata_full(G_OBJECT(mi), SYS_MENU_ITEM_ID, fi,
446 (GDestroyNotify)fm_file_info_unref);
727f696e 447 img = gtk_image_new();
cdf8468b 448 gtk_image_menu_item_set_image( GTK_IMAGE_MENU_ITEM(mi), img );
727f696e
HJYP
449 if( menu_cache_item_get_type(item) == MENU_CACHE_TYPE_APP )
450 {
b5f51ced 451 gtk_widget_set_tooltip_text( mi, menu_cache_item_get_comment(item) );
dd731fde 452 g_signal_connect(mi, "activate", G_CALLBACK(on_menu_item), m);
727f696e 453 }
dd731fde
AG
454 g_signal_connect(mi, "map", G_CALLBACK(on_menu_item_map), m);
455 g_signal_connect(mi, "style-set", G_CALLBACK(on_menu_item_style_set), m);
456 g_signal_connect(mi, "button-press-event", G_CALLBACK(on_menu_button_press), m);
d0d4d6f3
AG
457 /* allow drag and add empty set for now to allow dragging the item
458 the rest will be done by FmDndSrc after drag begins */
459 gtk_drag_source_set(mi, GDK_BUTTON1_MASK, NULL, 0, GDK_ACTION_COPY);
727f696e
HJYP
460 }
461 gtk_widget_show( mi );
727f696e 462 return mi;
70684259
HJYP
463}
464
6a4d4973 465static int load_menu(menup* m, MenuCacheDir* dir, GtkWidget* menu, int pos )
70684259 466{
2918994e 467 GSList * l;
6a4d4973 468 /* number of visible entries */
0bcf9045 469 gint count = 0;
bed635b7 470#if MENU_CACHE_CHECK_VERSION(0, 4, 0)
8d5685f6
AG
471 GSList *children;
472#if MENU_CACHE_CHECK_VERSION(0, 5, 0)
473 char *kfpath = menu_cache_item_get_file_path(MENU_CACHE_ITEM(dir));
474 GKeyFile *kf = g_key_file_new();
475 /* for version 0.5.0 we enable hidden so should test NoDisplay flag */
476 if (kfpath && g_key_file_load_from_file(kf, kfpath, 0, NULL) &&
477 g_key_file_get_boolean(kf, "Desktop Entry", "NoDisplay", NULL))
478 count = -1;
479 g_free(kfpath);
480 g_key_file_free(kf);
481 if (count < 0) /* directory is hidden, ignore children */
482 return 0;
483#endif
484 children = menu_cache_dir_list_children(dir);
bed635b7
AG
485 for (l = children; l; l = l->next)
486#else
727f696e 487 for( l = menu_cache_dir_get_children(dir); l; l = l->next )
bed635b7 488#endif
727f696e
HJYP
489 {
490 MenuCacheItem* item = MENU_CACHE_ITEM(l->data);
0bcf9045
AG
491
492 gboolean is_visible = ((menu_cache_item_get_type(item) != MENU_CACHE_TYPE_APP) ||
b6c8855c 493 (panel_menu_item_evaluate_visibility(item, m->visibility_flags)));
0bcf9045
AG
494
495 if (is_visible)
b6c8855c 496 {
dd731fde 497 GtkWidget * mi = create_item(item, m);
6a4d4973 498 count++;
2918994e 499 if (mi != NULL)
2918994e 500 gtk_menu_shell_insert( (GtkMenuShell*)menu, mi, pos );
501 if( pos >= 0 )
502 ++pos;
b6c8855c 503 /* process subentries */
0bcf9045 504 if (menu_cache_item_get_type(item) == MENU_CACHE_TYPE_DIR)
b6c8855c 505 {
2918994e 506 GtkWidget* sub = gtk_menu_new();
6a4d4973 507 /* always pass -1 for position */
0bcf9045
AG
508 gint s_count = load_menu( m, MENU_CACHE_DIR(item), sub, -1 );
509 if (s_count)
510 gtk_menu_item_set_submenu( GTK_MENU_ITEM(mi), sub );
511 else
6a4d4973
JH
512 {
513 /* don't keep empty submenus */
514 gtk_widget_destroy( sub );
515 gtk_widget_destroy( mi );
516 if (pos > 0)
517 pos--;
518 }
b6c8855c
JH
519 }
520 }
727f696e 521 }
bed635b7
AG
522#if MENU_CACHE_CHECK_VERSION(0, 4, 0)
523 g_slist_foreach(children, (GFunc)menu_cache_item_unref, NULL);
524 g_slist_free(children);
525#endif
6a4d4973 526 return count;
70684259
HJYP
527}
528
529
b6c8855c 530
70684259
HJYP
531static gboolean sys_menu_item_has_data( GtkMenuItem* item )
532{
727f696e 533 return (g_object_get_qdata( G_OBJECT(item), SYS_MENU_ITEM_ID ) != NULL);
70684259
HJYP
534}
535
dd731fde 536static void _unload_old_icons(GtkMenu* menu, GtkIconTheme* theme, menup* m)
3167c7b1
HJYP
537{
538 GList *children, *child;
539 GtkMenuItem* item;
3b6661f3 540 GtkWidget* sub_menu=NULL;
d56c6111 541
3167c7b1
HJYP
542 children = gtk_container_get_children( GTK_CONTAINER(menu) );
543 for( child = children; child; child = child->next )
544 {
545 item = GTK_MENU_ITEM( child->data );
546 if( sys_menu_item_has_data( item ) )
547 {
548 GtkImage* img;
549 item = GTK_MENU_ITEM( child->data );
550 if( GTK_IS_IMAGE_MENU_ITEM(item) )
551 {
cdf8468b 552 img = GTK_IMAGE(gtk_image_menu_item_get_image(GTK_IMAGE_MENU_ITEM(item)));
3167c7b1 553 gtk_image_clear(img);
09fa171b
AG
554#if GTK_CHECK_VERSION(2, 24, 0)
555 if (gtk_widget_get_mapped(GTK_WIDGET(img)))
556#else
d838148a 557 if( GTK_WIDGET_MAPPED(img) )
09fa171b 558#endif
dd731fde 559 on_menu_item_map(GTK_WIDGET(item), m);
3167c7b1
HJYP
560 }
561 }
562 else if( ( sub_menu = gtk_menu_item_get_submenu( item ) ) )
563 {
dd731fde 564 _unload_old_icons(GTK_MENU(sub_menu), theme, m);
3167c7b1
HJYP
565 }
566 }
567 g_list_free( children );
568}
569
dd731fde
AG
570static void unload_old_icons(GtkIconTheme* theme, menup* m)
571{
572 _unload_old_icons(GTK_MENU(m->menu), theme, m);
573}
574
b8ca9e81 575static void remove_change_handler(gpointer id, GObject* menu)
3167c7b1 576{
b8ca9e81 577 g_signal_handler_disconnect(gtk_icon_theme_get_default(), GPOINTER_TO_INT(id));
3167c7b1
HJYP
578}
579
70684259
HJYP
580/*
581 * Insert application menus into specified menu
582 * menu: The parent menu to which the items should be inserted
583 * pisition: Position to insert items.
584 Passing -1 in this parameter means append all items
585 at the end of menu.
586 */
587static void sys_menu_insert_items( menup* m, GtkMenu* menu, int position )
588{
727f696e 589 MenuCacheDir* dir;
3167c7b1
HJYP
590 guint change_handler;
591
727f696e
HJYP
592 if( G_UNLIKELY( SYS_MENU_ITEM_ID == 0 ) )
593 SYS_MENU_ITEM_ID = g_quark_from_static_string( "SysMenuItem" );
70684259 594
bed635b7
AG
595#if MENU_CACHE_CHECK_VERSION(0, 4, 0)
596 dir = menu_cache_dup_root_dir(m->menu_cache);
597#else
727f696e 598 dir = menu_cache_get_root_dir( m->menu_cache );
bed635b7 599#endif
80f3a04f 600 if(dir)
a5bdced1 601 {
80f3a04f 602 load_menu( m, dir, GTK_WIDGET(menu), position );
a5bdced1
AG
603#if MENU_CACHE_CHECK_VERSION(0, 4, 0)
604 menu_cache_item_unref(MENU_CACHE_ITEM(dir));
605#endif
606 }
80f3a04f
HJYP
607 else /* menu content is empty */
608 {
609 /* add a place holder */
610 GtkWidget* mi = gtk_menu_item_new();
611 g_object_set_qdata( G_OBJECT(mi), SYS_MENU_ITEM_ID, GINT_TO_POINTER(1) );
fcb35553 612 gtk_menu_shell_insert(GTK_MENU_SHELL(menu), mi, position);
80f3a04f 613 }
70684259 614
dd731fde 615 change_handler = g_signal_connect(gtk_icon_theme_get_default(), "changed", G_CALLBACK(unload_old_icons), m);
b8276d47 616 g_object_weak_ref( G_OBJECT(menu), remove_change_handler, GINT_TO_POINTER(change_handler) );
70684259
HJYP
617}
618
619
a52c2257 620static void
70684259 621reload_system_menu( menup* m, GtkMenu* menu )
a52c2257
HJYP
622{
623 GList *children, *child;
624 GtkMenuItem* item;
625 GtkWidget* sub_menu;
626 gint idx;
d56c6111 627
a52c2257 628 children = gtk_container_get_children( GTK_CONTAINER(menu) );
70684259 629 for( child = children, idx = 0; child; child = child->next, ++idx )
727f696e 630 {
a52c2257 631 item = GTK_MENU_ITEM( child->data );
70684259 632 if( sys_menu_item_has_data( item ) )
727f696e 633 {
70684259 634 do
727f696e 635 {
a52c2257
HJYP
636 item = GTK_MENU_ITEM( child->data );
637 child = child->next;
638 gtk_widget_destroy( GTK_WIDGET(item) );
70684259
HJYP
639 }while( child && sys_menu_item_has_data( child->data ) );
640 sys_menu_insert_items( m, menu, idx );
a52c2257
HJYP
641 if( ! child )
642 break;
643 }
70684259 644 else if( ( sub_menu = gtk_menu_item_get_submenu( item ) ) )
727f696e 645 {
70684259 646 reload_system_menu( m, GTK_MENU(sub_menu) );
a52c2257
HJYP
647 }
648 }
649 g_list_free( children );
650}
651
dd731fde 652static void show_menu( GtkWidget* widget, menup* m, int btn, guint32 time )
e996608e 653{
c6780e74 654 gtk_menu_popup(GTK_MENU(m->menu),
e996608e
HJYP
655 NULL, NULL,
656 (GtkMenuPositionFunc)menu_pos, widget,
657 btn, time);
658}
659
a52c2257 660static gboolean
dd731fde 661my_button_pressed(GtkWidget *widget, GdkEventButton *event, menup *m)
a52c2257
HJYP
662{
663 ENTER;
09fa171b
AG
664 GtkAllocation allocation;
665 gtk_widget_get_allocation(GTK_WIDGET(widget), &allocation);
7414a73f 666
ae212dd2 667 if ((event->type == GDK_BUTTON_PRESS) && event->button == 1
09fa171b
AG
668 && (event->x >=0 && event->x < allocation.width)
669 && (event->y >=0 && event->y < allocation.height)) {
dd731fde 670 show_menu( widget, m, event->button, event->time );
ae212dd2 671 RET(TRUE);
a52c2257 672 }
ae212dd2 673 RET(FALSE);
a52c2257
HJYP
674}
675
dd731fde 676static gboolean show_system_menu_idle(gpointer user_data)
e996608e 677{
dd731fde
AG
678 menup* m = (menup*)user_data;
679 if (g_source_is_destroyed(g_main_current_source()))
680 return FALSE;
681 show_menu( m->img, m, 0, GDK_CURRENT_TIME );
682 m->show_system_menu_idle = 0;
8c44345a 683 return FALSE;
e996608e 684}
a52c2257 685
dd731fde
AG
686static void show_system_menu(GtkWidget *p)
687{
688 menup *m = lxpanel_plugin_get_data(p);
689
690 if (m->has_system_menu && m->show_system_menu_idle == 0)
691 /* FIXME: I've no idea why this doesn't work without timeout
692 under some WMs, like icewm. */
693 m->show_system_menu_idle = g_timeout_add(200, show_system_menu_idle, m);
694}
695
a52c2257 696static GtkWidget *
dd731fde 697make_button(menup *m, const gchar *fname, const gchar *name, GdkColor* tint, GtkWidget *menu)
a52c2257 698{
017e5ad5 699 char* title = NULL;
a52c2257
HJYP
700
701 ENTER;
a52c2257 702 m->menu = menu;
017e5ad5
HJYP
703
704 if( name )
705 {
706 /* load the name from *.directory file if needed */
707 if( g_str_has_suffix( name, ".directory" ) )
708 {
709 GKeyFile* kf = g_key_file_new();
99683934 710 char* dir_file = g_build_filename( "desktop-directories", name, NULL );
017e5ad5
HJYP
711 if( g_key_file_load_from_data_dirs( kf, dir_file, NULL, 0, NULL ) )
712 {
713 title = g_key_file_get_locale_string( kf, "Desktop Entry", "Name", NULL, NULL );
714 }
715 g_free( dir_file );
716 g_key_file_free( kf );
717 }
017e5ad5 718
dd731fde 719 m->img = lxpanel_button_new_for_icon(m->panel, fname, tint, title ? title : name);
017e5ad5 720
dd731fde 721 g_free( title );
017e5ad5
HJYP
722 }
723 else
724 {
dd731fde 725 m->img = lxpanel_button_new_for_icon(m->panel, fname, tint, NULL);
017e5ad5 726 }
f49fdaeb 727
e2957bd2 728 gtk_widget_show(m->img);
2ec31e5c 729 gtk_box_pack_start(GTK_BOX(m->box), m->img, TRUE, FALSE, 0);
a52c2257 730
e2957bd2 731 m->handler_id = g_signal_connect (G_OBJECT (m->img), "button-press-event",
dd731fde
AG
732 G_CALLBACK (my_button_pressed), m);
733 g_object_set_data(G_OBJECT(m->img), "plugin", m);
a52c2257 734
d0d4d6f3
AG
735 m->ds = fm_dnd_src_new(NULL);
736
e2957bd2 737 RET(m->img);
a52c2257
HJYP
738}
739
dd731fde
AG
740/* those were in configurator.c initially but it's safer to have those here */
741typedef struct {
742 char *name;
743 char *disp_name;
744 void (*cmd)(void);
745} Command;
746
747static Command commands[] = {
748 //{ "configure", N_("Preferences"), configure },
749 { "run", N_("Run"), gtk_run },
750 { "restart", N_("Restart"), restart },
751 { "logout", N_("Logout"), logout },
752 { NULL, NULL },
753};
a52c2257
HJYP
754
755static GtkWidget *
dd731fde 756read_item(menup *m, config_setting_t *s)
a52c2257 757{
dd731fde 758 const gchar *name, *fname, *action, *str;
a52c2257 759 GtkWidget *item;
22242ed4 760 Command *cmd_entry = NULL;
dd731fde 761 char *tmp;
a52c2257
HJYP
762
763 ENTER;
08ea5341
HJYP
764 name = fname = action = NULL;
765
dd731fde
AG
766 config_setting_lookup_string(s, "name", &name);
767 config_setting_lookup_string(s, "image", &fname);
768 config_setting_lookup_string(s, "action", &action);
769 if (config_setting_lookup_string(s, "command", &str))
db449f6e 770 {
dd731fde
AG
771 Command *tmp;
772
773 for (tmp = commands; tmp->name; tmp++) {
774 if (!g_ascii_strcasecmp(str, tmp->name)) {
775 cmd_entry = tmp;
776 break;
a52c2257
HJYP
777 }
778 }
779 }
780 /* menu button */
08ea5341
HJYP
781 if( cmd_entry ) /* built-in commands */
782 {
783 item = gtk_image_menu_item_new_with_label( _(cmd_entry->disp_name) );
784 g_signal_connect(G_OBJECT(item), "activate", (GCallback)run_command, cmd_entry->cmd);
785 }
dd731fde 786 else if (action)
08ea5341
HJYP
787 {
788 item = gtk_image_menu_item_new_with_label(name ? name : "");
dd731fde
AG
789 tmp = g_strdup(action);
790 g_object_weak_ref(G_OBJECT(item), (GWeakNotify)g_free, tmp);
791 g_signal_connect(G_OBJECT(item), "activate", (GCallback)spawn_app, tmp);
08ea5341 792 }
dd731fde
AG
793 else
794 goto error;
a52c2257 795 gtk_container_set_border_width(GTK_CONTAINER(item), 0);
a52c2257
HJYP
796 if (fname) {
797 GtkWidget *img;
798
908d9fdc 799 /* FIXME: use FmIcon cache and fm_pixbuf_from_icon() API */
dd731fde
AG
800 tmp = expand_tilda(fname);
801 img = _gtk_image_new_from_file_scaled(tmp, m->iconsize, m->iconsize, TRUE);
a52c2257
HJYP
802 gtk_widget_show(img);
803 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
dd731fde 804 g_free(tmp);
a52c2257 805 }
a52c2257
HJYP
806 RET(item);
807
808 error:
a52c2257
HJYP
809 RET(NULL);
810}
811
812static GtkWidget *
dd731fde 813read_separator(menup *m, config_setting_t *s)
a52c2257 814{
a52c2257 815 ENTER;
a52c2257
HJYP
816 RET(gtk_separator_menu_item_new());
817}
818
d8f0087d 819static void on_reload_menu(MenuCache* cache, gpointer menu_pointer)
727f696e 820{
d8f0087d 821 menup *m = menu_pointer;
d56c6111 822 /* g_debug("reload system menu!!"); */
3b6661f3 823 reload_system_menu( m, GTK_MENU(m->menu) );
727f696e
HJYP
824}
825
a52c2257 826static void
dd731fde 827read_system_menu(GtkMenu *menu, menup *m, config_setting_t *s)
a52c2257 828{
2918994e 829 if (m->menu_cache == NULL)
727f696e 830 {
9df6826f
HJYP
831 guint32 flags;
832 m->menu_cache = panel_menu_cache_new(&flags);
2918994e 833 if (m->menu_cache == NULL)
727f696e 834 {
06e29ce1 835 g_warning("error loading applications menu");
727f696e
HJYP
836 return;
837 }
9df6826f 838 m->visibility_flags = flags;
d8f0087d 839 m->reload_notify = menu_cache_add_reload_notify(m->menu_cache, on_reload_menu, m);
dd731fde 840 sys_menu_insert_items( m, menu, -1 );
727f696e
HJYP
841 }
842
727f696e 843 m->has_system_menu = TRUE;
a52c2257
HJYP
844}
845
dd731fde 846#if 0
a52c2257 847static void
22242ed4 848read_include(Plugin *p, char **fp)
a52c2257 849{
5a343ad5 850 ENTER;
a52c2257
HJYP
851 gchar *name;
852 line s;
853 menup *m = (menup *)p->priv;
db449f6e 854 /* FIXME: this is disabled */
a52c2257
HJYP
855 ENTER;
856 s.len = 256;
857 name = NULL;
db449f6e
HJYP
858 if( fp )
859 {
860 while (lxpanel_get_line(fp, &s) != LINE_BLOCK_END) {
861 if (s.type == LINE_VAR) {
862 if (!g_ascii_strcasecmp(s.t[0], "name"))
863 name = expand_tilda(s.t[1]);
864 else {
865 ERR( "menu/include: unknown var %s\n", s.t[0]);
866 RET();
867 }
a52c2257
HJYP
868 }
869 }
870 }
871 if ((fp = fopen(name, "r"))) {
872 LOG(LOG_INFO, "Including %s\n", name);
db449f6e 873 m->files = g_slist_prepend(m->files, fp);
a52c2257
HJYP
874 p->fp = fp;
875 } else {
876 ERR("Can't include %s\n", name);
877 }
878 if (name) g_free(name);
879 RET();
880}
dd731fde 881#endif
a52c2257
HJYP
882
883static GtkWidget *
dd731fde 884read_submenu(menup *m, config_setting_t *s, gboolean as_item)
a52c2257 885{
a52c2257 886 GtkWidget *mi, *menu;
dd731fde
AG
887 const gchar *name, *fname, *str;
888 config_setting_t *list = config_setting_add(s, "", PANEL_CONF_TYPE_LIST);
4f8fac68 889 GdkColor color={0, 0, 36 * 0xffff / 0xff, 96 * 0xffff / 0xff};
dd731fde 890 guint i;
a52c2257 891
a52c2257 892 ENTER;
f49fdaeb 893
a52c2257
HJYP
894 menu = gtk_menu_new ();
895 gtk_container_set_border_width(GTK_CONTAINER(menu), 0);
896
30cf831c 897 fname = NULL;
898 name = NULL;
dd731fde
AG
899 config_setting_lookup_string(s, "name", &name);
900 config_setting_lookup_string(s, "image", &fname);
901 if (config_setting_lookup_string(s, "tintcolor", &str))
902 gdk_color_parse(str, &color);
903
904 for (i = 0; (s = config_setting_get_elem(list, i)) != NULL; i++)
905 {
906 str = config_setting_get_name(s);
907 if (!g_ascii_strcasecmp(str, "item")) {
908 mi = read_item(m, s);
909 } else if (!g_ascii_strcasecmp(str, "separator")) {
910 mi = read_separator(m, s);
911 } else if (!g_ascii_strcasecmp(str, "system")) {
912 read_system_menu(GTK_MENU(menu), m, s); /* add system menu items */
913 continue;
914 } else if (!g_ascii_strcasecmp(str, "menu")) {
915 mi = read_submenu(m, s, TRUE);
916#if 0
917 } else if (!g_ascii_strcasecmp(str, "include")) {
918 read_include(p, fp);
919 continue;
920#endif
921 } else {
06e29ce1 922 g_warning("menu: unknown block %s", str);
dd731fde
AG
923 goto error;
924 }
925 if (!mi) {
06e29ce1 926 g_warning("menu: can't create menu item");
a52c2257
HJYP
927 goto error;
928 }
dd731fde
AG
929 gtk_widget_show(mi);
930 gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi);
a52c2257
HJYP
931 }
932 if (as_item) {
18ecfe2a 933 mi = gtk_image_menu_item_new_with_label(name);
a52c2257
HJYP
934 if (fname) {
935 GtkWidget *img;
dd731fde 936 char *expanded = expand_tilda(fname);
908d9fdc 937 /* FIXME: use FmIcon cache and fm_pixbuf_from_icon() API */
dd731fde 938 img = _gtk_image_new_from_file_scaled(expanded, m->iconsize, m->iconsize, TRUE);
a52c2257
HJYP
939 gtk_widget_show(img);
940 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(mi), img);
dd731fde 941 g_free(expanded);
a52c2257
HJYP
942 }
943 gtk_menu_item_set_submenu (GTK_MENU_ITEM (mi), menu);
a52c2257 944 } else {
dd731fde
AG
945 m->fname = fname ? expand_tilda(fname) : g_strdup(DEFAULT_MENU_ICON);
946 m->caption = g_strdup(name);
947 mi = make_button(m, m->fname, name, &color, menu);
a52c2257
HJYP
948 }
949
30cf831c 950 RET(mi);
951
dd731fde 952error:
a52c2257
HJYP
953 // FIXME: we need to recursivly destroy all child menus and their items
954 gtk_widget_destroy(menu);
a52c2257
HJYP
955 RET(NULL);
956}
957
dd731fde 958static GtkWidget *
a7bd16a4 959menu_constructor(LXPanel *panel, config_setting_t *settings)
a52c2257
HJYP
960{
961 menup *m;
dd731fde 962 config_setting_t *s;
87cbb654 963 int iw, ih;
a52c2257 964
a52c2257
HJYP
965 m = g_new0(menup, 1);
966 g_return_val_if_fail(m != NULL, 0);
a52c2257 967
87cbb654
HJYP
968 gtk_icon_size_lookup( GTK_ICON_SIZE_MENU, &iw, &ih );
969 m->iconsize = MAX(iw, ih);
a52c2257 970
2ec31e5c 971 m->box = gtk_vbox_new(TRUE, 0);
dd731fde 972 lxpanel_plugin_set_data(m->box, m, menu_destructor);
a52c2257 973 gtk_container_set_border_width(GTK_CONTAINER(m->box), 0);
a52c2257 974
dd731fde
AG
975 /* Save construction pointers */
976 m->panel = panel;
977 m->settings = settings;
da76d5cf 978
dd731fde 979 /* Check if configuration exists */
38219c31 980 settings = config_setting_add(settings, "", PANEL_CONF_TYPE_LIST);
dd731fde
AG
981 if (config_setting_get_elem(settings, 0) == NULL)
982 {
983 /* create default menu */
984 config_setting_add(settings, "system", PANEL_CONF_TYPE_GROUP);
985 config_setting_add(settings, "separator", PANEL_CONF_TYPE_GROUP);
986 s = config_setting_add(settings, "item", PANEL_CONF_TYPE_GROUP);
a8d4af54 987 config_group_set_string(s, "command", "run");
dd731fde
AG
988 config_setting_add(settings, "separator", PANEL_CONF_TYPE_GROUP);
989 s = config_setting_add(settings, "item", PANEL_CONF_TYPE_GROUP);
a8d4af54
AG
990 config_group_set_string(s, "command", "logout");
991 config_group_set_string(s, "image", "gnome-logout");
992 config_group_set_string(m->settings, "image", DEFAULT_MENU_ICON);
5297da29 993 }
4542c20d 994
dd731fde 995 if (!read_submenu(m, m->settings, FALSE)) {
06e29ce1 996 g_warning("menu: plugin init failed");
dd731fde
AG
997 gtk_widget_destroy(m->box);
998 return NULL;
999 }
a52c2257 1000
dd731fde 1001 return m->box;
a52c2257
HJYP
1002}
1003
dd731fde 1004static gboolean apply_config(gpointer user_data)
9c97f69e 1005{
dd731fde
AG
1006 GtkWidget *p = user_data;
1007 menup* m = lxpanel_plugin_get_data(p);
a52c2257 1008
dd731fde
AG
1009 if( m->fname ) {
1010 lxpanel_button_set_icon(m->img, m->fname, panel_get_icon_size(m->panel));
1011 }
a8d4af54
AG
1012 config_group_set_string(m->settings, "image", m->fname);
1013 config_group_set_string(m->settings, "name", m->caption);
dd731fde 1014 return FALSE;
f49fdaeb
FC
1015}
1016
752ee4e2 1017static GtkWidget *menu_config(LXPanel *panel, GtkWidget *p)
f49fdaeb 1018{
dd731fde 1019 menup* menu = lxpanel_plugin_get_data(p);
131514c9
AG
1020 return lxpanel_generic_config_dlg(_("Menu"), panel, apply_config, p,
1021 _("Icon"), &menu->fname, CONF_TYPE_FILE_ENTRY,
1022 /* _("Caption"), &menu->caption, CONF_TYPE_STR, */
1023 NULL);
f49fdaeb
FC
1024}
1025
8f9e6256 1026/* Callback when panel configuration changes. */
a7bd16a4 1027static void menu_panel_configuration_changed(LXPanel *panel, GtkWidget *p)
8f9e6256 1028{
1029 apply_config(p);
1030}
1031
dd731fde 1032LXPanelPluginInit lxpanel_static_plugin_menu = {
3c3e9c9e 1033 .name = N_("Menu"),
3c3e9c9e
HG
1034 .description = N_("Application Menu"),
1035
dd731fde 1036 .new_instance = menu_constructor,
3c3e9c9e 1037 .config = menu_config,
dd731fde
AG
1038 .reconfigure = menu_panel_configuration_changed,
1039 .show_system_menu = show_system_menu
a52c2257
HJYP
1040};
1041
b8d0aacd 1042/* vim: set sw=4 et sts=4 : */