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