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