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