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