Adding upstream version 0.8.0.
[debian/lxpanel.git] / plugins / menu.c
CommitLineData
6b775dbb
AG
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"
f7ecd6ce 43#include "gtk-compat.h"
6b775dbb
AG
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
64typedef struct {
65 GtkWidget *menu, *box, *img, *label;
66 char *fname, *caption;
6b775dbb
AG
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
79static guint idle_loader = 0;
80
81GQuark SYS_MENU_ITEM_ID = 0;
82
6b775dbb
AG
83/* FIXME: those are defined on panel main code */
84void restart(void);
85void gtk_run(void);
86void logout(void);
87
88static 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
96static void
97menu_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
6b775dbb
AG
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
127static void
128spawn_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
138static void
139run_command(GtkWidget *widget, void (*cmd)(void))
140{
141 ENTER;
142 cmd();
143 RET();
144}
145
146static void
147menu_pos(GtkMenu *menu, gint *x, gint *y, gboolean *push_in, GtkWidget *widget)
148{
f7ecd6ce
AG
149 menup *m = lxpanel_plugin_get_data(widget);
150 lxpanel_plugin_popup_set_position_helper(m->panel, widget, GTK_WIDGET(menu), x, y);
6b775dbb 151 *push_in = TRUE;
6b775dbb
AG
152}
153
154static 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 */
162static 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
191static 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 */
199static 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
211static 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
256static 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*/
271static 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 {
6b775dbb 289 if (!gtk_widget_get_mapped(tmp))
6b775dbb
AG
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
324static 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
331static 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
382static 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 {
7dd482c5
AG
408 const char *comment = menu_cache_item_get_comment(item);
409 if (comment != NULL)
410 gtk_widget_set_tooltip_text(mi, comment);
6b775dbb
AG
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
424static 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;
7dd482c5
AG
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();
6b775dbb
AG
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;
7dd482c5
AG
450# endif /* < 1.0.0 */
451# endif /* < 0.5.0 */
6b775dbb
AG
452 children = menu_cache_dir_list_children(dir);
453 for (l = children; l; l = l->next)
7dd482c5 454#else /* < 0.4.0 */
6b775dbb
AG
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
499static 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
504static 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);
6b775dbb 522 if (gtk_widget_get_mapped(GTK_WIDGET(img)))
6b775dbb
AG
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
534static void unload_old_icons(GtkIconTheme* theme, menup* m)
535{
536 _unload_old_icons(GTK_MENU(m->menu), theme, m);
537}
538
539static 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 */
551static 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
584static void
585reload_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
616static 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
624static gboolean
f7ecd6ce 625menu_button_press_event(GtkWidget * widget, GdkEventButton * event, LXPanel * panel)
6b775dbb
AG
626{
627 ENTER;
6b775dbb 628
f7ecd6ce
AG
629 if (event->button == 1)
630 {
631 show_menu( widget, lxpanel_plugin_get_data(widget), event->button, event->time );
6b775dbb
AG
632 RET(TRUE);
633 }
634 RET(FALSE);
635}
636
637static 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;
f7ecd6ce 642 show_menu( m->box, m, 0, GDK_CURRENT_TIME );
6b775dbb
AG
643 m->show_system_menu_idle = 0;
644 return FALSE;
645}
646
647static 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
657static GtkWidget *
658make_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
f7ecd6ce 689 gtk_container_add(GTK_CONTAINER(m->box), m->img);
6b775dbb
AG
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 */
697typedef struct {
698 char *name;
699 char *disp_name;
700 void (*cmd)(void);
701} Command;
702
703static 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
711static GtkWidget *
712read_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
6b775dbb 755 tmp = expand_tilda(fname);
f7ecd6ce 756 img = lxpanel_image_new_for_icon(m->panel, tmp, m->iconsize, NULL);
6b775dbb
AG
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
767static GtkWidget *
768read_separator(menup *m, config_setting_t *s)
769{
770 ENTER;
771 RET(gtk_separator_menu_item_new());
772}
773
774static 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
781static void
782read_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
802static void
803read_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
838static GtkWidget *
839read_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);
f7ecd6ce 892 img = lxpanel_image_new_for_icon(m->panel, expanded, m->iconsize, NULL);
6b775dbb
AG
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
906error:
907 // FIXME: we need to recursivly destroy all child menus and their items
908 gtk_widget_destroy(menu);
909 RET(NULL);
910}
911
912static GtkWidget *
913menu_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
f7ecd6ce
AG
925 m->box = gtk_event_box_new();
926 gtk_widget_set_has_window(m->box, FALSE);
6b775dbb 927 lxpanel_plugin_set_data(m->box, m, menu_destructor);
6b775dbb
AG
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
f7ecd6ce 955 /* FIXME: allow bind a global key to toggle menu using libkeybinder */
6b775dbb
AG
956 return m->box;
957}
958
959static 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 ) {
f7ecd6ce 965 lxpanel_button_set_icon(m->img, m->fname, -1);
6b775dbb
AG
966 }
967 config_group_set_string(m->settings, "image", m->fname);
f7ecd6ce 968 /* config_group_set_int(m->settings, "panelSize", m->match_panel); */
6b775dbb
AG
969 config_group_set_string(m->settings, "name", m->caption);
970 return FALSE;
971}
972
973static 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,
f7ecd6ce 978 /* _("Use panel size as icon size"), &menu->match_panel, CONF_TYPE_INT, */
6b775dbb
AG
979 /* _("Caption"), &menu->caption, CONF_TYPE_STR, */
980 NULL);
981}
982
6b775dbb
AG
983LXPanelPluginInit lxpanel_static_plugin_menu = {
984 .name = N_("Menu"),
985 .description = N_("Application Menu"),
986
987 .new_instance = menu_constructor,
988 .config = menu_config,
f7ecd6ce 989 .button_press_event = menu_button_press_event,
6b775dbb
AG
990 .show_system_menu = show_system_menu
991};
992
993/* vim: set sw=4 et sts=4 : */