/*
 *  $Id: layer-quad.c 29656 2026-03-26 10:21:10Z yeti-dn $
 *  Copyright (C) 2024 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 <gdk/gdkkeysyms.h>

#include "libgwyddion/macros.h"
#include "libgwyddion/math.h"
#include "libgwyddion/selection-quad.h"

#include "libgwyui/data-view.h"
#include "libgwyui/layer-quad.h"
#include "libgwyui/cairo-utils.h"
#include "libgwyui/layer-utils.h"

enum {
    OBJECT_SIZE = 8
};

enum {
    PROP_0,
    PROP_N_LINES,
    PROP_CONVEX,
    NUM_PROPERTIES,
};

struct _GwyLayerQuadPrivate {
    /* Properties */
    gint n_lines;
    gboolean convex;

    /* Dynamic state */
    gint moving_point;
};

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 void     draw                     (GwyVectorLayer *layer,
                                          cairo_t *cr);
static void     draw_or_invalidate       (GwyVectorLayer *layer,
                                          cairo_t *cr,
                                          GwyDataView *dataview,
                                          cairo_region_t *region,
                                          const gdouble *xy);
static void     invalidate_object        (GwyVectorLayer *layer,
                                          cairo_region_t *region,
                                          const gdouble *xy);
static void     pointer_moved            (GwyVectorLayer *layer,
                                          gdouble x,
                                          gdouble y);
static void     button_pressed           (GwyVectorLayer *layer,
                                          gdouble x,
                                          gdouble y);
static void     button_released          (GwyVectorLayer *layer,
                                          gdouble x,
                                          gdouble y);
static gboolean quadrilateral_is_convex  (const gdouble *xy);
static gboolean solve_projection_from_1x1(const gdouble *xy,
                                          gdouble *matrix);
static gboolean project                  (const gdouble *xyfrom,
                                          const gdouble *matrix,
                                          gdouble *xyto);
static gint     near_vertex              (GwyVectorLayer *layer,
                                          gdouble xreal,
                                          gdouble yreal);
//static gboolean key_pressed        (GwyVectorLayer *layer,
//                                    GdkEventKey *event);

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

G_DEFINE_TYPE_WITH_CODE(GwyLayerQuad, gwy_layer_quad, GWY_TYPE_VECTOR_LAYER,
                        G_ADD_PRIVATE(GwyLayerQuad))

static void
gwy_layer_quad_class_init(GwyLayerQuadClass *klass)
{
    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
    GwyVectorLayerClass *vector_class = GWY_VECTOR_LAYER_CLASS(klass);

    parent_class = gwy_layer_quad_parent_class;

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

    vector_class->selection_type = GWY_TYPE_SELECTION_QUAD;
    vector_class->draw = draw;
    vector_class->invalidate_object = invalidate_object;
    vector_class->motion = pointer_moved;
    vector_class->button_press = button_pressed;
    vector_class->button_release = button_released;
    //vector_class->key_press = key_pressed;

    properties[PROP_N_LINES] = g_param_spec_uint("n-lines", NULL,
                                                 "Number of subdivision lines to draw inside the quadrilateral",
                                                 0, 1024, 0, GWY_GPARAM_RWE);

    properties[PROP_CONVEX] = g_param_spec_boolean("convex", NULL,
                                                   "Allow only convex quadrilaterals",
                                                   TRUE, GWY_GPARAM_RWE);

    g_object_class_install_properties(gobject_class, NUM_PROPERTIES, properties);
}

static void
gwy_layer_quad_init(GwyLayerQuad *layer)
{
    GwyLayerQuadPrivate *priv;

    layer->priv = priv = gwy_layer_quad_get_instance_private(layer);
    priv->moving_point = -1;
    priv->convex = TRUE;
}

static void
set_property(GObject *object,
             guint prop_id,
             const GValue *value,
             GParamSpec *pspec)
{
    GwyLayerQuad *layer = GWY_LAYER_QUAD(object);

    switch (prop_id) {
        case PROP_N_LINES:
        gwy_layer_quad_set_n_lines(layer, g_value_get_int(value));
        break;

        case PROP_CONVEX:
        gwy_layer_quad_set_convex(layer, g_value_get_boolean(value));
        break;

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

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

    switch (prop_id) {
        case PROP_N_LINES:
        g_value_set_int(value, priv->n_lines);
        break;

        case PROP_CONVEX:
        g_value_set_boolean(value, priv->convex);
        break;

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

static void
draw(GwyVectorLayer *layer, cairo_t *cr)
{
    GwySelection *selection = gwy_vector_layer_get_selection(layer);
    GtkWidget *parent = gwy_data_view_layer_get_parent(GWY_DATA_VIEW_LAYER(layer));
    if (!parent) {
        g_warning("Standalone drawing is not implemented yet.");
        return;
    }
    g_return_if_fail(GWY_IS_DATA_VIEW(parent));
    GwyDataView *dataview = GWY_DATA_VIEW(parent);

    cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
    cairo_set_line_width(cr, 1.0);
    cairo_set_operator(cr, CAIRO_OPERATOR_DIFFERENCE);

    gint n = gwy_selection_get_n_objects(selection);
    for (gint i = 0; i < n; i++) {
        gdouble xy[OBJECT_SIZE];
        gwy_selection_get_object(selection, i, xy);
        draw_or_invalidate(layer, cr, dataview, NULL, xy);
    }
}

static void
draw_or_invalidate(GwyVectorLayer *layer,
                   cairo_t *cr, GwyDataView *dataview,
                   cairo_region_t *region,
                   const gdouble *xy)
{
    GwyLayerQuadPrivate *priv = GWY_LAYER_QUAD(layer)->priv;
    gint nlines = priv->n_lines;
    g_assert(cr || region);

    gdouble xsize, ysize;
    gwy_data_view_get_real_data_sizes(dataview, &xsize, &ysize);

    /* The perimeted. And the only place we care about invalidation because subdivision lines are by definition
     * inside. */
    gdouble x, y;
    gwy_data_view_coords_real_to_widget(dataview, xy[0], xy[1], &x, &y);
    gdouble xmin = x, xmax = x, ymin = y, ymax = y;
    if (cr)
        cairo_move_to(cr, x, y);
    for (gint i = 1; i < 4; i++) {
        gwy_data_view_coords_real_to_widget(dataview, xy[2*i + 0], xy[2*i + 1], &x, &y);
        if (cr)
            cairo_line_to(cr, x, y);
        if (region) {
            xmin = fmin(xmin, x);
            xmax = fmax(xmax, x);
            ymin = fmin(ymin, y);
            ymax = fmax(ymax, y);
        }
    }
    if (cr) {
        cairo_close_path(cr);
        cairo_stroke(cr);
    }
    if (region) {
        cairo_rectangle_int_t rect;
        gdouble lw = 1.0;
        rect.x = (gint)floor(xmin - 0.6*lw);
        rect.y = (gint)floor(ymin - 0.6*lw);
        rect.width = (gint)ceil(xmax + 0.6*lw) - rect.x;
        rect.height = (gint)ceil(ymax + 0.6*lw) - rect.y;
        cairo_region_union_rectangle(region, &rect);
    }

    if (!nlines || !cr)
        return;

    gdouble from_unit_square[9];
    if (!solve_projection_from_1x1(xy, from_unit_square))
        return;

    static const gdouble dashes[2] = { 3.0, 4.0 };
    cairo_save(cr);
    cairo_set_dash(cr, dashes, G_N_ELEMENTS(dashes), 0.0);

    gdouble xyunit[2], xyproj[4], xl0, yl0, xl1, yl1;
    for (gint i = 0; i < nlines; i++) {
        xyunit[0] = (i + 1.0)/(nlines + 1.0);
        xyunit[1] = 0.0;
        project(xyunit, from_unit_square, xyproj + 0);
        xyunit[1] = 1.0;
        project(xyunit, from_unit_square, xyproj + 2);
        _gwy_transform_line_to_target(dataview, xyproj[0], xyproj[1], xyproj[2], xyproj[3], &xl0, &yl0, &xl1, &yl1);
        gwy_cairo_line(cr, xl0, yl0, xl1, yl1);
    }
    for (gint i = 0; i < nlines; i++) {
        xyunit[1] = (i + 1.0)/(nlines + 1.0);
        xyunit[0] = 0.0;
        project(xyunit, from_unit_square, xyproj + 0);
        xyunit[0] = 1.0;
        project(xyunit, from_unit_square, xyproj + 2);
        _gwy_transform_line_to_target(dataview, xyproj[0], xyproj[1], xyproj[2], xyproj[3], &xl0, &yl0, &xl1, &yl1);
        gwy_cairo_line(cr, xl0, yl0, xl1, yl1);
    }

    cairo_stroke(cr);
    cairo_restore(cr);
}

static void
invalidate_object(GwyVectorLayer *layer,
                  cairo_region_t *region,
                  const gdouble *xy)
{
    GtkWidget *parent = gwy_data_view_layer_get_parent(GWY_DATA_VIEW_LAYER(layer));
    g_return_if_fail(GWY_IS_DATA_VIEW(parent));
    draw_or_invalidate(layer, NULL, GWY_DATA_VIEW(parent), region, xy);
}

/* Anything better? */
static void
set_cursor_according_to_proximity(GwyVectorLayer *layer,
                                  G_GNUC_UNUSED gdouble xreal,
                                  G_GNUC_UNUSED gdouble yreal)
{
    gint ipt = near_vertex(layer, xreal, yreal);
    const gchar *name = (ipt >= 0 ? "move" : NULL);
    gwy_data_view_set_named_cursor(GWY_DATA_VIEW(gwy_data_view_layer_get_parent(GWY_DATA_VIEW_LAYER(layer))), name);
}

static gboolean
update_vertex_coordinates(GwyVectorLayer *layer, gint iobj,
                          gdouble x, gdouble y, gdouble *xy)
{
    GwyLayerQuadPrivate *priv = GWY_LAYER_QUAD(layer)->priv;
    g_return_val_if_fail(priv->moving_point >= 0 && priv->moving_point < 4, FALSE);

    GwySelection *selection = gwy_vector_layer_get_selection(layer);
    gwy_selection_get_object(selection, iobj, xy);
    xy[2*priv->moving_point + 0] = x;
    xy[2*priv->moving_point + 1] = y;
    return !priv->convex || quadrilateral_is_convex(xy);
}

static void
pointer_moved(GwyVectorLayer *layer, gdouble x, gdouble y)
{
    if (!gwy_vector_layer_get_editable(layer))
        return;

    GwySelection *selection = gwy_vector_layer_get_selection(layer);
    if (!selection)
        return;

    GtkWidget *parent = gwy_data_view_layer_get_parent(GWY_DATA_VIEW_LAYER(layer));
    g_return_if_fail(GWY_IS_DATA_VIEW(parent));
    GwyDataView *dataview = GWY_DATA_VIEW(parent);

    gwy_data_view_coords_widget_clamp(dataview, &x, &y);
    gdouble xreal, yreal;
    gwy_data_view_coords_widget_to_real(dataview, x, y, &xreal, &yreal);

    if (!gwy_vector_layer_get_current_button(layer)) {
        set_cursor_according_to_proximity(layer, xreal, yreal);
        return;
    }

    gint iobj = gwy_vector_layer_get_current_object(layer);
    if (iobj < 0)
        return;

    gdouble xy[OBJECT_SIZE];
    if (update_vertex_coordinates(layer, iobj, xreal, yreal, xy))
        iobj = gwy_vector_layer_update_object(layer, iobj, xy);
}

static void
button_pressed(GwyVectorLayer *layer, gdouble x, gdouble y)
{
    gint button = gwy_vector_layer_get_current_button(layer);
    if (button != 1)
        return;

    GwySelection *selection = gwy_vector_layer_get_selection(layer);
    GtkWidget *parent = gwy_data_view_layer_get_parent(GWY_DATA_VIEW_LAYER(layer));
    g_return_if_fail(GWY_IS_DATA_VIEW(parent));
    GwyDataView *dataview = GWY_DATA_VIEW(parent);

    /* Do nothing when we are outside. */
    if (!gwy_data_view_coords_widget_clamp(dataview, &x, &y))
        return;
    /* Give up when there is no selection. We do not know how to init a quadrilateral using a single mouse click. */
    if (!gwy_selection_get_n_objects(selection))
        return;

    GwyLayerQuadPrivate *priv = GWY_LAYER_QUAD(layer)->priv;
    gdouble xreal, yreal;
    gwy_data_view_coords_widget_to_real(dataview, x, y, &xreal, &yreal);

    gint i = near_vertex(layer, xreal, yreal), iobj = (i >= 0 ? i/4 : -1);
    if (just_choose_object(layer, iobj))
        return;

    /* Likewise, give up when the click is not near any point because we would need to create a new quadrilateral
     * from scratch. */
    if (i < 0)
        return;

    priv->moving_point = i % 4;
    gdouble xy[OBJECT_SIZE];
    gwy_selection_get_object(selection, iobj, xy);
    xy[2*priv->moving_point + 0] = xreal;
    xy[2*priv->moving_point + 1] = yreal;
    if (!priv->convex || quadrilateral_is_convex(xy))
        iobj = gwy_vector_layer_update_object(layer, iobj, xy);
    gwy_vector_layer_set_current_object(layer, iobj);
}

static void
button_released(GwyVectorLayer *layer, gdouble x, gdouble y)
{
    gint button = gwy_vector_layer_get_current_button(layer);
    gint iobj = gwy_vector_layer_get_current_object(layer);
    if (button != 1 || iobj < 0)
        return;

    GwySelection *selection = gwy_vector_layer_get_selection(layer);
    GtkWidget *parent = gwy_data_view_layer_get_parent(GWY_DATA_VIEW_LAYER(layer));
    g_return_if_fail(GWY_IS_DATA_VIEW(parent));
    GwyDataView *dataview = GWY_DATA_VIEW(parent);

    gdouble xreal, yreal;
    gwy_data_view_coords_widget_to_real(dataview, x, y, &xreal, &yreal);
    gdouble xy[OBJECT_SIZE];
    if (update_vertex_coordinates(layer, iobj, xreal, yreal, xy))
        iobj = gwy_vector_layer_update_object(layer, iobj, xy);

    gwy_vector_layer_set_current_object(layer, -1);
    set_cursor_according_to_proximity(layer, xreal, yreal);
    gwy_selection_finished(selection);
}

/* TODO: Later */
#if 0
static gboolean
key_pressed(GwyVectorLayer *layer, GdkEventKey *event)
{
    gboolean large_step = (event->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK));
    gint which = ((event->state & GDK_SHIFT_MASK) ? 2 : 0);
    GwyDataView *dataview;
    guint keyval = event->keyval;
    gint chosen = priv->chosen, xcurr, ycurr, xnew, ynew, move_distance;
    gdouble xy[4];

    if (chosen < 0
        || chosen >= gwy_selection_get_n_objects(priv->selection))
        return FALSE;

    if (keyval != GDK_KEY_Left && keyval != GDK_KEY_Right
        && keyval != GDK_KEY_Up && keyval != GDK_KEY_Down)
        return FALSE;

    dataview = GWY_DATA_VIEW(GWY_DATA_VIEW_LAYER(layer)->parent);
    g_return_val_if_fail(dataview, FALSE);

    gwy_selection_get_object(priv->selection, chosen, xy);
    gwy_data_view_coords_real_to_widget(dataview, xy[which], xy[which+1], &xcurr, &ycurr);
    xnew = xcurr;
    ynew = ycurr;
    move_distance = (large_step ? 16 : 1);
    if (keyval == GDK_KEY_Left)
        xnew -= move_distance;
    else if (keyval == GDK_KEY_Right)
        xnew += move_distance;
    else if (keyval == GDK_KEY_Up)
        ynew -= move_distance;
    else if (keyval == GDK_KEY_Down)
        ynew += move_distance;
    gwy_data_view_coords_widget_clamp(dataview, &xnew, &ynew);

    if (xnew != xcurr || ynew != ycurr) {
        gwy_data_view_coords_widget_to_real(dataview, xnew, ynew, xy+which, xy+which+1);
        gwy_selection_set_object(priv->selection, chosen, xy);
    }

    return TRUE;
}
#endif

/**
 * gwy_layer_quad_new:
 *
 * Creates a new quadrilateral vector layer.
 *
 * Returns: A newly created quadrilateral vector layer.
 **/
GwyVectorLayer*
gwy_layer_quad_new(void)
{
    return (GwyVectorLayer*)g_object_new(GWY_TYPE_LAYER_QUAD, NULL);
}

/**
 * gwy_layer_quad_get_n_lines:
 * @layer: A quadrilateral vector layer.
 *
 * Gets the number of refinement lines of a quadrilateral vector layer.
 *
 * Returns: The number of lines.
 **/
gint
gwy_layer_quad_get_n_lines(GwyLayerQuad *layer)
{
    g_return_val_if_fail(GWY_IS_LAYER_QUAD(layer), 0);
    return layer->priv->n_lines;
}

/**
 * gwy_layer_quad_set_n_lines:
 * @layer: A quadrilateral vector layer.
 * @n_lines: Number of lines.
 *
 * Sets the number of refinement lines of a quadrilateral vector layer.
 *
 * The perimeter is always drawn. If @n_lines is zero nothing else is drawn. Otherwise @n_lines extra lines are drawn
 * in the interior between opposite sides, corresponding to the refinement of the quadrilateral as if it was the
 * result of a projective transformation of a rectangle.
 **/
void
gwy_layer_quad_set_n_lines(GwyLayerQuad *layer,
                           gint n_lines)
{
    g_return_if_fail(GWY_IS_LAYER_QUAD(layer));
    g_return_if_fail(n_lines >= 0 && n_lines <= 1024);

    GwyLayerQuadPrivate *priv = layer->priv;
    if (n_lines == priv->n_lines)
        return;

    priv->n_lines = n_lines;
    g_object_notify_by_pspec(G_OBJECT(layer), properties[PROP_N_LINES]);
    gwy_data_view_layer_updated(GWY_DATA_VIEW_LAYER(layer));
}

/**
 * gwy_layer_quad_set_convex:
 * @layer: A quadrilateral vector layer.
 * @convex: %TRUE to only allow convex quadrilaterals; %FALSE to allow any.
 *
 * Sets whether a quadrilateral vector layer only allows convex quadrilaterals.
 *
 * If @convex is set to %TRUE the selection should not contain non-convex quadrilaterals. If it does, the behaviour
 * is undefined.
 **/
void
gwy_layer_quad_set_convex(GwyLayerQuad *layer, gboolean convex)
{
    g_return_if_fail(GWY_IS_LAYER_QUAD(layer));

    GwyLayerQuadPrivate *priv = layer->priv;
    if (!convex == !priv->convex)
        return;

    priv->convex = !!convex;
    g_object_notify_by_pspec(G_OBJECT(layer), properties[PROP_CONVEX]);
    gwy_data_view_layer_updated(GWY_DATA_VIEW_LAYER(layer));
}

/**
 * gwy_layer_quad_get_convex:
 * @layer: A quadrilateral vector layer.
 *
 * Reports whether a quadrilateral vector layer has visibly convex quads.
 *
 * Returns: %TRUE if quad numbers are shown; %FALSE if quad numbers are not shown.
 **/
gboolean
gwy_layer_quad_get_convex(GwyLayerQuad *layer)
{
    g_return_val_if_fail(GWY_IS_LAYER_QUAD(layer), FALSE);
    return layer->priv->convex;
}

static gboolean
quadrilateral_is_convex(const gdouble *xy)
{
    gdouble v[OBJECT_SIZE];

    /* Check if the two vectors seem to form non-degenerate angles and the sizes are also sufficiently non-degenerate.
     * Err on the side of caution. * */
    for (gint i = 0; i < OBJECT_SIZE/2; i++) {
        gint ii = (i + 1) % (OBJECT_SIZE/2);

        v[2*i] = xy[2*ii] - xy[2*i];
        v[2*i + 1] = xy[2*ii + 1] - xy[2*i + 1];
    }
    gdouble cpmax = 0.0;
    gdouble cpmin = G_MAXDOUBLE;
    for (gint i = 0; i < OBJECT_SIZE/2; i++) {
        gint ii = (i + 1) % (OBJECT_SIZE/2);
        gdouble cp1 = v[2*i]*v[2*ii + 1];
        gdouble cp2 = v[2*i + 1]*v[2*ii];
        cpmin = fmin(cp1 - cp2, cpmin);
        cpmax = fmax(cpmax, fmax(fabs(cp1), fabs(cp2)));
        if (cpmin <= 1e-9*cpmax)
            return FALSE;
    }

    return TRUE;
}

static gboolean
project(const gdouble *xyfrom, const gdouble *matrix, gdouble *xyto)
{
    const gdouble *mx = matrix, *my = matrix + 3, *m1 = matrix + 6;
    gdouble x = xyfrom[0], y = xyfrom[1], d;

    d = m1[0]*x + m1[1]*y + m1[2];
    xyto[0] = (mx[0]*x + mx[1]*y + mx[2])/d;
    xyto[1] = (my[0]*x + my[1]*y + my[2])/d;

    if (fabs(d) < 1e-12*(fabs(m1[0]*x) + fabs(m1[1]*y) + fabs(m1[2])))
        return FALSE;

    return TRUE;
}

static gboolean
solve_projection(const gdouble *xyfrom,
                 const gdouble *xyto,
                 gdouble *matrix)
{
    gdouble a[64], rhs[8];

    gwy_clear(a, 64);
    for (guint i = 0; i < 4; i++) {
        gdouble xf = xyfrom[2*i + 0], yf = xyfrom[2*i + 1];
        gdouble xt = xyto[2*i + 0], yt = xyto[2*i + 1];
        gdouble *axrow = a + 16*i, *ayrow = axrow + 8, *r = rhs + 2*i;

        axrow[0] = ayrow[3] = xf;
        axrow[1] = ayrow[4] = yf;
        axrow[2] = ayrow[5] = 1.0;
        axrow[6] = -xf*xt;
        axrow[7] = -yf*xt;
        ayrow[6] = -xf*yt;
        ayrow[7] = -yf*yt;
        r[0] = xt;
        r[1] = yt;
    }

    if (!gwy_math_lin_solve_rewrite(8, a, rhs, matrix))
        return FALSE;

    matrix[8] = 1.0;
    return TRUE;
}

static gboolean
solve_projection_from_1x1(const gdouble *xy, gdouble *matrix)
{
    static const gdouble unit_square[OBJECT_SIZE] = {
        0.0, 0.0,
        1.0, 0.0,
        1.0, 1.0,
        0.0, 1.0,
    };

    return solve_projection(unit_square, xy, matrix);
}

static gint
near_vertex(GwyVectorLayer *layer, gdouble xreal, gdouble yreal)
{
    gdouble d2min;
    gint i = gwy_vector_layer_find_nearest(layer, gwy_math_find_nearest_point, 4, xreal, yreal, &d2min);
    return (d2min > PROXIMITY_DISTANCE*PROXIMITY_DISTANCE) ? -1 : i;
}

/**
 * SECTION:layer-quad
 * @title: GwyLayerQuad
 * @short_description: Data view layer for quadrilateral selections
 *
 * #GwyLayerQuad allows selection of general quadrilaterals. It uses #GwySelectionQuad selection type.
 *
 * When it represents a projective transformation of a rectangle, it should be convex and GwyLayerQuad:n-lines can be
 * used to give a more refined view of the perspective transformation using subdivision lines. For the selection of
 * arbitrary quadrilaterals, GwyLayerQuad:n-lines should be zero.
 *
 * The quadrilateral selection needs to be initialised. The layer can only edit a quadrilateral selection; it cannot
 * create one from scratch.
 **/

/* 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 : */
