// SPDX-License-Identifier: GPL-2.0+
/*
 * Copyright (C) 2013 Red Hat, Inc.
 */

/**
 * SECTION:nmt-newt-section
 * @short_description: A collapsible section
 *
 * #NmtNewtSection is a container with two children; the header and
 * the body. The header is always visible, but the body is only
 * visible when the container is #NmtNewtSection:open.
 *
 * Note that there is no default way to open and close an
 * #NmtNewtSection. You need to implement this yourself. (Eg, by
 * binding the #NmtToggleButton:active property of an #NmtToggleButton
 * in the section's header to the section's #NmtNewtSection:open
 * property.)
 *
 * In addition to the header and body, the #NmtNewtSection also
 * optionally draws a border along the left side, indicating the
 * extent of the section.
 */

#include "nm-default.h"

#include "nmt-newt-section.h"

#include "nmt-newt-grid.h"
#include "nmt-newt-label.h"
#include "nmt-newt-utils.h"

G_DEFINE_TYPE (NmtNewtSection, nmt_newt_section, NMT_TYPE_NEWT_CONTAINER)

#define NMT_NEWT_SECTION_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NMT_TYPE_NEWT_SECTION, NmtNewtSectionPrivate))

typedef struct {
	NmtNewtWidget *header;
	int hheight_req, hwidth_req;

	NmtNewtWidget *body;
	int bheight_req, bwidth_req;

	gboolean show_border;
	NmtNewtWidget *border_grid;
	NmtNewtWidget *border_open_label;
	NmtNewtWidget *border_closed_label;
	NmtNewtWidget *border_end_label;
	GPtrArray *border_line_labels;

	gboolean open;
} NmtNewtSectionPrivate;

static char *closed_glyph, *open_glyph, *line_glyph, *end_glyph;

enum {
	PROP_0,

	PROP_SHOW_BORDER,
	PROP_OPEN,

	LAST_PROP
};

/**
 * nmt_newt_section_new:
 * @show_border: whether to show the border on the side of the section
 *
 * Creates a new #NmtNewtSection
 *
 * Returns: a new #NmtNewtSection
 */
NmtNewtWidget *
nmt_newt_section_new (gboolean show_border)
{
	return g_object_new (NMT_TYPE_NEWT_SECTION,
	                     "show-border", show_border,
	                     NULL);
}

static void
nmt_newt_section_init (NmtNewtSection *section)
{
	NmtNewtSectionPrivate *priv = NMT_NEWT_SECTION_GET_PRIVATE (section);
	NmtNewtContainerClass *parent_class = NMT_NEWT_CONTAINER_CLASS (nmt_newt_section_parent_class);

	priv->show_border = TRUE;

	priv->border_grid = nmt_newt_grid_new ();
	parent_class->add (NMT_NEWT_CONTAINER (section), priv->border_grid);

	priv->border_open_label = nmt_newt_label_new (open_glyph);
	nmt_newt_widget_set_visible (priv->border_open_label, FALSE);
	nmt_newt_grid_add (NMT_NEWT_GRID (priv->border_grid), priv->border_open_label, 0, 0);

	priv->border_closed_label = nmt_newt_label_new (closed_glyph);
	nmt_newt_grid_add (NMT_NEWT_GRID (priv->border_grid), priv->border_closed_label, 0, 0);

	priv->border_end_label = nmt_newt_label_new (end_glyph);
	nmt_newt_widget_set_visible (priv->border_open_label, FALSE);
	nmt_newt_grid_add (NMT_NEWT_GRID (priv->border_grid), priv->border_end_label, 0, 1);

	priv->border_line_labels = g_ptr_array_new ();
}

static void
nmt_newt_section_finalize (GObject *object)
{
	NmtNewtSectionPrivate *priv = NMT_NEWT_SECTION_GET_PRIVATE (object);

	g_ptr_array_unref (priv->border_line_labels);

	G_OBJECT_CLASS (nmt_newt_section_parent_class)->finalize (object);
}

/**
 * nmt_newt_section_set_header:
 * @section: an #NmtNewtSection
 * @header: the header widget
 *
 * Sets @section's header widget.
 */
void
nmt_newt_section_set_header (NmtNewtSection *section,
                             NmtNewtWidget  *header)
{
	NmtNewtSectionPrivate *priv = NMT_NEWT_SECTION_GET_PRIVATE (section);
	NmtNewtContainerClass *parent_class = NMT_NEWT_CONTAINER_CLASS (nmt_newt_section_parent_class);
	NmtNewtContainer *container = NMT_NEWT_CONTAINER (section);

	if (priv->header)
		parent_class->remove (container, priv->header);
	priv->header = header;
	parent_class->add (container, header);
}

/**
 * nmt_newt_section_get_header:
 * @section: an #NmtNewtSection
 *
 * Gets @section's header widget.
 *
 * Returns: @section's header widget.
 */
NmtNewtWidget *
nmt_newt_section_get_header (NmtNewtSection *section)
{
	NmtNewtSectionPrivate *priv = NMT_NEWT_SECTION_GET_PRIVATE (section);

	return priv->header;
}

/**
 * nmt_newt_section_set_body:
 * @section: an #NmtNewtSection
 * @body: the body widget
 *
 * Sets @section's body widget.
 */
void
nmt_newt_section_set_body (NmtNewtSection *section,
                           NmtNewtWidget  *body)
{
	NmtNewtSectionPrivate *priv = NMT_NEWT_SECTION_GET_PRIVATE (section);
	NmtNewtContainerClass *parent_class = NMT_NEWT_CONTAINER_CLASS (nmt_newt_section_parent_class);
	NmtNewtContainer *container = NMT_NEWT_CONTAINER (section);

	if (priv->body)
		parent_class->remove (container, priv->body);
	priv->body = body;
	parent_class->add (container, body);
}

/**
 * nmt_newt_section_get_body:
 * @section: an #NmtNewtSection
 *
 * Gets @section's body widget.
 *
 * Returns: @section's body widget.
 */
NmtNewtWidget *
nmt_newt_section_get_body (NmtNewtSection *section)
{
	NmtNewtSectionPrivate *priv = NMT_NEWT_SECTION_GET_PRIVATE (section);

	return priv->body;
}

static void
nmt_newt_section_remove (NmtNewtContainer *container,
                         NmtNewtWidget    *widget)
{
	NmtNewtSection *section = NMT_NEWT_SECTION (container);
	NmtNewtSectionPrivate *priv = NMT_NEWT_SECTION_GET_PRIVATE (section);
	NmtNewtContainerClass *parent_class = NMT_NEWT_CONTAINER_CLASS (nmt_newt_section_parent_class);

	if (widget == priv->header)
		priv->header = NULL;
	else if (widget == priv->body)
		priv->body = NULL;
	else if (widget == priv->border_grid)
		priv->border_grid = NULL;

	parent_class->remove (container, widget);
}

static newtComponent *
nmt_newt_section_get_components (NmtNewtWidget *widget)
{
	NmtNewtSectionPrivate *priv = NMT_NEWT_SECTION_GET_PRIVATE (widget);
	newtComponent *child_cos;
	GPtrArray *cos;
	int i;

	g_return_val_if_fail (priv->header != NULL && priv->body != NULL, NULL);

	cos = g_ptr_array_new ();

	if (priv->show_border) {
		child_cos = nmt_newt_widget_get_components (priv->border_grid);
		for (i = 0; child_cos[i]; i++)
			g_ptr_array_add (cos, child_cos[i]);
		g_free (child_cos);
	}

	child_cos = nmt_newt_widget_get_components (priv->header);
	for (i = 0; child_cos[i]; i++)
		g_ptr_array_add (cos, child_cos[i]);
	g_free (child_cos);

	if (priv->open) {
		child_cos = nmt_newt_widget_get_components (priv->body);
		for (i = 0; child_cos[i]; i++)
			g_ptr_array_add (cos, child_cos[i]);
		g_free (child_cos);
	}

	g_ptr_array_add (cos, NULL);
	return (newtComponent *) g_ptr_array_free (cos, FALSE);
}

static void
nmt_newt_section_size_request (NmtNewtWidget *widget,
                               int           *width,
                               int           *height)
{
	NmtNewtSectionPrivate *priv = NMT_NEWT_SECTION_GET_PRIVATE (widget);
	int w_ignore, h_ignore;

	g_return_if_fail (priv->header != NULL && priv->body != NULL);

	if (priv->show_border)
		nmt_newt_widget_size_request (priv->border_grid, &w_ignore, &h_ignore);
	nmt_newt_widget_size_request (priv->header, &priv->hwidth_req, &priv->hheight_req);
	nmt_newt_widget_size_request (priv->body, &priv->bwidth_req, &priv->bheight_req);

	*width = MAX (priv->hwidth_req, priv->bwidth_req) + 2;
	if (priv->open)
		*height = priv->hheight_req + priv->bheight_req + (priv->show_border ? 1 : 0);
	else
		*height = priv->hheight_req;
}

static void
adjust_border_for_allocation (NmtNewtSectionPrivate *priv,
                              int                    height)
{
	int i;

	/* We have to use a series of one-line labels rather than a multi-line
	 * textbox, because newt will hide any component that's partially offscreen,
	 * but we want the on-screen portion of the border to show even if part of
	 * it is offscreen.
	 */

	if (height == 1) {
		nmt_newt_widget_set_visible (priv->border_closed_label, TRUE);
		nmt_newt_widget_set_visible (priv->border_open_label, FALSE);
		for (i = 0; i < priv->border_line_labels->len; i++)
			nmt_newt_widget_set_visible (priv->border_line_labels->pdata[i], FALSE);
		nmt_newt_widget_set_visible (priv->border_end_label, FALSE);
	} else {
		nmt_newt_widget_set_visible (priv->border_closed_label, FALSE);
		nmt_newt_widget_set_visible (priv->border_open_label, TRUE);
		for (i = 0; i < height - 2; i++) {
			if (i >= priv->border_line_labels->len) {
				NmtNewtWidget *label;

				label = nmt_newt_label_new (line_glyph);
				g_ptr_array_add (priv->border_line_labels, label);
				nmt_newt_grid_add (NMT_NEWT_GRID (priv->border_grid), label, 0, i + 1);
			} else
				nmt_newt_widget_set_visible (priv->border_line_labels->pdata[i], TRUE);
		}
		nmt_newt_widget_set_visible (priv->border_end_label, TRUE);
		nmt_newt_grid_move (NMT_NEWT_GRID (priv->border_grid), priv->border_end_label, 0, height - 1);
	}
}

static void
nmt_newt_section_size_allocate (NmtNewtWidget *widget,
                                int            x,
                                int            y,
                                int            width,
                                int            height)
{
	NmtNewtSectionPrivate *priv = NMT_NEWT_SECTION_GET_PRIVATE (widget);

	if (priv->show_border) {
		int w_ignore, h_ignore;

		adjust_border_for_allocation (priv, height);
		nmt_newt_widget_size_request (priv->border_grid, &w_ignore, &h_ignore);
		nmt_newt_widget_size_allocate (priv->border_grid, x, y, 1, height);
		nmt_newt_widget_size_allocate (priv->header, x + 2, y, width, priv->hheight_req);
	} else
		nmt_newt_widget_size_allocate (priv->header, x, y, width, priv->hheight_req);

	if (priv->open) {
		nmt_newt_widget_size_allocate (priv->body, x + 2, y + priv->hheight_req,
		                               width, height - priv->hheight_req);
	}
}

static void
nmt_newt_section_set_property (GObject      *object,
                               guint         prop_id,
                               const GValue *value,
                               GParamSpec   *pspec)
{
	NmtNewtSectionPrivate *priv = NMT_NEWT_SECTION_GET_PRIVATE (object);

	switch (prop_id) {
	case PROP_SHOW_BORDER:
		priv->show_border = g_value_get_boolean (value);
		nmt_newt_widget_needs_rebuild (NMT_NEWT_WIDGET (object));
		break;
	case PROP_OPEN:
		priv->open = g_value_get_boolean (value);
		nmt_newt_widget_needs_rebuild (NMT_NEWT_WIDGET (object));
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}
}

static void
nmt_newt_section_get_property (GObject    *object,
                               guint       prop_id,
                               GValue     *value,
                               GParamSpec *pspec)
{
	NmtNewtSectionPrivate *priv = NMT_NEWT_SECTION_GET_PRIVATE (object);

	switch (prop_id) {
	case PROP_SHOW_BORDER:
		g_value_set_boolean (value, priv->show_border);
		break;
	case PROP_OPEN:
		g_value_set_boolean (value, priv->open);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}
}

static void
nmt_newt_section_class_init (NmtNewtSectionClass *section_class)
{
	GObjectClass *object_class = G_OBJECT_CLASS (section_class);
	NmtNewtWidgetClass *widget_class = NMT_NEWT_WIDGET_CLASS (section_class);
	NmtNewtContainerClass *container_class = NMT_NEWT_CONTAINER_CLASS (section_class);

	g_type_class_add_private (section_class, sizeof (NmtNewtSectionPrivate));

	/* virtual methods */
	object_class->set_property = nmt_newt_section_set_property;
	object_class->get_property = nmt_newt_section_get_property;
	object_class->finalize     = nmt_newt_section_finalize;

	widget_class->get_components = nmt_newt_section_get_components;
	widget_class->size_request   = nmt_newt_section_size_request;
	widget_class->size_allocate  = nmt_newt_section_size_allocate;

	container_class->remove = nmt_newt_section_remove;

	/* properties */

	/**
	 * NmtNewtSection:show-border:
	 *
	 * %TRUE if the section should show a border along the left side.
	 */
	g_object_class_install_property
		(object_class, PROP_SHOW_BORDER,
		 g_param_spec_boolean ("show-border", "", "",
		                       TRUE,
		                       G_PARAM_READWRITE |
		                       G_PARAM_STATIC_STRINGS));

	/**
	 * NmtNewtSection:open:
	 *
	 * %TRUE if the section is open (ie, its body is visible), %FALSE
	 * if not.
	 */
	g_object_class_install_property
		(object_class, PROP_OPEN,
		 g_param_spec_boolean ("open", "", "",
		                       FALSE,
		                       G_PARAM_READWRITE |
		                       G_PARAM_STATIC_STRINGS));

	/* globals */
	closed_glyph = nmt_newt_locale_from_utf8 ("\342\225\220"); /* ═ */
	open_glyph   = nmt_newt_locale_from_utf8 ("\342\225\244"); /* ╤ */
	line_glyph   = nmt_newt_locale_from_utf8 ("\342\224\202"); /* │ */
	end_glyph    = nmt_newt_locale_from_utf8 ("\342\224\224"); /* └ */
	if (!*closed_glyph || !*open_glyph || !*line_glyph || !*end_glyph) {
		g_free (closed_glyph);
		g_free (open_glyph);
		g_free (line_glyph);
		g_free (end_glyph);

		closed_glyph = g_strdup ("-");
		open_glyph   = g_strdup ("+");
		line_glyph   = g_strdup ("|");
		end_glyph    = g_strdup ("\\");
	}
}
