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