/*
 *  $Id: gl-setup.c 29656 2026-03-26 10:21:10Z yeti-dn $
 *  Copyright (C) 2006-2025 David Necas (Yeti), Petr Klapetek.
 *  E-mail: yeti@gwyddion.net, klapetek@gwyddion.net.
 *
 *  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; either version 2 of the License, or (at your option) any
 *  later version.
 *
 *  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, write to the
 *  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#include "config.h"

#include "libgwyddion/macros.h"
#include "libgwyddion/serializable-utils.h"

#include "libgwyui/types.h"
#include "libgwyui/gl-setup.h"

#define TYPE_NAME "GwyGLSetup"

enum {
    PROP_0,
    PROP_PROJECTION,
    PROP_VISUALIZATION,
    PROP_AXES_VISIBLE,
    PROP_LABELS_VISIBLE,
    PROP_FMSCALE_VISIBLE,
    PROP_ROTATION_X,
    PROP_ROTATION_Y,
    PROP_SCALE,
    PROP_Z_SCALE,
    PROP_LIGHT_PHI,
    PROP_LIGHT_THETA,
    PROP_HIDE_MASKED,
    PROP_LINE_WIDTH,
    PROP_FMSCALE_SIZE,
    PROP_FMSCALE_Y_ALIGN,
    PROP_FMSCALE_RESERVE_SPACE,
    NUM_PROPERTIES
};

enum {
    ITEM_PROJECTION, ITEM_VISUALIZATION,
    ITEM_AXES_VISIBLE, ITEM_LABELS_VISIBLE, ITEM_FMSCALE_VISIBLE,
    ITEM_ROTATION_X, ITEM_ROTATION_Y,
    ITEM_SCALE, ITEM_Z_SCALE,
    ITEM_LIGHT_PHI, ITEM_LIGHT_THETA,
    ITEM_HIDE_MASKED,
    ITEM_LINE_WIDTH,
    ITEM_FMSCALE_SIZE, ITEM_FMSCALE_Y_ALIGN, ITEM_FMSCALE_RESERVE_SPACE,
    NUM_ITEMS
};

struct _GwyGLSetupPrivate {
    GwyGLProjection projection;
    GwyGLVisualization visualization;

    gboolean axes_visible;
    gboolean labels_visible;
    gboolean fmscale_visible;
    gboolean hide_masked;
    gboolean fmscale_reserve_space;

    gdouble rotation_x;
    gdouble rotation_y;
    gdouble scale;
    gdouble z_scale;
    gdouble light_phi;
    gdouble light_theta;
    gdouble line_width;
    gdouble fmscale_size;
    gdouble fmscale_yalign;
};

static void             serializable_init     (GwySerializableInterface *iface);
static void             serializable_itemize  (GwySerializable *serializable,
                                               GwySerializableGroup *group);
static gboolean         serializable_construct(GwySerializable *serializable,
                                               GwySerializableGroup *group,
                                               GwyErrorList **error_list);
static GwySerializable* serializable_copy     (GwySerializable *serializable);
static void             serializable_assign   (GwySerializable *destination,
                                               GwySerializable *source);
static void             set_property          (GObject *object,
                                               guint prop_id,
                                               const GValue *value,
                                               GParamSpec *pspec);
static void             get_property          (GObject *object,
                                               guint prop_id,
                                               GValue *value,
                                               GParamSpec *pspec);

static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
static GObjectClass *parent_class = NULL;

static GwySerializableItem serializable_items[NUM_ITEMS] = {
    { .name = "projection",            .ctype = GWY_SERIALIZABLE_INT32,   },
    { .name = "visualization",         .ctype = GWY_SERIALIZABLE_INT32,   },
    { .name = "axes-visible",          .ctype = GWY_SERIALIZABLE_BOOLEAN, },
    { .name = "labels-visible",        .ctype = GWY_SERIALIZABLE_BOOLEAN, },
    { .name = "fmscale-visible",       .ctype = GWY_SERIALIZABLE_BOOLEAN, },
    { .name = "rotation-x",            .ctype = GWY_SERIALIZABLE_DOUBLE,  },
    { .name = "rotation-y",            .ctype = GWY_SERIALIZABLE_DOUBLE,  },
    { .name = "scale",                 .ctype = GWY_SERIALIZABLE_DOUBLE,  },
    { .name = "z-scale",               .ctype = GWY_SERIALIZABLE_DOUBLE,  },
    { .name = "light-phi",             .ctype = GWY_SERIALIZABLE_DOUBLE,  },
    { .name = "light-theta",           .ctype = GWY_SERIALIZABLE_DOUBLE,  },
    { .name = "hide-masked",           .ctype = GWY_SERIALIZABLE_BOOLEAN, },
    { .name = "line-width",            .ctype = GWY_SERIALIZABLE_DOUBLE,  },
    { .name = "fmscale-size",          .ctype = GWY_SERIALIZABLE_DOUBLE,  },
    { .name = "fmscale-y-align",       .ctype = GWY_SERIALIZABLE_DOUBLE,  },
    { .name = "fmscale-reserve-space", .ctype = GWY_SERIALIZABLE_BOOLEAN, },
};

G_DEFINE_TYPE_WITH_CODE(GwyGLSetup, gwy_gl_setup, G_TYPE_OBJECT,
                        G_ADD_PRIVATE(GwyGLSetup)
                        G_IMPLEMENT_INTERFACE(GWY_TYPE_SERIALIZABLE, serializable_init))

static void
define_properties(void)
{
    properties[PROP_PROJECTION] = g_param_spec_enum("projection", NULL,
                                                    "The type of the projection",
                                                    GWY_TYPE_GL_PROJECTION,
                                                    GWY_GL_PROJECTION_ORTHOGRAPHIC,
                                                    GWY_GPARAM_RWE);
    properties[PROP_VISUALIZATION] = g_param_spec_enum("visualization", NULL,
                                                       "Data visualization type",
                                                       GWY_TYPE_GL_VISUALIZATION,
                                                       GWY_GL_VISUALIZATION_GRADIENT,
                                                       GWY_GPARAM_RWE);
    properties[PROP_AXES_VISIBLE] = g_param_spec_boolean("axes-visible", NULL,
                                                         "Whether axes are visible",
                                                         TRUE,
                                                         GWY_GPARAM_RWE);
    properties[PROP_LABELS_VISIBLE] = g_param_spec_boolean("labels-visible", NULL,
                                                           "Whether axis labels are visible if axes are visible",
                                                           TRUE,
                                                           GWY_GPARAM_RWE);
    properties[PROP_FMSCALE_VISIBLE] = g_param_spec_boolean("fmscale-visible", NULL,
                                                            "Whether false color bar is visible",
                                                            FALSE,
                                                            GWY_GPARAM_RWE);
    properties[PROP_ROTATION_X] = g_param_spec_double("rotation-x", NULL,
                                                      "Angle of the first rotation around x-axis, in radians",
                                                      -G_MAXDOUBLE, G_MAXDOUBLE, G_PI/4.0,
                                                      GWY_GPARAM_RWE);
    properties[PROP_ROTATION_Y] = g_param_spec_double("rotation-y", NULL,
                                                      "Angle of the second rotation around y-axis, in radians",
                                                      -G_MAXDOUBLE, G_MAXDOUBLE, -G_PI/4.0,
                                                      GWY_GPARAM_RWE);
    properties[PROP_SCALE] = g_param_spec_double("scale", NULL,
                                                 "Overall view scale",
                                                 G_MINDOUBLE, G_MAXDOUBLE, 1.0,
                                                 GWY_GPARAM_RWE);
    properties[PROP_Z_SCALE] = g_param_spec_double("z-scale", NULL,
                                                   "Extra stretch along z (value) axis",
                                                   G_MINDOUBLE, G_MAXDOUBLE, 1.0,
                                                   GWY_GPARAM_RWE);
    properties[PROP_LIGHT_PHI] = g_param_spec_double("light-phi", NULL,
                                                     "Light source direction azimuth in horizontal plane, in radians",
                                                     -G_MAXDOUBLE, G_MAXDOUBLE, 0.0,
                                                     GWY_GPARAM_RWE);
    properties[PROP_LIGHT_THETA] = g_param_spec_double("light-theta", NULL,
                                                       "Light source direction deviation from the z-axis, in radians",
                                                       -G_MAXDOUBLE, G_MAXDOUBLE, 0.0,
                                                       GWY_GPARAM_RWE);
    properties[PROP_HIDE_MASKED] = g_param_spec_boolean("hide-masked", NULL,
                                                        "Hide masked vertices",
                                                        FALSE,
                                                        GWY_GPARAM_RWE);
    properties[PROP_LINE_WIDTH] = g_param_spec_double("line-width", NULL,
                                                      "Width of axis lines and ticks, in pixels.",
                                                      1.0, 10.0, 1.0,
                                                      GWY_GPARAM_RWE);
    properties[PROP_FMSCALE_SIZE] = g_param_spec_double("fmscale-size", NULL,
                                                        "Size of false colour scale relative to view height.",
                                                        0.0, 1.0, 1.0,
                                                        GWY_GPARAM_RWE);
    properties[PROP_FMSCALE_Y_ALIGN] = g_param_spec_double("fmscale-y-align", NULL,
                                                           "Vertical alignment of false colour scale.",
                                                           0.0, 1.0, 0.5,
                                                           GWY_GPARAM_RWE);
    properties[PROP_FMSCALE_RESERVE_SPACE] = g_param_spec_boolean("fmscale-reserve-space", NULL,
                                                                  "Whether to reserve horizontal space for the false "
                                                                  "colour scale.",
                                                                  TRUE,
                                                                  GWY_GPARAM_RWE);
}

static void
serializable_init(GwySerializableInterface *iface)
{
    iface->itemize   = serializable_itemize;
    iface->construct = serializable_construct;
    iface->copy      = serializable_copy;
    iface->assign    = serializable_assign;

    define_properties();
    serializable_items[ITEM_PROJECTION].aux.pspec = properties[PROP_PROJECTION];
    serializable_items[ITEM_VISUALIZATION].aux.pspec = properties[PROP_VISUALIZATION];
    serializable_items[ITEM_AXES_VISIBLE].aux.pspec = properties[PROP_AXES_VISIBLE];
    serializable_items[ITEM_LABELS_VISIBLE].aux.pspec = properties[PROP_LABELS_VISIBLE];
    serializable_items[ITEM_FMSCALE_VISIBLE].aux.pspec = properties[PROP_FMSCALE_VISIBLE];
    serializable_items[ITEM_ROTATION_X].aux.pspec = properties[PROP_ROTATION_X];
    serializable_items[ITEM_ROTATION_Y].aux.pspec = properties[PROP_ROTATION_Y];
    serializable_items[ITEM_SCALE].aux.pspec = properties[PROP_SCALE];
    serializable_items[ITEM_Z_SCALE].aux.pspec = properties[PROP_Z_SCALE];
    serializable_items[ITEM_LIGHT_PHI].aux.pspec = properties[PROP_LIGHT_PHI];
    serializable_items[ITEM_LIGHT_THETA].aux.pspec = properties[PROP_LIGHT_THETA];
    serializable_items[ITEM_HIDE_MASKED].aux.pspec = properties[PROP_HIDE_MASKED];
    serializable_items[ITEM_LINE_WIDTH].aux.pspec = properties[PROP_LINE_WIDTH];
    serializable_items[ITEM_FMSCALE_SIZE].aux.pspec = properties[PROP_FMSCALE_SIZE];
    serializable_items[ITEM_FMSCALE_Y_ALIGN].aux.pspec = properties[PROP_FMSCALE_Y_ALIGN];
    serializable_items[ITEM_FMSCALE_RESERVE_SPACE].aux.pspec = properties[PROP_FMSCALE_RESERVE_SPACE];
    gwy_fill_serializable_defaults_pspec(serializable_items, NUM_ITEMS, FALSE);
}

static void
gwy_gl_setup_class_init(GwyGLSetupClass *klass)
{
    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);

    parent_class = gwy_gl_setup_parent_class;

    gobject_class->set_property = set_property;
    gobject_class->get_property = get_property;

    define_properties();
    g_object_class_install_properties(gobject_class, NUM_PROPERTIES, properties);
}

static void
gwy_gl_setup_init(GwyGLSetup *setup)
{
    GwyGLSetupPrivate *priv;

    priv = setup->priv = gwy_gl_setup_get_instance_private(setup);

    priv->projection = GWY_GL_PROJECTION_ORTHOGRAPHIC;
    priv->visualization = GWY_GL_VISUALIZATION_GRADIENT;

    priv->axes_visible = TRUE;
    priv->labels_visible = TRUE;
    priv->fmscale_visible = FALSE;

    priv->rotation_x = G_PI/4.0;
    priv->rotation_y = -G_PI/4.0;
    priv->scale = 1.0;
    priv->z_scale = 1.0;
    priv->light_phi = 0.0;
    priv->light_theta = 0.0;
    priv->line_width = 1.0;
    priv->fmscale_size = 1.0;
    priv->fmscale_yalign = 0.5;
    priv->fmscale_reserve_space = TRUE;
}

static void
set_property(GObject *object,
             guint prop_id,
             const GValue *value,
             GParamSpec *pspec)
{
    GwyGLSetupPrivate *priv = GWY_GL_SETUP(object)->priv;
    gdouble d;
    gboolean b;
    guint e;
    gboolean changed = FALSE;

    switch (prop_id) {
        case PROP_PROJECTION:
        if ((changed = (priv->projection != (e = g_value_get_enum(value)))))
            priv->projection = e;
        break;

        case PROP_VISUALIZATION:
        if ((changed = (priv->visualization != (e = g_value_get_enum(value)))))
            priv->visualization = e;
        break;

        case PROP_AXES_VISIBLE:
        if ((changed = (priv->axes_visible != (b = g_value_get_boolean(value)))))
            priv->axes_visible = b;
        break;

        case PROP_LABELS_VISIBLE:
        if ((changed = (priv->labels_visible != (b = g_value_get_boolean(value)))))
            priv->labels_visible = b;
        break;

        case PROP_FMSCALE_VISIBLE:
        if ((changed = (priv->fmscale_visible != (b = g_value_get_boolean(value)))))
            priv->fmscale_visible = b;
        break;

        case PROP_ROTATION_X:
        if ((changed = (priv->rotation_x != (d = g_value_get_double(value)))))
            priv->rotation_x = d;
        break;

        case PROP_ROTATION_Y:
        if ((changed = (priv->rotation_y != (d = g_value_get_double(value)))))
            priv->rotation_y = d;
        break;

        case PROP_SCALE:
        if ((changed = (priv->scale != (d = g_value_get_double(value)))))
            priv->scale = d;
        break;

        case PROP_Z_SCALE:
        if ((changed = (priv->z_scale != (d = g_value_get_double(value)))))
            priv->z_scale = d;
        break;

        case PROP_LIGHT_PHI:
        if ((changed = (priv->light_phi != (d = g_value_get_double(value)))))
            priv->light_phi = d;
        break;

        case PROP_LIGHT_THETA:
        if ((changed = (priv->light_theta != (d = g_value_get_double(value)))))
            priv->light_theta = d;
        break;

        case PROP_HIDE_MASKED:
        if ((changed = (priv->hide_masked != (b = g_value_get_boolean(value)))))
            priv->hide_masked = b;
        break;

        case PROP_LINE_WIDTH:
        if ((changed = (priv->line_width != (d = g_value_get_double(value)))))
            priv->line_width = d;
        break;

        case PROP_FMSCALE_SIZE:
        if ((changed = (priv->fmscale_size != (d = g_value_get_double(value)))))
            priv->fmscale_size = d;
        break;

        case PROP_FMSCALE_Y_ALIGN:
        if ((changed = (priv->fmscale_yalign != (d = g_value_get_double(value)))))
            priv->fmscale_yalign = d;
        break;

        case PROP_FMSCALE_RESERVE_SPACE:
        if ((changed = (priv->fmscale_reserve_space != (b = g_value_get_boolean(value)))))
            priv->fmscale_reserve_space = b;
        break;

        default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
        return;
        break;
    }

    if (changed)
        g_object_notify_by_pspec(object, properties[prop_id]);
}

static void
get_property(GObject *object,
             guint prop_id,
             GValue *value,
             GParamSpec *pspec)
{
    GwyGLSetupPrivate *priv = GWY_GL_SETUP(object)->priv;

    switch (prop_id) {
        case PROP_PROJECTION:
        g_value_set_enum(value, priv->projection);
        break;

        case PROP_VISUALIZATION:
        g_value_set_enum(value, priv->visualization);
        break;

        case PROP_AXES_VISIBLE:
        g_value_set_boolean(value, priv->axes_visible);
        break;

        case PROP_LABELS_VISIBLE:
        g_value_set_boolean(value, priv->labels_visible);
        break;

        case PROP_FMSCALE_VISIBLE:
        g_value_set_boolean(value, priv->fmscale_visible);
        break;

        case PROP_ROTATION_X:
        g_value_set_double(value, priv->rotation_x);
        break;

        case PROP_ROTATION_Y:
        g_value_set_double(value, priv->rotation_y);
        break;

        case PROP_SCALE:
        g_value_set_double(value, priv->scale);
        break;

        case PROP_Z_SCALE:
        g_value_set_double(value, priv->z_scale);
        break;

        case PROP_LIGHT_PHI:
        g_value_set_double(value, priv->light_phi);
        break;

        case PROP_LIGHT_THETA:
        g_value_set_double(value, priv->light_theta);
        break;

        case PROP_HIDE_MASKED:
        g_value_set_boolean(value, priv->hide_masked);
        break;

        case PROP_LINE_WIDTH:
        g_value_set_double(value, priv->line_width);
        break;

        case PROP_FMSCALE_SIZE:
        g_value_set_double(value, priv->fmscale_size);
        break;

        case PROP_FMSCALE_Y_ALIGN:
        g_value_set_double(value, priv->fmscale_yalign);
        break;

        case PROP_FMSCALE_RESERVE_SPACE:
        g_value_set_boolean(value, priv->fmscale_reserve_space);
        break;

        default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
        break;
    }
}

static void
serializable_itemize(GwySerializable *serializable, GwySerializableGroup *group)
{
    GwyGLSetup *setup = GWY_GL_SETUP(serializable);
    GwyGLSetupPrivate *priv = setup->priv;

    gwy_serializable_group_alloc_size(group, NUM_ITEMS);
    gwy_serializable_group_append_int32(group, serializable_items + ITEM_PROJECTION, priv->projection);
    gwy_serializable_group_append_int32(group, serializable_items + ITEM_VISUALIZATION, priv->visualization);
    gwy_serializable_group_append_boolean(group, serializable_items + ITEM_AXES_VISIBLE, priv->axes_visible);
    gwy_serializable_group_append_boolean(group, serializable_items + ITEM_LABELS_VISIBLE, priv->labels_visible);
    gwy_serializable_group_append_boolean(group, serializable_items + ITEM_FMSCALE_VISIBLE, priv->fmscale_visible);
    gwy_serializable_group_append_double(group, serializable_items + ITEM_ROTATION_X, priv->rotation_x);
    gwy_serializable_group_append_double(group, serializable_items + ITEM_ROTATION_Y, priv->rotation_y);
    gwy_serializable_group_append_double(group, serializable_items + ITEM_SCALE, priv->scale);
    gwy_serializable_group_append_double(group, serializable_items + ITEM_Z_SCALE, priv->z_scale);
    gwy_serializable_group_append_double(group, serializable_items + ITEM_LIGHT_PHI, priv->light_phi);
    gwy_serializable_group_append_double(group, serializable_items + ITEM_LIGHT_THETA, priv->light_theta);
    gwy_serializable_group_append_boolean(group, serializable_items + ITEM_HIDE_MASKED, priv->hide_masked);
    gwy_serializable_group_append_double(group, serializable_items + ITEM_LINE_WIDTH, priv->line_width);
    gwy_serializable_group_append_double(group, serializable_items + ITEM_FMSCALE_SIZE, priv->fmscale_size);
    gwy_serializable_group_append_double(group, serializable_items + ITEM_FMSCALE_Y_ALIGN, priv->fmscale_yalign);
    gwy_serializable_group_append_boolean(group, serializable_items + ITEM_FMSCALE_RESERVE_SPACE,
                                          priv->fmscale_reserve_space);
}

static gboolean
serializable_construct(GwySerializable *serializable, GwySerializableGroup *group, GwyErrorList **error_list)
{
    GwySerializableItem its[NUM_ITEMS];
    gwy_assign(its, serializable_items, NUM_ITEMS);
    gwy_deserialize_filter_items(its, NUM_ITEMS, group, TYPE_NAME, error_list);

    GwyGLSetup *setup = GWY_GL_SETUP(serializable);
    GwyGLSetupPrivate *priv = setup->priv;

    /* Values are already validated by pspec. */
    priv->projection = its[ITEM_PROJECTION].value.v_int32;
    priv->visualization = its[ITEM_VISUALIZATION].value.v_int32;
    priv->axes_visible = its[ITEM_AXES_VISIBLE].value.v_boolean;
    priv->labels_visible = its[ITEM_LABELS_VISIBLE].value.v_boolean;
    priv->fmscale_visible = its[ITEM_FMSCALE_VISIBLE].value.v_boolean;
    priv->rotation_x = its[ITEM_ROTATION_X].value.v_double;
    priv->rotation_y = its[ITEM_ROTATION_Y].value.v_double;
    priv->scale = its[ITEM_SCALE].value.v_double;
    priv->z_scale = its[ITEM_Z_SCALE].value.v_double;
    priv->light_phi = its[ITEM_LIGHT_PHI].value.v_double;
    priv->light_theta = its[ITEM_LIGHT_THETA].value.v_double;
    priv->hide_masked = its[ITEM_HIDE_MASKED].value.v_boolean;
    priv->line_width = its[ITEM_LINE_WIDTH].value.v_double;
    priv->fmscale_size = its[ITEM_FMSCALE_SIZE].value.v_double;
    priv->fmscale_yalign = its[ITEM_FMSCALE_Y_ALIGN].value.v_double;
    priv->fmscale_reserve_space = its[ITEM_FMSCALE_RESERVE_SPACE].value.v_boolean;

    return TRUE;
}

static GwySerializable*
serializable_copy(GwySerializable *serializable)
{
    GwyGLSetup *setup = GWY_GL_SETUP(serializable);
    GwyGLSetupPrivate *priv = setup->priv;

    GwyGLSetup *copy = gwy_gl_setup_new();
    GwyGLSetupPrivate *cpriv = copy->priv;

    gwy_assign(cpriv, priv, 1);

    return GWY_SERIALIZABLE(copy);
}

static void
serializable_assign(GwySerializable *destination, GwySerializable *source)
{
    GwyGLSetup *destsetup = GWY_GL_SETUP(destination), *srcsetup = GWY_GL_SETUP(source);
    GwyGLSetupPrivate *dpriv = destsetup->priv, *spriv = srcsetup->priv;

    GObject *object = G_OBJECT(destination);
    g_object_freeze_notify(object);
    if (dpriv->projection != spriv->projection) {
        dpriv->projection = spriv->projection;
        g_object_notify_by_pspec(object, properties[PROP_PROJECTION]);
    }
    if (dpriv->visualization != spriv->visualization) {
        dpriv->visualization = spriv->visualization;
        g_object_notify_by_pspec(object, properties[PROP_VISUALIZATION]);
    }
    if (dpriv->axes_visible != spriv->axes_visible) {
        dpriv->axes_visible = !!spriv->axes_visible;
        g_object_notify_by_pspec(object, properties[PROP_AXES_VISIBLE]);
    }
    if (dpriv->labels_visible != spriv->labels_visible) {
        dpriv->labels_visible = !!spriv->labels_visible;
        g_object_notify_by_pspec(object, properties[PROP_LABELS_VISIBLE]);
    }
    if (dpriv->fmscale_visible != spriv->fmscale_visible) {
        dpriv->fmscale_visible = !!spriv->fmscale_visible;
        g_object_notify_by_pspec(object, properties[PROP_FMSCALE_VISIBLE]);
    }
    if (dpriv->rotation_x != spriv->rotation_x) {
        dpriv->rotation_x = spriv->rotation_x;
        g_object_notify_by_pspec(object, properties[PROP_ROTATION_X]);
    }
    if (dpriv->rotation_y != spriv->rotation_y) {
        dpriv->rotation_y = spriv->rotation_y;
        g_object_notify_by_pspec(object, properties[PROP_ROTATION_Y]);
    }
    if (dpriv->scale != spriv->scale) {
        dpriv->scale = spriv->scale;
        g_object_notify_by_pspec(object, properties[PROP_SCALE]);
    }
    if (dpriv->z_scale != spriv->z_scale) {
        dpriv->z_scale = spriv->z_scale;
        g_object_notify_by_pspec(object, properties[PROP_Z_SCALE]);
    }
    if (dpriv->light_phi != spriv->light_phi) {
        dpriv->light_phi = spriv->light_phi;
        g_object_notify_by_pspec(object, properties[PROP_LIGHT_PHI]);
    }
    if (dpriv->light_theta != spriv->light_theta) {
        dpriv->light_theta = spriv->light_theta;
        g_object_notify_by_pspec(object, properties[PROP_LIGHT_THETA]);
    }
    if (dpriv->hide_masked != spriv->hide_masked) {
        dpriv->hide_masked = !!spriv->hide_masked;
        g_object_notify_by_pspec(object, properties[PROP_HIDE_MASKED]);
    }
    if (dpriv->line_width != spriv->line_width) {
        dpriv->line_width = spriv->line_width;
        g_object_notify_by_pspec(object, properties[PROP_LINE_WIDTH]);
    }
    if (dpriv->fmscale_size != spriv->fmscale_size) {
        dpriv->fmscale_size = spriv->fmscale_size;
        g_object_notify_by_pspec(object, properties[PROP_FMSCALE_SIZE]);
    }
    if (dpriv->fmscale_yalign != spriv->fmscale_yalign) {
        dpriv->fmscale_yalign = spriv->fmscale_yalign;
        g_object_notify_by_pspec(object, properties[PROP_FMSCALE_Y_ALIGN]);
    }
    if (dpriv->fmscale_reserve_space != spriv->fmscale_reserve_space) {
        dpriv->fmscale_reserve_space = !!spriv->fmscale_reserve_space;
        g_object_notify_by_pspec(object, properties[PROP_FMSCALE_RESERVE_SPACE]);
    }

    g_object_thaw_notify(object);
}

/**
 * gwy_gl_setup_new:
 *
 * Creates a new OpenGL view setup with default values.
 *
 * Returns: A newly created OpenGL view setup.
 **/
GwyGLSetup*
gwy_gl_setup_new(void)
{
    return (GwyGLSetup*)g_object_new(GWY_TYPE_GL_SETUP, NULL);
}

/**
 * SECTION: gl-setup
 * @title: GwyGLSetup
 * @short_description: OpenGL 3D scene setup
 * @see_also: #GwyGLView -- the basic OpenGL data display widget
 *
 * #GwyGLSetup represents a basic 3D scene setup: viewpoint, projection, light, scale, etc.  It is serializable and
 * used to represent the #GwyGLView setup.
 *
 * Its components can be read directly in the struct or generically with g_object_get().  To set them you it is
 * necessary to use g_object_set().
 **/

/**
 * GwyGLMovement:
 * @GWY_GL_MOVEMENT_NONE: View cannot be changed by user.
 * @GWY_GL_MOVEMENT_ROTATION: View can be rotated.
 * @GWY_GL_MOVEMENT_SCALE: View can be scaled.
 * @GWY_GL_MOVEMENT_DEFORMATION: View can be scaled.
 * @GWY_GL_MOVEMENT_LIGHT: Light position can be changed.
 *
 * The type of OpenGL 3D view change that happens when user drags it with mouse.
 */

/**
 * GwyGLProjection:
 * @GWY_GL_PROJECTION_ORTHOGRAPHIC: Otrhographic projection.
 * @GWY_GL_PROJECTION_PERSPECTIVE: Perspective projection.
 *
 * OpenGL 3D view projection type.
 **/

/**
 * GwyGLVisualization:
 * @GWY_GL_VISUALIZATION_GRADIENT: Data are displayed with color corresponding to 2D view.
 * @GWY_GL_VISUALIZATION_LIGHTING: Data are displayed as an uniform material with some lighting.
 * @GWY_GL_VISUALIZATION_OVERLAY: Data are displayed with grading and lighting, with colour possibly taken from
 *                                a different data field.
 * @GWY_GL_VISUALIZATION_OVERLAY_NO_LIGHT: Data are displayed with grading and lighting, with colour possibly taken
 *                                         from a different data field.
 *
 * OpenGL 3D view data visualization type.
 **/

/**
 * GwyGLViewLabel:
 * @GWY_GL_VIEW_LABEL_X: X-axis label.
 * @GWY_GL_VIEW_LABEL_Y: Y-axis label.
 * @GWY_GL_VIEW_LABEL_MIN: Z-axis bottom label.
 * @GWY_GL_VIEW_LABEL_MAX: Z-axis top label.
 * @GWY_GL_VIEW_NLABELS: The number of labels.
 *
 * OpenGL 3D view label type.
 **/

/* vim: set cin columns=120 tw=118 et ts=4 sw=4 cino=>1s,e0,n0,f0,{0,}0,^0,\:1s,=0,g1s,h0,t0,+1s,c3,(0,u0 : */
