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