/* 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("\\");
}
}