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