Adding upstream version 0.9.0.
[debian/lxpanel.git] / src / panel.c
CommitLineData
7dd482c5 1/*
0688b017
AG
2 * Copyright (C) 2006-2010 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
3 * 2006-2008 Jim Huang <jserv.tw@gmail.com>
4 * 2008 Fred Chien <fred@lxde.org>
5 * 2009 Jürgen Hötzel <juergen@archlinux.org>
6 * 2009-2010 Marty Jack <martyj19@comcast.net>
7 * 2010 Lajos Kamocsay <lajos@panka.com>
8 * 2012 Piotr Sipika <Piotr.Sipika@gmail.com>
9 * 2012-2013 Henry Gebhardt <hsggebhardt@gmail.com>
10 * 2012 Jack Chen <speed.up08311990@gmail.com>
11 * 2012 Rafał Mużyło <galtgendo@gmail.com>
12 * 2012 Michael Rawson <michaelrawson76@gmail.com>
13 * 2012 Julien Lavergne <julien.lavergne@gmail.com>
14 * 2013 Rouslan <rouslan-k@users.sourceforge.net>
15 * 2013 peadaredwards <peadaredwards@users.sourceforge.net>
7a1c5048
AG
16 * 2014-2016 Andriy Grytsenko <andrej@rep.kiev.ua>
17 * 2015 Rafał Mużyło <galtgendo@gmail.com>
18 * 2015 Hanno Zulla <hhz@users.sf.net>
0688b017
AG
19 *
20 * This file is a part of LXPanel project.
6cc5e1a6
DB
21 *
22 * This program is free software; you can redistribute it and/or modify
23 * it under the terms of the GNU General Public License as published by
24 * the Free Software Foundation; either version 2 of the License, or
25 * (at your option) any later version.
26 *
27 * This program is distributed in the hope that it will be useful,
28 * but WITHOUT ANY WARRANTY; without even the implied warranty of
29 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30 * GNU General Public License for more details.
31 *
32 * You should have received a copy of the GNU General Public License
33 * along with this program; if not, write to the Free Software Foundation,
34 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
35 */
36
37#ifdef HAVE_CONFIG_H
38#include <config.h>
39#endif
40
41#include <glib/gi18n.h>
42#include <stdlib.h>
7486d297 43#include <glib/gstdio.h>
6cc5e1a6
DB
44#include <sys/types.h>
45#include <sys/stat.h>
46#include <unistd.h>
47#include <errno.h>
48#include <locale.h>
49#include <string.h>
50#include <gdk/gdkx.h>
6b775dbb 51#include <libfm/fm-gtk.h>
f7ecd6ce 52#include <cairo-xlib.h>
6cc5e1a6 53
6b775dbb
AG
54#define __LXPANEL_INTERNALS__
55
56#include "private.h"
6cc5e1a6 57#include "misc.h"
7a1c5048 58#include "space.h"
6cc5e1a6 59
6cc5e1a6
DB
60#include "lxpanelctl.h"
61#include "dbg.h"
f7ecd6ce 62#include "gtk-compat.h"
6cc5e1a6 63
6cc5e1a6
DB
64gchar *cprofile = "default";
65
6cc5e1a6
DB
66GSList* all_panels = NULL; /* a single-linked list storing all panels */
67
7486d297
DB
68gboolean is_in_lxde = FALSE;
69
f7ecd6ce
AG
70static GtkWindowGroup* win_grp = NULL; /* window group used to limit the scope of model dialog. */
71
72static gulong monitors_handler = 0;
73
74static void panel_start_gui(LXPanel *p, config_setting_t *list);
6b775dbb
AG
75static void ah_start(LXPanel *p);
76static void ah_stop(LXPanel *p);
f7ecd6ce
AG
77static void _panel_update_background(LXPanel * p, gboolean enforce);
78
79enum
80{
81 ICON_SIZE_CHANGED,
82 PANEL_FONT_CHANGED,
83 N_SIGNALS
84};
85
86static guint signals[N_SIGNALS];
2ba86315 87
6b775dbb
AG
88G_DEFINE_TYPE(PanelToplevel, lxpanel, GTK_TYPE_WINDOW);
89
90static void lxpanel_finalize(GObject *object)
2ba86315 91{
6b775dbb
AG
92 LXPanel *self = LXPANEL(object);
93 Panel *p = self->priv;
94
95 if( p->config_changed )
96 lxpanel_config_save( self );
97 config_destroy(p->config);
98
f7ecd6ce 99 //XFree(p->workarea);
6b775dbb
AG
100 g_free( p->background_file );
101 g_slist_free( p->system_menus );
102
103 g_free( p->name );
104 g_free(p);
105
106 G_OBJECT_CLASS(lxpanel_parent_class)->finalize(object);
107}
108
f7ecd6ce 109static void panel_stop_gui(LXPanel *self)
6cc5e1a6 110{
6b775dbb
AG
111 Panel *p = self->priv;
112 Display *xdisplay;
113
f7ecd6ce 114 g_debug("panel_stop_gui on '%s'", p->name);
6b775dbb
AG
115 if (p->autohide)
116 ah_stop(self);
117
118 if (p->pref_dialog != NULL)
119 gtk_widget_destroy(p->pref_dialog);
120 p->pref_dialog = NULL;
121
122 if (p->plugin_pref_dialog != NULL)
123 /* just close the dialog, it will do all required cleanup */
124 gtk_dialog_response(GTK_DIALOG(p->plugin_pref_dialog), GTK_RESPONSE_CLOSE);
125
6b775dbb
AG
126
127 if (p->initialized)
128 {
129 gtk_window_group_remove_window(win_grp, GTK_WINDOW(self));
130 xdisplay = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
131 gdk_flush();
132 XFlush(xdisplay);
133 XSync(xdisplay, True);
134 p->initialized = FALSE;
135 }
f7ecd6ce
AG
136 if (p->surface != NULL)
137 {
138 cairo_surface_destroy(p->surface);
139 p->surface = NULL;
140 }
6b775dbb 141
7dd482c5
AG
142 if (p->background_update_queued)
143 {
144 g_source_remove(p->background_update_queued);
145 p->background_update_queued = 0;
146 }
f7ecd6ce
AG
147 if (p->strut_update_queued)
148 {
149 g_source_remove(p->strut_update_queued);
150 p->strut_update_queued = 0;
151 }
8713e384
AG
152 if (p->reconfigure_queued)
153 {
154 g_source_remove(p->reconfigure_queued);
155 p->reconfigure_queued = 0;
156 }
f7ecd6ce
AG
157
158 if (gtk_bin_get_child(GTK_BIN(self)))
159 {
160 gtk_widget_destroy(p->box);
161 p->box = NULL;
162 }
163}
7dd482c5 164
f7ecd6ce
AG
165#if GTK_CHECK_VERSION(3, 0, 0)
166static void lxpanel_destroy(GtkWidget *object)
167#else
168static void lxpanel_destroy(GtkObject *object)
169#endif
170{
171 LXPanel *self = LXPANEL(object);
172
173 panel_stop_gui(self);
174
175#if GTK_CHECK_VERSION(3, 0, 0)
176 GTK_WIDGET_CLASS(lxpanel_parent_class)->destroy(object);
177#else
6b775dbb 178 GTK_OBJECT_CLASS(lxpanel_parent_class)->destroy(object);
f7ecd6ce 179#endif
6b775dbb
AG
180}
181
7dd482c5 182static gboolean idle_update_background(gpointer p)
2ba86315 183{
7dd482c5
AG
184 LXPanel *panel = LXPANEL(p);
185
186 if (g_source_is_destroyed(g_main_current_source()))
187 return FALSE;
188
6b775dbb 189 /* Panel could be destroyed while background update scheduled */
6b775dbb 190 if (gtk_widget_get_realized(p))
6b775dbb
AG
191 {
192 gdk_display_sync( gtk_widget_get_display(p) );
f7ecd6ce 193 _panel_update_background(panel, FALSE);
6b775dbb 194 }
7dd482c5 195 panel->priv->background_update_queued = 0;
6b775dbb
AG
196
197 return FALSE;
2ba86315 198}
6b775dbb 199
7dd482c5
AG
200void _panel_queue_update_background(LXPanel *panel)
201{
202 if (panel->priv->background_update_queued)
203 return;
204 panel->priv->background_update_queued = g_idle_add_full(G_PRIORITY_HIGH,
205 idle_update_background,
206 panel, NULL);
207}
208
f7ecd6ce
AG
209static gboolean idle_update_strut(gpointer p)
210{
211 LXPanel *panel = LXPANEL(p);
212
213 if (g_source_is_destroyed(g_main_current_source()))
214 return FALSE;
215
216 _panel_set_wm_strut(panel);
217 panel->priv->strut_update_queued = 0;
218
219 return FALSE;
220}
221
6b775dbb 222static void lxpanel_realize(GtkWidget *widget)
2ba86315 223{
6b775dbb
AG
224 GTK_WIDGET_CLASS(lxpanel_parent_class)->realize(widget);
225
7dd482c5 226 _panel_queue_update_background(LXPANEL(widget));
2ba86315
DB
227}
228
6b775dbb 229static void lxpanel_style_set(GtkWidget *widget, GtkStyle* prev)
2ba86315 230{
6b775dbb
AG
231 GTK_WIDGET_CLASS(lxpanel_parent_class)->style_set(widget, prev);
232
233 /* FIXME: This dirty hack is used to fix the background of systray... */
7dd482c5 234 _panel_queue_update_background(LXPANEL(widget));
6b775dbb
AG
235}
236
237static void lxpanel_size_request(GtkWidget *widget, GtkRequisition *req)
238{
f7ecd6ce
AG
239 LXPanel *panel = LXPANEL(widget);
240 Panel *p = panel->priv;
241 GdkRectangle rect;
6b775dbb 242
7a1c5048 243#if !GTK_CHECK_VERSION(3, 0, 0)
6b775dbb 244 GTK_WIDGET_CLASS(lxpanel_parent_class)->size_request(widget, req);
7a1c5048
AG
245#else
246 GTK_WIDGET_CLASS(lxpanel_parent_class)->get_preferred_width(widget, &req->width, &req->width);
247 GTK_WIDGET_CLASS(lxpanel_parent_class)->get_preferred_height(widget, &req->height, &req->height);
248#endif
6b775dbb
AG
249
250 if (!p->visible)
251 /* When the panel is in invisible state, the content box also got hidden, thus always
252 * report 0 size. Ask the content box instead for its size. */
253 gtk_widget_size_request(p->box, req);
254
f7ecd6ce
AG
255 rect.width = req->width;
256 rect.height = req->height;
257 _calculate_position(panel, &rect);
258 req->width = rect.width;
259 req->height = rect.height;
7a1c5048
AG
260 /* update data ahead of configuration request */
261 p->cw = rect.width;
262 p->ch = rect.height;
6b775dbb
AG
263}
264
7a1c5048
AG
265#if GTK_CHECK_VERSION(3, 0, 0)
266static void
267lxpanel_get_preferred_width (GtkWidget *widget,
268 gint *minimal_width,
269 gint *natural_width)
270{
271 GtkRequisition requisition;
272
273 lxpanel_size_request (widget, &requisition);
274
275 if (minimal_width)
276 *minimal_width = requisition.width;
277 if (natural_width)
278 *natural_width = requisition.width;
279}
280
281static void
282lxpanel_get_preferred_height (GtkWidget *widget,
283 gint *minimal_height,
284 gint *natural_height)
285{
286 GtkRequisition requisition;
287
288 lxpanel_size_request (widget, &requisition);
289
290 if (minimal_height)
291 *minimal_height = requisition.height;
292 if (natural_height)
293 *natural_height = requisition.height;
294}
295#endif
296
6b775dbb
AG
297static void lxpanel_size_allocate(GtkWidget *widget, GtkAllocation *a)
298{
f7ecd6ce
AG
299 LXPanel *panel = LXPANEL(widget);
300 Panel *p = panel->priv;
301 GdkRectangle rect;
302 gint x, y;
6b775dbb 303
7a1c5048
AG
304 /* some WM like mwm are too generous giving us space more that requested
305 so let correct it right now, as much as we can */
306 a->width = MAX(8, MIN(p->cw, a->width));
307 a->height = MAX(8, MIN(p->ch, a->height));
308
6b775dbb
AG
309 GTK_WIDGET_CLASS(lxpanel_parent_class)->size_allocate(widget, a);
310
311 if (p->widthtype == WIDTH_REQUEST)
312 p->width = (p->orientation == GTK_ORIENTATION_HORIZONTAL) ? a->width : a->height;
313 if (p->heighttype == HEIGHT_REQUEST)
314 p->height = (p->orientation == GTK_ORIENTATION_HORIZONTAL) ? a->height : a->width;
6b775dbb 315
f7ecd6ce
AG
316 if (!gtk_widget_get_realized(widget))
317 return;
318
319 rect = *a;
320 /* get real coords since a contains 0, 0 */
321 gdk_window_get_origin(gtk_widget_get_window(widget), &x, &y);
322 _calculate_position(panel, &rect);
323 p->ax = rect.x;
324 p->ay = rect.y;
325
326 if (a->width != p->aw || a->height != p->ah || x != p->ax || y != p->ay)
6b775dbb 327 {
f7ecd6ce
AG
328 p->aw = a->width;
329 p->ah = a->height;
7a1c5048 330 gdk_window_move_resize(gtk_widget_get_window(widget), p->ax, p->ay, p->aw, p->ah);
f7ecd6ce
AG
331 /* SF bug #708: strut update does not work while in size allocation */
332 if (!panel->priv->strut_update_queued)
333 panel->priv->strut_update_queued = g_idle_add_full(G_PRIORITY_HIGH,
334 idle_update_strut,
335 panel, NULL);
336 _panel_queue_update_background(panel);
7dd482c5 337 }
f7ecd6ce
AG
338
339 if (gtk_widget_get_mapped(widget))
340 _panel_establish_autohide(panel);
6b775dbb
AG
341}
342
343static gboolean lxpanel_configure_event (GtkWidget *widget, GdkEventConfigure *e)
344{
345 Panel *p = LXPANEL(widget)->priv;
346
6b775dbb
AG
347 p->cw = e->width;
348 p->ch = e->height;
349 p->cx = e->x;
350 p->cy = e->y;
351
6b775dbb
AG
352 return GTK_WIDGET_CLASS(lxpanel_parent_class)->configure_event(widget, e);
353}
354
355static gboolean lxpanel_map_event(GtkWidget *widget, GdkEventAny *event)
356{
357 Panel *p = PLUGIN_PANEL(widget)->priv;
358
359 if (p->autohide)
360 ah_start(LXPANEL(widget));
361 return GTK_WIDGET_CLASS(lxpanel_parent_class)->map_event(widget, event);
362}
363
364/* Handler for "button_press_event" signal with Panel as parameter. */
365static gboolean lxpanel_button_press(GtkWidget *widget, GdkEventButton *event)
366{
7a1c5048
AG
367 LXPanel *panel = PLUGIN_PANEL(widget);
368
369 if ((event->state & gtk_accelerator_get_default_mod_mask()) != 0)
370 /* ignore clicks with modifiers */
371 return FALSE;
372
6b775dbb
AG
373 if (event->button == 3) /* right button */
374 {
7a1c5048 375 GtkMenu* popup = (GtkMenu*) lxpanel_get_plugin_menu(panel, NULL, FALSE);
6b775dbb
AG
376 gtk_menu_popup(popup, NULL, NULL, NULL, NULL, event->button, event->time);
377 return TRUE;
378 }
7a1c5048
AG
379 else if (event->button == 2) /* middle button */
380 {
381 Panel *p = panel->priv;
382 if (p->move_state == PANEL_MOVE_STOP)
383 {
384 gdk_window_get_origin(event->window, &p->move_x, &p->move_y);
385 p->move_x += event->x - p->ax;
386 p->move_y += event->y - p->ay;
387 p->move_state = PANEL_MOVE_DETECT;
388 p->move_device = event->device;
389 /* rest of work see in panel-plugin-move.c file */
390 return TRUE;
391 }
392 }
19ab5cea 393 return FALSE;
6b775dbb
AG
394}
395
396static void lxpanel_class_init(PanelToplevelClass *klass)
397{
398 GObjectClass *gobject_class = (GObjectClass *)klass;
f7ecd6ce 399#if !GTK_CHECK_VERSION(3, 0, 0)
6b775dbb 400 GtkObjectClass *gtk_object_class = (GtkObjectClass *)klass;
f7ecd6ce 401#endif
6b775dbb
AG
402 GtkWidgetClass *widget_class = (GtkWidgetClass *)klass;
403
404 gobject_class->finalize = lxpanel_finalize;
f7ecd6ce
AG
405#if GTK_CHECK_VERSION(3, 0, 0)
406 widget_class->destroy = lxpanel_destroy;
407#else
6b775dbb 408 gtk_object_class->destroy = lxpanel_destroy;
f7ecd6ce 409#endif
6b775dbb 410 widget_class->realize = lxpanel_realize;
7a1c5048
AG
411#if GTK_CHECK_VERSION(3, 0, 0)
412 widget_class->get_preferred_width = lxpanel_get_preferred_width;
413 widget_class->get_preferred_height = lxpanel_get_preferred_height;
414#else
6b775dbb 415 widget_class->size_request = lxpanel_size_request;
7a1c5048 416#endif
6b775dbb
AG
417 widget_class->size_allocate = lxpanel_size_allocate;
418 widget_class->configure_event = lxpanel_configure_event;
419 widget_class->style_set = lxpanel_style_set;
420 widget_class->map_event = lxpanel_map_event;
421 widget_class->button_press_event = lxpanel_button_press;
7a1c5048
AG
422 widget_class->button_release_event = _lxpanel_button_release;
423 widget_class->motion_notify_event = _lxpanel_motion_notify;
f7ecd6ce
AG
424
425 signals[ICON_SIZE_CHANGED] =
426 g_signal_new("icon-size-changed",
427 G_TYPE_FROM_CLASS(klass),
428 G_SIGNAL_RUN_LAST,
429 G_STRUCT_OFFSET(PanelToplevelClass, icon_size_changed),
430 NULL, NULL,
431 g_cclosure_marshal_VOID__VOID,
432 G_TYPE_NONE, 0, G_TYPE_NONE);
433
434 signals[PANEL_FONT_CHANGED] =
435 g_signal_new("panel-font-changed",
436 G_TYPE_FROM_CLASS(klass),
437 G_SIGNAL_RUN_LAST,
438 G_STRUCT_OFFSET(PanelToplevelClass, panel_font_changed),
439 NULL, NULL,
440 g_cclosure_marshal_VOID__VOID,
441 G_TYPE_NONE, 0, G_TYPE_NONE);
6b775dbb
AG
442}
443
444static void lxpanel_init(PanelToplevel *self)
445{
446 Panel *p = g_new0(Panel, 1);
447
448 self->priv = p;
449 p->topgwin = self;
f7ecd6ce 450 p->align = ALIGN_CENTER;
2ba86315
DB
451 p->edge = EDGE_NONE;
452 p->widthtype = WIDTH_PERCENT;
453 p->width = 100;
454 p->heighttype = HEIGHT_PIXEL;
455 p->height = PANEL_HEIGHT_DEFAULT;
6b775dbb 456 p->monitor = 0;
2ba86315
DB
457 p->setdocktype = 1;
458 p->setstrut = 1;
459 p->round_corners = 0;
460 p->autohide = 0;
461 p->visible = TRUE;
462 p->height_when_hidden = 2;
463 p->transparent = 0;
32a67dc7
DB
464 p->alpha = 255;
465 gdk_color_parse("white", &p->gtintcolor);
466 p->tintcolor = gcolor2rgb24(&p->gtintcolor);
2ba86315
DB
467 p->usefontcolor = 0;
468 p->fontcolor = 0x00000000;
514580cf
DB
469 p->usefontsize = 0;
470 p->fontsize = 10;
2ba86315
DB
471 p->spacing = 0;
472 p->icon_size = PANEL_ICON_SIZE;
aaccad27 473 p->icon_theme = gtk_icon_theme_get_default();
6b775dbb 474 p->config = config_new();
19ab5cea 475 p->defstyle = gtk_widget_get_default_style();
6b775dbb
AG
476}
477
478/* Allocate and initialize new Panel structure. */
7a1c5048 479static LXPanel* panel_allocate(GdkScreen *screen)
6b775dbb 480{
f7ecd6ce
AG
481 return g_object_new(LX_TYPE_PANEL,
482 "border-width", 0,
483 "decorated", FALSE,
484 "name", "PanelToplevel",
485 "resizable", FALSE,
486 "title", "panel",
487 "type-hint", GDK_WINDOW_TYPE_HINT_DOCK,
488 "window-position", GTK_WIN_POS_NONE,
7a1c5048 489 "screen", screen,
f7ecd6ce
AG
490 NULL);
491}
492
493void _panel_emit_icon_size_changed(LXPanel *p)
494{
495 g_signal_emit(p, signals[ICON_SIZE_CHANGED], 0);
496}
497
498void _panel_emit_font_changed(LXPanel *p)
499{
500 g_signal_emit(p, signals[PANEL_FONT_CHANGED], 0);
2ba86315
DB
501}
502
503/* Normalize panel configuration after load from file or reconfiguration. */
504static void panel_normalize_configuration(Panel* p)
505{
506 panel_set_panel_configuration_changed( p );
507 if (p->width < 0)
508 p->width = 100;
509 if (p->widthtype == WIDTH_PERCENT && p->width > 100)
510 p->width = 100;
511 p->heighttype = HEIGHT_PIXEL;
512 if (p->heighttype == HEIGHT_PIXEL) {
513 if (p->height < PANEL_HEIGHT_MIN)
514 p->height = PANEL_HEIGHT_MIN;
515 else if (p->height > PANEL_HEIGHT_MAX)
516 p->height = PANEL_HEIGHT_MAX;
517 }
6b775dbb 518 if (p->monitor < 0)
f7ecd6ce 519 p->monitor = -1;
2ba86315
DB
520 if (p->background)
521 p->transparent = 0;
6cc5e1a6 522}
6cc5e1a6 523
f7ecd6ce
AG
524gboolean _panel_edge_can_strut(LXPanel *panel, int edge, gint monitor, gulong *size)
525{
526 Panel *p;
527 GdkScreen *screen;
528 GdkRectangle rect;
529 GdkRectangle rect2;
530 gint n, i;
531 gulong s;
532
533 if (!gtk_widget_get_mapped(GTK_WIDGET(panel)))
534 return FALSE;
535
536 p = panel->priv;
537 /* Handle autohide case. EWMH recommends having the strut be the minimized size. */
538 if (p->autohide)
539 s = p->height_when_hidden;
540 else switch (edge)
541 {
542 case EDGE_LEFT:
543 case EDGE_RIGHT:
544 s = p->aw;
545 break;
546 case EDGE_TOP:
547 case EDGE_BOTTOM:
548 s = p->ah;
549 break;
550 default: /* error! */
551 return FALSE;
552 }
553 if (s == 0)
554 return FALSE; /* nothing to strut here */
555
556 if (monitor < 0) /* screen span */
557 {
558 if (G_LIKELY(size))
559 *size = s;
560 return TRUE;
561 }
562
563 screen = gtk_widget_get_screen(GTK_WIDGET(panel));
564 n = gdk_screen_get_n_monitors(screen);
565 if (monitor >= n) /* hidden now */
566 return FALSE;
567 gdk_screen_get_monitor_geometry(screen, monitor, &rect);
568 switch (edge)
569 {
570 case EDGE_LEFT:
571 rect.width = rect.x;
572 rect.x = 0;
573 s += rect.width;
574 break;
575 case EDGE_RIGHT:
576 rect.x += rect.width;
577 rect.width = gdk_screen_get_width(screen) - rect.x;
578 s += rect.width;
579 break;
580 case EDGE_TOP:
581 rect.height = rect.y;
582 rect.y = 0;
583 s += rect.height;
584 break;
585 case EDGE_BOTTOM:
586 rect.y += rect.height;
587 rect.height = gdk_screen_get_height(screen) - rect.y;
588 s += rect.height;
589 break;
590 default: ;
591 }
592 if (rect.height == 0 || rect.width == 0) ; /* on a border of monitor */
593 else
594 {
595 for (i = 0; i < n; i++)
596 {
597 if (i == monitor)
598 continue;
599 gdk_screen_get_monitor_geometry(screen, i, &rect2);
600 if (gdk_rectangle_intersect(&rect, &rect2, NULL))
601 /* that monitor lies over the edge */
602 return FALSE;
603 }
604 }
605 if (G_LIKELY(size))
606 *size = s;
607 return TRUE;
608}
609
2ba86315
DB
610/****************************************************
611 * panel's handlers for WM events *
612 ****************************************************/
6cc5e1a6
DB
613
614void panel_set_wm_strut(Panel *p)
615{
6b775dbb
AG
616 _panel_set_wm_strut(p->topgwin);
617}
618
619void _panel_set_wm_strut(LXPanel *panel)
620{
2ba86315 621 int index;
6b775dbb
AG
622 Display *xdisplay = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
623 Panel *p = panel->priv;
2ba86315
DB
624 gulong strut_size;
625 gulong strut_lower;
626 gulong strut_upper;
6cc5e1a6 627
6b775dbb 628 if (!gtk_widget_get_mapped(GTK_WIDGET(panel)))
6b775dbb
AG
629 return;
630 /* most wm's tend to ignore struts of unmapped windows, and that's how
631 * lxpanel hides itself. so no reason to set it. */
632 if (p->autohide && p->height_when_hidden <= 0)
633 return;
634
2ba86315
DB
635 /* Dispatch on edge to set up strut parameters. */
636 switch (p->edge)
6cc5e1a6 637 {
2ba86315
DB
638 case EDGE_LEFT:
639 index = 0;
2ba86315
DB
640 strut_lower = p->ay;
641 strut_upper = p->ay + p->ah;
642 break;
643 case EDGE_RIGHT:
644 index = 1;
2ba86315
DB
645 strut_lower = p->ay;
646 strut_upper = p->ay + p->ah;
647 break;
648 case EDGE_TOP:
649 index = 2;
2ba86315
DB
650 strut_lower = p->ax;
651 strut_upper = p->ax + p->aw;
652 break;
653 case EDGE_BOTTOM:
654 index = 3;
2ba86315
DB
655 strut_lower = p->ax;
656 strut_upper = p->ax + p->aw;
657 break;
658 default:
659 return;
6cc5e1a6 660 }
6cc5e1a6 661
2ba86315 662 /* Set up strut value in property format. */
7dd482c5 663 gulong desired_strut[12];
2ba86315 664 memset(desired_strut, 0, sizeof(desired_strut));
f7ecd6ce
AG
665 if (p->setstrut &&
666 _panel_edge_can_strut(panel, p->edge, p->monitor, &strut_size))
2ba86315
DB
667 {
668 desired_strut[index] = strut_size;
669 desired_strut[4 + index * 2] = strut_lower;
f7ecd6ce 670 desired_strut[5 + index * 2] = strut_upper - 1;
2ba86315
DB
671 }
672 else
673 {
674 strut_size = 0;
675 strut_lower = 0;
676 strut_upper = 0;
677 }
6cc5e1a6 678
2ba86315
DB
679 /* If strut value changed, set the property value on the panel window.
680 * This avoids property change traffic when the panel layout is recalculated but strut geometry hasn't changed. */
6b775dbb 681 if ((p->strut_size != strut_size) || (p->strut_lower != strut_lower) || (p->strut_upper != strut_upper) || (p->strut_edge != p->edge))
2ba86315
DB
682 {
683 p->strut_size = strut_size;
684 p->strut_lower = strut_lower;
685 p->strut_upper = strut_upper;
686 p->strut_edge = p->edge;
687
688 /* If window manager supports STRUT_PARTIAL, it will ignore STRUT.
689 * Set STRUT also for window managers that do not support STRUT_PARTIAL. */
690 if (strut_size != 0)
691 {
6b775dbb 692 XChangeProperty(xdisplay, p->topxwin, a_NET_WM_STRUT_PARTIAL,
2ba86315 693 XA_CARDINAL, 32, PropModeReplace, (unsigned char *) desired_strut, 12);
6b775dbb 694 XChangeProperty(xdisplay, p->topxwin, a_NET_WM_STRUT,
2ba86315
DB
695 XA_CARDINAL, 32, PropModeReplace, (unsigned char *) desired_strut, 4);
696 }
697 else
698 {
6b775dbb
AG
699 XDeleteProperty(xdisplay, p->topxwin, a_NET_WM_STRUT);
700 XDeleteProperty(xdisplay, p->topxwin, a_NET_WM_STRUT_PARTIAL);
2ba86315
DB
701 }
702 }
6cc5e1a6 703}
6cc5e1a6 704
f7ecd6ce
AG
705/****************************************************
706 * panel's handlers for GTK events *
707 ****************************************************/
708
709static void paint_root_pixmap(LXPanel *panel, cairo_t *cr)
6cc5e1a6 710{
f7ecd6ce
AG
711 /*
712 * this code was extracted from code for FbBg object
713 *
714 * Copyright (C) 2001, 2002 Ian McKellar <yakk@yakk.net>
715 * 2002 Sun Microsystems, Inc.
716 */
717 XGCValues gcv;
718 uint mask;
719 Window xroot;
720 GC gc;
721 Display *dpy;
722 Pixmap *prop;
723#if GTK_CHECK_VERSION(3, 0, 0)
724 cairo_surface_t *surface;
725#else
726 GdkPixmap *pixmap;
727#endif
728 Pixmap xpixmap;
729 Panel *p = panel->priv;
6b775dbb 730
f7ecd6ce
AG
731 dpy = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
732 xroot = DefaultRootWindow(dpy);
733 gcv.ts_x_origin = 0;
734 gcv.ts_y_origin = 0;
735 gcv.fill_style = FillTiled;
736 mask = GCTileStipXOrigin | GCTileStipYOrigin | GCFillStyle;
737 prop = get_xaproperty(xroot, a_XROOTPMAP_ID, XA_PIXMAP, NULL);
738 if (prop)
739 {
740 gcv.tile = *prop;
741 mask |= GCTile;
742 XFree(prop);
743 }
744 gc = XCreateGC(dpy, xroot, mask, &gcv);
745#if GTK_CHECK_VERSION(3, 0, 0)
746 xpixmap = XCreatePixmap(dpy, xroot, p->aw, p->ah,
747 DefaultDepth(dpy, DefaultScreen(dpy)));
748 surface = cairo_xlib_surface_create(dpy, xpixmap,
749 DefaultVisual(dpy, DefaultScreen(dpy)),
750 p->aw, p->ah);
751#else
752 pixmap = gdk_pixmap_new(gtk_widget_get_window(GTK_WIDGET(panel)),
753 p->aw, p->ah, -1);
754 xpixmap = gdk_x11_drawable_get_xid(pixmap);
755#endif
756 XSetTSOrigin(dpy, gc, -p->ax, -p->ay);
757 XFillRectangle(dpy, xpixmap, gc, 0, 0, p->aw, p->ah);
758 XFreeGC(dpy, gc);
759#if GTK_CHECK_VERSION(3, 0, 0)
760 cairo_set_source_surface(cr, surface, 0, 0);
761#else
762 gdk_cairo_set_source_pixmap(cr, pixmap, 0, 0);
763#endif
764 cairo_paint(cr);
765#if GTK_CHECK_VERSION(3, 0, 0)
766 cairo_surface_destroy(surface);
767 XFreePixmap(dpy, xpixmap);
768#else
769 g_object_unref(pixmap);
7486d297 770#endif
6cc5e1a6
DB
771}
772
f7ecd6ce 773void _panel_determine_background_pixmap(LXPanel * panel)
6cc5e1a6 774{
f7ecd6ce
AG
775#if GTK_CHECK_VERSION(3, 0, 0)
776 cairo_pattern_t *pattern;
777#else
778 GdkPixmap * pixmap = NULL;
779#endif
780 GtkWidget * widget = GTK_WIDGET(panel);
781 GdkWindow * window = gtk_widget_get_window(widget);
782 Panel * p = panel->priv;
783 cairo_t *cr;
784 gint x = 0, y = 0;
785
786 if (!p->background && !p->transparent)
787 goto not_paintable;
788 else if (p->aw <= 1 || p->ah <= 1)
789 goto not_paintable;
790 else if (p->surface == NULL)
aa0e9095 791 {
f7ecd6ce 792 GdkPixbuf *pixbuf = NULL;
6cc5e1a6 793
f7ecd6ce
AG
794 p->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, p->aw, p->ah);
795 cr = cairo_create(p->surface);
796 if (p->background)
aa0e9095 797 {
f7ecd6ce
AG
798 /* User specified background pixmap. */
799 pixbuf = gdk_pixbuf_new_from_file(p->background_file, NULL);
aa0e9095 800 }
f7ecd6ce
AG
801 if ((p->transparent && p->alpha != 255) || /* ignore it for opaque panel */
802 (pixbuf != NULL && gdk_pixbuf_get_has_alpha(pixbuf)))
aa0e9095 803 {
f7ecd6ce
AG
804 /* Transparent. Determine the appropriate value from the root pixmap. */
805 paint_root_pixmap(panel, cr);
aa0e9095 806 }
f7ecd6ce 807 if (pixbuf != NULL)
aa0e9095 808 {
f7ecd6ce
AG
809 gint w = gdk_pixbuf_get_width(pixbuf);
810 gint h = gdk_pixbuf_get_height(pixbuf);
811
812 /* Tile the image */
813 for (y = 0; y < p->ah; y += h)
814 for (x = 0; x < p->aw; x += w)
815 {
816 gdk_cairo_set_source_pixbuf(cr, pixbuf, x, y);
817 cairo_paint(cr);
6cc5e1a6 818 }
f7ecd6ce
AG
819 y = 0;
820 g_object_unref(pixbuf);
aa0e9095 821 }
f7ecd6ce 822 else
aa0e9095 823 {
f7ecd6ce
AG
824 /* Either color is set or image is invalid, fill the background */
825 gdk_cairo_set_source_color(cr, &p->gtintcolor);
826 cairo_paint_with_alpha(cr, p->transparent ? (double)p->alpha/255 : 1.0);
aa0e9095 827 }
f7ecd6ce 828 cairo_destroy(cr);
2ba86315 829 }
6cc5e1a6 830
f7ecd6ce 831 if (p->surface != NULL)
2ba86315 832 {
f7ecd6ce
AG
833 gtk_widget_set_app_paintable(widget, TRUE);
834#if GTK_CHECK_VERSION(3, 0, 0)
835 pattern = cairo_pattern_create_for_surface(p->surface);
836 gdk_window_set_background_pattern(window, pattern);
837 cairo_pattern_destroy(pattern);
838#else
839 pixmap = gdk_pixmap_new(window, p->aw, p->ah, -1);
840 cr = gdk_cairo_create(pixmap);
841 cairo_set_source_surface(cr, p->surface, 0, 0);
842 cairo_paint(cr);
843 cairo_destroy(cr);
844 gdk_window_set_back_pixmap(window, pixmap, FALSE);
845 g_object_unref(pixmap);
846#endif
6cc5e1a6 847 }
f7ecd6ce 848 else
6cc5e1a6 849 {
f7ecd6ce
AG
850not_paintable:
851 gtk_widget_set_app_paintable(widget, FALSE);
6cc5e1a6 852 }
f7ecd6ce 853}
6cc5e1a6 854
f7ecd6ce
AG
855void panel_determine_background_pixmap(Panel * panel, GtkWidget * widget, GdkWindow * window)
856{
857 if (GTK_WIDGET(panel->topgwin) != widget)
6cc5e1a6 858 {
f7ecd6ce
AG
859 /* Backward compatibility:
860 reset background for the child, using background of panel */
861 gtk_widget_set_app_paintable(widget, (panel->background || panel->transparent));
862#if GTK_CHECK_VERSION(3, 0, 0)
863 gdk_window_set_background_pattern(window, NULL);
864#else
865 gdk_window_set_back_pixmap(window, NULL, TRUE);
866#endif
6cc5e1a6
DB
867 }
868 else
f7ecd6ce 869 _panel_determine_background_pixmap(panel->topgwin);
2ba86315 870}
6cc5e1a6 871
2ba86315
DB
872/* Update the background of the entire panel.
873 * This function should only be called after the panel has been realized. */
874void panel_update_background(Panel * p)
875{
f7ecd6ce 876 _panel_update_background(p->topgwin, TRUE);
6b775dbb
AG
877}
878
f7ecd6ce 879static void _panel_update_background(LXPanel * p, gboolean enforce)
6b775dbb
AG
880{
881 GtkWidget *w = GTK_WIDGET(p);
f7ecd6ce
AG
882 GList *plugins = NULL, *l;
883
884 /* reset background image */
885 if (p->priv->surface != NULL) /* FIXME: honor enforce on composited screen */
886 {
887 cairo_surface_destroy(p->priv->surface);
888 p->priv->surface = NULL;
889 }
6b775dbb 890
2ba86315 891 /* Redraw the top level widget. */
f7ecd6ce 892 _panel_determine_background_pixmap(p);
7a1c5048 893#if !GTK_CHECK_VERSION(3, 0, 0)
6b775dbb 894 gdk_window_clear(gtk_widget_get_window(w));
7a1c5048 895#endif
6b775dbb 896 gtk_widget_queue_draw(w);
2ba86315
DB
897
898 /* Loop over all plugins redrawing each plugin. */
f7ecd6ce
AG
899 if (p->priv->box != NULL)
900 plugins = gtk_container_get_children(GTK_CONTAINER(p->priv->box));
6b775dbb
AG
901 for (l = plugins; l != NULL; l = l->next)
902 plugin_widget_set_background(l->data, p);
903 g_list_free(plugins);
6cc5e1a6
DB
904}
905
6b775dbb
AG
906/****************************************************
907 * autohide : borrowed from fbpanel *
908 ****************************************************/
6cc5e1a6 909
6b775dbb
AG
910/* Autohide is behaviour when panel hides itself when mouse is "far enough"
911 * and pops up again when mouse comes "close enough".
912 * Formally, it's a state machine with 3 states that driven by mouse
913 * coordinates and timer:
914 * 1. VISIBLE - ensures that panel is visible. When/if mouse goes "far enough"
915 * switches to WAITING state
916 * 2. WAITING - starts timer. If mouse comes "close enough", stops timer and
917 * switches to VISIBLE. If timer expires, switches to HIDDEN
918 * 3. HIDDEN - hides panel. When mouse comes "close enough" switches to VISIBLE
919 *
920 * Note 1
921 * Mouse coordinates are queried every PERIOD milisec
922 *
923 * Note 2
924 * If mouse is less then GAP pixels to panel it's considered to be close,
925 * otherwise it's far
926 */
6cc5e1a6 927
6b775dbb
AG
928#define GAP 2
929#define PERIOD 300
930
931typedef enum
6cc5e1a6 932{
6b775dbb
AG
933 AH_STATE_VISIBLE,
934 AH_STATE_WAITING,
935 AH_STATE_HIDDEN
936} PanelAHState;
6cc5e1a6 937
6b775dbb
AG
938static void ah_state_set(LXPanel *p, PanelAHState ah_state);
939
940static gboolean
941mouse_watch(LXPanel *panel)
6cc5e1a6 942{
6b775dbb
AG
943 Panel *p = panel->priv;
944 gint x, y;
945
946 if (g_source_is_destroyed(g_main_current_source()))
947 return FALSE;
948
6cc5e1a6 949 ENTER;
6b775dbb 950 gdk_display_get_pointer(gdk_display_get_default(), NULL, &x, &y, NULL);
6cc5e1a6 951
6b775dbb
AG
952/* Reduce sensitivity area
953 p->ah_far = ((x < p->cx - GAP) || (x > p->cx + p->cw + GAP)
954 || (y < p->cy - GAP) || (y > p->cy + p->ch + GAP));
955*/
eea54180 956
6b775dbb 957 gint cx, cy, cw, ch, gap;
eea54180 958
7dd482c5
AG
959 cx = p->ax;
960 cy = p->ay;
19ab5cea
AG
961 cw = p->cw;
962 ch = p->ch;
eea54180 963
7a1c5048
AG
964 if (p->move_state != PANEL_MOVE_STOP)
965 /* prevent autohide when dragging is on */
966 return TRUE;
967
7dd482c5
AG
968 if (cw == 1) cw = 0;
969 if (ch == 1) ch = 0;
6b775dbb
AG
970 /* reduce area which will raise panel so it does not interfere with apps */
971 if (p->ah_state == AH_STATE_HIDDEN) {
972 gap = MAX(p->height_when_hidden, GAP);
973 switch (p->edge) {
974 case EDGE_LEFT:
975 cw = gap;
976 break;
977 case EDGE_RIGHT:
978 cx = cx + cw - gap;
979 cw = gap;
980 break;
981 case EDGE_TOP:
982 ch = gap;
983 break;
984 case EDGE_BOTTOM:
985 cy = cy + ch - gap;
986 ch = gap;
987 break;
988 }
989 }
990 p->ah_far = ((x < cx) || (x > cx + cw) || (y < cy) || (y > cy + ch));
6cc5e1a6 991
6b775dbb
AG
992 ah_state_set(panel, p->ah_state);
993 RET(TRUE);
6cc5e1a6
DB
994}
995
6b775dbb 996static gboolean ah_state_hide_timeout(gpointer p)
6cc5e1a6 997{
6b775dbb
AG
998 if (!g_source_is_destroyed(g_main_current_source()))
999 {
1000 ah_state_set(p, AH_STATE_HIDDEN);
1001 ((LXPanel *)p)->priv->hide_timeout = 0;
6cc5e1a6 1002 }
6b775dbb 1003 return FALSE;
6cc5e1a6
DB
1004}
1005
6b775dbb 1006static void ah_state_set(LXPanel *panel, PanelAHState ah_state)
6cc5e1a6 1007{
6b775dbb 1008 Panel *p = panel->priv;
f7ecd6ce 1009 GdkRectangle rect;
6b775dbb 1010
6cc5e1a6 1011 ENTER;
6b775dbb
AG
1012 if (p->ah_state != ah_state) {
1013 p->ah_state = ah_state;
1014 switch (ah_state) {
1015 case AH_STATE_VISIBLE:
f7ecd6ce
AG
1016 p->visible = TRUE;
1017 _calculate_position(panel, &rect);
1018 gtk_window_move(GTK_WINDOW(panel), rect.x, rect.y);
6b775dbb
AG
1019 gtk_widget_show(GTK_WIDGET(panel));
1020 gtk_widget_show(p->box);
7dd482c5 1021 gtk_widget_queue_resize(GTK_WIDGET(panel));
6b775dbb 1022 gtk_window_stick(GTK_WINDOW(panel));
6b775dbb
AG
1023 break;
1024 case AH_STATE_WAITING:
7dd482c5
AG
1025 if (p->hide_timeout)
1026 g_source_remove(p->hide_timeout);
6b775dbb
AG
1027 p->hide_timeout = g_timeout_add(2 * PERIOD, ah_state_hide_timeout, panel);
1028 break;
1029 case AH_STATE_HIDDEN:
1030 if (p->height_when_hidden > 0)
1031 gtk_widget_hide(p->box);
1032 else
1033 gtk_widget_hide(GTK_WIDGET(panel));
1034 p->visible = FALSE;
1035 }
1036 } else if (p->autohide && p->ah_far) {
1037 switch (ah_state) {
1038 case AH_STATE_VISIBLE:
1039 ah_state_set(panel, AH_STATE_WAITING);
1040 break;
1041 case AH_STATE_WAITING:
1042 break;
1043 case AH_STATE_HIDDEN:
1044 /* configurator might change height_when_hidden value */
1045 if (p->height_when_hidden > 0)
1046 {
1047 if (gtk_widget_get_visible(p->box))
1048 {
1049 gtk_widget_hide(p->box);
1050 gtk_widget_show(GTK_WIDGET(panel));
1051 }
1052 }
1053 else
1054 if (gtk_widget_get_visible(GTK_WIDGET(panel)))
1055 {
1056 gtk_widget_hide(GTK_WIDGET(panel));
1057 gtk_widget_show(p->box);
1058 }
1059 }
1060 } else {
1061 switch (ah_state) {
1062 case AH_STATE_VISIBLE:
1063 break;
1064 case AH_STATE_WAITING:
1065 if (p->hide_timeout)
1066 g_source_remove(p->hide_timeout);
1067 p->hide_timeout = 0;
1068 /* continue with setting visible */
1069 case AH_STATE_HIDDEN:
1070 ah_state_set(panel, AH_STATE_VISIBLE);
1071 }
1072 }
1073 RET();
1074}
6cc5e1a6 1075
6b775dbb
AG
1076/* starts autohide behaviour */
1077static void ah_start(LXPanel *p)
1078{
1079 ENTER;
1080 if (!p->priv->mouse_timeout)
1081 p->priv->mouse_timeout = g_timeout_add(PERIOD, (GSourceFunc) mouse_watch, p);
1082 RET();
1083}
6cc5e1a6 1084
6b775dbb
AG
1085/* stops autohide */
1086static void ah_stop(LXPanel *p)
1087{
1088 ENTER;
1089 if (p->priv->mouse_timeout) {
1090 g_source_remove(p->priv->mouse_timeout);
1091 p->priv->mouse_timeout = 0;
1092 }
1093 if (p->priv->hide_timeout) {
1094 g_source_remove(p->priv->hide_timeout);
1095 p->priv->hide_timeout = 0;
1096 }
1097 RET();
6cc5e1a6 1098}
6b775dbb
AG
1099/* end of the autohide code
1100 * ------------------------------------------------------------- */
6cc5e1a6
DB
1101
1102static gint
1103panel_popupmenu_configure(GtkWidget *widget, gpointer user_data)
1104{
6b775dbb 1105 panel_configure( (LXPanel*)user_data, 0 );
6cc5e1a6
DB
1106 return TRUE;
1107}
1108
6b775dbb 1109static void panel_popupmenu_config_plugin( GtkMenuItem* item, GtkWidget* plugin )
6cc5e1a6 1110{
6b775dbb 1111 Panel *panel = PLUGIN_PANEL(plugin)->priv;
6cc5e1a6 1112
6b775dbb 1113 lxpanel_plugin_show_config_dialog(plugin);
39c13576
DB
1114
1115 /* FIXME: this should be more elegant */
6b775dbb 1116 panel->config_changed = TRUE;
6cc5e1a6
DB
1117}
1118
6b775dbb 1119static void panel_popupmenu_add_item( GtkMenuItem* item, LXPanel* panel )
6cc5e1a6
DB
1120{
1121 /* panel_add_plugin( panel, panel->topgwin ); */
2ba86315 1122 panel_configure( panel, 2 );
6cc5e1a6
DB
1123}
1124
6b775dbb 1125static void panel_popupmenu_remove_item( GtkMenuItem* item, GtkWidget* plugin )
6cc5e1a6 1126{
7a1c5048
AG
1127 lxpanel_remove_plugin(PLUGIN_PANEL(plugin), plugin);
1128}
1129
1130void lxpanel_remove_plugin(LXPanel *p, GtkWidget *plugin)
1131{
1132 Panel* panel = p->priv;
4652f59b
DB
1133
1134 /* If the configuration dialog is open, there will certainly be a crash if the
1135 * user manipulates the Configured Plugins list, after we remove this entry.
1136 * Close the configuration dialog if it is open. */
1137 if (panel->pref_dialog != NULL)
1138 {
1139 gtk_widget_destroy(panel->pref_dialog);
1140 panel->pref_dialog = NULL;
1141 }
7a1c5048
AG
1142 _lxpanel_remove_plugin(p, plugin);
1143}
1144
1145void _lxpanel_remove_plugin(LXPanel *p, GtkWidget *plugin)
1146{
1147 Panel* panel = p->priv;
1148 GtkWidget *prev, *next;
1149 GList *children;
1150 GtkAllocation alloc;
1151 gint idx = -1, size1, size2;
1152 gboolean expand;
1153
6b775dbb
AG
1154 config_setting_destroy(g_object_get_qdata(G_OBJECT(plugin), lxpanel_plugin_qconf));
1155 /* reset conf pointer because the widget still may be referenced by configurator */
1156 g_object_set_qdata(G_OBJECT(plugin), lxpanel_plugin_qconf, NULL);
4652f59b 1157
7a1c5048
AG
1158 /* squash previous and next spaces if this plugin was between two spaces */
1159 children = gtk_container_get_children(GTK_CONTAINER(panel->box));
1160 idx = g_list_index(children, plugin);
1161 if (idx > 0)
1162 {
1163
1164 prev = g_list_nth_data(children, idx - 1);
1165 next = g_list_nth_data(children, idx + 1);
1166 if (next && PANEL_IS_SPACE(next) && PANEL_IS_SPACE(prev))
1167 {
1168 expand = FALSE;
1169 gtk_container_child_get(GTK_CONTAINER(panel->box), prev, "expand", &expand, NULL);
1170 if (expand == TRUE)
1171 {
1172 /* prev is expandable, remove next */
1173 config_setting_destroy(g_object_get_qdata(G_OBJECT(next), lxpanel_plugin_qconf));
1174 g_object_set_qdata(G_OBJECT(next), lxpanel_plugin_qconf, NULL);
1175 gtk_widget_destroy(next);
1176 }
1177 else
1178 {
1179 gtk_container_child_get(GTK_CONTAINER(panel->box), next, "expand", &expand, NULL);
1180 if (expand == TRUE)
1181 {
1182 /* next is expandable, remove prev */
1183 config_setting_destroy(g_object_get_qdata(G_OBJECT(prev), lxpanel_plugin_qconf));
1184 g_object_set_qdata(G_OBJECT(prev), lxpanel_plugin_qconf, NULL);
1185 gtk_widget_destroy(prev);
1186 }
1187 else
1188 {
1189 /* calculate size of prev -- add size of this and next to it */
1190 size1 = _panel_space_get_size(prev);
1191 size2 = _panel_space_get_size(next);
1192 gtk_widget_get_allocation(plugin, &alloc);
1193 if (panel->orientation == GTK_ORIENTATION_HORIZONTAL)
1194 size1 += alloc.width + size2;
1195 else
1196 size1 += alloc.height + size2;
1197 /* remove next */
1198 config_setting_destroy(g_object_get_qdata(G_OBJECT(next), lxpanel_plugin_qconf));
1199 g_object_set_qdata(G_OBJECT(next), lxpanel_plugin_qconf, NULL);
1200 gtk_widget_destroy(next);
1201 _panel_space_resize(prev, size1);
1202 }
1203 }
1204 }
1205 }
1206 g_list_free(children);
1207
1208 lxpanel_config_save(p);
6b775dbb 1209 gtk_widget_destroy(plugin);
6cc5e1a6
DB
1210}
1211
1212/* FIXME: Potentially we can support multiple panels at the same edge,
1213 * but currently this cannot be done due to some positioning problems. */
6b775dbb 1214static char* gen_panel_name( int edge, gint monitor )
6cc5e1a6
DB
1215{
1216 const char* edge_str = num2str( edge_pair, edge, "" );
1217 char* name = NULL;
6b775dbb 1218 char* dir = _user_config_file_name("panels", NULL);
6cc5e1a6
DB
1219 int i;
1220 for( i = 0; i < G_MAXINT; ++i )
1221 {
1222 char* f;
6b775dbb
AG
1223 if(monitor != 0)
1224 name = g_strdup_printf( "%s-m%d-%d", edge_str, monitor, i );
1225 else if( G_LIKELY( i > 0 ) )
6cc5e1a6
DB
1226 name = g_strdup_printf( "%s%d", edge_str, i );
1227 else
1228 name = g_strdup( edge_str );
6b775dbb 1229
6cc5e1a6
DB
1230 f = g_build_filename( dir, name, NULL );
1231 if( ! g_file_test( f, G_FILE_TEST_EXISTS ) )
1232 {
1233 g_free( f );
1234 break;
1235 }
1236 g_free( name );
1237 g_free( f );
1238 }
1239 g_free( dir );
1240 return name;
1241}
1242
1243/* FIXME: Potentially we can support multiple panels at the same edge,
1244 * but currently this cannot be done due to some positioning problems. */
6b775dbb 1245static void panel_popupmenu_create_panel( GtkMenuItem* item, LXPanel* panel )
6cc5e1a6 1246{
6b775dbb 1247 gint m, e, monitors;
7a1c5048
AG
1248 GdkScreen *screen = gtk_widget_get_screen(GTK_WIDGET(panel));
1249 LXPanel *new_panel = panel_allocate(screen);
6b775dbb 1250 Panel *p = new_panel->priv;
7dd482c5 1251 config_setting_t *global;
2ba86315
DB
1252
1253 /* Allocate the edge. */
6b775dbb
AG
1254 g_assert(screen);
1255 monitors = gdk_screen_get_n_monitors(screen);
f7ecd6ce
AG
1256 /* try to allocate edge on current monitor first */
1257 m = panel->priv->monitor;
1258 if (m < 0)
1259 {
1260 /* panel is spanned over the screen, guess from pointer now */
1261 gint x, y;
1262 gdk_display_get_pointer(gdk_display_get_default(), NULL, &x, &y, NULL);
1263 m = gdk_screen_get_monitor_at_point(screen, x, y);
1264 }
1265 for (e = 1; e < 5; ++e)
1266 {
1267 if (panel_edge_available(p, e, m))
1268 {
1269 p->edge = e;
1270 p->monitor = m;
1271 goto found_edge;
1272 }
1273 }
1274 /* try all monitors */
6b775dbb
AG
1275 for(m=0; m<monitors; ++m)
1276 {
1277 /* try each of the four edges */
1278 for(e=1; e<5; ++e)
1279 {
1280 if(panel_edge_available(p,e,m)) {
1281 p->edge = e;
1282 p->monitor = m;
1283 goto found_edge;
1284 }
1285 }
1286 }
1287
1288 gtk_widget_destroy(GTK_WIDGET(new_panel));
1289 g_warning("Error adding panel: There is no room for another panel. All the edges are taken.");
1290 fm_show_error(NULL, NULL, _("There is no room for another panel. All the edges are taken."));
1291 return;
1292
1293found_edge:
1294 p->name = gen_panel_name(p->edge, p->monitor);
2ba86315 1295
6b775dbb 1296 /* create new config with first group "Global" */
7dd482c5
AG
1297 global = config_group_add_subgroup(config_root_setting(p->config), "Global");
1298 config_group_set_string(global, "edge", num2str(edge_pair, p->edge, "none"));
1299 config_group_set_int(global, "monitor", p->monitor);
2ba86315 1300 panel_configure(new_panel, 0);
6b775dbb 1301 panel_normalize_configuration(p);
f7ecd6ce 1302 panel_start_gui(new_panel, NULL);
2ba86315 1303
6b775dbb 1304 lxpanel_config_save(new_panel);
2ba86315 1305 all_panels = g_slist_prepend(all_panels, new_panel);
6cc5e1a6
DB
1306}
1307
6b775dbb 1308static void panel_popupmenu_delete_panel( GtkMenuItem* item, LXPanel* panel )
6cc5e1a6
DB
1309{
1310 GtkWidget* dlg;
1311 gboolean ok;
6b775dbb 1312 dlg = gtk_message_dialog_new_with_markup( GTK_WINDOW(panel),
6cc5e1a6
DB
1313 GTK_DIALOG_MODAL,
1314 GTK_MESSAGE_QUESTION,
1315 GTK_BUTTONS_OK_CANCEL,
1316 _("Really delete this panel?\n<b>Warning: This can not be recovered.</b>") );
2ba86315 1317 panel_apply_icon(GTK_WINDOW(dlg));
6cc5e1a6
DB
1318 gtk_window_set_title( (GtkWindow*)dlg, _("Confirm") );
1319 ok = ( gtk_dialog_run( (GtkDialog*)dlg ) == GTK_RESPONSE_OK );
1320 gtk_widget_destroy( dlg );
1321 if( ok )
1322 {
6b775dbb 1323 gchar *fname;
6cc5e1a6
DB
1324 all_panels = g_slist_remove( all_panels, panel );
1325
1326 /* delete the config file of this panel */
6b775dbb 1327 fname = _user_config_file_name("panels", panel->priv->name);
6cc5e1a6 1328 g_unlink( fname );
6b775dbb
AG
1329 g_free(fname);
1330 panel->priv->config_changed = 0;
1331 gtk_widget_destroy(GTK_WIDGET(panel));
6cc5e1a6
DB
1332 }
1333}
1334
2ba86315
DB
1335static void panel_popupmenu_about( GtkMenuItem* item, Panel* panel )
1336{
1337 GtkWidget *about;
1338 const gchar* authors[] = {
1339 "Hong Jen Yee (PCMan) <pcman.tw@gmail.com>",
1340 "Jim Huang <jserv.tw@gmail.com>",
1341 "Greg McNew <gmcnew@gmail.com> (battery plugin)",
1342 "Fred Chien <cfsghost@gmail.com>",
1343 "Daniel Kesler <kesler.daniel@gmail.com>",
1344 "Juergen Hoetzel <juergen@archlinux.org>",
1345 "Marty Jack <martyj19@comcast.net>",
cb7f7ba8 1346 "Martin Bagge <brother@bsnet.se>",
6b775dbb
AG
1347 "Andriy Grytsenko <andrej@rep.kiev.ua>",
1348 "Giuseppe Penone <giuspen@gmail.com>",
1349 "Piotr Sipika <piotr.sipika@gmail.com>",
2ba86315
DB
1350 NULL
1351 };
1352 /* TRANSLATORS: Replace this string with your names, one name per line. */
1353 gchar *translators = _( "translator-credits" );
1354
1355 about = gtk_about_dialog_new();
1356 panel_apply_icon(GTK_WINDOW(about));
1357 gtk_about_dialog_set_version(GTK_ABOUT_DIALOG(about), VERSION);
6b775dbb 1358 gtk_about_dialog_set_program_name(GTK_ABOUT_DIALOG(about), _("LXPanel"));
aaccad27
AL
1359
1360 if(gtk_icon_theme_has_icon(panel->icon_theme, "video-display"))
1361 {
1362 gtk_about_dialog_set_logo( GTK_ABOUT_DIALOG(about),
1363 gtk_icon_theme_load_icon(panel->icon_theme, "video-display", 48, 0, NULL));
1364 }
1365 else if (gtk_icon_theme_has_icon(panel->icon_theme, "start-here"))
1366 {
1367 gtk_about_dialog_set_logo( GTK_ABOUT_DIALOG(about),
1368 gtk_icon_theme_load_icon(panel->icon_theme, "start-here", 48, 0, NULL));
1369 }
6b775dbb 1370 else
aaccad27
AL
1371 {
1372 gtk_about_dialog_set_logo( GTK_ABOUT_DIALOG(about),
6b775dbb 1373 gdk_pixbuf_new_from_file(PACKAGE_DATA_DIR "/images/my-computer.png", NULL));
aaccad27
AL
1374 }
1375
7a1c5048 1376 gtk_about_dialog_set_copyright(GTK_ABOUT_DIALOG(about), _("Copyright (C) 2008-2016"));
2ba86315
DB
1377 gtk_about_dialog_set_comments(GTK_ABOUT_DIALOG(about), _( "Desktop panel for LXDE project"));
1378 gtk_about_dialog_set_license(GTK_ABOUT_DIALOG(about), "This program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.");
1379 gtk_about_dialog_set_website(GTK_ABOUT_DIALOG(about), "http://lxde.org/");
1380 gtk_about_dialog_set_authors(GTK_ABOUT_DIALOG(about), authors);
1381 gtk_about_dialog_set_translator_credits(GTK_ABOUT_DIALOG(about), translators);
1382 gtk_dialog_run(GTK_DIALOG(about));
6b775dbb 1383 gtk_widget_destroy(about);
2ba86315
DB
1384}
1385
1386void panel_apply_icon( GtkWindow *w )
1387{
6b775dbb
AG
1388 GdkPixbuf* window_icon;
1389
1390 if(gtk_icon_theme_has_icon(gtk_icon_theme_get_default(), "video-display"))
aaccad27 1391 {
6b775dbb
AG
1392 window_icon = gtk_icon_theme_load_icon(gtk_icon_theme_get_default(), "video-display", 24, 0, NULL);
1393 }
1394 else if(gtk_icon_theme_has_icon(gtk_icon_theme_get_default(), "start-here"))
aaccad27 1395 {
6b775dbb
AG
1396 window_icon = gtk_icon_theme_load_icon(gtk_icon_theme_get_default(), "start-here", 24, 0, NULL);
1397 }
aaccad27
AL
1398 else
1399 {
6b775dbb
AG
1400 window_icon = gdk_pixbuf_new_from_file(PACKAGE_DATA_DIR "/images/my-computer.png", NULL);
1401 }
aaccad27 1402 gtk_window_set_icon(w, window_icon);
2ba86315
DB
1403}
1404
6b775dbb 1405GtkMenu* lxpanel_get_plugin_menu( LXPanel* panel, GtkWidget* plugin, gboolean use_sub_menu )
6cc5e1a6 1406{
7486d297
DB
1407 GtkWidget *menu_item, *img;
1408 GtkMenu *ret,*menu;
6b775dbb 1409 const LXPanelPluginInit *init;
6cc5e1a6 1410 char* tmp;
6b775dbb 1411
7486d297 1412 ret = menu = GTK_MENU(gtk_menu_new());
6cc5e1a6 1413
6b775dbb
AG
1414 if (plugin)
1415 {
1416 init = PLUGIN_CLASS(plugin);
1417 /* create single item - plugin instance settings */
1418 img = gtk_image_new_from_stock( GTK_STOCK_PREFERENCES, GTK_ICON_SIZE_MENU );
7a1c5048
AG
1419 tmp = g_strdup_printf(_("\"%s\" Settings"),
1420 g_dgettext(init->gettext_package, init->name));
6b775dbb
AG
1421 menu_item = gtk_image_menu_item_new_with_label( tmp );
1422 g_free( tmp );
1423 gtk_image_menu_item_set_image( (GtkImageMenuItem*)menu_item, img );
1424 gtk_menu_shell_prepend(GTK_MENU_SHELL(ret), menu_item);
1425 if( init->config )
1426 g_signal_connect( menu_item, "activate", G_CALLBACK(panel_popupmenu_config_plugin), plugin );
1427 else
1428 gtk_widget_set_sensitive( menu_item, FALSE );
1429 /* add custom items by plugin if requested */
1430 if (init->update_context_menu != NULL)
1431 use_sub_menu = init->update_context_menu(plugin, ret);
1432 /* append a separator */
1433 menu_item = gtk_separator_menu_item_new();
1434 gtk_menu_shell_append(GTK_MENU_SHELL(ret), menu_item);
1435 }
1436 if (use_sub_menu)
1437 menu = GTK_MENU(gtk_menu_new());
1438
6cc5e1a6
DB
1439 img = gtk_image_new_from_stock( GTK_STOCK_EDIT, GTK_ICON_SIZE_MENU );
1440 menu_item = gtk_image_menu_item_new_with_label(_("Add / Remove Panel Items"));
1441 gtk_image_menu_item_set_image( (GtkImageMenuItem*)menu_item, img );
1442 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
1443 g_signal_connect( menu_item, "activate", G_CALLBACK(panel_popupmenu_add_item), panel );
1444
1445 if( plugin )
1446 {
1447 img = gtk_image_new_from_stock( GTK_STOCK_REMOVE, GTK_ICON_SIZE_MENU );
6b775dbb 1448 tmp = g_strdup_printf( _("Remove \"%s\" From Panel"), _(init->name) );
6cc5e1a6
DB
1449 menu_item = gtk_image_menu_item_new_with_label( tmp );
1450 g_free( tmp );
1451 gtk_image_menu_item_set_image( (GtkImageMenuItem*)menu_item, img );
1452 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
1453 g_signal_connect( menu_item, "activate", G_CALLBACK(panel_popupmenu_remove_item), plugin );
1454 }
1455
1456 menu_item = gtk_separator_menu_item_new();
1457 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
1458
1459 img = gtk_image_new_from_stock( GTK_STOCK_PREFERENCES, GTK_ICON_SIZE_MENU );
1460 menu_item = gtk_image_menu_item_new_with_label(_("Panel Settings"));
1461 gtk_image_menu_item_set_image( (GtkImageMenuItem*)menu_item, img );
1462 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
1463 g_signal_connect(G_OBJECT(menu_item), "activate", G_CALLBACK(panel_popupmenu_configure), panel );
1464
1465 img = gtk_image_new_from_stock( GTK_STOCK_NEW, GTK_ICON_SIZE_MENU );
1466 menu_item = gtk_image_menu_item_new_with_label(_("Create New Panel"));
1467 gtk_image_menu_item_set_image( (GtkImageMenuItem*)menu_item, img );
1468 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
1469 g_signal_connect( menu_item, "activate", G_CALLBACK(panel_popupmenu_create_panel), panel );
6cc5e1a6
DB
1470
1471 img = gtk_image_new_from_stock( GTK_STOCK_DELETE, GTK_ICON_SIZE_MENU );
1472 menu_item = gtk_image_menu_item_new_with_label(_("Delete This Panel"));
1473 gtk_image_menu_item_set_image( (GtkImageMenuItem*)menu_item, img );
1474 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
1475 g_signal_connect( menu_item, "activate", G_CALLBACK(panel_popupmenu_delete_panel), panel );
1476 if( ! all_panels->next ) /* if this is the only panel */
1477 gtk_widget_set_sensitive( menu_item, FALSE );
1478
2ba86315
DB
1479 menu_item = gtk_separator_menu_item_new();
1480 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
1481
1482 img = gtk_image_new_from_stock( GTK_STOCK_ABOUT, GTK_ICON_SIZE_MENU );
1483 menu_item = gtk_image_menu_item_new_with_label(_("About"));
1484 gtk_image_menu_item_set_image( (GtkImageMenuItem*)menu_item, img );
1485 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
6b775dbb 1486 g_signal_connect( menu_item, "activate", G_CALLBACK(panel_popupmenu_about), panel->priv );
2ba86315 1487
6cc5e1a6
DB
1488 if( use_sub_menu )
1489 {
6cc5e1a6
DB
1490 menu_item = gtk_image_menu_item_new_with_label(_("Panel"));
1491 gtk_menu_shell_append(GTK_MENU_SHELL(ret), menu_item);
7486d297 1492 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), GTK_WIDGET(menu) );
6cc5e1a6
DB
1493 }
1494
6b775dbb 1495 gtk_widget_show_all(GTK_WIDGET(ret));
6cc5e1a6
DB
1496
1497 g_signal_connect( ret, "selection-done", G_CALLBACK(gtk_widget_destroy), NULL );
1498 return ret;
1499}
1500
6b775dbb
AG
1501/* for old plugins compatibility */
1502GtkMenu* lxpanel_get_panel_menu( Panel* panel, Plugin* plugin, gboolean use_sub_menu )
1503{
1504 return lxpanel_get_plugin_menu(panel->topgwin, plugin->pwid, use_sub_menu);
1505}
1506
6cc5e1a6
DB
1507/****************************************************
1508 * panel creation *
1509 ****************************************************/
2ba86315 1510
6cc5e1a6
DB
1511static void
1512make_round_corners(Panel *p)
1513{
1514 /* FIXME: This should be re-written with shape extension of X11 */
1515 /* gdk_window_shape_combine_mask() can be used */
1516}
1517
1518void panel_set_dock_type(Panel *p)
1519{
6b775dbb
AG
1520 Display *xdisplay = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
1521
6cc5e1a6
DB
1522 if (p->setdocktype) {
1523 Atom state = a_NET_WM_WINDOW_TYPE_DOCK;
6b775dbb 1524 XChangeProperty(xdisplay, p->topxwin,
6cc5e1a6
DB
1525 a_NET_WM_WINDOW_TYPE, XA_ATOM, 32,
1526 PropModeReplace, (unsigned char *) &state, 1);
1527 }
1528 else {
6b775dbb 1529 XDeleteProperty( xdisplay, p->topxwin, a_NET_WM_WINDOW_TYPE );
6cc5e1a6
DB
1530 }
1531}
1532
6b775dbb 1533void panel_establish_autohide(Panel *p)
2ba86315 1534{
6b775dbb 1535 _panel_establish_autohide(p->topgwin);
2ba86315
DB
1536}
1537
6b775dbb 1538void _panel_establish_autohide(LXPanel *p)
2ba86315 1539{
6b775dbb
AG
1540 if (p->priv->autohide)
1541 ah_start(p);
32a67dc7
DB
1542 else
1543 {
6b775dbb
AG
1544 ah_stop(p);
1545 ah_state_set(p, AH_STATE_VISIBLE);
2ba86315
DB
1546 }
1547}
1548
1549/* Set an image from a file with scaling to the panel icon size. */
514580cf 1550void panel_image_set_from_file(Panel * p, GtkWidget * image, const char * file)
2ba86315
DB
1551{
1552 GdkPixbuf * pixbuf = gdk_pixbuf_new_from_file_at_scale(file, p->icon_size, p->icon_size, TRUE, NULL);
1553 if (pixbuf != NULL)
1554 {
1555 gtk_image_set_from_pixbuf(GTK_IMAGE(image), pixbuf);
1556 g_object_unref(pixbuf);
1557 }
1558}
1559
6b775dbb
AG
1560void lxpanel_image_set_from_file(LXPanel * p, GtkWidget * image, const char * file)
1561{
1562 panel_image_set_from_file(p->priv, image, file);
1563}
1564
32a67dc7
DB
1565/* Set an image from a icon theme with scaling to the panel icon size. */
1566gboolean panel_image_set_icon_theme(Panel * p, GtkWidget * image, const gchar * icon)
1567{
aaccad27 1568 if (gtk_icon_theme_has_icon(p->icon_theme, icon))
32a67dc7 1569 {
aaccad27 1570 GdkPixbuf * pixbuf = gtk_icon_theme_load_icon(p->icon_theme, icon, p->icon_size, 0, NULL);
32a67dc7
DB
1571 gtk_image_set_from_pixbuf(GTK_IMAGE(image), pixbuf);
1572 g_object_unref(pixbuf);
1573 return TRUE;
1574 }
1575 return FALSE;
1576}
1577
6b775dbb
AG
1578gboolean lxpanel_image_set_icon_theme(LXPanel * p, GtkWidget * image, const gchar * icon)
1579{
1580 return panel_image_set_icon_theme(p->priv, image, icon);
1581}
1582
f7ecd6ce
AG
1583static int
1584panel_parse_plugin(LXPanel *p, config_setting_t *cfg)
1585{
1586 const char *type = NULL;
1587
1588 ENTER;
1589 config_setting_lookup_string(cfg, "type", &type);
1590 DBG("plug %s\n", type);
1591
1592 if (!type || lxpanel_add_plugin(p, type, cfg, -1) == NULL) {
1593 g_warning( "lxpanel: can't load %s plugin", type);
1594 goto error;
1595 }
1596 RET(1);
1597
1598error:
1599 RET(0);
1600}
1601
6cc5e1a6 1602static void
f7ecd6ce 1603panel_start_gui(LXPanel *panel, config_setting_t *list)
6cc5e1a6
DB
1604{
1605 Atom state[3];
1606 XWMHints wmhints;
7dd482c5 1607 gulong val;
7a1c5048
AG
1608 Screen *xscreen = GDK_SCREEN_XSCREEN(gtk_widget_get_screen(GTK_WIDGET(panel)));
1609 Display *xdisplay = DisplayOfScreen(xscreen);
6b775dbb
AG
1610 Panel *p = panel->priv;
1611 GtkWidget *w = GTK_WIDGET(panel);
f7ecd6ce
AG
1612 config_setting_t *s;
1613 GdkRectangle rect;
1614 int i;
6cc5e1a6
DB
1615
1616 ENTER;
1617
f7ecd6ce 1618 g_debug("panel_start_gui on '%s'", p->name);
2ba86315
DB
1619 p->curdesk = get_net_current_desktop();
1620 p->desknum = get_net_number_of_desktops();
f7ecd6ce
AG
1621 //p->workarea = get_xaproperty (GDK_ROOT_WINDOW(), a_NET_WORKAREA, XA_CARDINAL, &p->wa_len);
1622 p->ax = p->ay = p->aw = p->ah = 0;
2ba86315 1623
2ba86315 1624 p->display = gdk_display_get_default();
6b775dbb 1625 gtk_window_set_wmclass(GTK_WINDOW(panel), "panel", "lxpanel");
6b775dbb 1626
f7ecd6ce
AG
1627 if (G_UNLIKELY(win_grp == NULL))
1628 {
1629 win_grp = gtk_window_group_new();
1630 g_object_add_weak_pointer(G_OBJECT(win_grp), (gpointer *)&win_grp);
1631 gtk_window_group_add_window(win_grp, (GtkWindow*)panel);
1632 g_object_unref(win_grp);
1633 }
1634 else
1635 gtk_window_group_add_window(win_grp, (GtkWindow*)panel);
6b775dbb
AG
1636
1637 gtk_widget_add_events( w, GDK_BUTTON_PRESS_MASK );
1638
1639 gtk_widget_realize(w);
1640 //gdk_window_set_decorations(gtk_widget_get_window(p->topgwin), 0);
6cc5e1a6
DB
1641
1642 // main layout manager as a single child of panel
6b775dbb 1643 p->box = panel_box_new(panel, FALSE, 0);
6cc5e1a6 1644 gtk_container_set_border_width(GTK_CONTAINER(p->box), 0);
6b775dbb 1645 gtk_container_add(GTK_CONTAINER(panel), p->box);
6cc5e1a6
DB
1646 gtk_widget_show(p->box);
1647 if (p->round_corners)
1648 make_round_corners(p);
1649
f7ecd6ce 1650 p->topxwin = GDK_WINDOW_XID(gtk_widget_get_window(w));
6cc5e1a6
DB
1651 DBG("topxwin = %x\n", p->topxwin);
1652
1653 /* the settings that should be done before window is mapped */
1654 wmhints.flags = InputHint;
1655 wmhints.input = 0;
6b775dbb 1656 XSetWMHints (xdisplay, p->topxwin, &wmhints);
6cc5e1a6
DB
1657#define WIN_HINTS_SKIP_FOCUS (1<<0) /* "alt-tab" skips this win */
1658 val = WIN_HINTS_SKIP_FOCUS;
6b775dbb
AG
1659 XChangeProperty(xdisplay, p->topxwin,
1660 XInternAtom(xdisplay, "_WIN_HINTS", False), XA_CARDINAL, 32,
6cc5e1a6
DB
1661 PropModeReplace, (unsigned char *) &val, 1);
1662
1663 panel_set_dock_type(p);
1664
1665 /* window mapping point */
f7ecd6ce
AG
1666 p->visible = TRUE;
1667 _calculate_position(panel, &rect);
1668 gtk_window_move(GTK_WINDOW(panel), rect.x, rect.y);
1669 gtk_window_present(GTK_WINDOW(panel));
6cc5e1a6
DB
1670
1671 /* the settings that should be done after window is mapped */
1672
1673 /* send it to running wm */
7a1c5048 1674 Xclimsgx(xscreen, p->topxwin, a_NET_WM_DESKTOP, G_MAXULONG, 0, 0, 0, 0);
6cc5e1a6 1675 /* and assign it ourself just for case when wm is not running */
7dd482c5 1676 val = G_MAXULONG;
6b775dbb 1677 XChangeProperty(xdisplay, p->topxwin, a_NET_WM_DESKTOP, XA_CARDINAL, 32,
6cc5e1a6
DB
1678 PropModeReplace, (unsigned char *) &val, 1);
1679
1680 state[0] = a_NET_WM_STATE_SKIP_PAGER;
1681 state[1] = a_NET_WM_STATE_SKIP_TASKBAR;
1682 state[2] = a_NET_WM_STATE_STICKY;
6b775dbb 1683 XChangeProperty(xdisplay, p->topxwin, a_NET_WM_STATE, XA_ATOM,
6cc5e1a6
DB
1684 32, PropModeReplace, (unsigned char *) state, 3);
1685
6b775dbb 1686 p->initialized = TRUE;
6cc5e1a6 1687
f7ecd6ce
AG
1688 if (list) for (i = 1; (s = config_setting_get_elem(list, i)) != NULL; )
1689 if (strcmp(config_setting_get_name(s), "Plugin") == 0 &&
1690 panel_parse_plugin(panel, s)) /* success on plugin start */
1691 i++;
1692 else /* remove invalid data from config */
1693 config_setting_remove_elem(list, i);
1694
6cc5e1a6
DB
1695 RET();
1696}
1697
2ba86315
DB
1698/* Exchange the "width" and "height" terminology for vertical and horizontal panels. */
1699void panel_adjust_geometry_terminology(Panel * p)
1700{
aa0e9095
DB
1701 if ((p->height_label != NULL) && (p->width_label != NULL)
1702 && (p->alignment_left_label != NULL) && (p->alignment_right_label != NULL))
2ba86315
DB
1703 {
1704 if ((p->edge == EDGE_TOP) || (p->edge == EDGE_BOTTOM))
1705 {
1706 gtk_label_set_text(GTK_LABEL(p->height_label), _("Height:"));
1707 gtk_label_set_text(GTK_LABEL(p->width_label), _("Width:"));
1708 gtk_button_set_label(GTK_BUTTON(p->alignment_left_label), _("Left"));
1709 gtk_button_set_label(GTK_BUTTON(p->alignment_right_label), _("Right"));
1710 }
1711 else
1712 {
1713 gtk_label_set_text(GTK_LABEL(p->height_label), _("Width:"));
1714 gtk_label_set_text(GTK_LABEL(p->width_label), _("Height:"));
1715 gtk_button_set_label(GTK_BUTTON(p->alignment_left_label), _("Top"));
1716 gtk_button_set_label(GTK_BUTTON(p->alignment_right_label), _("Bottom"));
1717 }
1718 }
1719}
1720
1721/* Draw text into a label, with the user preference color and optionally bold. */
6b775dbb
AG
1722void panel_draw_label_text(Panel * p, GtkWidget * label, const char * text,
1723 gboolean bold, float custom_size_factor,
1724 gboolean custom_color)
2ba86315 1725{
2ba86315
DB
1726 if (text == NULL)
1727 {
1728 /* Null string. */
1729 gtk_label_set_text(GTK_LABEL(label), NULL);
6b775dbb 1730 return;
2ba86315
DB
1731 }
1732
6b775dbb
AG
1733 /* Compute an appropriate size so the font will scale with the panel's icon size. */
1734 int font_desc;
1735 if (p->usefontsize)
1736 font_desc = p->fontsize;
2ba86315
DB
1737 else
1738 {
19ab5cea
AG
1739 GtkStyle *style = gtk_widget_get_style(label);
1740 font_desc = pango_font_description_get_size(style->font_desc) / PANGO_SCALE;
6b775dbb
AG
1741 }
1742 font_desc *= custom_size_factor;
1743
1744 /* Check the string for characters that need to be escaped.
1745 * If any are found, create the properly escaped string and use it instead. */
1746 const char * valid_markup = text;
1747 char * escaped_text = NULL;
1748 const char * q;
1749 for (q = text; *q != '\0'; q += 1)
1750 {
1751 if ((*q == '<') || (*q == '>') || (*q == '&'))
2ba86315 1752 {
6b775dbb
AG
1753 escaped_text = g_markup_escape_text(text, -1);
1754 valid_markup = escaped_text;
1755 break;
2ba86315 1756 }
6b775dbb 1757 }
2ba86315 1758
6b775dbb
AG
1759 gchar * formatted_text;
1760 if ((custom_color) && (p->usefontcolor))
1761 {
1762 /* Color, optionally bold. */
1763 formatted_text = g_strdup_printf("<span font_desc=\"%d\" color=\"#%06x\">%s%s%s</span>",
2ba86315
DB
1764 font_desc,
1765 gcolor2rgb24(&p->gfontcolor),
1766 ((bold) ? "<b>" : ""),
1767 valid_markup,
1768 ((bold) ? "</b>" : ""));
6b775dbb
AG
1769 }
1770 else
1771 {
1772 /* No color, optionally bold. */
1773 formatted_text = g_strdup_printf("<span font_desc=\"%d\">%s%s%s</span>",
2ba86315
DB
1774 font_desc,
1775 ((bold) ? "<b>" : ""),
1776 valid_markup,
1777 ((bold) ? "</b>" : ""));
2ba86315 1778 }
6b775dbb
AG
1779
1780 gtk_label_set_markup(GTK_LABEL(label), formatted_text);
1781 g_free(formatted_text);
1782 g_free(escaped_text);
1783}
1784
1785void lxpanel_draw_label_text(LXPanel * p, GtkWidget * label, const char * text,
1786 gboolean bold, float custom_size_factor,
1787 gboolean custom_color)
1788{
1789 panel_draw_label_text(p->priv, label, text, bold, custom_size_factor, custom_color);
2ba86315
DB
1790}
1791
1792void panel_set_panel_configuration_changed(Panel *p)
6cc5e1a6 1793{
6b775dbb
AG
1794 _panel_set_panel_configuration_changed(p->topgwin);
1795}
1796
0688b017
AG
1797static inline void _update_orientation(Panel *p)
1798{
1799 p->orientation = (p->edge == EDGE_TOP || p->edge == EDGE_BOTTOM)
1800 ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL;
1801}
1802
8713e384 1803static gboolean _panel_idle_reconfigure(gpointer widget)
6b775dbb 1804{
8713e384
AG
1805 LXPanel *panel;
1806 Panel *p;
6b775dbb 1807 GList *plugins, *l;
8713e384 1808 GtkOrientation previous_orientation;
2ba86315 1809
8713e384
AG
1810 if (g_source_is_destroyed(g_main_current_source()))
1811 return FALSE;
1812
1813 panel = LXPANEL(widget);
1814 p = panel->priv;
1815 previous_orientation = p->orientation;
0688b017 1816 _update_orientation(p);
2ba86315 1817
6b775dbb 1818 /* either first run or orientation was changed */
0688b017 1819 if (previous_orientation != p->orientation)
2ba86315
DB
1820 {
1821 panel_adjust_geometry_terminology(p);
0688b017 1822 p->height = ((p->orientation == GTK_ORIENTATION_HORIZONTAL) ? PANEL_HEIGHT_DEFAULT : PANEL_WIDTH_DEFAULT);
2ba86315
DB
1823 if (p->height_control != NULL)
1824 gtk_spin_button_set_value(GTK_SPIN_BUTTON(p->height_control), p->height);
1825 if ((p->widthtype == WIDTH_PIXEL) && (p->width_control != NULL))
1826 {
6b775dbb 1827 int value = ((p->orientation == GTK_ORIENTATION_HORIZONTAL) ? gdk_screen_width() : gdk_screen_height());
2ba86315
DB
1828 gtk_spin_button_set_range(GTK_SPIN_BUTTON(p->width_control), 0, value);
1829 gtk_spin_button_set_value(GTK_SPIN_BUTTON(p->width_control), value);
1830 }
2ba86315
DB
1831 }
1832
6b775dbb
AG
1833 /* FIXME: it's deprecated, kept for binary compatibility */
1834 if (p->orientation == GTK_ORIENTATION_HORIZONTAL) {
6cc5e1a6
DB
1835 p->my_box_new = gtk_hbox_new;
1836 p->my_separator_new = gtk_vseparator_new;
1837 } else {
1838 p->my_box_new = gtk_vbox_new;
1839 p->my_separator_new = gtk_hseparator_new;
1840 }
1841
1842 /* recreate the main layout box */
2ba86315
DB
1843 if (p->box != NULL)
1844 {
6b775dbb 1845 gtk_orientable_set_orientation(GTK_ORIENTABLE(p->box), p->orientation);
6cc5e1a6 1846 }
2ba86315 1847
6cc5e1a6
DB
1848 /* NOTE: This loop won't be executed when panel started since
1849 plugins are not loaded at that time.
1850 This is used when the orientation of the panel is changed
1851 from the config dialog, and plugins should be re-layout.
1852 */
6b775dbb
AG
1853 plugins = p->box ? gtk_container_get_children(GTK_CONTAINER(p->box)) : NULL;
1854 for( l = plugins; l; l = l->next ) {
1855 GtkWidget *w = (GtkWidget*)l->data;
1856 const LXPanelPluginInit *init = PLUGIN_CLASS(w);
1857 if (init->reconfigure)
1858 init->reconfigure(panel, w);
6cc5e1a6 1859 }
6b775dbb
AG
1860 g_list_free(plugins);
1861 /* panel geometry changed? update panel background then */
7dd482c5 1862 _panel_queue_update_background(panel);
8713e384
AG
1863
1864 p->reconfigure_queued = 0;
1865
1866 return FALSE;
1867}
1868
1869void _panel_set_panel_configuration_changed(LXPanel *panel)
1870{
1871 if (panel->priv->reconfigure_queued)
1872 return;
1873 panel->priv->reconfigure_queued = g_idle_add(_panel_idle_reconfigure, panel);
6cc5e1a6
DB
1874}
1875
1876static int
6b775dbb 1877panel_parse_global(Panel *p, config_setting_t *cfg)
6cc5e1a6 1878{
6b775dbb
AG
1879 const char *str;
1880 gint i;
6cc5e1a6 1881
6b775dbb
AG
1882 /* check Global config */
1883 if (!cfg || strcmp(config_setting_get_name(cfg), "Global") != 0)
6cc5e1a6 1884 {
6b775dbb
AG
1885 g_warning( "lxpanel: Global section not found");
1886 RET(0);
1887 }
1888 if (config_setting_lookup_string(cfg, "edge", &str))
1889 p->edge = str2num(edge_pair, str, EDGE_NONE);
f7ecd6ce
AG
1890 if (config_setting_lookup_string(cfg, "align", &str) ||
1891 /* NOTE: supporting "allign" for backward compatibility */
1892 config_setting_lookup_string(cfg, "allign", &str))
1893 p->align = str2num(allign_pair, str, ALIGN_NONE);
6b775dbb
AG
1894 config_setting_lookup_int(cfg, "monitor", &p->monitor);
1895 config_setting_lookup_int(cfg, "margin", &p->margin);
1896 if (config_setting_lookup_string(cfg, "widthtype", &str))
1897 p->widthtype = str2num(width_pair, str, WIDTH_NONE);
1898 config_setting_lookup_int(cfg, "width", &p->width);
1899 if (config_setting_lookup_string(cfg, "heighttype", &str))
1900 p->heighttype = str2num(height_pair, str, HEIGHT_NONE);
1901 config_setting_lookup_int(cfg, "height", &p->height);
1902 if (config_setting_lookup_int(cfg, "spacing", &i) && i > 0)
1903 p->spacing = i;
1904 if (config_setting_lookup_int(cfg, "setdocktype", &i))
1905 p->setdocktype = i != 0;
1906 if (config_setting_lookup_int(cfg, "setpartialstrut", &i))
1907 p->setstrut = i != 0;
1908 if (config_setting_lookup_int(cfg, "RoundCorners", &i))
1909 p->round_corners = i != 0;
1910 if (config_setting_lookup_int(cfg, "transparent", &i))
1911 p->transparent = i != 0;
1912 if (config_setting_lookup_int(cfg, "alpha", &p->alpha))
1913 {
1914 if (p->alpha > 255)
1915 p->alpha = 255;
1916 }
1917 if (config_setting_lookup_int(cfg, "autohide", &i))
1918 p->autohide = i != 0;
1919 if (config_setting_lookup_int(cfg, "heightwhenhidden", &i))
1920 p->height_when_hidden = MAX(0, i);
1921 if (config_setting_lookup_string(cfg, "tintcolor", &str))
1922 {
1923 if (!gdk_color_parse (str, &p->gtintcolor))
1924 gdk_color_parse ("white", &p->gtintcolor);
1925 p->tintcolor = gcolor2rgb24(&p->gtintcolor);
1926 DBG("tintcolor=%x\n", p->tintcolor);
6cc5e1a6 1927 }
6b775dbb
AG
1928 if (config_setting_lookup_int(cfg, "usefontcolor", &i))
1929 p->usefontcolor = i != 0;
1930 if (config_setting_lookup_string(cfg, "fontcolor", &str))
1931 {
1932 if (!gdk_color_parse (str, &p->gfontcolor))
1933 gdk_color_parse ("black", &p->gfontcolor);
1934 p->fontcolor = gcolor2rgb24(&p->gfontcolor);
1935 DBG("fontcolor=%x\n", p->fontcolor);
1936 }
1937 if (config_setting_lookup_int(cfg, "usefontsize", &i))
1938 p->usefontsize = i != 0;
1939 if (config_setting_lookup_int(cfg, "fontsize", &i) && i > 0)
1940 p->fontsize = i;
1941 if (config_setting_lookup_int(cfg, "background", &i))
1942 p->background = i != 0;
1943 if (config_setting_lookup_string(cfg, "backgroundfile", &str))
1944 p->background_file = g_strdup(str);
1945 config_setting_lookup_int(cfg, "iconsize", &p->icon_size);
6cc5e1a6 1946
0688b017 1947 _update_orientation(p);
2ba86315 1948 panel_normalize_configuration(p);
6cc5e1a6 1949
2ba86315 1950 return 1;
6cc5e1a6
DB
1951}
1952
f7ecd6ce 1953static void on_monitors_changed(GdkScreen* screen, gpointer unused)
6cc5e1a6 1954{
f7ecd6ce
AG
1955 GSList *pl;
1956 int monitors = gdk_screen_get_n_monitors(screen);
6cc5e1a6 1957
f7ecd6ce
AG
1958 for (pl = all_panels; pl; pl = pl->next)
1959 {
1960 LXPanel *p = pl->data;
1961
1962 /* handle connecting and disconnecting monitors now */
1963 if (p->priv->monitor < monitors && !p->priv->initialized)
1964 panel_start_gui(p, config_setting_get_member(config_root_setting(p->priv->config), ""));
1965 else if (p->priv->monitor >= monitors && p->priv->initialized)
1966 panel_stop_gui(p);
1967 /* resize panel if appropriate monitor changed its size or position */
1968 else
1969 {
1970 /* SF bug #666: after screen resize panel cannot establish
1971 right size since cannot be moved while is hidden */
1972 ah_state_set(p, AH_STATE_VISIBLE);
1973 gtk_widget_queue_resize(GTK_WIDGET(p));
1974 }
6cc5e1a6 1975 }
6cc5e1a6
DB
1976}
1977
f7ecd6ce 1978static int panel_start(LXPanel *p)
6cc5e1a6 1979{
f7ecd6ce 1980 config_setting_t *list;
7a1c5048 1981 GdkScreen *screen = gtk_widget_get_screen(GTK_WIDGET(p));
6cc5e1a6
DB
1982
1983 /* parse global section */
1984 ENTER;
6cc5e1a6 1985
6b775dbb
AG
1986 list = config_setting_get_member(config_root_setting(p->priv->config), "");
1987 if (!list || !panel_parse_global(p->priv, config_setting_get_elem(list, 0)))
6cc5e1a6
DB
1988 RET(0);
1989
f7ecd6ce
AG
1990 if (p->priv->monitor < gdk_screen_get_n_monitors(screen))
1991 panel_start_gui(p, list);
1992 if (monitors_handler == 0)
1993 monitors_handler = g_signal_connect(screen, "monitors-changed",
1994 G_CALLBACK(on_monitors_changed), NULL);
2ba86315 1995 return 1;
6cc5e1a6
DB
1996}
1997
6cc5e1a6
DB
1998void panel_destroy(Panel *p)
1999{
6b775dbb 2000 gtk_widget_destroy(GTK_WIDGET(p->topgwin));
6cc5e1a6
DB
2001}
2002
f7ecd6ce 2003LXPanel* panel_new( const char* config_file, const char* config_name )
6cc5e1a6 2004{
6b775dbb 2005 LXPanel* panel = NULL;
7486d297 2006
6b775dbb 2007 if (G_LIKELY(config_file))
6cc5e1a6 2008 {
7a1c5048 2009 panel = panel_allocate(gdk_screen_get_default());
6b775dbb
AG
2010 panel->priv->name = g_strdup(config_name);
2011 g_debug("starting panel from file %s",config_file);
2012 if (!config_read_file(panel->priv->config, config_file) ||
2013 !panel_start(panel))
6cc5e1a6 2014 {
6b775dbb
AG
2015 g_warning( "lxpanel: can't start panel");
2016 gtk_widget_destroy(GTK_WIDGET(panel));
2017 panel = NULL;
6cc5e1a6
DB
2018 }
2019 }
2020 return panel;
2021}
2022
6b775dbb
AG
2023
2024GtkOrientation panel_get_orientation(LXPanel *panel)
2025{
2026 return panel->priv->orientation;
2027}
2028
2029gint panel_get_icon_size(LXPanel *panel)
2030{
2031 return panel->priv->icon_size;
2032}
2033
2034gint panel_get_height(LXPanel *panel)
2035{
2036 return panel->priv->height;
2037}
2038
2039Window panel_get_xwindow(LXPanel *panel)
2040{
2041 return panel->priv->topxwin;
2042}
2043
2044gint panel_get_monitor(LXPanel *panel)
2045{
2046 return panel->priv->monitor;
2047}
2048
2049GtkStyle *panel_get_defstyle(LXPanel *panel)
2050{
2051 return panel->priv->defstyle;
2052}
2053
2054GtkIconTheme *panel_get_icon_theme(LXPanel *panel)
2055{
2056 return panel->priv->icon_theme;
2057}
2058
2059gboolean panel_is_at_bottom(LXPanel *panel)
2060{
2061 return panel->priv->edge == EDGE_BOTTOM;
2062}
2063
2064gboolean panel_is_dynamic(LXPanel *panel)
2065{
2066 return panel->priv->widthtype == WIDTH_REQUEST;
2067}
2068
2069GtkWidget *panel_box_new(LXPanel *panel, gboolean homogeneous, gint spacing)
2070{
2071 if (panel->priv->orientation == GTK_ORIENTATION_HORIZONTAL)
2072 return gtk_hbox_new(homogeneous, spacing);
2073 return gtk_vbox_new(homogeneous, spacing);
2074}
2075
2076GtkWidget *panel_separator_new(LXPanel *panel)
2077{
2078 if (panel->priv->orientation == GTK_ORIENTATION_HORIZONTAL)
2079 return gtk_vseparator_new();
2080 return gtk_hseparator_new();
2081}
2082
2083gboolean _class_is_present(const LXPanelPluginInit *init)
2084{
2085 GSList *sl;
2086
2087 for (sl = all_panels; sl; sl = sl->next )
2088 {
2089 LXPanel *panel = (LXPanel*)sl->data;
2090 GList *plugins, *p;
2091
f7ecd6ce
AG
2092 if (panel->priv->box == NULL)
2093 continue;
6b775dbb
AG
2094 plugins = gtk_container_get_children(GTK_CONTAINER(panel->priv->box));
2095 for (p = plugins; p; p = p->next)
2096 if (PLUGIN_CLASS(p->data) == init)
2097 {
2098 g_list_free(plugins);
2099 return TRUE;
2100 }
2101 g_list_free(plugins);
2102 }
2103 return FALSE;
6cc5e1a6 2104}