Merging upstream version 0.7.0 (Closes: #493243, #510888, #567617, #699414, #709777...
[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 gtk_widget_set_tooltip_text( mi, menu_cache_item_get_comment(item) );
452 g_signal_connect(mi, "activate", G_CALLBACK(on_menu_item), m);
453 }
454 g_signal_connect(mi, "map", G_CALLBACK(on_menu_item_map), m);
455 g_signal_connect(mi, "style-set", G_CALLBACK(on_menu_item_style_set), m);
456 g_signal_connect(mi, "button-press-event", G_CALLBACK(on_menu_button_press), m);
457 /* allow drag and add empty set for now to allow dragging the item
458 the rest will be done by FmDndSrc after drag begins */
459 gtk_drag_source_set(mi, GDK_BUTTON1_MASK, NULL, 0, GDK_ACTION_COPY);
460 }
461 gtk_widget_show( mi );
462 return mi;
463 }
464
465 static int load_menu(menup* m, MenuCacheDir* dir, GtkWidget* menu, int pos )
466 {
467 GSList * l;
468 /* number of visible entries */
469 gint count = 0;
470 #if MENU_CACHE_CHECK_VERSION(0, 4, 0)
471 GSList *children;
472 #if MENU_CACHE_CHECK_VERSION(0, 5, 0)
473 char *kfpath = menu_cache_item_get_file_path(MENU_CACHE_ITEM(dir));
474 GKeyFile *kf = g_key_file_new();
475 /* for version 0.5.0 we enable hidden so should test NoDisplay flag */
476 if (kfpath && g_key_file_load_from_file(kf, kfpath, 0, NULL) &&
477 g_key_file_get_boolean(kf, "Desktop Entry", "NoDisplay", NULL))
478 count = -1;
479 g_free(kfpath);
480 g_key_file_free(kf);
481 if (count < 0) /* directory is hidden, ignore children */
482 return 0;
483 #endif
484 children = menu_cache_dir_list_children(dir);
485 for (l = children; l; l = l->next)
486 #else
487 for( l = menu_cache_dir_get_children(dir); l; l = l->next )
488 #endif
489 {
490 MenuCacheItem* item = MENU_CACHE_ITEM(l->data);
491
492 gboolean is_visible = ((menu_cache_item_get_type(item) != MENU_CACHE_TYPE_APP) ||
493 (panel_menu_item_evaluate_visibility(item, m->visibility_flags)));
494
495 if (is_visible)
496 {
497 GtkWidget * mi = create_item(item, m);
498 count++;
499 if (mi != NULL)
500 gtk_menu_shell_insert( (GtkMenuShell*)menu, mi, pos );
501 if( pos >= 0 )
502 ++pos;
503 /* process subentries */
504 if (menu_cache_item_get_type(item) == MENU_CACHE_TYPE_DIR)
505 {
506 GtkWidget* sub = gtk_menu_new();
507 /* always pass -1 for position */
508 gint s_count = load_menu( m, MENU_CACHE_DIR(item), sub, -1 );
509 if (s_count)
510 gtk_menu_item_set_submenu( GTK_MENU_ITEM(mi), sub );
511 else
512 {
513 /* don't keep empty submenus */
514 gtk_widget_destroy( sub );
515 gtk_widget_destroy( mi );
516 if (pos > 0)
517 pos--;
518 }
519 }
520 }
521 }
522 #if MENU_CACHE_CHECK_VERSION(0, 4, 0)
523 g_slist_foreach(children, (GFunc)menu_cache_item_unref, NULL);
524 g_slist_free(children);
525 #endif
526 return count;
527 }
528
529
530
531 static gboolean sys_menu_item_has_data( GtkMenuItem* item )
532 {
533 return (g_object_get_qdata( G_OBJECT(item), SYS_MENU_ITEM_ID ) != NULL);
534 }
535
536 static void _unload_old_icons(GtkMenu* menu, GtkIconTheme* theme, menup* m)
537 {
538 GList *children, *child;
539 GtkMenuItem* item;
540 GtkWidget* sub_menu=NULL;
541
542 children = gtk_container_get_children( GTK_CONTAINER(menu) );
543 for( child = children; child; child = child->next )
544 {
545 item = GTK_MENU_ITEM( child->data );
546 if( sys_menu_item_has_data( item ) )
547 {
548 GtkImage* img;
549 item = GTK_MENU_ITEM( child->data );
550 if( GTK_IS_IMAGE_MENU_ITEM(item) )
551 {
552 img = GTK_IMAGE(gtk_image_menu_item_get_image(GTK_IMAGE_MENU_ITEM(item)));
553 gtk_image_clear(img);
554 #if GTK_CHECK_VERSION(2, 24, 0)
555 if (gtk_widget_get_mapped(GTK_WIDGET(img)))
556 #else
557 if( GTK_WIDGET_MAPPED(img) )
558 #endif
559 on_menu_item_map(GTK_WIDGET(item), m);
560 }
561 }
562 else if( ( sub_menu = gtk_menu_item_get_submenu( item ) ) )
563 {
564 _unload_old_icons(GTK_MENU(sub_menu), theme, m);
565 }
566 }
567 g_list_free( children );
568 }
569
570 static void unload_old_icons(GtkIconTheme* theme, menup* m)
571 {
572 _unload_old_icons(GTK_MENU(m->menu), theme, m);
573 }
574
575 static void remove_change_handler(gpointer id, GObject* menu)
576 {
577 g_signal_handler_disconnect(gtk_icon_theme_get_default(), GPOINTER_TO_INT(id));
578 }
579
580 /*
581 * Insert application menus into specified menu
582 * menu: The parent menu to which the items should be inserted
583 * pisition: Position to insert items.
584 Passing -1 in this parameter means append all items
585 at the end of menu.
586 */
587 static void sys_menu_insert_items( menup* m, GtkMenu* menu, int position )
588 {
589 MenuCacheDir* dir;
590 guint change_handler;
591
592 if( G_UNLIKELY( SYS_MENU_ITEM_ID == 0 ) )
593 SYS_MENU_ITEM_ID = g_quark_from_static_string( "SysMenuItem" );
594
595 #if MENU_CACHE_CHECK_VERSION(0, 4, 0)
596 dir = menu_cache_dup_root_dir(m->menu_cache);
597 #else
598 dir = menu_cache_get_root_dir( m->menu_cache );
599 #endif
600 if(dir)
601 {
602 load_menu( m, dir, GTK_WIDGET(menu), position );
603 #if MENU_CACHE_CHECK_VERSION(0, 4, 0)
604 menu_cache_item_unref(MENU_CACHE_ITEM(dir));
605 #endif
606 }
607 else /* menu content is empty */
608 {
609 /* add a place holder */
610 GtkWidget* mi = gtk_menu_item_new();
611 g_object_set_qdata( G_OBJECT(mi), SYS_MENU_ITEM_ID, GINT_TO_POINTER(1) );
612 gtk_menu_shell_insert(GTK_MENU_SHELL(menu), mi, position);
613 }
614
615 change_handler = g_signal_connect(gtk_icon_theme_get_default(), "changed", G_CALLBACK(unload_old_icons), m);
616 g_object_weak_ref( G_OBJECT(menu), remove_change_handler, GINT_TO_POINTER(change_handler) );
617 }
618
619
620 static void
621 reload_system_menu( menup* m, GtkMenu* menu )
622 {
623 GList *children, *child;
624 GtkMenuItem* item;
625 GtkWidget* sub_menu;
626 gint idx;
627
628 children = gtk_container_get_children( GTK_CONTAINER(menu) );
629 for( child = children, idx = 0; child; child = child->next, ++idx )
630 {
631 item = GTK_MENU_ITEM( child->data );
632 if( sys_menu_item_has_data( item ) )
633 {
634 do
635 {
636 item = GTK_MENU_ITEM( child->data );
637 child = child->next;
638 gtk_widget_destroy( GTK_WIDGET(item) );
639 }while( child && sys_menu_item_has_data( child->data ) );
640 sys_menu_insert_items( m, menu, idx );
641 if( ! child )
642 break;
643 }
644 else if( ( sub_menu = gtk_menu_item_get_submenu( item ) ) )
645 {
646 reload_system_menu( m, GTK_MENU(sub_menu) );
647 }
648 }
649 g_list_free( children );
650 }
651
652 static void show_menu( GtkWidget* widget, menup* m, int btn, guint32 time )
653 {
654 gtk_menu_popup(GTK_MENU(m->menu),
655 NULL, NULL,
656 (GtkMenuPositionFunc)menu_pos, widget,
657 btn, time);
658 }
659
660 static gboolean
661 my_button_pressed(GtkWidget *widget, GdkEventButton *event, menup *m)
662 {
663 ENTER;
664 GtkAllocation allocation;
665 gtk_widget_get_allocation(GTK_WIDGET(widget), &allocation);
666
667 if ((event->type == GDK_BUTTON_PRESS) && event->button == 1
668 && (event->x >=0 && event->x < allocation.width)
669 && (event->y >=0 && event->y < allocation.height)) {
670 show_menu( widget, m, event->button, event->time );
671 RET(TRUE);
672 }
673 RET(FALSE);
674 }
675
676 static gboolean show_system_menu_idle(gpointer user_data)
677 {
678 menup* m = (menup*)user_data;
679 if (g_source_is_destroyed(g_main_current_source()))
680 return FALSE;
681 show_menu( m->img, m, 0, GDK_CURRENT_TIME );
682 m->show_system_menu_idle = 0;
683 return FALSE;
684 }
685
686 static void show_system_menu(GtkWidget *p)
687 {
688 menup *m = lxpanel_plugin_get_data(p);
689
690 if (m->has_system_menu && m->show_system_menu_idle == 0)
691 /* FIXME: I've no idea why this doesn't work without timeout
692 under some WMs, like icewm. */
693 m->show_system_menu_idle = g_timeout_add(200, show_system_menu_idle, m);
694 }
695
696 static GtkWidget *
697 make_button(menup *m, const gchar *fname, const gchar *name, GdkColor* tint, GtkWidget *menu)
698 {
699 char* title = NULL;
700
701 ENTER;
702 m->menu = menu;
703
704 if( name )
705 {
706 /* load the name from *.directory file if needed */
707 if( g_str_has_suffix( name, ".directory" ) )
708 {
709 GKeyFile* kf = g_key_file_new();
710 char* dir_file = g_build_filename( "desktop-directories", name, NULL );
711 if( g_key_file_load_from_data_dirs( kf, dir_file, NULL, 0, NULL ) )
712 {
713 title = g_key_file_get_locale_string( kf, "Desktop Entry", "Name", NULL, NULL );
714 }
715 g_free( dir_file );
716 g_key_file_free( kf );
717 }
718
719 m->img = lxpanel_button_new_for_icon(m->panel, fname, tint, title ? title : name);
720
721 g_free( title );
722 }
723 else
724 {
725 m->img = lxpanel_button_new_for_icon(m->panel, fname, tint, NULL);
726 }
727
728 gtk_widget_show(m->img);
729 gtk_box_pack_start(GTK_BOX(m->box), m->img, TRUE, FALSE, 0);
730
731 m->handler_id = g_signal_connect (G_OBJECT (m->img), "button-press-event",
732 G_CALLBACK (my_button_pressed), m);
733 g_object_set_data(G_OBJECT(m->img), "plugin", m);
734
735 m->ds = fm_dnd_src_new(NULL);
736
737 RET(m->img);
738 }
739
740 /* those were in configurator.c initially but it's safer to have those here */
741 typedef struct {
742 char *name;
743 char *disp_name;
744 void (*cmd)(void);
745 } Command;
746
747 static Command commands[] = {
748 //{ "configure", N_("Preferences"), configure },
749 { "run", N_("Run"), gtk_run },
750 { "restart", N_("Restart"), restart },
751 { "logout", N_("Logout"), logout },
752 { NULL, NULL },
753 };
754
755 static GtkWidget *
756 read_item(menup *m, config_setting_t *s)
757 {
758 const gchar *name, *fname, *action, *str;
759 GtkWidget *item;
760 Command *cmd_entry = NULL;
761 char *tmp;
762
763 ENTER;
764 name = fname = action = NULL;
765
766 config_setting_lookup_string(s, "name", &name);
767 config_setting_lookup_string(s, "image", &fname);
768 config_setting_lookup_string(s, "action", &action);
769 if (config_setting_lookup_string(s, "command", &str))
770 {
771 Command *tmp;
772
773 for (tmp = commands; tmp->name; tmp++) {
774 if (!g_ascii_strcasecmp(str, tmp->name)) {
775 cmd_entry = tmp;
776 break;
777 }
778 }
779 }
780 /* menu button */
781 if( cmd_entry ) /* built-in commands */
782 {
783 item = gtk_image_menu_item_new_with_label( _(cmd_entry->disp_name) );
784 g_signal_connect(G_OBJECT(item), "activate", (GCallback)run_command, cmd_entry->cmd);
785 }
786 else if (action)
787 {
788 item = gtk_image_menu_item_new_with_label(name ? name : "");
789 tmp = g_strdup(action);
790 g_object_weak_ref(G_OBJECT(item), (GWeakNotify)g_free, tmp);
791 g_signal_connect(G_OBJECT(item), "activate", (GCallback)spawn_app, tmp);
792 }
793 else
794 goto error;
795 gtk_container_set_border_width(GTK_CONTAINER(item), 0);
796 if (fname) {
797 GtkWidget *img;
798
799 /* FIXME: use FmIcon cache and fm_pixbuf_from_icon() API */
800 tmp = expand_tilda(fname);
801 img = _gtk_image_new_from_file_scaled(tmp, m->iconsize, m->iconsize, TRUE);
802 gtk_widget_show(img);
803 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
804 g_free(tmp);
805 }
806 RET(item);
807
808 error:
809 RET(NULL);
810 }
811
812 static GtkWidget *
813 read_separator(menup *m, config_setting_t *s)
814 {
815 ENTER;
816 RET(gtk_separator_menu_item_new());
817 }
818
819 static void on_reload_menu(MenuCache* cache, gpointer menu_pointer)
820 {
821 menup *m = menu_pointer;
822 /* g_debug("reload system menu!!"); */
823 reload_system_menu( m, GTK_MENU(m->menu) );
824 }
825
826 static void
827 read_system_menu(GtkMenu *menu, menup *m, config_setting_t *s)
828 {
829 if (m->menu_cache == NULL)
830 {
831 guint32 flags;
832 m->menu_cache = panel_menu_cache_new(&flags);
833 if (m->menu_cache == NULL)
834 {
835 g_warning("error loading applications menu");
836 return;
837 }
838 m->visibility_flags = flags;
839 m->reload_notify = menu_cache_add_reload_notify(m->menu_cache, on_reload_menu, m);
840 sys_menu_insert_items( m, menu, -1 );
841 }
842
843 m->has_system_menu = TRUE;
844 }
845
846 #if 0
847 static void
848 read_include(Plugin *p, char **fp)
849 {
850 ENTER;
851 gchar *name;
852 line s;
853 menup *m = (menup *)p->priv;
854 /* FIXME: this is disabled */
855 ENTER;
856 s.len = 256;
857 name = NULL;
858 if( fp )
859 {
860 while (lxpanel_get_line(fp, &s) != LINE_BLOCK_END) {
861 if (s.type == LINE_VAR) {
862 if (!g_ascii_strcasecmp(s.t[0], "name"))
863 name = expand_tilda(s.t[1]);
864 else {
865 ERR( "menu/include: unknown var %s\n", s.t[0]);
866 RET();
867 }
868 }
869 }
870 }
871 if ((fp = fopen(name, "r"))) {
872 LOG(LOG_INFO, "Including %s\n", name);
873 m->files = g_slist_prepend(m->files, fp);
874 p->fp = fp;
875 } else {
876 ERR("Can't include %s\n", name);
877 }
878 if (name) g_free(name);
879 RET();
880 }
881 #endif
882
883 static GtkWidget *
884 read_submenu(menup *m, config_setting_t *s, gboolean as_item)
885 {
886 GtkWidget *mi, *menu;
887 const gchar *name, *fname, *str;
888 config_setting_t *list = config_setting_add(s, "", PANEL_CONF_TYPE_LIST);
889 GdkColor color={0, 0, 36 * 0xffff / 0xff, 96 * 0xffff / 0xff};
890 guint i;
891
892 ENTER;
893
894 menu = gtk_menu_new ();
895 gtk_container_set_border_width(GTK_CONTAINER(menu), 0);
896
897 fname = NULL;
898 name = NULL;
899 config_setting_lookup_string(s, "name", &name);
900 config_setting_lookup_string(s, "image", &fname);
901 if (config_setting_lookup_string(s, "tintcolor", &str))
902 gdk_color_parse(str, &color);
903
904 for (i = 0; (s = config_setting_get_elem(list, i)) != NULL; i++)
905 {
906 str = config_setting_get_name(s);
907 if (!g_ascii_strcasecmp(str, "item")) {
908 mi = read_item(m, s);
909 } else if (!g_ascii_strcasecmp(str, "separator")) {
910 mi = read_separator(m, s);
911 } else if (!g_ascii_strcasecmp(str, "system")) {
912 read_system_menu(GTK_MENU(menu), m, s); /* add system menu items */
913 continue;
914 } else if (!g_ascii_strcasecmp(str, "menu")) {
915 mi = read_submenu(m, s, TRUE);
916 #if 0
917 } else if (!g_ascii_strcasecmp(str, "include")) {
918 read_include(p, fp);
919 continue;
920 #endif
921 } else {
922 g_warning("menu: unknown block %s", str);
923 goto error;
924 }
925 if (!mi) {
926 g_warning("menu: can't create menu item");
927 goto error;
928 }
929 gtk_widget_show(mi);
930 gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi);
931 }
932 if (as_item) {
933 mi = gtk_image_menu_item_new_with_label(name);
934 if (fname) {
935 GtkWidget *img;
936 char *expanded = expand_tilda(fname);
937 /* FIXME: use FmIcon cache and fm_pixbuf_from_icon() API */
938 img = _gtk_image_new_from_file_scaled(expanded, m->iconsize, m->iconsize, TRUE);
939 gtk_widget_show(img);
940 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(mi), img);
941 g_free(expanded);
942 }
943 gtk_menu_item_set_submenu (GTK_MENU_ITEM (mi), menu);
944 } else {
945 m->fname = fname ? expand_tilda(fname) : g_strdup(DEFAULT_MENU_ICON);
946 m->caption = g_strdup(name);
947 mi = make_button(m, m->fname, name, &color, menu);
948 }
949
950 RET(mi);
951
952 error:
953 // FIXME: we need to recursivly destroy all child menus and their items
954 gtk_widget_destroy(menu);
955 RET(NULL);
956 }
957
958 static GtkWidget *
959 menu_constructor(LXPanel *panel, config_setting_t *settings)
960 {
961 menup *m;
962 config_setting_t *s;
963 int iw, ih;
964
965 m = g_new0(menup, 1);
966 g_return_val_if_fail(m != NULL, 0);
967
968 gtk_icon_size_lookup( GTK_ICON_SIZE_MENU, &iw, &ih );
969 m->iconsize = MAX(iw, ih);
970
971 m->box = gtk_vbox_new(TRUE, 0);
972 lxpanel_plugin_set_data(m->box, m, menu_destructor);
973 gtk_container_set_border_width(GTK_CONTAINER(m->box), 0);
974
975 /* Save construction pointers */
976 m->panel = panel;
977 m->settings = settings;
978
979 /* Check if configuration exists */
980 settings = config_setting_add(settings, "", PANEL_CONF_TYPE_LIST);
981 if (config_setting_get_elem(settings, 0) == NULL)
982 {
983 /* create default menu */
984 config_setting_add(settings, "system", PANEL_CONF_TYPE_GROUP);
985 config_setting_add(settings, "separator", PANEL_CONF_TYPE_GROUP);
986 s = config_setting_add(settings, "item", PANEL_CONF_TYPE_GROUP);
987 config_group_set_string(s, "command", "run");
988 config_setting_add(settings, "separator", PANEL_CONF_TYPE_GROUP);
989 s = config_setting_add(settings, "item", PANEL_CONF_TYPE_GROUP);
990 config_group_set_string(s, "command", "logout");
991 config_group_set_string(s, "image", "gnome-logout");
992 config_group_set_string(m->settings, "image", DEFAULT_MENU_ICON);
993 }
994
995 if (!read_submenu(m, m->settings, FALSE)) {
996 g_warning("menu: plugin init failed");
997 gtk_widget_destroy(m->box);
998 return NULL;
999 }
1000
1001 return m->box;
1002 }
1003
1004 static gboolean apply_config(gpointer user_data)
1005 {
1006 GtkWidget *p = user_data;
1007 menup* m = lxpanel_plugin_get_data(p);
1008
1009 if( m->fname ) {
1010 lxpanel_button_set_icon(m->img, m->fname, panel_get_icon_size(m->panel));
1011 }
1012 config_group_set_string(m->settings, "image", m->fname);
1013 config_group_set_string(m->settings, "name", m->caption);
1014 return FALSE;
1015 }
1016
1017 static GtkWidget *menu_config(LXPanel *panel, GtkWidget *p)
1018 {
1019 menup* menu = lxpanel_plugin_get_data(p);
1020 return lxpanel_generic_config_dlg(_("Menu"), panel, apply_config, p,
1021 _("Icon"), &menu->fname, CONF_TYPE_FILE_ENTRY,
1022 /* _("Caption"), &menu->caption, CONF_TYPE_STR, */
1023 NULL);
1024 }
1025
1026 /* Callback when panel configuration changes. */
1027 static void menu_panel_configuration_changed(LXPanel *panel, GtkWidget *p)
1028 {
1029 apply_config(p);
1030 }
1031
1032 LXPanelPluginInit lxpanel_static_plugin_menu = {
1033 .name = N_("Menu"),
1034 .description = N_("Application Menu"),
1035
1036 .new_instance = menu_constructor,
1037 .config = menu_config,
1038 .reconfigure = menu_panel_configuration_changed,
1039 .show_system_menu = show_system_menu
1040 };
1041
1042 /* vim: set sw=4 et sts=4 : */