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