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