Enabling multithreaded compilation.
[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;
10862fa6 599
6cc5e1a6 600 children = gtk_container_get_children( GTK_CONTAINER(menu) );
10862fa6
DB
601 for( child = children, idx = 0; child; child = child->next, ++idx )
602 {
6cc5e1a6 603 item = GTK_MENU_ITEM( child->data );
10862fa6
DB
604 if( sys_menu_item_has_data( item ) )
605 {
606 do
607 {
6cc5e1a6
DB
608 item = GTK_MENU_ITEM( child->data );
609 child = child->next;
610 gtk_widget_destroy( GTK_WIDGET(item) );
10862fa6
DB
611 }while( child && sys_menu_item_has_data( child->data ) );
612 sys_menu_insert_items( m, menu, idx );
6cc5e1a6
DB
613 if( ! child )
614 break;
615 }
10862fa6
DB
616 else if( ( sub_menu = gtk_menu_item_get_submenu( item ) ) )
617 {
618 reload_system_menu( m, GTK_MENU(sub_menu) );
6cc5e1a6
DB
619 }
620 }
621 g_list_free( children );
622}
623
624static void show_menu( GtkWidget* widget, Plugin* p, int btn, guint32 time )
625{
626 menup* m = (menup*)p->priv;
6cc5e1a6
DB
627 gtk_menu_popup(GTK_MENU(m->menu),
628 NULL, NULL,
629 (GtkMenuPositionFunc)menu_pos, widget,
630 btn, time);
631}
632
633static gboolean
634my_button_pressed(GtkWidget *widget, GdkEventButton *event, Plugin* plugin)
635{
636 ENTER;
ca14ea2b 637#if GTK_CHECK_VERSION(2,18,0)
f8c25730
DB
638 GtkAllocation *allocation = g_new0 (GtkAllocation, 1);
639 gtk_widget_get_allocation(GTK_WIDGET(widget), allocation);
ca14ea2b 640#endif
6cc5e1a6 641
1ea75322
DB
642 /* Standard right-click handling. */
643 if (plugin_button_press_event(widget, event, plugin))
6cc5e1a6 644 return TRUE;
6cc5e1a6
DB
645
646 if ((event->type == GDK_BUTTON_PRESS)
ca14ea2b 647#if GTK_CHECK_VERSION(2,18,0)
f8c25730
DB
648 && (event->x >=0 && event->x < allocation->width)
649 && (event->y >=0 && event->y < allocation->height)) {
ca14ea2b 650#else
6cc5e1a6
DB
651 && (event->x >=0 && event->x < widget->allocation.width)
652 && (event->y >=0 && event->y < widget->allocation.height)) {
ca14ea2b 653#endif
6cc5e1a6
DB
654 show_menu( widget, plugin, event->button, event->time );
655 }
f8c25730
DB
656#if GTK_CHECK_VERSION(2,18,0)
657 g_free (allocation);
658#endif
6cc5e1a6
DB
659 RET(TRUE);
660}
661
662gboolean show_system_menu( gpointer system_menu )
663{
664 Plugin* p = (Plugin*)system_menu;
665 menup* m = (menup*)p->priv;
10862fa6 666 show_menu( m->img, p, 0, GDK_CURRENT_TIME );
6cc5e1a6
DB
667 return FALSE;
668}
669
670static GtkWidget *
671make_button(Plugin *p, gchar *fname, gchar *name, GdkColor* tint, GtkWidget *menu)
672{
05ddbe60 673 char* title = NULL;
6cc5e1a6
DB
674 menup *m;
675
676 ENTER;
677 m = (menup *)p->priv;
678 m->menu = menu;
05ddbe60
DB
679
680 if( name )
681 {
682 /* load the name from *.directory file if needed */
683 if( g_str_has_suffix( name, ".directory" ) )
684 {
685 GKeyFile* kf = g_key_file_new();
67aeed17 686 char* dir_file = g_build_filename( "desktop-directories", name, NULL );
05ddbe60
DB
687 if( g_key_file_load_from_data_dirs( kf, dir_file, NULL, 0, NULL ) )
688 {
689 title = g_key_file_get_locale_string( kf, "Desktop Entry", "Name", NULL, NULL );
690 }
691 g_free( dir_file );
692 g_key_file_free( kf );
693 }
694 else
695 title = name;
696
1ea75322 697 m->img = fb_button_new_from_file_with_label(fname, -1, p->panel->icon_size, gcolor2rgb24(tint), TRUE, p->panel, title);
05ddbe60
DB
698
699 if( title != name )
700 g_free( title );
701 }
702 else
703 {
1ea75322 704 m->img = fb_button_new_from_file(fname, -1, p->panel->icon_size, gcolor2rgb24(tint), TRUE);
05ddbe60
DB
705 }
706
10862fa6
DB
707 gtk_widget_show(m->img);
708 gtk_box_pack_start(GTK_BOX(m->box), m->img, FALSE, FALSE, 0);
6cc5e1a6 709
10862fa6 710 m->handler_id = g_signal_connect (G_OBJECT (m->img), "button-press-event",
6cc5e1a6 711 G_CALLBACK (my_button_pressed), p);
10862fa6 712 g_object_set_data(G_OBJECT(m->img), "plugin", p);
6cc5e1a6 713
10862fa6 714 RET(m->img);
6cc5e1a6
DB
715}
716
717
718static GtkWidget *
719read_item(Plugin *p, char** fp)
720{
721 line s;
722 gchar *name, *fname, *action;
723 GtkWidget *item;
724 menup *m = (menup *)p->priv;
725 Command *cmd_entry = NULL;
726
727 ENTER;
728 s.len = 256;
729 name = fname = action = NULL;
730
731 if( fp )
732 {
733 while (lxpanel_get_line(fp, &s) != LINE_BLOCK_END) {
734 if (s.type == LINE_VAR) {
735 if (!g_ascii_strcasecmp(s.t[0], "image"))
736 fname = expand_tilda(s.t[1]);
737 else if (!g_ascii_strcasecmp(s.t[0], "name"))
738 name = g_strdup(s.t[1]);
739 else if (!g_ascii_strcasecmp(s.t[0], "action"))
740 action = g_strdup(s.t[1]);
741 else if (!g_ascii_strcasecmp(s.t[0], "command")) {
742 Command *tmp;
743
744 for (tmp = commands; tmp->name; tmp++) {
745 if (!g_ascii_strcasecmp(s.t[1], tmp->name)) {
746 cmd_entry = tmp;
747 break;
748 }
749 }
750 } else {
751 ERR( "menu/item: unknown var %s\n", s.t[0]);
752 goto error;
753 }
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
764 {
765 item = gtk_image_menu_item_new_with_label(name ? name : "");
766 if (action) {
767 g_signal_connect(G_OBJECT(item), "activate", (GCallback)spawn_app, action);
768 }
769 }
770 gtk_container_set_border_width(GTK_CONTAINER(item), 0);
771 g_free(name);
772 if (fname) {
773 GtkWidget *img;
774
67aeed17 775 img = _gtk_image_new_from_file_scaled(fname, m->iconsize, m->iconsize, TRUE);
6cc5e1a6
DB
776 gtk_widget_show(img);
777 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
778 g_free(fname);
779 }
780 RET(item);
781
782 error:
783 g_free(fname);
784 g_free(name);
785 g_free(action);
786 RET(NULL);
787}
788
789static GtkWidget *
790read_separator(Plugin *p, char **fp)
791{
792 line s;
793
794 ENTER;
795 s.len = 256;
796 if( fp )
797 {
798 while (lxpanel_get_line(fp, &s) != LINE_BLOCK_END) {
799 ERR("menu: error - separator can not have paramteres\n");
800 RET(NULL);
801 }
802 }
803 RET(gtk_separator_menu_item_new());
804}
805
10862fa6 806static void on_reload_menu( MenuCache* cache, menup* m )
6cc5e1a6 807{
10862fa6
DB
808 /* g_debug("reload system menu!!"); */
809 reload_system_menu( m, GTK_MENU(m->menu) );
6cc5e1a6
DB
810}
811
812static void
813read_system_menu(GtkMenu* menu, Plugin *p, char** fp)
814{
10862fa6
DB
815 line s;
816 menup *m = (menup *)p->priv;
6cc5e1a6 817
1ea75322 818 if (m->menu_cache == NULL)
10862fa6 819 {
bfba7517
DB
820 guint32 flags;
821 m->menu_cache = panel_menu_cache_new(&flags);
1ea75322 822 if (m->menu_cache == NULL)
10862fa6
DB
823 {
824 ERR("error loading applications menu");
825 return;
826 }
bfba7517 827 m->visibility_flags = flags;
1ea75322 828 m->reload_notify = menu_cache_add_reload_notify(m->menu_cache, (GFunc) on_reload_menu, m);
10862fa6
DB
829 }
830
831 s.len = 256;
832 if( fp )
833 {
6cc5e1a6
DB
834 while (lxpanel_get_line(fp, &s) != LINE_BLOCK_END) {
835 ERR("menu: error - system can not have paramteres\n");
10862fa6 836 return;
6cc5e1a6 837 }
10862fa6 838 }
6cc5e1a6 839
10862fa6
DB
840 sys_menu_insert_items( m, menu, -1 );
841 m->has_system_menu = TRUE;
6cc5e1a6 842
10862fa6 843 p->panel->system_menus = g_slist_append( p->panel->system_menus, p );
6cc5e1a6
DB
844}
845
846static void
847read_include(Plugin *p, char **fp)
848{
849 ENTER;
850#if 0
851 gchar *name;
852 line s;
853 menup *m = (menup *)p->priv;
854 /* FIXME: this is disabled */
855 ENTER;
856 s.len = 256;
857 name = NULL;
858 if( fp )
859 {
860 while (lxpanel_get_line(fp, &s) != LINE_BLOCK_END) {
861 if (s.type == LINE_VAR) {
862 if (!g_ascii_strcasecmp(s.t[0], "name"))
863 name = expand_tilda(s.t[1]);
864 else {
865 ERR( "menu/include: unknown var %s\n", s.t[0]);
866 RET();
867 }
868 }
869 }
870 }
871 if ((fp = fopen(name, "r"))) {
872 LOG(LOG_INFO, "Including %s\n", name);
873 m->files = g_slist_prepend(m->files, fp);
874 p->fp = fp;
875 } else {
876 ERR("Can't include %s\n", name);
877 }
878 if (name) g_free(name);
879#endif
880 RET();
881}
882
883static GtkWidget *
884read_submenu(Plugin *p, char** fp, gboolean as_item)
885{
886 line s;
887 GtkWidget *mi, *menu;
1ea75322 888 gchar *name, *fname;
6cc5e1a6
DB
889 menup *m = (menup *)p->priv;
890 GdkColor color={0, 0, 36 * 0xffff / 0xff, 96 * 0xffff / 0xff};
891
892 ENTER;
05ddbe60 893
6cc5e1a6
DB
894 s.len = 256;
895 menu = gtk_menu_new ();
896 gtk_container_set_border_width(GTK_CONTAINER(menu), 0);
897
1ea75322
DB
898 fname = NULL;
899 name = NULL;
6cc5e1a6
DB
900 while (lxpanel_get_line(fp, &s) != LINE_BLOCK_END) {
901 if (s.type == LINE_BLOCK_START) {
902 mi = NULL;
903 if (!g_ascii_strcasecmp(s.t[0], "item")) {
904 mi = read_item(p, fp);
905 } else if (!g_ascii_strcasecmp(s.t[0], "separator")) {
906 mi = read_separator(p, fp);
907 } else if (!g_ascii_strcasecmp(s.t[0], "system")) {
908 read_system_menu(GTK_MENU(menu), p, fp); /* add system menu items */
909 continue;
910 } else if (!g_ascii_strcasecmp(s.t[0], "menu")) {
911 mi = read_submenu(p, fp, TRUE);
912 } else if (!g_ascii_strcasecmp(s.t[0], "include")) {
913 read_include(p, fp);
914 continue;
915 } else {
916 ERR("menu: unknown block %s\n", s.t[0]);
917 goto error;
918 }
919 if (!mi) {
920 ERR("menu: can't create menu item\n");
921 goto error;
922 }
923 gtk_widget_show(mi);
924 gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi);
925 } else if (s.type == LINE_VAR) {
05ddbe60 926 m->config_start = *fp;
6cc5e1a6
DB
927 if (!g_ascii_strcasecmp(s.t[0], "image"))
928 fname = expand_tilda(s.t[1]);
929 else if (!g_ascii_strcasecmp(s.t[0], "name"))
1ea75322 930 name = g_strdup(s.t[1]);
10862fa6 931 /* FIXME: tintcolor will not be saved. */
6cc5e1a6
DB
932 else if (!g_ascii_strcasecmp(s.t[0], "tintcolor"))
933 gdk_color_parse( s.t[1], &color);
934 else {
935 ERR("menu: unknown var %s\n", s.t[0]);
6cc5e1a6
DB
936 }
937 } else if (s.type == LINE_NONE) {
938 if (m->files) {
939 /*
940 fclose(p->fp);
941 p->fp = m->files->data;
942 */
943 m->files = g_slist_delete_link(m->files, m->files);
944 }
945 } else {
946 ERR("menu: illegal in this context %s\n", s.str);
947 goto error;
948 }
949 }
950 if (as_item) {
1ea75322 951 mi = gtk_image_menu_item_new_with_label(name);
6cc5e1a6
DB
952 if (fname) {
953 GtkWidget *img;
67aeed17 954 img = _gtk_image_new_from_file_scaled(fname, m->iconsize, m->iconsize, TRUE);
6cc5e1a6
DB
955 gtk_widget_show(img);
956 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(mi), img);
6cc5e1a6
DB
957 }
958 gtk_menu_item_set_submenu (GTK_MENU_ITEM (mi), menu);
6cc5e1a6 959 } else {
1ea75322
DB
960 m->fname = fname ? g_strdup(fname) : g_strdup( DEFAULT_MENU_ICON );
961 m->caption = name ? g_strdup(name) : NULL;
6cc5e1a6 962 mi = make_button(p, fname, name, &color, menu);
6cc5e1a6
DB
963 RET(mi);
964 }
965
1ea75322
DB
966 g_free(fname);
967 g_free(name);
968 RET(mi);
969
6cc5e1a6
DB
970 error:
971 // FIXME: we need to recursivly destroy all child menus and their items
972 gtk_widget_destroy(menu);
973 g_free(fname);
974 g_free(name);
975 RET(NULL);
976}
977
978static int
979menu_constructor(Plugin *p, char **fp)
980{
10862fa6 981 char *start;
6cc5e1a6
DB
982 menup *m;
983 static char default_config[] =
6cc5e1a6
DB
984 "system {\n"
985 "}\n"
986 "separator {\n"
987 "}\n"
988 "item {\n"
989 "command=run\n"
990 "}\n"
991 "separator {\n"
992 "}\n"
993 "item {\n"
994 "image=gnome-logout\n"
995 "command=logout\n"
996 "}\n"
1ea75322 997 "image=" DEFAULT_MENU_ICON "\n"
6cc5e1a6 998 "}\n";
1ea75322 999 char *config_default = default_config;
10862fa6 1000 int iw, ih;
6cc5e1a6 1001
6cc5e1a6
DB
1002 m = g_new0(menup, 1);
1003 g_return_val_if_fail(m != NULL, 0);
05ddbe60
DB
1004 m->fname = NULL;
1005 m->caption = NULL;
10862fa6 1006
6cc5e1a6
DB
1007 p->priv = m;
1008
10862fa6
DB
1009 gtk_icon_size_lookup( GTK_ICON_SIZE_MENU, &iw, &ih );
1010 m->iconsize = MAX(iw, ih);
6cc5e1a6
DB
1011
1012 m->box = gtk_hbox_new(FALSE, 0);
1013 gtk_container_set_border_width(GTK_CONTAINER(m->box), 0);
1014
1015 if( ! fp )
1016 fp = &config_default;
1017
10862fa6 1018 m->config_start = start = *fp;
6cc5e1a6
DB
1019 if (!read_submenu(p, fp, FALSE)) {
1020 ERR("menu: plugin init failed\n");
1ea75322 1021 return 0;
6cc5e1a6 1022 }
05ddbe60
DB
1023 m->config_end = *fp - 1;
1024 while( *m->config_end != '}' && m->config_end > m->config_start ) {
1025 --m->config_end;
6cc5e1a6 1026 }
05ddbe60
DB
1027 if( *m->config_end == '}' )
1028 --m->config_end;
6cc5e1a6 1029
10862fa6 1030 m->config_data = g_strndup( start, (m->config_end - start) );
6cc5e1a6
DB
1031
1032 p->pwid = m->box;
1033
1034 RET(1);
1035
6cc5e1a6
DB
1036}
1037
1038static void save_config( Plugin* p, FILE* fp )
1039{
1040 menup* menu = (menup*)p->priv;
10862fa6 1041 int level = 0;
05ddbe60
DB
1042 lxpanel_put_str( fp, "name", menu->caption );
1043 lxpanel_put_str( fp, "image", menu->fname );
6cc5e1a6
DB
1044 if( menu->config_data ) {
1045 char** lines = g_strsplit( menu->config_data, "\n", 0 );
1046 char** line;
1047 for( line = lines; *line; ++line ) {
1048 g_strstrip( *line );
1049 if( **line )
10862fa6
DB
1050 {
1051 if( level == 0 )
1052 {
1053 /* skip image and caption since we already save these two items */
1054 if( g_str_has_prefix(*line, "image") || g_str_has_prefix(*line, "caption") )
1055 continue;
1056 }
1057 g_strchomp(*line); /* remove trailing spaces */
1058 if( g_str_has_suffix( *line, "{" ) )
1059 ++level;
1060 else if( g_str_has_suffix( *line, "}" ) )
1061 --level;
6cc5e1a6 1062 lxpanel_put_line( fp, *line );
10862fa6 1063 }
6cc5e1a6
DB
1064 }
1065 g_strfreev( lines );
1066 }
1067}
1068
05ddbe60
DB
1069static void apply_config(Plugin* p)
1070{
10862fa6 1071 menup* m = (menup*)p->priv;
0f7f2ef3
AL
1072 if( m->fname ) {
1073 fb_button_set_from_file( m->img, m->fname, -1, p->panel->icon_size, TRUE );
1074 }
05ddbe60
DB
1075}
1076
1077static void menu_config( Plugin *p, GtkWindow* parent )
1078{
1079 GtkWidget* dlg;
1080 menup* menu = (menup*)p->priv;
1081 dlg = create_generic_config_dlg( _(p->class->name),
1082 GTK_WIDGET(parent),
1083 (GSourceFunc) apply_config, (gpointer) p,
10862fa6
DB
1084 _("Icon"), &menu->fname, CONF_TYPE_FILE_ENTRY,
1085 /* _("Caption"), &menu->caption, CONF_TYPE_STR, */
05ddbe60
DB
1086 NULL );
1087 gtk_window_present( GTK_WINDOW(dlg) );
1088}
1089
1ea75322
DB
1090/* Callback when panel configuration changes. */
1091static void menu_panel_configuration_changed(Plugin * p)
1092{
1093 apply_config(p);
1094}
1095
6cc5e1a6 1096PluginClass menu_plugin_class = {
1ea75322
DB
1097
1098 PLUGINCLASS_VERSIONING,
6cc5e1a6
DB
1099
1100 type : "menu",
1101 name : N_("Menu"),
10862fa6 1102 version: "2.0",
1ea75322 1103 description : N_("Application Menu"),
6cc5e1a6
DB
1104
1105 constructor : menu_constructor,
1106 destructor : menu_destructor,
10862fa6 1107 config : menu_config,
1ea75322
DB
1108 save : save_config,
1109 panel_configuration_changed : menu_panel_configuration_changed
6cc5e1a6
DB
1110};
1111