Merging upstream version 0.3.8.1.
[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
26#include "panel.h"
27#include "misc.h"
28#include "plugin.h"
29#include "bg.h"
30
31#include "ptk-app-menu.h"
32
33#include "dbg.h"
34
35/*
36 * SuxPanel version 0.1
37 * Copyright (c) 2003 Leandro Pereira <leandro@linuxmag.com.br>
38 */
39
40/*
41 * menu style code was taken from suxpanel
42 */
43
44typedef struct {
45 GtkTooltips *tips;
46 GtkWidget *menu, *box, *bg, *label;
05ddbe60 47 char *fname, *caption;
6cc5e1a6
DB
48 gulong handler_id;
49 int iconsize, paneliconsize;
50 GSList *files;
51 gboolean has_system_menu;
52 char* config_data;
53 int sysmenu_pos;
05ddbe60 54 char *config_start, *config_end;
6cc5e1a6
DB
55} menup;
56
57static guint idle_loader = 0;
58
59static void
60menu_destructor(Plugin *p)
61{
62 menup *m = (menup *)p->priv;
63
64 ENTER;
65
66 if( G_UNLIKELY( idle_loader ) )
67 {
68 g_source_remove( idle_loader );
69 idle_loader = 0;
70 }
71 if( m->has_system_menu )
72 p->panel->system_menus = g_slist_remove( p->panel->system_menus, p );
73
74 g_signal_handler_disconnect(G_OBJECT(m->bg), m->handler_id);
75 gtk_widget_destroy(m->menu);
76 /* The widget is destroyed in plugin_stop().
77 gtk_widget_destroy(m->box);
78 */
05ddbe60
DB
79 g_free(m->fname);
80 g_free(m->caption);
6cc5e1a6
DB
81 g_free(m);
82 RET();
83}
84
85static void
86spawn_app(GtkWidget *widget, gpointer data)
87{
88 GError *error = NULL;
89
90 ENTER;
91 if (data) {
92 if (! g_spawn_command_line_async(data, &error) ) {
93 ERR("can't spawn %s\nError is %s\n", (char *)data, error->message);
94 g_error_free (error);
95 }
96 }
97 RET();
98}
99
100
101static void
102run_command(GtkWidget *widget, void (*cmd)(void))
103{
104 ENTER;
105 cmd();
106 RET();
107}
108
109static void
110menu_pos(GtkMenu *menu, gint *x, gint *y, gboolean *push_in, GtkWidget *widget)
111{
112 int ox, oy, w, h;
113 Plugin *p;
114
115 ENTER;
116 p = g_object_get_data(G_OBJECT(widget), "plugin");
117 gdk_window_get_origin(widget->window, &ox, &oy);
118 w = GTK_WIDGET(menu)->requisition.width;
119 h = GTK_WIDGET(menu)->requisition.height;
120 if (p->panel->orientation == ORIENT_HORIZ) {
121 *x = ox;
122 if (*x + w > gdk_screen_width())
123 *x = ox + widget->allocation.width - w;
124 *y = oy - h;
125 if (*y < 0)
126 *y = oy + widget->allocation.height;
127 } else {
128 *x = ox + widget->allocation.width;
129 if (*x > gdk_screen_width())
130 *x = ox - w;
131 *y = oy;
132 if (*y + h > gdk_screen_height())
133 *y = oy + widget->allocation.height - h;
134 }
135 DBG("widget: x,y=%d,%d w,h=%d,%d\n", ox, oy,
136 widget->allocation.width, widget->allocation.height );
137 DBG("w-h %d %d\n", w, h);
138 *push_in = TRUE;
139 RET();
140}
141
142static void
143reload_system_menu( GtkMenu* menu )
144{
145 GList *children, *child;
146 GtkMenuItem* item;
147 GtkWidget* sub_menu;
148 gint idx;
149 children = gtk_container_get_children( GTK_CONTAINER(menu) );
150 for( child = children, idx = 0; child; child = child->next, ++idx ) {
151 item = GTK_MENU_ITEM( child->data );
152 if( ptk_app_menu_item_has_data( item ) ) {
153 do {
154 item = GTK_MENU_ITEM( child->data );
155 child = child->next;
156 gtk_widget_destroy( GTK_WIDGET(item) );
157 }while( child && ptk_app_menu_item_has_data( child->data ) );
158 ptk_app_menu_insert_items( menu, idx );
159 if( ! child )
160 break;
161 }
162 else if( ( sub_menu = gtk_menu_item_get_submenu( item ) ) ) {
163 reload_system_menu( GTK_MENU(sub_menu) );
164 }
165 }
166 g_list_free( children );
167}
168
169static void show_menu( GtkWidget* widget, Plugin* p, int btn, guint32 time )
170{
171 menup* m = (menup*)p->priv;
172 /* reload system menu items if needed */
173 if( m->has_system_menu && ptk_app_menu_need_reload() ) {
174 GSList* l;
175 /* FIXME: Reload all system menus here.
176 This is dirty, but I don't know any better way. */
177 for( l = p->panel->system_menus; l; l = l->next ) {
178 Plugin* _p = (Plugin*)l->data;
179 menup* _m = (menup*)_p->priv;
180 reload_system_menu( GTK_MENU(_m->menu) );
181 }
182 }
183 gtk_menu_popup(GTK_MENU(m->menu),
184 NULL, NULL,
185 (GtkMenuPositionFunc)menu_pos, widget,
186 btn, time);
187}
188
189static gboolean
190my_button_pressed(GtkWidget *widget, GdkEventButton *event, Plugin* plugin)
191{
192 ENTER;
193
194 if( event->button == 3 ) /* right button */
195 {
196 GtkMenu* popup = lxpanel_get_panel_menu( plugin->panel, plugin, FALSE );
197 gtk_menu_popup( popup, NULL, NULL, NULL, NULL, event->button, event->time );
198 return TRUE;
199 }
200
201 if ((event->type == GDK_BUTTON_PRESS)
202 && (event->x >=0 && event->x < widget->allocation.width)
203 && (event->y >=0 && event->y < widget->allocation.height)) {
204 show_menu( widget, plugin, event->button, event->time );
205 }
206 RET(TRUE);
207}
208
209gboolean show_system_menu( gpointer system_menu )
210{
211 Plugin* p = (Plugin*)system_menu;
212 menup* m = (menup*)p->priv;
213 show_menu( m->bg, p, 0, GDK_CURRENT_TIME );
214 return FALSE;
215}
216
217static GtkWidget *
218make_button(Plugin *p, gchar *fname, gchar *name, GdkColor* tint, GtkWidget *menu)
219{
220 int w, h;
05ddbe60 221 char* title = NULL;
6cc5e1a6
DB
222 menup *m;
223
224 ENTER;
225 m = (menup *)p->priv;
226 m->menu = menu;
227 if (p->panel->orientation == ORIENT_HORIZ) {
228 w = 10000;
229 h = p->panel->ah;
230 } else {
231 w = p->panel->aw;
232 h = 10000;
233 }
05ddbe60
DB
234
235 if( name )
236 {
237 /* load the name from *.directory file if needed */
238 if( g_str_has_suffix( name, ".directory" ) )
239 {
240 GKeyFile* kf = g_key_file_new();
67aeed17 241 char* dir_file = g_build_filename( "desktop-directories", name, NULL );
05ddbe60
DB
242 if( g_key_file_load_from_data_dirs( kf, dir_file, NULL, 0, NULL ) )
243 {
244 title = g_key_file_get_locale_string( kf, "Desktop Entry", "Name", NULL, NULL );
245 }
246 g_free( dir_file );
247 g_key_file_free( kf );
248 }
249 else
250 title = name;
251
252 /* FIXME: handle orientation problems */
253 if (p->panel->usefontcolor)
254 m->bg = fb_button_new_from_file_with_colorlabel(fname, w, h, gcolor2rgb24(tint),
255 p->panel->fontcolor, TRUE, title);
256 else
257 m->bg = fb_button_new_from_file_with_label(fname, w, h, gcolor2rgb24(tint), TRUE, title);
258
259 if( title != name )
260 g_free( title );
261 }
262 else
263 {
264 m->bg = fb_button_new_from_file(fname, w, h, gcolor2rgb24(tint), TRUE );
265 }
266
6cc5e1a6
DB
267 gtk_widget_show(m->bg);
268 gtk_box_pack_start(GTK_BOX(m->box), m->bg, FALSE, FALSE, 0);
269
270 m->handler_id = g_signal_connect (G_OBJECT (m->bg), "button-press-event",
271 G_CALLBACK (my_button_pressed), p);
272 g_object_set_data(G_OBJECT(m->bg), "plugin", p);
273
274 RET(m->bg);
275}
276
277
278static GtkWidget *
279read_item(Plugin *p, char** fp)
280{
281 line s;
282 gchar *name, *fname, *action;
283 GtkWidget *item;
284 menup *m = (menup *)p->priv;
285 Command *cmd_entry = NULL;
286
287 ENTER;
288 s.len = 256;
289 name = fname = action = NULL;
290
291 if( fp )
292 {
293 while (lxpanel_get_line(fp, &s) != LINE_BLOCK_END) {
294 if (s.type == LINE_VAR) {
295 if (!g_ascii_strcasecmp(s.t[0], "image"))
296 fname = expand_tilda(s.t[1]);
297 else if (!g_ascii_strcasecmp(s.t[0], "name"))
298 name = g_strdup(s.t[1]);
299 else if (!g_ascii_strcasecmp(s.t[0], "action"))
300 action = g_strdup(s.t[1]);
301 else if (!g_ascii_strcasecmp(s.t[0], "command")) {
302 Command *tmp;
303
304 for (tmp = commands; tmp->name; tmp++) {
305 if (!g_ascii_strcasecmp(s.t[1], tmp->name)) {
306 cmd_entry = tmp;
307 break;
308 }
309 }
310 } else {
311 ERR( "menu/item: unknown var %s\n", s.t[0]);
312 goto error;
313 }
314 }
315 }
316 }
317 /* menu button */
318 if( cmd_entry ) /* built-in commands */
319 {
320 item = gtk_image_menu_item_new_with_label( _(cmd_entry->disp_name) );
321 g_signal_connect(G_OBJECT(item), "activate", (GCallback)run_command, cmd_entry->cmd);
322 }
323 else
324 {
325 item = gtk_image_menu_item_new_with_label(name ? name : "");
326 if (action) {
327 g_signal_connect(G_OBJECT(item), "activate", (GCallback)spawn_app, action);
328 }
329 }
330 gtk_container_set_border_width(GTK_CONTAINER(item), 0);
331 g_free(name);
332 if (fname) {
333 GtkWidget *img;
334
67aeed17 335 img = _gtk_image_new_from_file_scaled(fname, m->iconsize, m->iconsize, TRUE);
6cc5e1a6
DB
336 gtk_widget_show(img);
337 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
338 g_free(fname);
339 }
340 RET(item);
341
342 error:
343 g_free(fname);
344 g_free(name);
345 g_free(action);
346 RET(NULL);
347}
348
349static GtkWidget *
350read_separator(Plugin *p, char **fp)
351{
352 line s;
353
354 ENTER;
355 s.len = 256;
356 if( fp )
357 {
358 while (lxpanel_get_line(fp, &s) != LINE_BLOCK_END) {
359 ERR("menu: error - separator can not have paramteres\n");
360 RET(NULL);
361 }
362 }
363 RET(gtk_separator_menu_item_new());
364}
365
366static gboolean on_idle( Panel* p )
367{
368 GSList* l;
369 /* Reload all system menus here.
370 This is dirty, but I don't know any better way. */
371 for( l = p->system_menus; l; l = l->next ) {
372 Plugin* _p = (Plugin*)l->data;
373 menup* _m = (menup*)_p->priv;
374 reload_system_menu( GTK_MENU(_m->menu) );
375 }
376 idle_loader = 0;
377 return FALSE; /* remove the handler */
378}
379
380static void
381read_system_menu(GtkMenu* menu, Plugin *p, char** fp)
382{
383 line s;
384 menup *m = (menup *)p->priv;
385 GtkWidget* fake;
386
387 ENTER;
388 s.len = 256;
389 if( fp )
390 {
391 while (lxpanel_get_line(fp, &s) != LINE_BLOCK_END) {
392 ERR("menu: error - system can not have paramteres\n");
393 RET();
394 }
395 }
396
397 /* ptk_app_menu_insert_items( menu, -1 ); */
398 /* Don't load the real system menu here to speed up startup.
399 * Let's add a fake item to cheat PtkAppMenu as a place holder,
400 * and we utilize reload_system_menu() to load the real menu later. */
401 fake = gtk_separator_menu_item_new();
402 PTK_APP_MENU_ITEM_ID = g_quark_from_static_string( "PtkAppMenuItem" );
403 g_object_set_qdata( fake, PTK_APP_MENU_ITEM_ID, GUINT_TO_POINTER(TRUE) );
404 gtk_menu_shell_append( menu, fake);
405
406 m->has_system_menu = TRUE;
407
408 p->panel->system_menus = g_slist_append( p->panel->system_menus, p );
409
410 if( idle_loader == 0 ) /* delay the loading, and do it in idle handler */
411 idle_loader = g_idle_add( (GSourceFunc)on_idle, p->panel );
412
413 RET();
414}
415
416static void
417read_include(Plugin *p, char **fp)
418{
419 ENTER;
420#if 0
421 gchar *name;
422 line s;
423 menup *m = (menup *)p->priv;
424 /* FIXME: this is disabled */
425 ENTER;
426 s.len = 256;
427 name = NULL;
428 if( fp )
429 {
430 while (lxpanel_get_line(fp, &s) != LINE_BLOCK_END) {
431 if (s.type == LINE_VAR) {
432 if (!g_ascii_strcasecmp(s.t[0], "name"))
433 name = expand_tilda(s.t[1]);
434 else {
435 ERR( "menu/include: unknown var %s\n", s.t[0]);
436 RET();
437 }
438 }
439 }
440 }
441 if ((fp = fopen(name, "r"))) {
442 LOG(LOG_INFO, "Including %s\n", name);
443 m->files = g_slist_prepend(m->files, fp);
444 p->fp = fp;
445 } else {
446 ERR("Can't include %s\n", name);
447 }
448 if (name) g_free(name);
449#endif
450 RET();
451}
452
453static GtkWidget *
454read_submenu(Plugin *p, char** fp, gboolean as_item)
455{
456 line s;
457 GtkWidget *mi, *menu;
458 gchar name[256], *fname;
459 menup *m = (menup *)p->priv;
460 GdkColor color={0, 0, 36 * 0xffff / 0xff, 96 * 0xffff / 0xff};
461
462 ENTER;
05ddbe60
DB
463
464
6cc5e1a6
DB
465 s.len = 256;
466 menu = gtk_menu_new ();
467 gtk_container_set_border_width(GTK_CONTAINER(menu), 0);
468
469 fname = 0;
470 name[0] = 0;
471 while (lxpanel_get_line(fp, &s) != LINE_BLOCK_END) {
472 if (s.type == LINE_BLOCK_START) {
473 mi = NULL;
474 if (!g_ascii_strcasecmp(s.t[0], "item")) {
475 mi = read_item(p, fp);
476 } else if (!g_ascii_strcasecmp(s.t[0], "separator")) {
477 mi = read_separator(p, fp);
478 } else if (!g_ascii_strcasecmp(s.t[0], "system")) {
479 read_system_menu(GTK_MENU(menu), p, fp); /* add system menu items */
480 continue;
481 } else if (!g_ascii_strcasecmp(s.t[0], "menu")) {
482 mi = read_submenu(p, fp, TRUE);
483 } else if (!g_ascii_strcasecmp(s.t[0], "include")) {
484 read_include(p, fp);
485 continue;
486 } else {
487 ERR("menu: unknown block %s\n", s.t[0]);
488 goto error;
489 }
490 if (!mi) {
491 ERR("menu: can't create menu item\n");
492 goto error;
493 }
494 gtk_widget_show(mi);
495 gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi);
496 } else if (s.type == LINE_VAR) {
05ddbe60 497 m->config_start = *fp;
6cc5e1a6
DB
498 if (!g_ascii_strcasecmp(s.t[0], "image"))
499 fname = expand_tilda(s.t[1]);
500 else if (!g_ascii_strcasecmp(s.t[0], "name"))
501 strcpy(name, s.t[1]);
05ddbe60 502 /* FIXME: tintcolor will not be saved. */
6cc5e1a6
DB
503 else if (!g_ascii_strcasecmp(s.t[0], "tintcolor"))
504 gdk_color_parse( s.t[1], &color);
505 else {
506 ERR("menu: unknown var %s\n", s.t[0]);
507 goto error;
508 }
509 } else if (s.type == LINE_NONE) {
510 if (m->files) {
511 /*
512 fclose(p->fp);
513 p->fp = m->files->data;
514 */
515 m->files = g_slist_delete_link(m->files, m->files);
516 }
517 } else {
518 ERR("menu: illegal in this context %s\n", s.str);
519 goto error;
520 }
521 }
522 if (as_item) {
523 mi = gtk_image_menu_item_new_with_label(name ? name : "");
524 if (fname) {
525 GtkWidget *img;
67aeed17 526 img = _gtk_image_new_from_file_scaled(fname, m->iconsize, m->iconsize, TRUE);
6cc5e1a6
DB
527 gtk_widget_show(img);
528 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(mi), img);
529 g_free(fname);
530 }
531 gtk_menu_item_set_submenu (GTK_MENU_ITEM (mi), menu);
532 RET(mi);
533 } else {
05ddbe60
DB
534 m->fname = g_strdup(fname);
535 m->caption = g_strdup(name);
6cc5e1a6
DB
536 mi = make_button(p, fname, name, &color, menu);
537 if (fname)
538 g_free(fname);
539 RET(mi);
540 }
541
542 error:
543 // FIXME: we need to recursivly destroy all child menus and their items
544 gtk_widget_destroy(menu);
545 g_free(fname);
546 g_free(name);
547 RET(NULL);
548}
549
550static int
551menu_constructor(Plugin *p, char **fp)
552{
553 menup *m;
554 static char default_config[] =
555 "image=" PACKAGE_DATA_DIR "/lxpanel/images/my-computer.png\n"
556 "system {\n"
557 "}\n"
558 "separator {\n"
559 "}\n"
560 "item {\n"
561 "command=run\n"
562 "}\n"
563 "separator {\n"
564 "}\n"
565 "item {\n"
566 "image=gnome-logout\n"
567 "command=logout\n"
568 "}\n"
569 "}\n";
570 char *config_start, *config_end, *config_default = default_config;
571
572 ENTER;
573 m = g_new0(menup, 1);
574 g_return_val_if_fail(m != NULL, 0);
05ddbe60
DB
575 m->fname = NULL;
576 m->caption = NULL;
6cc5e1a6
DB
577 p->priv = m;
578
579 //gtk_rc_parse_string(menu_rc);
580 if (p->panel->orientation == ORIENT_HORIZ)
581 m->paneliconsize = p->panel->ah
582 - 2* GTK_WIDGET(p->panel->box)->style->ythickness;
583 else
584 m->paneliconsize = p->panel->aw
585 - 2* GTK_WIDGET(p->panel->box)->style->xthickness;
586 m->iconsize = 22;
587
588 m->box = gtk_hbox_new(FALSE, 0);
589 gtk_container_set_border_width(GTK_CONTAINER(m->box), 0);
590
591 if( ! fp )
592 fp = &config_default;
593
05ddbe60 594 m->config_start = *fp;
6cc5e1a6
DB
595 if (!read_submenu(p, fp, FALSE)) {
596 ERR("menu: plugin init failed\n");
597 goto error;
598 }
05ddbe60
DB
599 m->config_end = *fp - 1;
600 while( *m->config_end != '}' && m->config_end > m->config_start ) {
601 --m->config_end;
6cc5e1a6 602 }
05ddbe60
DB
603 if( *m->config_end == '}' )
604 --m->config_end;
6cc5e1a6 605
05ddbe60
DB
606 m->config_data = g_strndup( m->config_start,
607 (m->config_end-m->config_start) );
6cc5e1a6
DB
608
609 p->pwid = m->box;
610
611 RET(1);
612
613 error:
614 menu_destructor(p);
615 RET(0);
616}
617
618static void save_config( Plugin* p, FILE* fp )
619{
620 menup* menu = (menup*)p->priv;
05ddbe60
DB
621 lxpanel_put_str( fp, "name", menu->caption );
622 lxpanel_put_str( fp, "image", menu->fname );
6cc5e1a6
DB
623 if( menu->config_data ) {
624 char** lines = g_strsplit( menu->config_data, "\n", 0 );
625 char** line;
626 for( line = lines; *line; ++line ) {
627 g_strstrip( *line );
628 if( **line )
629 lxpanel_put_line( fp, *line );
630 }
631 g_strfreev( lines );
632 }
633}
634
05ddbe60
DB
635static void apply_config(Plugin* p)
636{
637 /* FIXME: update menu for new setting */
638}
639
640static void menu_config( Plugin *p, GtkWindow* parent )
641{
642 GtkWidget* dlg;
643 menup* menu = (menup*)p->priv;
644 dlg = create_generic_config_dlg( _(p->class->name),
645 GTK_WIDGET(parent),
646 (GSourceFunc) apply_config, (gpointer) p,
647 _("Icon"), &menu->fname, G_TYPE_STRING,
648 _("Caption"), &menu->caption, G_TYPE_STRING,
649 NULL );
650 gtk_window_present( GTK_WINDOW(dlg) );
651}
652
6cc5e1a6
DB
653PluginClass menu_plugin_class = {
654 fname: NULL,
655 count: 0,
656
657 type : "menu",
658 name : N_("Menu"),
659 version: "1.0",
660 description : N_("Provide Menu"),
661
662 constructor : menu_constructor,
663 destructor : menu_destructor,
b3df3353 664 /* config : menu_config, */
6cc5e1a6
DB
665 save : save_config
666};
667