/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* gimppivotselector.c
* Copyright (C) 2019 Ell
*
* 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 3 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, see .
*/
#include "config.h"
#include
#include
#include "libgimpbase/gimpbase.h"
#include "libgimpmath/gimpmath.h"
#include "libgimpwidgets/gimpwidgets.h"
#include "widgets-types.h"
#include "core/gimpmarshal.h"
#include "gimppivotselector.h"
#define EPSILON 1e-6
enum
{
CHANGED,
LAST_SIGNAL
};
enum
{
PROP_0,
PROP_LEFT,
PROP_TOP,
PROP_RIGHT,
PROP_BOTTOM,
PROP_X,
PROP_Y
};
struct _GimpPivotSelectorPrivate
{
gdouble left;
gdouble top;
gdouble right;
gdouble bottom;
gdouble x;
gdouble y;
GtkWidget *buttons[9];
GtkWidget *active_button;
};
/* local function prototypes */
static void gimp_pivot_selector_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec);
static void gimp_pivot_selector_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec);
static void gimp_pivot_selector_button_toggled (GtkToggleButton *button,
GimpPivotSelector *selector);
static GtkWidget * gimp_pivot_selector_position_to_button (GimpPivotSelector *selector,
gdouble x,
gdouble y);
static void gimp_pivot_selector_button_to_position (GimpPivotSelector *selector,
GtkWidget *button,
gdouble *x,
gdouble *y);
static void gimp_pivot_selector_update_active_button (GimpPivotSelector *selector);
G_DEFINE_TYPE_WITH_PRIVATE (GimpPivotSelector, gimp_pivot_selector, GTK_TYPE_TABLE)
#define parent_class gimp_pivot_selector_parent_class
static guint pivot_selector_signals[LAST_SIGNAL];
/* private functions */
static void
gimp_pivot_selector_class_init (GimpPivotSelectorClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
pivot_selector_signals[CHANGED] =
g_signal_new ("changed",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (GimpPivotSelectorClass, changed),
NULL, NULL,
gimp_marshal_VOID__VOID,
G_TYPE_NONE, 0);
object_class->get_property = gimp_pivot_selector_get_property;
object_class->set_property = gimp_pivot_selector_set_property;
g_object_class_install_property (object_class, PROP_LEFT,
g_param_spec_double ("left",
NULL, NULL,
-G_MAXDOUBLE,
+G_MAXDOUBLE,
0.0,
GIMP_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
g_object_class_install_property (object_class, PROP_TOP,
g_param_spec_double ("top",
NULL, NULL,
-G_MAXDOUBLE,
+G_MAXDOUBLE,
0.0,
GIMP_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
g_object_class_install_property (object_class, PROP_RIGHT,
g_param_spec_double ("right",
NULL, NULL,
-G_MAXDOUBLE,
+G_MAXDOUBLE,
0.0,
GIMP_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
g_object_class_install_property (object_class, PROP_BOTTOM,
g_param_spec_double ("bottom",
NULL, NULL,
-G_MAXDOUBLE,
+G_MAXDOUBLE,
0.0,
GIMP_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
g_object_class_install_property (object_class, PROP_X,
g_param_spec_double ("x",
NULL, NULL,
-G_MAXDOUBLE,
+G_MAXDOUBLE,
0.0,
GIMP_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
g_object_class_install_property (object_class, PROP_Y,
g_param_spec_double ("y",
NULL, NULL,
-G_MAXDOUBLE,
+G_MAXDOUBLE,
0.0,
GIMP_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
}
static void
gimp_pivot_selector_init (GimpPivotSelector *selector)
{
GtkTable *table = GTK_TABLE (selector);
gint i;
selector->priv = gimp_pivot_selector_get_instance_private (selector);
gtk_table_resize (table, 3, 3);
gtk_table_set_homogeneous (table, TRUE);
for (i = 0; i < 9; i++)
{
static const gchar *icon_names[9] = {
GIMP_ICON_PIVOT_NORTH_WEST,
GIMP_ICON_PIVOT_NORTH,
GIMP_ICON_PIVOT_NORTH_EAST,
GIMP_ICON_PIVOT_WEST,
GIMP_ICON_PIVOT_CENTER,
GIMP_ICON_PIVOT_EAST,
GIMP_ICON_PIVOT_SOUTH_WEST,
GIMP_ICON_PIVOT_SOUTH,
GIMP_ICON_PIVOT_SOUTH_EAST
};
GtkWidget *button;
GtkWidget *image;
gint x, y;
x = i % 3;
y = i / 3;
button = gtk_toggle_button_new ();
gtk_widget_set_can_focus (button, FALSE);
gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
gtk_table_attach_defaults (table, button,
x, x + 1, y, y + 1);
gtk_widget_show (button);
selector->priv->buttons[i] = button;
g_signal_connect (button, "toggled",
G_CALLBACK (gimp_pivot_selector_button_toggled),
selector);
image = gtk_image_new_from_icon_name (icon_names[i], GTK_ICON_SIZE_MENU);
gtk_image_set_pixel_size (GTK_IMAGE (image), 12);
gtk_container_add (GTK_CONTAINER (button), image);
gtk_widget_show (image);
}
}
static void
gimp_pivot_selector_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
GimpPivotSelector *selector = GIMP_PIVOT_SELECTOR (object);
switch (property_id)
{
case PROP_LEFT:
gimp_pivot_selector_set_bounds (selector,
g_value_get_double (value),
selector->priv->top,
selector->priv->right,
selector->priv->bottom);
break;
case PROP_TOP:
gimp_pivot_selector_set_bounds (selector,
selector->priv->left,
g_value_get_double (value),
selector->priv->right,
selector->priv->bottom);
break;
case PROP_RIGHT:
gimp_pivot_selector_set_bounds (selector,
selector->priv->left,
selector->priv->top,
g_value_get_double (value),
selector->priv->bottom);
break;
case PROP_BOTTOM:
gimp_pivot_selector_set_bounds (selector,
selector->priv->left,
selector->priv->top,
selector->priv->right,
g_value_get_double (value));
break;
case PROP_X:
gimp_pivot_selector_set_position (selector,
g_value_get_double (value),
selector->priv->y);
break;
case PROP_Y:
gimp_pivot_selector_set_position (selector,
selector->priv->x,
g_value_get_double (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gimp_pivot_selector_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
GimpPivotSelector *selector = GIMP_PIVOT_SELECTOR (object);
switch (property_id)
{
case PROP_LEFT:
g_value_set_double (value, selector->priv->left);
break;
case PROP_TOP:
g_value_set_double (value, selector->priv->top);
break;
case PROP_RIGHT:
g_value_set_double (value, selector->priv->right);
break;
case PROP_BOTTOM:
g_value_set_double (value, selector->priv->bottom);
break;
case PROP_X:
g_value_set_double (value, selector->priv->x);
break;
case PROP_Y:
g_value_set_double (value, selector->priv->y);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gimp_pivot_selector_button_toggled (GtkToggleButton *button,
GimpPivotSelector *selector)
{
if (GTK_WIDGET (button) == selector->priv->active_button)
{
gtk_toggle_button_set_active (button, TRUE);
}
else
{
gdouble x, y;
gimp_pivot_selector_button_to_position (selector, GTK_WIDGET (button),
&x, &y);
gimp_pivot_selector_set_position (selector, x, y);
}
}
static GtkWidget *
gimp_pivot_selector_position_to_button (GimpPivotSelector *selector,
gdouble x,
gdouble y)
{
gint ix;
gint iy;
if (selector->priv->left == selector->priv->right ||
selector->priv->top == selector->priv->bottom)
{
return NULL;
}
x = 2.0 * (x - selector->priv->left) /
(selector->priv->right - selector->priv->left);
y = 2.0 * (y - selector->priv->top) /
(selector->priv->bottom - selector->priv->top);
ix = RINT (x);
iy = RINT (y);
if (fabs (x - ix) > EPSILON || fabs (y - iy) > EPSILON)
return NULL;
if (ix < 0 || ix > 2 || iy < 0 || iy > 2)
return NULL;
return selector->priv->buttons[3 * iy + ix];
}
static void
gimp_pivot_selector_button_to_position (GimpPivotSelector *selector,
GtkWidget *button,
gdouble *x,
gdouble *y)
{
gint i;
for (i = 0; selector->priv->buttons[i] != button; i++);
*x = selector->priv->left +
(selector->priv->right - selector->priv->left) * (i % 3) / 2.0;
*y = selector->priv->top +
(selector->priv->bottom - selector->priv->top) * (i / 3) / 2.0;
}
static void
gimp_pivot_selector_update_active_button (GimpPivotSelector *selector)
{
GtkWidget *button;
button = gimp_pivot_selector_position_to_button (selector,
selector->priv->x,
selector->priv->y);
if (button != selector->priv->active_button)
{
if (selector->priv->active_button)
{
g_signal_handlers_block_by_func (
selector->priv->active_button,
gimp_pivot_selector_button_toggled,
selector);
gtk_toggle_button_set_active (
GTK_TOGGLE_BUTTON (selector->priv->active_button), FALSE);
g_signal_handlers_unblock_by_func (
selector->priv->active_button,
gimp_pivot_selector_button_toggled,
selector);
}
selector->priv->active_button = button;
if (selector->priv->active_button)
{
g_signal_handlers_block_by_func (
selector->priv->active_button,
gimp_pivot_selector_button_toggled,
selector);
gtk_toggle_button_set_active (
GTK_TOGGLE_BUTTON (selector->priv->active_button), TRUE);
g_signal_handlers_unblock_by_func (
selector->priv->active_button,
gimp_pivot_selector_button_toggled,
selector);
}
}
}
/* public functions */
GtkWidget *
gimp_pivot_selector_new (gdouble left,
gdouble top,
gdouble right,
gdouble bottom)
{
return g_object_new (GIMP_TYPE_PIVOT_SELECTOR,
"left", left,
"top", top,
"right", right,
"bottom", bottom,
"x", (left + right) / 2.0,
"y", (top + bottom) / 2.0,
NULL);
}
void
gimp_pivot_selector_set_bounds (GimpPivotSelector *selector,
gdouble left,
gdouble top,
gdouble right,
gdouble bottom)
{
g_return_if_fail (GIMP_IS_PIVOT_SELECTOR (selector));
if (left != selector->priv->left || top != selector->priv->top ||
right != selector->priv->right || bottom != selector->priv->bottom)
{
g_object_freeze_notify (G_OBJECT (selector));
selector->priv->left = left;
selector->priv->top = top;
selector->priv->right = right;
selector->priv->bottom = bottom;
gimp_pivot_selector_update_active_button (selector);
if (left != selector->priv->left)
g_object_notify (G_OBJECT (selector), "left");
if (top != selector->priv->top)
g_object_notify (G_OBJECT (selector), "top");
if (right != selector->priv->right)
g_object_notify (G_OBJECT (selector), "right");
if (left != selector->priv->bottom)
g_object_notify (G_OBJECT (selector), "bottom");
g_object_thaw_notify (G_OBJECT (selector));
}
}
void
gimp_pivot_selector_get_bounds (GimpPivotSelector *selector,
gdouble *left,
gdouble *top,
gdouble *right,
gdouble *bottom)
{
g_return_if_fail (GIMP_IS_PIVOT_SELECTOR (selector));
if (left) *left = selector->priv->left;
if (top) *top = selector->priv->top;
if (right) *right = selector->priv->right;
if (bottom) *bottom = selector->priv->bottom;
}
void
gimp_pivot_selector_set_position (GimpPivotSelector *selector,
gdouble x,
gdouble y)
{
g_return_if_fail (GIMP_IS_PIVOT_SELECTOR (selector));
if (x != selector->priv->x || y != selector->priv->y)
{
g_object_freeze_notify (G_OBJECT (selector));
selector->priv->x = x;
selector->priv->y = y;
gimp_pivot_selector_update_active_button (selector);
g_signal_emit (selector, pivot_selector_signals[CHANGED], 0);
if (x != selector->priv->x)
g_object_notify (G_OBJECT (selector), "x");
if (y != selector->priv->y)
g_object_notify (G_OBJECT (selector), "y");
g_object_thaw_notify (G_OBJECT (selector));
}
}
void
gimp_pivot_selector_get_position (GimpPivotSelector *selector,
gdouble *x,
gdouble *y)
{
g_return_if_fail (GIMP_IS_PIVOT_SELECTOR (selector));
if (x) *x = selector->priv->x;
if (y) *y = selector->priv->y;
}