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