/* $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;
}
