summaryrefslogtreecommitdiffstats
path: root/app/display/gimptoolgyroscope.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:30:19 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:30:19 +0000
commit5c1676dfe6d2f3c837a5e074117b45613fd29a72 (patch)
treecbffb45144febf451e54061db2b21395faf94bfe /app/display/gimptoolgyroscope.c
parentInitial commit. (diff)
downloadgimp-5c1676dfe6d2f3c837a5e074117b45613fd29a72.tar.xz
gimp-5c1676dfe6d2f3c837a5e074117b45613fd29a72.zip
Adding upstream version 2.10.34.upstream/2.10.34upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'app/display/gimptoolgyroscope.c')
-rw-r--r--app/display/gimptoolgyroscope.c879
1 files changed, 879 insertions, 0 deletions
diff --git a/app/display/gimptoolgyroscope.c b/app/display/gimptoolgyroscope.c
new file mode 100644
index 0000000..4d6b175
--- /dev/null
+++ b/app/display/gimptoolgyroscope.c
@@ -0,0 +1,879 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptoolgyroscope.c
+ * Copyright (C) 2018 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "libgimpmath/gimpmath.h"
+
+#include "display-types.h"
+
+#include "widgets/gimpwidgets-utils.h"
+
+#include "gimpdisplayshell.h"
+#include "gimpdisplayshell-transform.h"
+#include "gimptoolgyroscope.h"
+
+#include "gimp-intl.h"
+
+
+#define EPSILON 1e-6
+#define DEG_TO_RAD (G_PI / 180.0)
+
+
+typedef enum
+{
+ MODE_NONE,
+ MODE_PAN,
+ MODE_ROTATE,
+ MODE_ZOOM
+} Mode;
+
+typedef enum
+{
+ CONSTRAINT_NONE,
+ CONSTRAINT_UNKNOWN,
+ CONSTRAINT_HORIZONTAL,
+ CONSTRAINT_VERTICAL
+} Constraint;
+
+enum
+{
+ PROP_0,
+ PROP_YAW,
+ PROP_PITCH,
+ PROP_ROLL,
+ PROP_ZOOM,
+ PROP_INVERT,
+ PROP_SPEED,
+ PROP_PIVOT_X,
+ PROP_PIVOT_Y
+};
+
+struct _GimpToolGyroscopePrivate
+{
+ gdouble yaw;
+ gdouble pitch;
+ gdouble roll;
+ gdouble zoom;
+
+ gdouble orig_yaw;
+ gdouble orig_pitch;
+ gdouble orig_roll;
+ gdouble orig_zoom;
+
+ gboolean invert;
+
+ gdouble speed;
+
+ gdouble pivot_x;
+ gdouble pivot_y;
+
+ Mode mode;
+ Constraint constraint;
+
+ gdouble last_x;
+ gdouble last_y;
+
+ gdouble last_angle;
+ gdouble curr_angle;
+
+ gdouble last_zoom;
+};
+
+
+/* local function prototypes */
+
+static void gimp_tool_gyroscope_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_tool_gyroscope_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static gint gimp_tool_gyroscope_button_press (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type);
+static void gimp_tool_gyroscope_button_release (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type);
+static void gimp_tool_gyroscope_motion (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state);
+static GimpHit gimp_tool_gyroscope_hit (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity);
+static void gimp_tool_gyroscope_hover (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity);
+static gboolean gimp_tool_gyroscope_key_press (GimpToolWidget *widget,
+ GdkEventKey *kevent);
+static void gimp_tool_gyroscope_motion_modifier (GimpToolWidget *widget,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state);
+static gboolean gimp_tool_gyroscope_get_cursor (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpCursorType *cursor,
+ GimpToolCursorType *tool_cursor,
+ GimpCursorModifier *modifier);
+
+static void gimp_tool_gyroscope_update_status (GimpToolGyroscope *gyroscope,
+ GdkModifierType state);
+
+static void gimp_tool_gyroscope_save (GimpToolGyroscope *gyroscope);
+static void gimp_tool_gyroscope_restore (GimpToolGyroscope *gyroscope);
+
+static void gimp_tool_gyroscope_rotate (GimpToolGyroscope *gyroscope,
+ const GimpVector3 *axis);
+static void gimp_tool_gyroscope_rotate_vector (GimpVector3 *vector,
+ const GimpVector3 *axis);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpToolGyroscope, gimp_tool_gyroscope,
+ GIMP_TYPE_TOOL_WIDGET)
+
+#define parent_class gimp_tool_gyroscope_parent_class
+
+
+static void
+gimp_tool_gyroscope_class_init (GimpToolGyroscopeClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpToolWidgetClass *widget_class = GIMP_TOOL_WIDGET_CLASS (klass);
+
+ object_class->set_property = gimp_tool_gyroscope_set_property;
+ object_class->get_property = gimp_tool_gyroscope_get_property;
+
+ widget_class->button_press = gimp_tool_gyroscope_button_press;
+ widget_class->button_release = gimp_tool_gyroscope_button_release;
+ widget_class->motion = gimp_tool_gyroscope_motion;
+ widget_class->hit = gimp_tool_gyroscope_hit;
+ widget_class->hover = gimp_tool_gyroscope_hover;
+ widget_class->key_press = gimp_tool_gyroscope_key_press;
+ widget_class->motion_modifier = gimp_tool_gyroscope_motion_modifier;
+ widget_class->get_cursor = gimp_tool_gyroscope_get_cursor;
+
+ g_object_class_install_property (object_class, PROP_YAW,
+ g_param_spec_double ("yaw", NULL, NULL,
+ -G_MAXDOUBLE,
+ +G_MAXDOUBLE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_PITCH,
+ g_param_spec_double ("pitch", NULL, NULL,
+ -G_MAXDOUBLE,
+ +G_MAXDOUBLE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_ROLL,
+ g_param_spec_double ("roll", NULL, NULL,
+ -G_MAXDOUBLE,
+ +G_MAXDOUBLE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_ZOOM,
+ g_param_spec_double ("zoom", NULL, NULL,
+ 0.0,
+ +G_MAXDOUBLE,
+ 1.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_INVERT,
+ g_param_spec_boolean ("invert", NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_SPEED,
+ g_param_spec_double ("speed", NULL, NULL,
+ -G_MAXDOUBLE,
+ +G_MAXDOUBLE,
+ 1.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_PIVOT_X,
+ g_param_spec_double ("pivot-x", NULL, NULL,
+ -G_MAXDOUBLE,
+ +G_MAXDOUBLE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_PIVOT_Y,
+ g_param_spec_double ("pivot-y", NULL, NULL,
+ -G_MAXDOUBLE,
+ +G_MAXDOUBLE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+}
+
+static void
+gimp_tool_gyroscope_init (GimpToolGyroscope *gyroscope)
+{
+ gyroscope->private = gimp_tool_gyroscope_get_instance_private (gyroscope);
+}
+
+static void
+gimp_tool_gyroscope_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpToolGyroscope *gyroscope = GIMP_TOOL_GYROSCOPE (object);
+ GimpToolGyroscopePrivate *private = gyroscope->private;
+
+ switch (property_id)
+ {
+ case PROP_YAW:
+ private->yaw = g_value_get_double (value);
+ break;
+ case PROP_PITCH:
+ private->pitch = g_value_get_double (value);
+ break;
+ case PROP_ROLL:
+ private->roll = g_value_get_double (value);
+ break;
+ case PROP_ZOOM:
+ private->zoom = g_value_get_double (value);
+ break;
+ case PROP_INVERT:
+ private->invert = g_value_get_boolean (value);
+ break;
+ case PROP_SPEED:
+ private->speed = g_value_get_double (value);
+ break;
+ case PROP_PIVOT_X:
+ private->pivot_x = g_value_get_double (value);
+ break;
+ case PROP_PIVOT_Y:
+ private->pivot_y = g_value_get_double (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_tool_gyroscope_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpToolGyroscope *gyroscope = GIMP_TOOL_GYROSCOPE (object);
+ GimpToolGyroscopePrivate *private = gyroscope->private;
+
+ switch (property_id)
+ {
+ case PROP_YAW:
+ g_value_set_double (value, private->yaw);
+ break;
+ case PROP_PITCH:
+ g_value_set_double (value, private->pitch);
+ break;
+ case PROP_ROLL:
+ g_value_set_double (value, private->roll);
+ break;
+ case PROP_ZOOM:
+ g_value_set_double (value, private->zoom);
+ break;
+ case PROP_INVERT:
+ g_value_set_boolean (value, private->invert);
+ break;
+ case PROP_SPEED:
+ g_value_set_double (value, private->speed);
+ break;
+ case PROP_PIVOT_X:
+ g_value_set_double (value, private->pivot_x);
+ break;
+ case PROP_PIVOT_Y:
+ g_value_set_double (value, private->pivot_y);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static gint
+gimp_tool_gyroscope_button_press (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type)
+{
+ GimpToolGyroscope *gyroscope = GIMP_TOOL_GYROSCOPE (widget);
+ GimpToolGyroscopePrivate *private = gyroscope->private;
+
+ gimp_tool_gyroscope_save (gyroscope);
+
+ if (state & GDK_MOD1_MASK)
+ {
+ private->mode = MODE_ZOOM;
+
+ private->last_zoom = private->zoom;
+ }
+ else if (state & gimp_get_extend_selection_mask ())
+ {
+ private->mode = MODE_ROTATE;
+
+ private->last_angle = atan2 (coords->y - private->pivot_y,
+ coords->x - private->pivot_x);
+ private->curr_angle = private->last_angle;
+ }
+ else
+ {
+ private->mode = MODE_PAN;
+
+ if (state & gimp_get_constrain_behavior_mask ())
+ private->constraint = CONSTRAINT_UNKNOWN;
+ else
+ private->constraint = CONSTRAINT_NONE;
+ }
+
+ private->last_x = coords->x;
+ private->last_y = coords->y;
+
+ gimp_tool_gyroscope_update_status (gyroscope, state);
+
+ return 1;
+}
+
+static void
+gimp_tool_gyroscope_button_release (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type)
+{
+ GimpToolGyroscope *gyroscope = GIMP_TOOL_GYROSCOPE (widget);
+ GimpToolGyroscopePrivate *private = gyroscope->private;
+
+ if (release_type == GIMP_BUTTON_RELEASE_CANCEL)
+ gimp_tool_gyroscope_restore (gyroscope);
+
+ private->mode = MODE_NONE;
+
+ gimp_tool_gyroscope_update_status (gyroscope, state);
+}
+
+static void
+gimp_tool_gyroscope_motion (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state)
+{
+ GimpToolGyroscope *gyroscope = GIMP_TOOL_GYROSCOPE (widget);
+ GimpToolGyroscopePrivate *private = gyroscope->private;
+ GimpDisplayShell *shell = gimp_tool_widget_get_shell (widget);
+ GimpVector3 axis = {};
+
+ switch (private->mode)
+ {
+ case MODE_PAN:
+ {
+ gdouble x1 = private->last_x;
+ gdouble y1 = private->last_y;
+ gdouble x2 = coords->x;
+ gdouble y2 = coords->y;
+ gdouble factor = 1.0 / private->zoom;
+
+ if (private->constraint != CONSTRAINT_NONE)
+ {
+ gimp_display_shell_rotate_xy_f (shell, x1, y1, &x1, &y1);
+ gimp_display_shell_rotate_xy_f (shell, x2, y2, &x2, &y2);
+
+ if (private->constraint == CONSTRAINT_UNKNOWN)
+ {
+ if (fabs (x2 - x1) > fabs (y2 - y1))
+ private->constraint = CONSTRAINT_HORIZONTAL;
+ else if (fabs (y2 - y1) > fabs (x2 - x1))
+ private->constraint = CONSTRAINT_VERTICAL;
+ }
+
+ if (private->constraint == CONSTRAINT_HORIZONTAL)
+ y2 = y1;
+ else if (private->constraint == CONSTRAINT_VERTICAL)
+ x2 = x1;
+
+ gimp_display_shell_unrotate_xy_f (shell, x1, y1, &x1, &y1);
+ gimp_display_shell_unrotate_xy_f (shell, x2, y2, &x2, &y2);
+ }
+
+ if (private->invert)
+ factor = 1.0 / factor;
+
+ gimp_vector3_set (&axis, y2 - y1, x2 - x1, 0.0);
+ gimp_vector3_mul (&axis, factor * private->speed);
+ }
+ break;
+
+ case MODE_ROTATE:
+ {
+ gdouble angle;
+
+ angle = atan2 (coords->y - private->pivot_y,
+ coords->x - private->pivot_x);
+
+ private->curr_angle = angle;
+
+ angle -= private->last_angle;
+
+ if (state & gimp_get_constrain_behavior_mask ())
+ angle = RINT (angle / (G_PI / 6.0)) * (G_PI / 6.0);
+
+ gimp_vector3_set (&axis, 0.0, 0.0, angle);
+
+ private->last_angle += angle;
+ }
+ break;
+
+ case MODE_ZOOM:
+ {
+ gdouble x1, y1;
+ gdouble x2, y2;
+ gdouble zoom;
+
+ gimp_display_shell_transform_xy_f (shell,
+ private->last_x, private->last_y,
+ &x1, &y1);
+ gimp_display_shell_transform_xy_f (shell,
+ coords->x, coords->y,
+ &x2, &y2);
+
+ zoom = (y1 - y2) * shell->scale_y / 128.0;
+
+ if (private->invert)
+ zoom = -zoom;
+
+ private->last_zoom *= pow (2.0, zoom);
+
+ zoom = log (private->last_zoom / private->zoom) / G_LN2;
+
+ if (state & gimp_get_constrain_behavior_mask ())
+ zoom = RINT (zoom * 2.0) / 2.0;
+
+ g_object_set (gyroscope,
+ "zoom", private->zoom * pow (2.0, zoom),
+ NULL);
+ }
+ break;
+
+ case MODE_NONE:
+ g_return_if_reached ();
+ }
+
+ private->last_x = coords->x;
+ private->last_y = coords->y;
+
+ gimp_tool_gyroscope_rotate (gyroscope, &axis);
+}
+
+static GimpHit
+gimp_tool_gyroscope_hit (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity)
+{
+ return GIMP_HIT_INDIRECT;
+}
+
+static void
+gimp_tool_gyroscope_hover (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity)
+{
+ gimp_tool_gyroscope_update_status (GIMP_TOOL_GYROSCOPE (widget), state);
+}
+
+static gboolean
+gimp_tool_gyroscope_key_press (GimpToolWidget *widget,
+ GdkEventKey *kevent)
+{
+ GimpToolGyroscope *gyroscope = GIMP_TOOL_GYROSCOPE (widget);
+ GimpToolGyroscopePrivate *private = gyroscope->private;
+ GimpDisplayShell *shell = gimp_tool_widget_get_shell (widget);
+ GimpVector3 axis = {};
+ gboolean fast;
+ gboolean result = FALSE;
+
+ fast = (kevent->state & gimp_get_constrain_behavior_mask ());
+
+ if (kevent->state & GDK_MOD1_MASK)
+ {
+ /* zoom */
+ gdouble zoom = 0.0;
+
+ switch (kevent->keyval)
+ {
+ case GDK_KEY_Up:
+ zoom = fast ? +1.0 : +1.0 / 8.0;
+ result = TRUE;
+ break;
+
+ case GDK_KEY_Down:
+ zoom = fast ? -1.0 : -1.0 / 8.0;
+ result = TRUE;
+ break;
+ }
+
+ if (private->invert)
+ zoom = -zoom;
+
+ if (zoom)
+ {
+ g_object_set (gyroscope,
+ "zoom", private->zoom * pow (2.0, zoom),
+ NULL);
+ }
+ }
+ else if (kevent->state & gimp_get_extend_selection_mask ())
+ {
+ /* rotate */
+ gdouble angle = 0.0;
+
+ switch (kevent->keyval)
+ {
+ case GDK_KEY_Left:
+ angle = fast ? +15.0 : +1.0;
+ result = TRUE;
+ break;
+
+ case GDK_KEY_Right:
+ angle = fast ? -15.0 : -1.0;
+ result = TRUE;
+ break;
+ }
+
+ if (shell->flip_horizontally ^ shell->flip_vertically)
+ angle = -angle;
+
+ gimp_vector3_set (&axis, 0.0, 0.0, angle * DEG_TO_RAD);
+ }
+ else
+ {
+ /* pan */
+ gdouble x0 = 0.0;
+ gdouble y0 = 0.0;
+ gdouble x = 0.0;
+ gdouble y = 0.0;
+ gdouble factor = 1.0 / private->zoom;
+
+ if (private->invert)
+ factor = 1.0 / factor;
+
+ switch (kevent->keyval)
+ {
+ case GDK_KEY_Left:
+ x = fast ? +15.0 : +1.0;
+ result = TRUE;
+ break;
+
+ case GDK_KEY_Right:
+ x = fast ? -15.0 : -1.0;
+ result = TRUE;
+ break;
+
+ case GDK_KEY_Up:
+ y = fast ? +15.0 : +1.0;
+ result = TRUE;
+ break;
+
+ case GDK_KEY_Down:
+ y = fast ? -15.0 : -1.0;
+ result = TRUE;
+ break;
+ }
+
+ gimp_display_shell_unrotate_xy_f (shell, x0, y0, &x0, &y0);
+ gimp_display_shell_unrotate_xy_f (shell, x, y, &x, &y);
+
+ gimp_vector3_set (&axis,
+ (y - y0) * DEG_TO_RAD,
+ (x - x0) * DEG_TO_RAD,
+ 0.0);
+ gimp_vector3_mul (&axis, factor);
+ }
+
+ gimp_tool_gyroscope_rotate (gyroscope, &axis);
+
+ return result;
+}
+
+static void
+gimp_tool_gyroscope_motion_modifier (GimpToolWidget *widget,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state)
+{
+ GimpToolGyroscope *gyroscope = GIMP_TOOL_GYROSCOPE (widget);
+ GimpToolGyroscopePrivate *private = gyroscope->private;
+
+ gimp_tool_gyroscope_update_status (gyroscope, state);
+
+ if (key == gimp_get_constrain_behavior_mask ())
+ {
+ switch (private->mode)
+ {
+ case MODE_PAN:
+ if (state & gimp_get_constrain_behavior_mask ())
+ private->constraint = CONSTRAINT_UNKNOWN;
+ else
+ private->constraint = CONSTRAINT_NONE;
+ break;
+
+ case MODE_ROTATE:
+ if (! (state & gimp_get_constrain_behavior_mask ()))
+ private->last_angle = private->curr_angle;
+ break;
+
+ case MODE_ZOOM:
+ if (! (state & gimp_get_constrain_behavior_mask ()))
+ private->last_zoom = private->zoom;
+ break;
+
+ case MODE_NONE:
+ break;
+ }
+ }
+}
+
+static gboolean
+gimp_tool_gyroscope_get_cursor (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpCursorType *cursor,
+ GimpToolCursorType *tool_cursor,
+ GimpCursorModifier *modifier)
+{
+ if (state & GDK_MOD1_MASK)
+ *modifier = GIMP_CURSOR_MODIFIER_ZOOM;
+ else if (state & gimp_get_extend_selection_mask ())
+ *modifier = GIMP_CURSOR_MODIFIER_ROTATE;
+ else
+ *modifier = GIMP_CURSOR_MODIFIER_MOVE;
+
+ return TRUE;
+}
+
+static void
+gimp_tool_gyroscope_update_status (GimpToolGyroscope *gyroscope,
+ GdkModifierType state)
+{
+ GimpToolGyroscopePrivate *private = gyroscope->private;
+ gchar *status;
+
+ if (private->mode == MODE_ZOOM ||
+ (private->mode == MODE_NONE &&
+ state & GDK_MOD1_MASK))
+ {
+ status = gimp_suggest_modifiers (_("Click-Drag to zoom"),
+ gimp_get_toggle_behavior_mask () &
+ ~state,
+ NULL,
+ _("%s for constrained steps"),
+ NULL);
+ }
+ else if (private->mode == MODE_ROTATE ||
+ (private->mode == MODE_NONE &&
+ state & gimp_get_extend_selection_mask ()))
+ {
+ status = gimp_suggest_modifiers (_("Click-Drag to rotate"),
+ gimp_get_toggle_behavior_mask () &
+ ~state,
+ NULL,
+ _("%s for constrained angles"),
+ NULL);
+ }
+ else
+ {
+ status = gimp_suggest_modifiers (_("Click-Drag to pan"),
+ (((gimp_get_extend_selection_mask () |
+ GDK_MOD1_MASK) *
+ (private->mode == MODE_NONE)) |
+ gimp_get_toggle_behavior_mask ()) &
+ ~state,
+ _("%s to rotate"),
+ _("%s for a constrained axis"),
+ _("%s to zoom"));
+ }
+
+ gimp_tool_widget_set_status (GIMP_TOOL_WIDGET (gyroscope), status);
+
+ g_free (status);
+}
+
+static void
+gimp_tool_gyroscope_save (GimpToolGyroscope *gyroscope)
+{
+ GimpToolGyroscopePrivate *private = gyroscope->private;
+
+ private->orig_yaw = private->yaw;
+ private->orig_pitch = private->pitch;
+ private->orig_roll = private->roll;
+ private->orig_zoom = private->zoom;
+}
+
+static void
+gimp_tool_gyroscope_restore (GimpToolGyroscope *gyroscope)
+{
+ GimpToolGyroscopePrivate *private = gyroscope->private;
+
+ g_object_set (gyroscope,
+ "yaw", private->orig_yaw,
+ "pitch", private->orig_pitch,
+ "roll", private->orig_roll,
+ "zoom", private->orig_zoom,
+ NULL);
+}
+
+static void
+gimp_tool_gyroscope_rotate (GimpToolGyroscope *gyroscope,
+ const GimpVector3 *axis)
+{
+ GimpToolGyroscopePrivate *private = gyroscope->private;
+ GimpVector3 real_axis;
+ GimpVector3 basis[2];
+ gdouble yaw;
+ gdouble pitch;
+ gdouble roll;
+ gint i;
+
+ if (gimp_vector3_length (axis) < EPSILON)
+ return;
+
+ real_axis = *axis;
+
+ if (private->invert)
+ gimp_vector3_neg (&real_axis);
+
+ for (i = 0; i < 2; i++)
+ {
+ gimp_vector3_set (&basis[i], i == 0, i == 1, 0.0);
+
+ if (private->invert)
+ gimp_tool_gyroscope_rotate_vector (&basis[i], &real_axis);
+
+ gimp_tool_gyroscope_rotate_vector (
+ &basis[i], &(GimpVector3) {0.0, private->yaw * DEG_TO_RAD, 0.0});
+ gimp_tool_gyroscope_rotate_vector (
+ &basis[i], &(GimpVector3) {private->pitch * DEG_TO_RAD, 0.0, 0.0});
+ gimp_tool_gyroscope_rotate_vector (
+ &basis[i], &(GimpVector3) {0.0, 0.0, private->roll * DEG_TO_RAD});
+
+ if (! private->invert)
+ gimp_tool_gyroscope_rotate_vector (&basis[i], &real_axis);
+ }
+
+ roll = atan2 (basis[1].x, basis[1].y);
+
+ for (i = 0; i < 2; i++)
+ {
+ gimp_tool_gyroscope_rotate_vector (
+ &basis[i], &(GimpVector3) {0.0, 0.0, -roll});
+ }
+
+ pitch = atan2 (-basis[1].z, basis[1].y);
+
+ for (i = 0; i < 1; i++)
+ {
+ gimp_tool_gyroscope_rotate_vector (
+ &basis[i], &(GimpVector3) {-pitch, 0.0, 0.0});
+ }
+
+ yaw = atan2 (basis[0].z, basis[0].x);
+
+ g_object_set (gyroscope,
+ "yaw", yaw / DEG_TO_RAD,
+ "pitch", pitch / DEG_TO_RAD,
+ "roll", roll / DEG_TO_RAD,
+ NULL);
+}
+
+static void
+gimp_tool_gyroscope_rotate_vector (GimpVector3 *vector,
+ const GimpVector3 *axis)
+{
+ GimpVector3 normalized_axis;
+ GimpVector3 projection;
+ GimpVector3 u;
+ GimpVector3 v;
+ gdouble angle;
+
+ angle = gimp_vector3_length (axis);
+
+ if (angle < EPSILON)
+ return;
+
+ normalized_axis = gimp_vector3_mul_val (*axis, 1.0 / angle);
+
+ projection = gimp_vector3_mul_val (
+ normalized_axis,
+ gimp_vector3_inner_product (vector, &normalized_axis));
+
+ u = gimp_vector3_sub_val (*vector, projection);
+ v = gimp_vector3_cross_product (&u, &normalized_axis);
+
+ gimp_vector3_mul (&u, cos (angle));
+ gimp_vector3_mul (&v, sin (angle));
+
+ gimp_vector3_add (vector, &u, &v);
+ gimp_vector3_add (vector, vector, &projection);
+}
+
+
+/* public functions */
+
+
+GimpToolWidget *
+gimp_tool_gyroscope_new (GimpDisplayShell *shell)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
+
+ return g_object_new (GIMP_TYPE_TOOL_GYROSCOPE,
+ "shell", shell,
+ NULL);
+}