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