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

/**
 * SECTION:nmt-newt-component
 * @short_description: Base class for widgets that wrap #newtComponents
 *
 * #NmtNewtComponent is the abstract class for #NmtNewtWidgets that
 * wrap a (single) #newtComponent.
 */

#include "libnm-client-aux-extern/nm-default-client.h"

#include "nmt-newt-component.h"
#include "nmt-newt-form.h"
#include "nmt-newt-hacks.h"

G_DEFINE_ABSTRACT_TYPE(NmtNewtComponent, nmt_newt_component, NMT_TYPE_NEWT_WIDGET)

#define NMT_NEWT_COMPONENT_GET_PRIVATE(o) \
    (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_NEWT_COMPONENT, NmtNewtComponentPrivate))

typedef struct {
    newtComponent co;
    gboolean      own_component;
    gboolean      sensitive;
} NmtNewtComponentPrivate;

enum {
    PROP_0,

    PROP_COMPONENT,
    PROP_SENSITIVE,

    LAST_PROP
};

static void
nmt_newt_component_init(NmtNewtComponent *component)
{
    NmtNewtComponentPrivate *priv = NMT_NEWT_COMPONENT_GET_PRIVATE(component);

    priv->sensitive = TRUE;
}

static void
nmt_newt_component_unrealize(NmtNewtWidget *widget)
{
    NmtNewtComponentPrivate *priv = NMT_NEWT_COMPONENT_GET_PRIVATE(widget);

    if (!priv->co)
        return;

    newtComponentAddCallback(priv->co, NULL, NULL);
    newtComponentAddDestroyCallback(priv->co, NULL, NULL);

    if (priv->own_component)
        newtComponentDestroy(priv->co);
    priv->co = NULL;
}

static void
component_destroy_callback(newtComponent co, void *component)
{
    NmtNewtComponentPrivate *priv = NMT_NEWT_COMPONENT_GET_PRIVATE(component);

    priv->own_component = FALSE;
    nmt_newt_widget_unrealize(component);
    nmt_newt_widget_needs_rebuild(component);
}

static void
nmt_newt_component_realize(NmtNewtWidget *widget)
{
    NmtNewtComponentPrivate *priv = NMT_NEWT_COMPONENT_GET_PRIVATE(widget);

    nmt_newt_component_unrealize(widget);
    priv->co = NMT_NEWT_COMPONENT_GET_CLASS(widget)->build_component(NMT_NEWT_COMPONENT(widget),
                                                                     priv->sensitive);
    priv->own_component = TRUE;
    if (!priv->sensitive)
        newtComponentTakesFocus(priv->co, FALSE);
    newtComponentAddDestroyCallback(priv->co, component_destroy_callback, widget);
}

static newtComponent *
nmt_newt_component_get_components(NmtNewtWidget *widget)
{
    NmtNewtComponentPrivate *priv = NMT_NEWT_COMPONENT_GET_PRIVATE(widget);
    newtComponent           *cos;

    priv->own_component = FALSE;
    cos                 = g_new0(newtComponent, 2);
    cos[0]              = priv->co;
    return cos;
}

static NmtNewtWidget *
nmt_newt_component_find_component(NmtNewtWidget *widget, newtComponent co)
{
    NmtNewtComponentPrivate *priv = NMT_NEWT_COMPONENT_GET_PRIVATE(widget);

    if (co == priv->co)
        return widget;
    else
        return NULL;
}

static void
nmt_newt_component_size_request(NmtNewtWidget *widget, int *width, int *height)
{
    NmtNewtComponentPrivate *priv = NMT_NEWT_COMPONENT_GET_PRIVATE(widget);

    newtComponentGetSize(priv->co, width, height);
}

static void
nmt_newt_component_size_allocate(NmtNewtWidget *widget, int x, int y, int width, int height)
{
    NmtNewtComponentPrivate *priv = NMT_NEWT_COMPONENT_GET_PRIVATE(widget);
    newtGrid                 grid;

    /* You can't directly place a newtComponent, so we create a newtGrid,
     * position the component within that, and then place the grid.
     */
    grid = newtCreateGrid(1, 1);
    newtGridSetField(grid,
                     0,
                     0,
                     NEWT_GRID_COMPONENT,
                     priv->co,
                     x,
                     y,
                     0,
                     0,
                     NEWT_ANCHOR_LEFT | NEWT_ANCHOR_TOP,
                     0);
    newtGridPlace(grid, 0, 0);
    newtGridFree(grid, FALSE);
}

static newtComponent
nmt_newt_component_get_focus_component(NmtNewtWidget *widget)
{
    NmtNewtComponentPrivate *priv = NMT_NEWT_COMPONENT_GET_PRIVATE(widget);

    return priv->co;
}

/**
 * nmt_newt_component_get_component:
 * @component: an #NmtNewtComponent
 *
 * A simpler version of nmt_newt_widget_get_components() for the
 * single-component case. Also, unlike
 * nmt_newt_widget_get_component(), this does not realize the widget
 * if it isn't already realized. FIXME: why?
 *
 * Returns: @component's #newtComponent
 */
newtComponent
nmt_newt_component_get_component(NmtNewtComponent *component)
{
    NmtNewtComponentPrivate *priv = NMT_NEWT_COMPONENT_GET_PRIVATE(component);

    return priv->co;
}

/**
 * nmt_newt_component_get_sensitive:
 * @component: an #NmtNewtComponent
 *
 * Gets @component's #NmtNewtComponent:sensitive property, indicating
 * whether the widget is available for manipulation. Insensitive
 * components will be skipped over in the keyboard tab chain, and may
 * be displayed differently.
 *
 * Returns: @component's #NmtNewtComponent:sensitive property
 */
gboolean
nmt_newt_component_get_sensitive(NmtNewtComponent *component)
{
    NmtNewtComponentPrivate *priv = NMT_NEWT_COMPONENT_GET_PRIVATE(component);

    return priv->sensitive;
}

/**
 * nmt_newt_component_set_sensitive:
 * @component: an #NmtNewtComponent
 * @sensitive: whether @component should be sensitive
 *
 * Sets @component's #NmtNewtComponent:sensitive property.
 */
void
nmt_newt_component_set_sensitive(NmtNewtComponent *component, gboolean sensitive)
{
    NmtNewtComponentPrivate *priv = NMT_NEWT_COMPONENT_GET_PRIVATE(component);

    sensitive = !!sensitive;
    if (priv->sensitive == sensitive)
        return;

    priv->sensitive = sensitive;
    g_object_notify(G_OBJECT(component), "sensitive");
    nmt_newt_widget_needs_rebuild(NMT_NEWT_WIDGET(component));
}

static void
nmt_newt_component_set_property(GObject      *object,
                                guint         prop_id,
                                const GValue *value,
                                GParamSpec   *pspec)
{
    NmtNewtComponent *component = NMT_NEWT_COMPONENT(object);

    switch (prop_id) {
    case PROP_SENSITIVE:
        nmt_newt_component_set_sensitive(component, g_value_get_boolean(value));
        break;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
        break;
    }
}

static void
nmt_newt_component_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
{
    NmtNewtComponent *component = NMT_NEWT_COMPONENT(object);

    switch (prop_id) {
    case PROP_COMPONENT:
        g_value_set_pointer(value, nmt_newt_component_get_component(component));
        break;
    case PROP_SENSITIVE:
        g_value_set_boolean(value, nmt_newt_component_get_sensitive(component));
        break;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
        break;
    }
}

static void
nmt_newt_component_class_init(NmtNewtComponentClass *component_class)
{
    GObjectClass       *object_class = G_OBJECT_CLASS(component_class);
    NmtNewtWidgetClass *widget_class = NMT_NEWT_WIDGET_CLASS(component_class);

    g_type_class_add_private(component_class, sizeof(NmtNewtComponentPrivate));

    /* virtual methods */
    object_class->set_property = nmt_newt_component_set_property;
    object_class->get_property = nmt_newt_component_get_property;

    widget_class->realize             = nmt_newt_component_realize;
    widget_class->unrealize           = nmt_newt_component_unrealize;
    widget_class->get_components      = nmt_newt_component_get_components;
    widget_class->find_component      = nmt_newt_component_find_component;
    widget_class->size_request        = nmt_newt_component_size_request;
    widget_class->size_allocate       = nmt_newt_component_size_allocate;
    widget_class->get_focus_component = nmt_newt_component_get_focus_component;

    /* properties */

    /**
     * NmtNewtComponent:component:
     *
     * The component's #newtComponent
     */
    g_object_class_install_property(
        object_class,
        PROP_COMPONENT,
        g_param_spec_pointer("component", "", "", G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
    /**
     * NmtNewtComponent:sensitive:
     *
     * Whether the component is sensitive. Insensitive components will
     * be skipped over in the keyboard tab chain, and may be displayed
     * differently.
     */
    g_object_class_install_property(
        object_class,
        PROP_SENSITIVE,
        g_param_spec_boolean("sensitive",
                             "",
                             "",
                             TRUE,
                             G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
}
