1148d35c0b11fb51482e057fea580cd7dca1ee38
[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 load_menu( m, dir, GTK_WIDGET(menu), position );
599 else /* menu content is empty */
600 {
601 /* add a place holder */
602 GtkWidget* mi = gtk_menu_item_new();
603 g_object_set_qdata( G_OBJECT(mi), SYS_MENU_ITEM_ID, GINT_TO_POINTER(1) );
604 gtk_menu_shell_insert(GTK_MENU_SHELL(menu), mi, position);
605 }
606 #if MENU_CACHE_CHECK_VERSION(0, 4, 0)
607 menu_cache_item_unref(MENU_CACHE_ITEM(dir));
608 #endif
609
610 change_handler = g_signal_connect(gtk_icon_theme_get_default(), "changed", G_CALLBACK(unload_old_icons), m);
611 g_object_weak_ref( G_OBJECT(menu), remove_change_handler, GINT_TO_POINTER(change_handler) );
612 }
613
614
615 static void
616 reload_system_menu( menup* m, GtkMenu* menu )
617 {
618 GList *children, *child;
619 GtkMenuItem* item;
620 GtkWidget* sub_menu;
621 gint idx;
622
623 children = gtk_container_get_children( GTK_CONTAINER(menu) );
624 for( child = children, idx = 0; child; child = child->next, ++idx )
625 {
626 item = GTK_MENU_ITEM( child->data );
627 if( sys_menu_item_has_data( item ) )
628 {
629 do
630 {
631 item = GTK_MENU_ITEM( child->data );
632 child = child->next;
633 gtk_widget_destroy( GTK_WIDGET(item) );
634 }while( child && sys_menu_item_has_data( child->data ) );
635 sys_menu_insert_items( m, menu, idx );
636 if( ! child )
637 break;
638 }
639 else if( ( sub_menu = gtk_menu_item_get_submenu( item ) ) )
640 {
641 reload_system_menu( m, GTK_MENU(sub_menu) );
642 }
643 }
644 g_list_free( children );
645 }
646
647 static void show_menu( GtkWidget* widget, menup* m, int btn, guint32 time )
648 {
649 gtk_menu_popup(GTK_MENU(m->menu),
650 NULL, NULL,
651 (GtkMenuPositionFunc)menu_pos, widget,
652 btn, time);
653 }
654
655 static gboolean
656 my_button_pressed(GtkWidget *widget, GdkEventButton *event, menup *m)
657 {
658 ENTER;
659 #if GTK_CHECK_VERSION(2,18,0)
660 GtkAllocation *allocation = g_new0 (GtkAllocation, 1);
661 gtk_widget_get_allocation(GTK_WIDGET(widget), allocation);
662 #endif
663
664 /* Standard right-click handling. */
665 if (lxpanel_plugin_button_press_event(m->box, event, m->panel))
666 return TRUE;
667
668 if ((event->type == GDK_BUTTON_PRESS)
669 #if GTK_CHECK_VERSION(2,18,0)
670 && (event->x >=0 && event->x < allocation->width)
671 && (event->y >=0 && event->y < allocation->height)) {
672 #else
673 && (event->x >=0 && event->x < widget->allocation.width)
674 && (event->y >=0 && event->y < widget->allocation.height)) {
675 #endif
676 show_menu( widget, m, event->button, event->time );
677 }
678 #if GTK_CHECK_VERSION(2,18,0)
679 g_free (allocation);
680 #endif
681 RET(TRUE);
682 }
683
684 static gboolean show_system_menu_idle(gpointer user_data)
685 {
686 menup* m = (menup*)user_data;
687 if (g_source_is_destroyed(g_main_current_source()))
688 return FALSE;
689 show_menu( m->img, m, 0, GDK_CURRENT_TIME );
690 m->show_system_menu_idle = 0;
691 return FALSE;
692 }
693
694 static void show_system_menu(GtkWidget *p)
695 {
696 menup *m = lxpanel_plugin_get_data(p);
697
698 if (m->has_system_menu && m->show_system_menu_idle == 0)
699 /* FIXME: I've no idea why this doesn't work without timeout
700 under some WMs, like icewm. */
701 m->show_system_menu_idle = g_timeout_add(200, show_system_menu_idle, m);
702 }
703
704 static GtkWidget *
705 make_button(menup *m, const gchar *fname, const gchar *name, GdkColor* tint, GtkWidget *menu)
706 {
707 char* title = NULL;
708
709 ENTER;
710 m->menu = menu;
711
712 if( name )
713 {
714 /* load the name from *.directory file if needed */
715 if( g_str_has_suffix( name, ".directory" ) )
716 {
717 GKeyFile* kf = g_key_file_new();
718 char* dir_file = g_build_filename( "desktop-directories", name, NULL );
719 if( g_key_file_load_from_data_dirs( kf, dir_file, NULL, 0, NULL ) )
720 {
721 title = g_key_file_get_locale_string( kf, "Desktop Entry", "Name", NULL, NULL );
722 }
723 g_free( dir_file );
724 g_key_file_free( kf );
725 }
726
727 m->img = lxpanel_button_new_for_icon(m->panel, fname, tint, title ? title : name);
728
729 g_free( title );
730 }
731 else
732 {
733 m->img = lxpanel_button_new_for_icon(m->panel, fname, tint, NULL);
734 }
735
736 gtk_widget_show(m->img);
737 gtk_box_pack_start(GTK_BOX(m->box), m->img, FALSE, FALSE, 0);
738
739 m->handler_id = g_signal_connect (G_OBJECT (m->img), "button-press-event",
740 G_CALLBACK (my_button_pressed), m);
741 g_object_set_data(G_OBJECT(m->img), "plugin", m);
742
743 RET(m->img);
744 }
745
746 /* those were in configurator.c initially but it's safer to have those here */
747 typedef struct {
748 char *name;
749 char *disp_name;
750 void (*cmd)(void);
751 } Command;
752
753 static Command commands[] = {
754 //{ "configure", N_("Preferences"), configure },
755 { "run", N_("Run"), gtk_run },
756 { "restart", N_("Restart"), restart },
757 { "logout", N_("Logout"), logout },
758 { NULL, NULL },
759 };
760
761 static GtkWidget *
762 read_item(menup *m, config_setting_t *s)
763 {
764 const gchar *name, *fname, *action, *str;
765 GtkWidget *item;
766 Command *cmd_entry = NULL;
767 char *tmp;
768
769 ENTER;
770 name = fname = action = NULL;
771
772 config_setting_lookup_string(s, "name", &name);
773 config_setting_lookup_string(s, "image", &fname);
774 config_setting_lookup_string(s, "action", &action);
775 if (config_setting_lookup_string(s, "command", &str))
776 {
777 Command *tmp;
778
779 for (tmp = commands; tmp->name; tmp++) {
780 if (!g_ascii_strcasecmp(str, tmp->name)) {
781 cmd_entry = tmp;
782 break;
783 }
784 }
785 }
786 /* menu button */
787 if( cmd_entry ) /* built-in commands */
788 {
789 item = gtk_image_menu_item_new_with_label( _(cmd_entry->disp_name) );
790 g_signal_connect(G_OBJECT(item), "activate", (GCallback)run_command, cmd_entry->cmd);
791 }
792 else if (action)
793 {
794 item = gtk_image_menu_item_new_with_label(name ? name : "");
795 tmp = g_strdup(action);
796 g_object_weak_ref(G_OBJECT(item), (GWeakNotify)g_free, tmp);
797 g_signal_connect(G_OBJECT(item), "activate", (GCallback)spawn_app, tmp);
798 }
799 else
800 goto error;
801 gtk_container_set_border_width(GTK_CONTAINER(item), 0);
802 if (fname) {
803 GtkWidget *img;
804
805 /* FIXME: use FmIcon cache and fm_pixbuf_from_icon() API */
806 tmp = expand_tilda(fname);
807 img = _gtk_image_new_from_file_scaled(tmp, m->iconsize, m->iconsize, TRUE);
808 gtk_widget_show(img);
809 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
810 g_free(tmp);
811 }
812 RET(item);
813
814 error:
815 RET(NULL);
816 }
817
818 static GtkWidget *
819 read_separator(menup *m, config_setting_t *s)
820 {
821 ENTER;
822 RET(gtk_separator_menu_item_new());
823 }
824
825 static void on_reload_menu(MenuCache* cache, gpointer menu_pointer)
826 {
827 menup *m = menu_pointer;
828 /* g_debug("reload system menu!!"); */
829 reload_system_menu( m, GTK_MENU(m->menu) );
830 }
831
832 static void
833 read_system_menu(GtkMenu *menu, menup *m, config_setting_t *s)
834 {
835 if (m->menu_cache == NULL)
836 {
837 guint32 flags;
838 m->menu_cache = panel_menu_cache_new(&flags);
839 if (m->menu_cache == NULL)
840 {
841 ERR("error loading applications menu");
842 return;
843 }
844 m->visibility_flags = flags;
845 m->reload_notify = menu_cache_add_reload_notify(m->menu_cache, on_reload_menu, m);
846 sys_menu_insert_items( m, menu, -1 );
847 }
848
849 m->has_system_menu = TRUE;
850 }
851
852 #if 0
853 static void
854 read_include(Plugin *p, char **fp)
855 {
856 ENTER;
857 gchar *name;
858 line s;
859 menup *m = (menup *)p->priv;
860 /* FIXME: this is disabled */
861 ENTER;
862 s.len = 256;
863 name = NULL;
864 if( fp )
865 {
866 while (lxpanel_get_line(fp, &s) != LINE_BLOCK_END) {
867 if (s.type == LINE_VAR) {
868 if (!g_ascii_strcasecmp(s.t[0], "name"))
869 name = expand_tilda(s.t[1]);
870 else {
871 ERR( "menu/include: unknown var %s\n", s.t[0]);
872 RET();
873 }
874 }
875 }
876 }
877 if ((fp = fopen(name, "r"))) {
878 LOG(LOG_INFO, "Including %s\n", name);
879 m->files = g_slist_prepend(m->files, fp);
880 p->fp = fp;
881 } else {
882 ERR("Can't include %s\n", name);
883 }
884 if (name) g_free(name);
885 RET();
886 }
887 #endif
888
889 static GtkWidget *
890 read_submenu(menup *m, config_setting_t *s, gboolean as_item)
891 {
892 GtkWidget *mi, *menu;
893 const gchar *name, *fname, *str;
894 config_setting_t *list = config_setting_add(s, "", PANEL_CONF_TYPE_LIST);
895 GdkColor color={0, 0, 36 * 0xffff / 0xff, 96 * 0xffff / 0xff};
896 guint i;
897
898 ENTER;
899
900 menu = gtk_menu_new ();
901 gtk_container_set_border_width(GTK_CONTAINER(menu), 0);
902
903 fname = NULL;
904 name = NULL;
905 config_setting_lookup_string(s, "name", &name);
906 config_setting_lookup_string(s, "image", &fname);
907 if (config_setting_lookup_string(s, "tintcolor", &str))
908 gdk_color_parse(str, &color);
909
910 for (i = 0; (s = config_setting_get_elem(list, i)) != NULL; i++)
911 {
912 str = config_setting_get_name(s);
913 if (!g_ascii_strcasecmp(str, "item")) {
914 mi = read_item(m, s);
915 } else if (!g_ascii_strcasecmp(str, "separator")) {
916 mi = read_separator(m, s);
917 } else if (!g_ascii_strcasecmp(str, "system")) {
918 read_system_menu(GTK_MENU(menu), m, s); /* add system menu items */
919 continue;
920 } else if (!g_ascii_strcasecmp(str, "menu")) {
921 mi = read_submenu(m, s, TRUE);
922 #if 0
923 } else if (!g_ascii_strcasecmp(str, "include")) {
924 read_include(p, fp);
925 continue;
926 #endif
927 } else {
928 ERR("menu: unknown block %s\n", str);
929 goto error;
930 }
931 if (!mi) {
932 ERR("menu: can't create menu item\n");
933 goto error;
934 }
935 gtk_widget_show(mi);
936 gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi);
937 }
938 if (as_item) {
939 mi = gtk_image_menu_item_new_with_label(name);
940 if (fname) {
941 GtkWidget *img;
942 char *expanded = expand_tilda(fname);
943 /* FIXME: use FmIcon cache and fm_pixbuf_from_icon() API */
944 img = _gtk_image_new_from_file_scaled(expanded, m->iconsize, m->iconsize, TRUE);
945 gtk_widget_show(img);
946 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(mi), img);
947 g_free(expanded);
948 }
949 gtk_menu_item_set_submenu (GTK_MENU_ITEM (mi), menu);
950 } else {
951 m->fname = fname ? expand_tilda(fname) : g_strdup(DEFAULT_MENU_ICON);
952 m->caption = g_strdup(name);
953 mi = make_button(m, m->fname, name, &color, menu);
954 }
955
956 RET(mi);
957
958 error:
959 // FIXME: we need to recursivly destroy all child menus and their items
960 gtk_widget_destroy(menu);
961 RET(NULL);
962 }
963
964 static GtkWidget *
965 menu_constructor(Panel *panel, config_setting_t *settings)
966 {
967 menup *m;
968 config_setting_t *s;
969 int iw, ih;
970
971 m = g_new0(menup, 1);
972 g_return_val_if_fail(m != NULL, 0);
973
974 gtk_icon_size_lookup( GTK_ICON_SIZE_MENU, &iw, &ih );
975 m->iconsize = MAX(iw, ih);
976
977 m->box = gtk_hbox_new(FALSE, 0);
978 lxpanel_plugin_set_data(m->box, m, menu_destructor);
979 gtk_container_set_border_width(GTK_CONTAINER(m->box), 0);
980
981 /* Save construction pointers */
982 m->panel = panel;
983 m->settings = settings;
984
985 /* Check if configuration exists */
986 settings = config_setting_add(settings, "", PANEL_CONF_TYPE_LIST);
987 if (config_setting_get_elem(settings, 0) == NULL)
988 {
989 /* create default menu */
990 config_setting_add(settings, "system", PANEL_CONF_TYPE_GROUP);
991 config_setting_add(settings, "separator", PANEL_CONF_TYPE_GROUP);
992 s = config_setting_add(settings, "item", PANEL_CONF_TYPE_GROUP);
993 config_group_set_string(s, "command", "run");
994 config_setting_add(settings, "separator", PANEL_CONF_TYPE_GROUP);
995 s = config_setting_add(settings, "item", PANEL_CONF_TYPE_GROUP);
996 config_group_set_string(s, "command", "logout");
997 config_group_set_string(s, "image", "gnome-logout");
998 config_group_set_string(m->settings, "image", DEFAULT_MENU_ICON);
999 }
1000
1001 if (!read_submenu(m, m->settings, FALSE)) {
1002 ERR("menu: plugin init failed\n");
1003 gtk_widget_destroy(m->box);
1004 return NULL;
1005 }
1006
1007 return m->box;
1008 }
1009
1010 static gboolean apply_config(gpointer user_data)
1011 {
1012 GtkWidget *p = user_data;
1013 menup* m = lxpanel_plugin_get_data(p);
1014
1015 if( m->fname ) {
1016 lxpanel_button_set_icon(m->img, m->fname, panel_get_icon_size(m->panel));
1017 }
1018 config_group_set_string(m->settings, "image", m->fname);
1019 config_group_set_string(m->settings, "name", m->caption);
1020 return FALSE;
1021 }
1022
1023 static GtkWidget *menu_config(Panel *panel, GtkWidget *p, GtkWindow *parent)
1024 {
1025 menup* menu = lxpanel_plugin_get_data(p);
1026 return lxpanel_generic_config_dlg(_("Menu"), panel, apply_config, p,
1027 _("Icon"), &menu->fname, CONF_TYPE_FILE_ENTRY,
1028 /* _("Caption"), &menu->caption, CONF_TYPE_STR, */
1029 NULL);
1030 }
1031
1032 /* Callback when panel configuration changes. */
1033 static void menu_panel_configuration_changed(Panel *panel, GtkWidget *p)
1034 {
1035 apply_config(p);
1036 }
1037
1038 LXPanelPluginInit lxpanel_static_plugin_menu = {
1039 .name = N_("Menu"),
1040 .description = N_("Application Menu"),
1041
1042 .new_instance = menu_constructor,
1043 .config = menu_config,
1044 .reconfigure = menu_panel_configuration_changed,
1045 .show_system_menu = show_system_menu
1046 };
1047
1048 /* vim: set sw=4 et sts=4 : */