Replace all other lxpanel_image_* calls with new ones to make code simpler.
[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 42#include "dbg.h"
1b0b42f7 43#include "gtk-compat.h"
a52c2257 44
bed635b7
AG
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
0b806437 54#define DEFAULT_MENU_ICON PACKAGE_DATA_DIR "/images/my-computer.png"
a52c2257
HJYP
55/*
56 * SuxPanel version 0.1
57 * Copyright (c) 2003 Leandro Pereira <leandro@linuxmag.com.br>
58 */
59
60/*
61 * menu style code was taken from suxpanel
62 */
63
a52c2257 64typedef struct {
e2957bd2 65 GtkWidget *menu, *box, *img, *label;
f49fdaeb 66 char *fname, *caption;
a52c2257 67 gulong handler_id;
dd731fde 68 int iconsize;
a52c2257 69 gboolean has_system_menu;
dd731fde 70 guint show_system_menu_idle;
a7bd16a4 71 LXPanel *panel;
dd731fde 72 config_setting_t *settings;
70684259
HJYP
73
74 MenuCache* menu_cache;
9df6826f 75 guint visibility_flags;
727f696e 76 gpointer reload_notify;
d0d4d6f3 77 FmDndSrc *ds;
a52c2257
HJYP
78} menup;
79
871c8f42
HJYP
80static guint idle_loader = 0;
81
70684259
HJYP
82GQuark SYS_MENU_ITEM_ID = 0;
83
dd731fde
AG
84/* FIXME: those are defined on panel main code */
85void restart(void);
86void gtk_run(void);
87void logout(void);
70684259 88
d0d4d6f3
AG
89static void on_data_get(FmDndSrc *ds, GtkWidget *mi)
90{
91 FmFileInfo *fi = g_object_get_qdata(G_OBJECT(mi), SYS_MENU_ITEM_ID);
92
13cf6185 93 /* g_debug("on_data_get(...)"); */
d0d4d6f3
AG
94 fm_dnd_src_set_file(ds, fi);
95}
96
a52c2257 97static void
dd731fde 98menu_destructor(gpointer user_data)
a52c2257 99{
dd731fde 100 menup *m = (menup *)user_data;
a52c2257 101
871c8f42
HJYP
102 if( G_UNLIKELY( idle_loader ) )
103 {
104 g_source_remove( idle_loader );
105 idle_loader = 0;
106 }
87cbb654 107
dd731fde
AG
108 if (m->show_system_menu_idle)
109 g_source_remove(m->show_system_menu_idle);
2918994e 110
0d2ee633 111 /* g_signal_handler_disconnect(G_OBJECT(m->img), m->handler_id); */
d0d4d6f3
AG
112 g_signal_handlers_disconnect_matched(m->ds, G_SIGNAL_MATCH_FUNC, 0, 0, NULL,
113 on_data_get, NULL);
114 g_object_unref(G_OBJECT(m->ds));
a52c2257 115 gtk_widget_destroy(m->menu);
70684259 116
727f696e
HJYP
117 if( m->menu_cache )
118 {
119 menu_cache_remove_reload_notify(m->menu_cache, m->reload_notify);
120 menu_cache_unref( m->menu_cache );
121 }
70684259 122
f49fdaeb
FC
123 g_free(m->fname);
124 g_free(m->caption);
a52c2257
HJYP
125 g_free(m);
126 RET();
127}
128
129static void
130spawn_app(GtkWidget *widget, gpointer data)
131{
a52c2257
HJYP
132 ENTER;
133 if (data) {
dd731fde 134 fm_launch_command_simple(NULL, NULL, 0, data, NULL);
a52c2257
HJYP
135 }
136 RET();
137}
138
139
140static void
141run_command(GtkWidget *widget, void (*cmd)(void))
142{
143 ENTER;
144 cmd();
145 RET();
146}
147
148static void
149menu_pos(GtkMenu *menu, gint *x, gint *y, gboolean *push_in, GtkWidget *widget)
150{
151 int ox, oy, w, h;
dd731fde 152 menup *m;
09fa171b
AG
153 GtkAllocation allocation;
154
155 gtk_widget_get_allocation(GTK_WIDGET(widget), &allocation);
a52c2257 156 ENTER;
dd731fde 157 m = g_object_get_data(G_OBJECT(widget), "plugin");
d5c46ffc 158 gdk_window_get_origin(gtk_widget_get_window(widget), &ox, &oy);
d5c46ffc 159#if GTK_CHECK_VERSION(2,20,0)
200121f9
JL
160 GtkRequisition requisition;
161 gtk_widget_get_requisition(GTK_WIDGET(menu), &requisition);
162 w = requisition.width;
163 h = requisition.height;
d5c46ffc
JL
164
165#else
a52c2257
HJYP
166 w = GTK_WIDGET(menu)->requisition.width;
167 h = GTK_WIDGET(menu)->requisition.height;
d5c46ffc 168#endif
dd731fde 169 if (panel_get_orientation(m->panel) == GTK_ORIENTATION_HORIZONTAL) {
a52c2257
HJYP
170 *x = ox;
171 if (*x + w > gdk_screen_width())
09fa171b 172 *x = ox + allocation.width - w;
a52c2257
HJYP
173 *y = oy - h;
174 if (*y < 0)
09fa171b 175 *y = oy + allocation.height;
a52c2257 176 } else {
09fa171b 177 *x = ox + allocation.width;
a52c2257
HJYP
178 if (*x > gdk_screen_width())
179 *x = ox - w;
180 *y = oy;
181 if (*y + h > gdk_screen_height())
09fa171b 182 *y = oy + allocation.height - h;
a52c2257
HJYP
183 }
184 DBG("widget: x,y=%d,%d w,h=%d,%d\n", ox, oy,
09fa171b 185 allocation.width, allocation.height );
a52c2257
HJYP
186 DBG("w-h %d %d\n", w, h);
187 *push_in = TRUE;
188 RET();
189}
190
dd731fde 191static void on_menu_item( GtkMenuItem* mi, menup* m )
70684259 192{
dd731fde
AG
193 FmFileInfo *fi = g_object_get_qdata(G_OBJECT(mi), SYS_MENU_ITEM_ID);
194
195 lxpanel_launch_path(m->panel, fm_file_info_get_path(fi));
727f696e
HJYP
196}
197
7df0f080 198/* load icon when mapping the menu item to speed up */
dd731fde 199static void on_menu_item_map(GtkWidget *mi, menup *m)
727f696e 200{
cdf8468b 201 GtkImage* img = GTK_IMAGE(gtk_image_menu_item_get_image(GTK_IMAGE_MENU_ITEM(mi)));
727f696e
HJYP
202 if( img )
203 {
dd731fde
AG
204 FmFileInfo *fi;
205 if (gtk_image_get_storage_type(img) == GTK_IMAGE_EMPTY &&
206 (fi = g_object_get_qdata(G_OBJECT(mi), SYS_MENU_ITEM_ID)) != NULL &&
207 fi != (gpointer)1 /* ignore placeholder or separator */)
727f696e 208 {
dd731fde
AG
209 FmIcon *fm_icon = fm_file_info_get_icon(fi);
210 FmIcon *_fm_icon = NULL;
211 GdkPixbuf *icon = NULL;
212
213 if (fm_icon == NULL)
214 fm_icon = _fm_icon = fm_icon_from_name("application-x-executable");
215 icon = fm_pixbuf_from_icon_with_fallback(fm_icon, m->iconsize,
216 "application-x-executable");
217 if (_fm_icon)
218 g_object_unref(_fm_icon);
e7a42ecf 219 if (icon)
65a36853 220 {
e7a42ecf 221 gtk_image_set_from_pixbuf(img, icon);
222 g_object_unref(icon);
65a36853 223 }
727f696e
HJYP
224 }
225 }
7df0f080
HJYP
226}
227
dd731fde 228static void on_menu_item_style_set(GtkWidget* mi, GtkStyle* prev, menup *m)
7df0f080
HJYP
229{
230 /* reload icon */
dd731fde 231 on_menu_item_map(mi, m);
727f696e
HJYP
232}
233
dd731fde
AG
234/* FIXME: this is very dirty, especially if desktop is set not to ~/Desktop
235 therefore it should be removed and drag & drop gestures used instead */
236static void on_add_menu_item_to_desktop(GtkMenuItem* item, GtkWidget* mi)
f1286efa 237{
dd731fde 238 FmFileInfo *fi = g_object_get_qdata(G_OBJECT(mi), SYS_MENU_ITEM_ID);
72e2e990 239 FmPathList *files = fm_path_list_new();
f1286efa 240
72e2e990
AG
241 fm_path_list_push_tail(files, fm_file_info_get_path(fi));
242 fm_link_files(NULL, files, fm_path_get_desktop());
243 fm_path_list_unref(files);
f1286efa
HJYP
244}
245
4f4f4ecb 246/* TODO: add menu item to panel */
18ecfe2a 247#if 0
f1286efa
HJYP
248static void on_add_menu_item_to_panel(GtkMenuItem* item, MenuCacheApp* app)
249{
250 /* Find a penel containing launchbar applet.
251 * The launchbar with most buttons will be choosen if
252 * there are several launchbar applets loaded.
253 */
254 GSList* l;
255 Plugin* lb = NULL;
256 int n_btns = -1;
257
258 for(l = all_panels; !lb && l; l = l->next)
259 {
260 Panel* panel = (Panel*)l->data;
261 GList* pl;
262 for(pl=panel->plugins; pl; pl = pl->next)
263 {
264 Plugin* plugin = (Plugin*)pl;
265 if( strcmp(plugin->class->type, "launchbar") == 0 )
266 {
267 /* FIXME: should we let the users choose which launcherbar to add the btn? */
268 break;
269#if 0
270 int n = launchbar_get_n_btns(plugin);
271 if( n > n_btns )
272 {
273 lb = plugin;
274 n_btns = n;
275 }
276#endif
277 }
278 }
279 }
280
281 if( ! lb ) /* launchbar is not currently in use */
282 {
283 /* FIXME: add a launchbar plugin to the panel which has a menu, too. */
284 }
285
286 if( lb )
287 {
288
289 }
290}
18ecfe2a 291#endif
f1286efa 292
dd731fde 293static void on_menu_item_properties(GtkMenuItem* item, GtkWidget* mi)
f1286efa 294{
dd731fde
AG
295 FmFileInfo *fi = g_object_get_qdata(G_OBJECT(mi), SYS_MENU_ITEM_ID);
296 FmFileInfoList *files = fm_file_info_list_new();
297
298 fm_file_info_list_push_tail(files, fi);
299 fm_show_file_properties(NULL, files);
300 fm_file_info_list_unref(files);
f1286efa
HJYP
301}
302
4f47af9e 303#if 0
f1286efa
HJYP
304/* This following function restore_grabs is taken from menu.c of
305 * gnome-panel.
306 */
307/*most of this function stolen from the real gtk_menu_popup*/
308static void restore_grabs(GtkWidget *w, gpointer data)
727f696e 309{
f1286efa 310 GtkWidget *menu_item = data;
d5c46ffc 311 GtkMenu *menu = GTK_MENU(gtk_widget_get_parent(menu_item));
f1286efa
HJYP
312 GtkWidget *xgrab_shell;
313 GtkWidget *parent;
314
315 /* Find the last viewable ancestor, and make an X grab on it
316 */
317 parent = GTK_WIDGET (menu);
318 xgrab_shell = NULL;
319 while (parent)
320 {
321 gboolean viewable = TRUE;
322 GtkWidget *tmp = parent;
323
324 while (tmp)
325 {
09fa171b 326 if (!gtk_widget_get_mapped(tmp))
f1286efa
HJYP
327 {
328 viewable = FALSE;
329 break;
330 }
d5c46ffc 331 tmp = gtk_widget_get_parent(tmp);
f1286efa
HJYP
332 }
333
334 if (viewable)
335 xgrab_shell = parent;
336
d5c46ffc 337 parent = gtk_widget_get_parent(parent);
f1286efa
HJYP
338 }
339
340 /*only grab if this HAD a grab before*/
d5c46ffc 341 if (xgrab_shell && (gtk_widget_has_focus(xgrab_shell)))
d5c46ffc 342 {
d5c46ffc 343 if (gdk_pointer_grab (gtk_widget_get_window(xgrab_shell), TRUE,
f1286efa
HJYP
344 GDK_BUTTON_PRESS_MASK |
345 GDK_BUTTON_RELEASE_MASK |
346 GDK_ENTER_NOTIFY_MASK |
347 GDK_LEAVE_NOTIFY_MASK,
348 NULL, NULL, 0) == 0)
349 {
d5c46ffc 350 if (gdk_keyboard_grab (gtk_widget_get_window(xgrab_shell), TRUE,
f1286efa 351 GDK_CURRENT_TIME) == 0)
d5c46ffc 352 gtk_widget_grab_focus (xgrab_shell);
f1286efa
HJYP
353 else
354 gdk_pointer_ungrab (GDK_CURRENT_TIME);
355 }
356 }
357 gtk_grab_add (GTK_WIDGET (menu));
358}
4f47af9e
AG
359#endif
360
361static void restore_submenu(GtkMenuItem *mi, GtkWidget *submenu)
362{
363 g_signal_handlers_disconnect_by_func(mi, restore_submenu, submenu);
364 gtk_menu_item_set_submenu(mi, submenu);
365 g_object_set_data(G_OBJECT(mi), "PanelMenuItemSubmenu", NULL);
366}
f1286efa 367
dd731fde 368static gboolean on_menu_button_press(GtkWidget* mi, GdkEventButton* evt, menup* m)
f1286efa
HJYP
369{
370 if( evt->button == 3 ) /* right */
371 {
87cbb654 372 GtkWidget* item;
4f47af9e
AG
373 GtkMenu* p;
374
375 /* don't make duplicates */
376 if (g_signal_handler_find(mi, G_SIGNAL_MATCH_FUNC, 0, 0, NULL,
377 restore_submenu, NULL))
378 {
379 return FALSE;
380 }
381
382 p = GTK_MENU(gtk_menu_new());
e7a42ecf 383
87cbb654 384 item = gtk_menu_item_new_with_label(_("Add to desktop"));
dd731fde 385 g_signal_connect(item, "activate", G_CALLBACK(on_add_menu_item_to_desktop), mi);
cdf8468b 386 gtk_menu_shell_append(GTK_MENU_SHELL(p), item);
f1286efa 387
dd731fde
AG
388 item = gtk_separator_menu_item_new();
389 gtk_menu_shell_append(GTK_MENU_SHELL(p), item);
390
391 item = gtk_menu_item_new_with_label(_("Properties"));
392 g_signal_connect(item, "activate", G_CALLBACK(on_menu_item_properties), mi);
393 gtk_menu_shell_append(GTK_MENU_SHELL(p), item);
f1286efa 394
4f47af9e
AG
395 item = gtk_menu_item_get_submenu(GTK_MENU_ITEM(mi)); /* reuse it */
396 if (item)
397 {
398 /* set object data to keep reference on the submenu we preserve */
399 g_object_set_data_full(G_OBJECT(mi), "PanelMenuItemSubmenu",
400 g_object_ref(item), g_object_unref);
401 gtk_menu_popdown(GTK_MENU(item));
402 }
403 gtk_menu_item_set_submenu(GTK_MENU_ITEM(mi), GTK_WIDGET(p));
404 g_signal_connect(mi, "deselect", G_CALLBACK(restore_submenu), item);
cdf8468b 405 gtk_widget_show_all(GTK_WIDGET(p));
727f696e 406 }
d0d4d6f3
AG
407 else if (evt->button == 1) /* allow drag on clicked item */
408 {
409 /* disconnect previous menu item */
410 g_signal_handlers_disconnect_matched(m->ds, G_SIGNAL_MATCH_FUNC, 0, 0,
411 NULL, on_data_get, NULL);
412 /* remap FmDndSrc onto current item */
413 fm_dnd_src_set_widget(m->ds, mi);
414 g_signal_connect(m->ds, "data-get", G_CALLBACK(on_data_get), mi);
415 }
727f696e 416 return FALSE;
70684259
HJYP
417}
418
dd731fde 419static GtkWidget* create_item(MenuCacheItem *item, menup *m)
70684259 420{
727f696e
HJYP
421 GtkWidget* mi;
422 if( menu_cache_item_get_type(item) == MENU_CACHE_TYPE_SEP )
dd731fde 423 {
727f696e 424 mi = gtk_separator_menu_item_new();
dd731fde
AG
425 g_object_set_qdata(G_OBJECT(mi), SYS_MENU_ITEM_ID, (gpointer)1);
426 }
727f696e
HJYP
427 else
428 {
429 GtkWidget* img;
dd731fde
AG
430 /* create FmFileInfo for the item, it will be used in callbacks */
431 char *mpath = menu_cache_dir_make_path(MENU_CACHE_DIR(item));
432 FmPath *path = fm_path_new_relative(fm_path_get_apps_menu(), mpath+13);
433 /* skip "/Applications" */
434 FmFileInfo *fi = fm_file_info_new_from_menu_cache_item(path, item);
435
436 g_free(mpath);
437 fm_path_unref(path);
3e84388d 438 mi = gtk_image_menu_item_new_with_mnemonic( menu_cache_item_get_name(item) );
dd731fde
AG
439 g_object_set_qdata_full(G_OBJECT(mi), SYS_MENU_ITEM_ID, fi,
440 (GDestroyNotify)fm_file_info_unref);
727f696e 441 img = gtk_image_new();
cdf8468b 442 gtk_image_menu_item_set_image( GTK_IMAGE_MENU_ITEM(mi), img );
727f696e
HJYP
443 if( menu_cache_item_get_type(item) == MENU_CACHE_TYPE_APP )
444 {
1b275785
AG
445 const char *comment = menu_cache_item_get_comment(item);
446 if (comment != NULL)
447 gtk_widget_set_tooltip_text(mi, comment);
dd731fde 448 g_signal_connect(mi, "activate", G_CALLBACK(on_menu_item), m);
727f696e 449 }
dd731fde
AG
450 g_signal_connect(mi, "map", G_CALLBACK(on_menu_item_map), m);
451 g_signal_connect(mi, "style-set", G_CALLBACK(on_menu_item_style_set), m);
452 g_signal_connect(mi, "button-press-event", G_CALLBACK(on_menu_button_press), m);
d0d4d6f3
AG
453 /* allow drag and add empty set for now to allow dragging the item
454 the rest will be done by FmDndSrc after drag begins */
455 gtk_drag_source_set(mi, GDK_BUTTON1_MASK, NULL, 0, GDK_ACTION_COPY);
727f696e
HJYP
456 }
457 gtk_widget_show( mi );
727f696e 458 return mi;
70684259
HJYP
459}
460
6a4d4973 461static int load_menu(menup* m, MenuCacheDir* dir, GtkWidget* menu, int pos )
70684259 462{
2918994e 463 GSList * l;
6a4d4973 464 /* number of visible entries */
0bcf9045 465 gint count = 0;
bed635b7 466#if MENU_CACHE_CHECK_VERSION(0, 4, 0)
8d5685f6 467 GSList *children;
880d25d2
AG
468# if MENU_CACHE_CHECK_VERSION(0, 5, 0)
469# if !MENU_CACHE_CHECK_VERSION(1, 0, 0)
470 char *kfpath;
471 GKeyFile *kf;
472# endif
473 if (!menu_cache_dir_is_visible(dir)) /* directory is hidden, ignore children */
474 return 0;
475# if !MENU_CACHE_CHECK_VERSION(1, 0, 0)
476 /* version 1.0.0 has NoDisplay checked internally */
477 kfpath = menu_cache_item_get_file_path(MENU_CACHE_ITEM(dir));
478 kf = g_key_file_new();
8d5685f6
AG
479 /* for version 0.5.0 we enable hidden so should test NoDisplay flag */
480 if (kfpath && g_key_file_load_from_file(kf, kfpath, 0, NULL) &&
481 g_key_file_get_boolean(kf, "Desktop Entry", "NoDisplay", NULL))
482 count = -1;
483 g_free(kfpath);
484 g_key_file_free(kf);
485 if (count < 0) /* directory is hidden, ignore children */
486 return 0;
880d25d2
AG
487# endif /* < 1.0.0 */
488# endif /* < 0.5.0 */
8d5685f6 489 children = menu_cache_dir_list_children(dir);
bed635b7 490 for (l = children; l; l = l->next)
880d25d2 491#else /* < 0.4.0 */
727f696e 492 for( l = menu_cache_dir_get_children(dir); l; l = l->next )
bed635b7 493#endif
727f696e
HJYP
494 {
495 MenuCacheItem* item = MENU_CACHE_ITEM(l->data);
0bcf9045
AG
496
497 gboolean is_visible = ((menu_cache_item_get_type(item) != MENU_CACHE_TYPE_APP) ||
b6c8855c 498 (panel_menu_item_evaluate_visibility(item, m->visibility_flags)));
0bcf9045
AG
499
500 if (is_visible)
b6c8855c 501 {
dd731fde 502 GtkWidget * mi = create_item(item, m);
6a4d4973 503 count++;
2918994e 504 if (mi != NULL)
2918994e 505 gtk_menu_shell_insert( (GtkMenuShell*)menu, mi, pos );
506 if( pos >= 0 )
507 ++pos;
b6c8855c 508 /* process subentries */
0bcf9045 509 if (menu_cache_item_get_type(item) == MENU_CACHE_TYPE_DIR)
b6c8855c 510 {
2918994e 511 GtkWidget* sub = gtk_menu_new();
6a4d4973 512 /* always pass -1 for position */
0bcf9045
AG
513 gint s_count = load_menu( m, MENU_CACHE_DIR(item), sub, -1 );
514 if (s_count)
515 gtk_menu_item_set_submenu( GTK_MENU_ITEM(mi), sub );
516 else
6a4d4973
JH
517 {
518 /* don't keep empty submenus */
519 gtk_widget_destroy( sub );
520 gtk_widget_destroy( mi );
521 if (pos > 0)
522 pos--;
523 }
b6c8855c
JH
524 }
525 }
727f696e 526 }
bed635b7
AG
527#if MENU_CACHE_CHECK_VERSION(0, 4, 0)
528 g_slist_foreach(children, (GFunc)menu_cache_item_unref, NULL);
529 g_slist_free(children);
530#endif
6a4d4973 531 return count;
70684259
HJYP
532}
533
534
b6c8855c 535
70684259
HJYP
536static gboolean sys_menu_item_has_data( GtkMenuItem* item )
537{
727f696e 538 return (g_object_get_qdata( G_OBJECT(item), SYS_MENU_ITEM_ID ) != NULL);
70684259
HJYP
539}
540
dd731fde 541static void _unload_old_icons(GtkMenu* menu, GtkIconTheme* theme, menup* m)
3167c7b1
HJYP
542{
543 GList *children, *child;
544 GtkMenuItem* item;
3b6661f3 545 GtkWidget* sub_menu=NULL;
d56c6111 546
3167c7b1
HJYP
547 children = gtk_container_get_children( GTK_CONTAINER(menu) );
548 for( child = children; child; child = child->next )
549 {
550 item = GTK_MENU_ITEM( child->data );
551 if( sys_menu_item_has_data( item ) )
552 {
553 GtkImage* img;
554 item = GTK_MENU_ITEM( child->data );
555 if( GTK_IS_IMAGE_MENU_ITEM(item) )
556 {
cdf8468b 557 img = GTK_IMAGE(gtk_image_menu_item_get_image(GTK_IMAGE_MENU_ITEM(item)));
3167c7b1 558 gtk_image_clear(img);
09fa171b 559 if (gtk_widget_get_mapped(GTK_WIDGET(img)))
dd731fde 560 on_menu_item_map(GTK_WIDGET(item), m);
3167c7b1
HJYP
561 }
562 }
563 else if( ( sub_menu = gtk_menu_item_get_submenu( item ) ) )
564 {
dd731fde 565 _unload_old_icons(GTK_MENU(sub_menu), theme, m);
3167c7b1
HJYP
566 }
567 }
568 g_list_free( children );
569}
570
dd731fde
AG
571static void unload_old_icons(GtkIconTheme* theme, menup* m)
572{
573 _unload_old_icons(GTK_MENU(m->menu), theme, m);
574}
575
b8ca9e81 576static void remove_change_handler(gpointer id, GObject* menu)
3167c7b1 577{
b8ca9e81 578 g_signal_handler_disconnect(gtk_icon_theme_get_default(), GPOINTER_TO_INT(id));
3167c7b1
HJYP
579}
580
70684259
HJYP
581/*
582 * Insert application menus into specified menu
583 * menu: The parent menu to which the items should be inserted
584 * pisition: Position to insert items.
585 Passing -1 in this parameter means append all items
586 at the end of menu.
587 */
588static void sys_menu_insert_items( menup* m, GtkMenu* menu, int position )
589{
727f696e 590 MenuCacheDir* dir;
3167c7b1
HJYP
591 guint change_handler;
592
727f696e
HJYP
593 if( G_UNLIKELY( SYS_MENU_ITEM_ID == 0 ) )
594 SYS_MENU_ITEM_ID = g_quark_from_static_string( "SysMenuItem" );
70684259 595
bed635b7
AG
596#if MENU_CACHE_CHECK_VERSION(0, 4, 0)
597 dir = menu_cache_dup_root_dir(m->menu_cache);
598#else
727f696e 599 dir = menu_cache_get_root_dir( m->menu_cache );
bed635b7 600#endif
80f3a04f 601 if(dir)
a5bdced1 602 {
80f3a04f 603 load_menu( m, dir, GTK_WIDGET(menu), position );
a5bdced1
AG
604#if MENU_CACHE_CHECK_VERSION(0, 4, 0)
605 menu_cache_item_unref(MENU_CACHE_ITEM(dir));
606#endif
607 }
80f3a04f
HJYP
608 else /* menu content is empty */
609 {
610 /* add a place holder */
611 GtkWidget* mi = gtk_menu_item_new();
612 g_object_set_qdata( G_OBJECT(mi), SYS_MENU_ITEM_ID, GINT_TO_POINTER(1) );
fcb35553 613 gtk_menu_shell_insert(GTK_MENU_SHELL(menu), mi, position);
80f3a04f 614 }
70684259 615
dd731fde 616 change_handler = g_signal_connect(gtk_icon_theme_get_default(), "changed", G_CALLBACK(unload_old_icons), m);
b8276d47 617 g_object_weak_ref( G_OBJECT(menu), remove_change_handler, GINT_TO_POINTER(change_handler) );
70684259
HJYP
618}
619
620
a52c2257 621static void
70684259 622reload_system_menu( menup* m, GtkMenu* menu )
a52c2257
HJYP
623{
624 GList *children, *child;
625 GtkMenuItem* item;
626 GtkWidget* sub_menu;
627 gint idx;
d56c6111 628
a52c2257 629 children = gtk_container_get_children( GTK_CONTAINER(menu) );
70684259 630 for( child = children, idx = 0; child; child = child->next, ++idx )
727f696e 631 {
a52c2257 632 item = GTK_MENU_ITEM( child->data );
70684259 633 if( sys_menu_item_has_data( item ) )
727f696e 634 {
70684259 635 do
727f696e 636 {
a52c2257
HJYP
637 item = GTK_MENU_ITEM( child->data );
638 child = child->next;
639 gtk_widget_destroy( GTK_WIDGET(item) );
70684259
HJYP
640 }while( child && sys_menu_item_has_data( child->data ) );
641 sys_menu_insert_items( m, menu, idx );
a52c2257
HJYP
642 if( ! child )
643 break;
644 }
70684259 645 else if( ( sub_menu = gtk_menu_item_get_submenu( item ) ) )
727f696e 646 {
70684259 647 reload_system_menu( m, GTK_MENU(sub_menu) );
a52c2257
HJYP
648 }
649 }
650 g_list_free( children );
651}
652
dd731fde 653static void show_menu( GtkWidget* widget, menup* m, int btn, guint32 time )
e996608e 654{
c6780e74 655 gtk_menu_popup(GTK_MENU(m->menu),
e996608e
HJYP
656 NULL, NULL,
657 (GtkMenuPositionFunc)menu_pos, widget,
658 btn, time);
659}
660
a52c2257 661static gboolean
dd731fde 662my_button_pressed(GtkWidget *widget, GdkEventButton *event, menup *m)
a52c2257
HJYP
663{
664 ENTER;
09fa171b
AG
665 GtkAllocation allocation;
666 gtk_widget_get_allocation(GTK_WIDGET(widget), &allocation);
7414a73f 667
ae212dd2 668 if ((event->type == GDK_BUTTON_PRESS) && event->button == 1
09fa171b
AG
669 && (event->x >=0 && event->x < allocation.width)
670 && (event->y >=0 && event->y < allocation.height)) {
dd731fde 671 show_menu( widget, m, event->button, event->time );
ae212dd2 672 RET(TRUE);
a52c2257 673 }
ae212dd2 674 RET(FALSE);
a52c2257
HJYP
675}
676
dd731fde 677static gboolean show_system_menu_idle(gpointer user_data)
e996608e 678{
dd731fde
AG
679 menup* m = (menup*)user_data;
680 if (g_source_is_destroyed(g_main_current_source()))
681 return FALSE;
682 show_menu( m->img, m, 0, GDK_CURRENT_TIME );
683 m->show_system_menu_idle = 0;
8c44345a 684 return FALSE;
e996608e 685}
a52c2257 686
dd731fde
AG
687static void show_system_menu(GtkWidget *p)
688{
689 menup *m = lxpanel_plugin_get_data(p);
690
691 if (m->has_system_menu && m->show_system_menu_idle == 0)
692 /* FIXME: I've no idea why this doesn't work without timeout
693 under some WMs, like icewm. */
694 m->show_system_menu_idle = g_timeout_add(200, show_system_menu_idle, m);
695}
696
a52c2257 697static GtkWidget *
dd731fde 698make_button(menup *m, const gchar *fname, const gchar *name, GdkColor* tint, GtkWidget *menu)
a52c2257 699{
017e5ad5 700 char* title = NULL;
a52c2257
HJYP
701
702 ENTER;
a52c2257 703 m->menu = menu;
017e5ad5
HJYP
704
705 if( name )
706 {
707 /* load the name from *.directory file if needed */
708 if( g_str_has_suffix( name, ".directory" ) )
709 {
710 GKeyFile* kf = g_key_file_new();
99683934 711 char* dir_file = g_build_filename( "desktop-directories", name, NULL );
017e5ad5
HJYP
712 if( g_key_file_load_from_data_dirs( kf, dir_file, NULL, 0, NULL ) )
713 {
714 title = g_key_file_get_locale_string( kf, "Desktop Entry", "Name", NULL, NULL );
715 }
716 g_free( dir_file );
717 g_key_file_free( kf );
718 }
017e5ad5 719
dd731fde 720 m->img = lxpanel_button_new_for_icon(m->panel, fname, tint, title ? title : name);
017e5ad5 721
dd731fde 722 g_free( title );
017e5ad5
HJYP
723 }
724 else
725 {
dd731fde 726 m->img = lxpanel_button_new_for_icon(m->panel, fname, tint, NULL);
017e5ad5 727 }
f49fdaeb 728
e2957bd2 729 gtk_widget_show(m->img);
2ec31e5c 730 gtk_box_pack_start(GTK_BOX(m->box), m->img, TRUE, FALSE, 0);
a52c2257 731
e2957bd2 732 m->handler_id = g_signal_connect (G_OBJECT (m->img), "button-press-event",
dd731fde
AG
733 G_CALLBACK (my_button_pressed), m);
734 g_object_set_data(G_OBJECT(m->img), "plugin", m);
a52c2257 735
d0d4d6f3
AG
736 m->ds = fm_dnd_src_new(NULL);
737
e2957bd2 738 RET(m->img);
a52c2257
HJYP
739}
740
dd731fde
AG
741/* those were in configurator.c initially but it's safer to have those here */
742typedef struct {
743 char *name;
744 char *disp_name;
745 void (*cmd)(void);
746} Command;
747
748static Command commands[] = {
749 //{ "configure", N_("Preferences"), configure },
750 { "run", N_("Run"), gtk_run },
751 { "restart", N_("Restart"), restart },
752 { "logout", N_("Logout"), logout },
753 { NULL, NULL },
754};
a52c2257
HJYP
755
756static GtkWidget *
dd731fde 757read_item(menup *m, config_setting_t *s)
a52c2257 758{
dd731fde 759 const gchar *name, *fname, *action, *str;
a52c2257 760 GtkWidget *item;
22242ed4 761 Command *cmd_entry = NULL;
dd731fde 762 char *tmp;
a52c2257
HJYP
763
764 ENTER;
08ea5341
HJYP
765 name = fname = action = NULL;
766
dd731fde
AG
767 config_setting_lookup_string(s, "name", &name);
768 config_setting_lookup_string(s, "image", &fname);
769 config_setting_lookup_string(s, "action", &action);
770 if (config_setting_lookup_string(s, "command", &str))
db449f6e 771 {
dd731fde
AG
772 Command *tmp;
773
774 for (tmp = commands; tmp->name; tmp++) {
775 if (!g_ascii_strcasecmp(str, tmp->name)) {
776 cmd_entry = tmp;
777 break;
a52c2257
HJYP
778 }
779 }
780 }
781 /* menu button */
08ea5341
HJYP
782 if( cmd_entry ) /* built-in commands */
783 {
784 item = gtk_image_menu_item_new_with_label( _(cmd_entry->disp_name) );
785 g_signal_connect(G_OBJECT(item), "activate", (GCallback)run_command, cmd_entry->cmd);
786 }
dd731fde 787 else if (action)
08ea5341
HJYP
788 {
789 item = gtk_image_menu_item_new_with_label(name ? name : "");
dd731fde
AG
790 tmp = g_strdup(action);
791 g_object_weak_ref(G_OBJECT(item), (GWeakNotify)g_free, tmp);
792 g_signal_connect(G_OBJECT(item), "activate", (GCallback)spawn_app, tmp);
08ea5341 793 }
dd731fde
AG
794 else
795 goto error;
a52c2257 796 gtk_container_set_border_width(GTK_CONTAINER(item), 0);
a52c2257
HJYP
797 if (fname) {
798 GtkWidget *img;
799
dd731fde 800 tmp = expand_tilda(fname);
8ec08843 801 img = lxpanel_image_new_for_icon(m->panel, tmp, m->iconsize, NULL);
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);
8ec08843 937 img = lxpanel_image_new_for_icon(m->panel, expanded, m->iconsize, NULL);
a52c2257
HJYP
938 gtk_widget_show(img);
939 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(mi), img);
dd731fde 940 g_free(expanded);
a52c2257
HJYP
941 }
942 gtk_menu_item_set_submenu (GTK_MENU_ITEM (mi), menu);
a52c2257 943 } else {
dd731fde
AG
944 m->fname = fname ? expand_tilda(fname) : g_strdup(DEFAULT_MENU_ICON);
945 m->caption = g_strdup(name);
946 mi = make_button(m, m->fname, name, &color, menu);
a52c2257
HJYP
947 }
948
30cf831c 949 RET(mi);
950
dd731fde 951error:
a52c2257
HJYP
952 // FIXME: we need to recursivly destroy all child menus and their items
953 gtk_widget_destroy(menu);
a52c2257
HJYP
954 RET(NULL);
955}
956
dd731fde 957static GtkWidget *
a7bd16a4 958menu_constructor(LXPanel *panel, config_setting_t *settings)
a52c2257
HJYP
959{
960 menup *m;
dd731fde 961 config_setting_t *s;
87cbb654 962 int iw, ih;
a52c2257 963
a52c2257
HJYP
964 m = g_new0(menup, 1);
965 g_return_val_if_fail(m != NULL, 0);
a52c2257 966
87cbb654
HJYP
967 gtk_icon_size_lookup( GTK_ICON_SIZE_MENU, &iw, &ih );
968 m->iconsize = MAX(iw, ih);
a52c2257 969
2ec31e5c 970 m->box = gtk_vbox_new(TRUE, 0);
dd731fde 971 lxpanel_plugin_set_data(m->box, m, menu_destructor);
a52c2257 972 gtk_container_set_border_width(GTK_CONTAINER(m->box), 0);
a52c2257 973
dd731fde
AG
974 /* Save construction pointers */
975 m->panel = panel;
976 m->settings = settings;
da76d5cf 977
dd731fde 978 /* Check if configuration exists */
38219c31 979 settings = config_setting_add(settings, "", PANEL_CONF_TYPE_LIST);
dd731fde
AG
980 if (config_setting_get_elem(settings, 0) == NULL)
981 {
982 /* create default menu */
983 config_setting_add(settings, "system", PANEL_CONF_TYPE_GROUP);
984 config_setting_add(settings, "separator", PANEL_CONF_TYPE_GROUP);
985 s = config_setting_add(settings, "item", PANEL_CONF_TYPE_GROUP);
a8d4af54 986 config_group_set_string(s, "command", "run");
dd731fde
AG
987 config_setting_add(settings, "separator", PANEL_CONF_TYPE_GROUP);
988 s = config_setting_add(settings, "item", PANEL_CONF_TYPE_GROUP);
a8d4af54
AG
989 config_group_set_string(s, "command", "logout");
990 config_group_set_string(s, "image", "gnome-logout");
991 config_group_set_string(m->settings, "image", DEFAULT_MENU_ICON);
5297da29 992 }
4542c20d 993
dd731fde 994 if (!read_submenu(m, m->settings, FALSE)) {
06e29ce1 995 g_warning("menu: plugin init failed");
dd731fde
AG
996 gtk_widget_destroy(m->box);
997 return NULL;
998 }
a52c2257 999
6044fbfe 1000 /* FIXME: allow bind a global key to toggle menu using libkeybinder */
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 : */