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