Panel
/* $Id$ */
							/* Copyright (c) 2011-2022 Pierre Pronchery <khorben@defora.org> */
							/* This file is part of DeforaOS Desktop Panel */
							/* This program is free software: you can redistribute it and/or modify
							 * it under the terms of the GNU General Public License as published by
							 * the Free Software Foundation, version 3 of the License.
							 *
							 * This program is distributed in the hope that it will be useful,
							 * but WITHOUT ANY WARRANTY; without even the implied warranty of
							 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
							 * GNU General Public License for more details.
							 *
							 * You should have received a copy of the GNU General Public License
							 * along with this program.  If not, see <http://www.gnu.org/licenses/>. */
							#include <System.h>
							#include <stdlib.h>
							#include <string.h>
							#include <errno.h>
							#include <libintl.h>
							#include <gtk/gtk.h>
							#if GTK_CHECK_VERSION(3, 0, 0)
							# include <gtk/gtkx.h>
							#endif
							#include "window.h"
							#include "../config.h"
							#define _(string) gettext(string)
							#define N_(string) string
							/* constants */
							#ifndef PREFIX
							# define PREFIX		"/usr/local"
							#endif
							#ifndef LIBDIR
							# define LIBDIR		PREFIX "/lib"
							#endif
							/* PanelWindow */
							/* private */
							/* types */
							struct _PanelApplet
							{
								Plugin * plugin;
								PanelAppletDefinition * pad;
								PanelApplet * pa;
								GtkWidget * widget;
							};
							struct _PanelWindow
							{
								PanelWindowType type;
								PanelWindowPosition position;
								GtkIconSize iconsize;
								gint height;
								GdkRectangle root;
								/* applets */
								PanelAppletHelper * helper;
								PanelApplet * applets;
								size_t applets_cnt;
								/* widgets */
								GtkWidget * window;
								GtkWidget * box;
							};
							/* prototypes */
							static void _panel_window_reset(PanelWindow * panel);
							static void _panel_window_reset_strut(PanelWindow * panel);
							/* callbacks */
							static gboolean _panel_window_on_closex(gpointer data);
							static gboolean _panel_window_on_configure_event(GtkWidget * widget,
									GdkEvent * event, gpointer data);
							/* public */
							/* functions */
							/* panel_window_new */
							PanelWindow * panel_window_new(PanelAppletHelper * helper,
									PanelWindowType type, PanelWindowPosition position,
									GtkIconSize iconsize, GdkRectangle * root)
							{
								PanelWindow * panel;
								int icon_width;
								int icon_height;
								GtkOrientation orientation;
								if(gtk_icon_size_lookup(iconsize, &icon_width, &icon_height) != TRUE)
								{
									error_set_code(1, _("Invalid panel size"));
									return NULL;
								}
								if((panel = object_new(sizeof(*panel))) == NULL)
									return NULL;
								panel->type = type;
								panel->position = position;
								panel->iconsize = iconsize;
								panel->helper = helper;
								panel->applets = NULL;
								panel->applets_cnt = 0;
								if(position != PANEL_WINDOW_POSITION_EMBEDDED)
								{
									panel->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
							#if GTK_CHECK_VERSION(3, 0, 0) && !GTK_CHECK_VERSION(3, 14, 0)
									gtk_window_set_has_resize_grip(GTK_WINDOW(panel->window),
											FALSE);
							#endif
								}
								else
								{
									panel->window = gtk_plug_new(0);
									gtk_widget_show(panel->window);
								}
								gtk_container_set_border_width(GTK_CONTAINER(panel->window), 2);
								panel->height = icon_height + (PANEL_BORDER_WIDTH * 4);
							#ifdef DEBUG
								fprintf(stderr, "DEBUG: %s() %u height=%d\n", __func__, position,
										panel->height);
							#endif
								panel->box = NULL;
								orientation = panel_window_get_orientation(panel);
							#if GTK_CHECK_VERSION(3, 0, 0)
								panel->box = gtk_box_new(orientation, 2);
							#else
								panel->box = (orientation == GTK_ORIENTATION_HORIZONTAL)
									? gtk_hbox_new(FALSE, 2) : gtk_vbox_new(FALSE, 2);
							#endif
								switch(position)
								{
									case PANEL_WINDOW_POSITION_TOP:
									case PANEL_WINDOW_POSITION_BOTTOM:
							#if GTK_CHECK_VERSION(2, 6, 0)
										gtk_window_set_focus_on_map(GTK_WINDOW(panel->window),
												FALSE);
							#endif
										gtk_window_set_type_hint(GTK_WINDOW(panel->window),
												GDK_WINDOW_TYPE_HINT_DOCK);
										gtk_window_stick(GTK_WINDOW(panel->window));
										g_signal_connect(panel->window, "configure-event",
												G_CALLBACK(_panel_window_on_configure_event),
												panel);
										break;
									case PANEL_WINDOW_POSITION_LEFT:
									case PANEL_WINDOW_POSITION_RIGHT:
							#if GTK_CHECK_VERSION(2, 6, 0)
										gtk_window_set_focus_on_map(GTK_WINDOW(panel->window),
												FALSE);
							#endif
										gtk_window_set_type_hint(GTK_WINDOW(panel->window),
												GDK_WINDOW_TYPE_HINT_DOCK);
										gtk_window_stick(GTK_WINDOW(panel->window));
										g_signal_connect(panel->window, "configure-event",
												G_CALLBACK(_panel_window_on_configure_event),
												panel);
										break;
									case PANEL_WINDOW_POSITION_CENTER:
										gtk_window_set_position(GTK_WINDOW(panel->window),
												GTK_WIN_POS_CENTER_ALWAYS);
										gtk_window_stick(GTK_WINDOW(panel->window));
										/* fallthrough */
									case PANEL_WINDOW_POSITION_FLOATING:
										gtk_window_set_accept_focus(GTK_WINDOW(panel->window),
												FALSE);
										gtk_window_set_decorated(GTK_WINDOW(panel->window),
												FALSE);
									case PANEL_WINDOW_POSITION_EMBEDDED:
									case PANEL_WINDOW_POSITION_MANAGED:
										break;
								}
								g_signal_connect_swapped(panel->window, "delete-event", G_CALLBACK(
											_panel_window_on_closex), panel);
								gtk_container_add(GTK_CONTAINER(panel->window), panel->box);
								gtk_widget_show_all(panel->box);
								panel_window_reset(panel, root);
								return panel;
							}
							/* panel_window_delete */
							void panel_window_delete(PanelWindow * panel)
							{
								panel_window_remove_all(panel);
								gtk_widget_destroy(panel->window);
								object_delete(panel);
							}
							/* accessors */
							/* panel_window_get_height */
							int panel_window_get_height(PanelWindow * panel)
							{
								return panel->height;
							}
							/* panel_window_get_icon_size */
							GtkIconSize panel_window_get_icon_size(PanelWindow * panel)
							{
								return panel->iconsize;
							}
							/* panel_window_get_orientation */
							GtkOrientation panel_window_get_orientation(PanelWindow * panel)
							{
							#if GTK_CHECK_VERSION(2, 16, 0)
								if(panel->box != NULL)
									return gtk_orientable_get_orientation(GTK_ORIENTABLE(
												panel->box));
							#endif
								switch(panel->position)
								{
									case PANEL_WINDOW_POSITION_LEFT:
									case PANEL_WINDOW_POSITION_RIGHT:
										return GTK_ORIENTATION_VERTICAL;
									case PANEL_WINDOW_POSITION_BOTTOM:
									case PANEL_WINDOW_POSITION_CENTER:
									case PANEL_WINDOW_POSITION_FLOATING:
									case PANEL_WINDOW_POSITION_MANAGED:
									case PANEL_WINDOW_POSITION_TOP:
									default:
										return GTK_ORIENTATION_HORIZONTAL;
								}
							}
							/* panel_window_get_position */
							void panel_window_get_position(PanelWindow * panel, gint * x, gint * y)
							{
								GdkWindow * window;
								window = gtk_widget_get_window(panel->window);
								gdk_window_get_position(window, x, y);
							}
							/* panel_window_get_size */
							void panel_window_get_size(PanelWindow * panel, gint * width, gint * height)
							{
								gtk_window_get_size(GTK_WINDOW(panel->window), width, height);
							}
							/* panel_window_get_type */
							PanelWindowType panel_window_get_type(PanelWindow * panel)
							{
								return panel->type;
							}
							/* panel_window_get_width */
							int panel_window_get_width(PanelWindow * panel)
							{
								gint width;
								gtk_window_get_size(GTK_WINDOW(panel->window), &width, NULL);
								return width;
							}
							/* panel_window_get_xid */
							uint32_t panel_window_get_xid(PanelWindow * panel)
							{
								return (panel->position == PANEL_WINDOW_POSITION_EMBEDDED)
									? gtk_plug_get_id(GTK_PLUG(panel->window)) : 0;
							}
							/* panel_window_set_accept_focus */
							void panel_window_set_accept_focus(PanelWindow * panel, gboolean accept)
							{
								gtk_window_set_accept_focus(GTK_WINDOW(panel->window), accept);
							}
							/* panel_window_set_keep_above */
							void panel_window_set_keep_above(PanelWindow * panel, gboolean keep)
							{
								gtk_window_set_keep_above(GTK_WINDOW(panel->window), keep);
							}
							/* panel_window_set_title */
							void panel_window_set_title(PanelWindow * panel, char const * title)
							{
								gtk_window_set_title(GTK_WINDOW(panel->window), title);
							}
							/* useful */
							/* panel_window_append */
							int panel_window_append(PanelWindow * panel, char const * applet)
							{
								PanelAppletHelper * helper = panel->helper;
								PanelApplet * pa;
								if((pa = realloc(panel->applets, sizeof(*pa)
												* (panel->applets_cnt + 1))) == NULL)
									return -error_set_code(1, "%s", strerror(errno));
								panel->applets = pa;
								pa = &panel->applets[panel->applets_cnt];
								if((pa->plugin = plugin_new(LIBDIR, PACKAGE, "applets", applet))
										== NULL)
									return -1;
								pa->widget = NULL;
								if((pa->pad = plugin_lookup(pa->plugin, "applet")) == NULL
										|| (pa->pa = pa->pad->init(helper, &pa->widget)) == NULL
										|| pa->widget == NULL)
								{
									if(pa->pa != NULL)
										pa->pad->destroy(pa->pa);
									plugin_delete(pa->plugin);
									return -1;
								}
								gtk_box_pack_start(GTK_BOX(panel->box), pa->widget, pa->pad->expand,
										pa->pad->fill, 0);
								gtk_widget_show_all(pa->widget);
								panel->applets_cnt++;
								return 0;
							}
							/* panel_window_remove_all */
							void panel_window_remove_all(PanelWindow * panel)
							{
								size_t i;
								PanelApplet * pa;
								for(i = 0; i < panel->applets_cnt; i++)
								{
									pa = &panel->applets[i];
									pa->pad->destroy(pa->pa);
									plugin_delete(pa->plugin);
								}
								free(panel->applets);
								panel->applets = NULL;
								panel->applets_cnt = 0;
							}
							/* panel_window_reset */
							void panel_window_reset(PanelWindow * panel, GdkRectangle * root)
							{
								memcpy(&panel->root, root, sizeof(*root));
								_panel_window_reset(panel);
							}
							/* panel_window_show */
							void panel_window_show(PanelWindow * panel, gboolean show)
							{
								if(show)
									gtk_widget_show(panel->window);
								else
									gtk_widget_hide(panel->window);
							}
							/* private */
							/* functions */
							/* panel_window_reset */
							static void _panel_window_reset(PanelWindow * panel)
							{
								switch(panel->position)
								{
									case PANEL_WINDOW_POSITION_TOP:
										gtk_window_move(GTK_WINDOW(panel->window),
												panel->root.x, 0);
										gtk_window_resize(GTK_WINDOW(panel->window),
												panel->root.width, panel->height);
										break;
									case PANEL_WINDOW_POSITION_BOTTOM:
										gtk_window_move(GTK_WINDOW(panel->window),
												panel->root.x,
												panel->root.y + panel->root.height
												- panel->height);
										gtk_window_resize(GTK_WINDOW(panel->window),
												panel->root.width, panel->height);
										break;
									case PANEL_WINDOW_POSITION_LEFT:
										/* FIXME really implement */
										gtk_window_move(GTK_WINDOW(panel->window),
												panel->root.x, 0);
										gtk_window_resize(GTK_WINDOW(panel->window), 48,
												panel->root.height);
										break;
									case PANEL_WINDOW_POSITION_RIGHT:
										/* FIXME really implement */
										gtk_window_move(GTK_WINDOW(panel->window),
												panel->root.x + panel->root.width - 48,
												panel->root.y);
										gtk_window_resize(GTK_WINDOW(panel->window), 48,
												panel->root.height);
										break;
									case PANEL_WINDOW_POSITION_CENTER:
									case PANEL_WINDOW_POSITION_FLOATING:
									case PANEL_WINDOW_POSITION_MANAGED:
									case PANEL_WINDOW_POSITION_EMBEDDED:
										break;
								}
							}
							/* panel_window_reset_strut */
							static void _panel_window_reset_strut(PanelWindow * panel)
							{
								GdkWindow * window;
								GdkAtom atom;
								GdkAtom cardinal;
								unsigned long strut[12];
							#if GTK_CHECK_VERSION(2, 14, 0)
								window = gtk_widget_get_window(panel->window);
							#else
								window = panel->window->window;
							#endif
								memset(&strut, 0, sizeof(strut));
								/* FIXME check that this code is correct */
								switch(panel->position)
								{
									case PANEL_WINDOW_POSITION_TOP:
										strut[2] = panel->height;
										strut[8] = panel->root.x;
										strut[9] = panel->root.x + panel->root.width;
										break;
									case PANEL_WINDOW_POSITION_BOTTOM:
										strut[3] = panel->height;
										strut[10] = panel->root.x;
										strut[11] = panel->root.x + panel->root.width;
										break;
							#if 0 /* FIXME implement */
									case PANEL_WINDOW_POSITION_LEFT:
									case PANEL_WINDOW_POSITION_RIGHT:
										break;
							#endif
									case PANEL_WINDOW_POSITION_CENTER:
									case PANEL_WINDOW_POSITION_FLOATING:
									case PANEL_WINDOW_POSITION_MANAGED:
									case PANEL_WINDOW_POSITION_EMBEDDED:
										break;
								}
								cardinal = gdk_atom_intern("CARDINAL", FALSE);
								atom = gdk_atom_intern("_NET_WM_STRUT", FALSE);
								gdk_property_change(window, atom, cardinal, 32, GDK_PROP_MODE_REPLACE,
										(guchar *)strut, 4);
								atom = gdk_atom_intern("_NET_WM_STRUT_PARTIAL", FALSE);
								gdk_property_change(window, atom, cardinal, 32, GDK_PROP_MODE_REPLACE,
										(guchar *)strut, 12);
							}
							/* callbacks */
							/* panel_window_on_closex */
							static gboolean _panel_window_on_closex(gpointer data)
							{
								PanelWindow * panel = data;
								panel_window_show(panel, FALSE);
								gtk_main_quit();
								return TRUE;
							}
							/* panel_window_on_configure_event */
							static gboolean _panel_window_on_configure_event(GtkWidget * widget,
									GdkEvent * event, gpointer data)
							{
								PanelWindow * panel = data;
								GdkEventConfigure * cevent = &event->configure;
								(void) widget;
								if(event->type != GDK_CONFIGURE)
									return FALSE;
								panel->height = cevent->height;
							#ifdef DEBUG
								fprintf(stderr, "DEBUG: %s() %u height=%d\n", __func__, panel->position,
										panel->height);
							#endif
								/* move to the proper position again if necessary */
								switch(panel->position)
								{
									case PANEL_WINDOW_POSITION_TOP:
										if(cevent->y != panel->root.y)
											_panel_window_reset(panel);
										else
											_panel_window_reset_strut(panel);
										break;
									case PANEL_WINDOW_POSITION_BOTTOM:
										if(cevent->y + cevent->height != panel->root.height)
											_panel_window_reset(panel);
										else
											_panel_window_reset_strut(panel);
										break;
									default:
										_panel_window_reset_strut(panel);
										break;
								}
								return FALSE;
							}
							