summaryrefslogtreecommitdiffstats
path: root/app/tools/gimptransformgridtool.c
diff options
context:
space:
mode:
Diffstat (limited to 'app/tools/gimptransformgridtool.c')
-rw-r--r--app/tools/gimptransformgridtool.c2209
1 files changed, 2209 insertions, 0 deletions
diff --git a/app/tools/gimptransformgridtool.c b/app/tools/gimptransformgridtool.c
new file mode 100644
index 0000000..87ade22
--- /dev/null
+++ b/app/tools/gimptransformgridtool.c
@@ -0,0 +1,2209 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-2001 Spencer Kimball, Peter Mattis, and others
+ *
+ * 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 "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "gegl/gimpapplicator.h"
+#include "gegl/gimp-gegl-nodes.h"
+#include "gegl/gimp-gegl-utils.h"
+
+#include "core/gimp.h"
+#include "core/gimp-transform-resize.h"
+#include "core/gimp-transform-utils.h"
+#include "core/gimpboundary.h"
+#include "core/gimpcontainer.h"
+#include "core/gimpdrawablefilter.h"
+#include "core/gimperror.h"
+#include "core/gimpfilter.h"
+#include "core/gimpgrouplayer.h"
+#include "core/gimpimage.h"
+#include "core/gimpimage-item-list.h"
+#include "core/gimpimage-undo.h"
+#include "core/gimpimage-undo-push.h"
+#include "core/gimplayer.h"
+#include "core/gimplayermask.h"
+#include "core/gimppickable.h"
+#include "core/gimpprojection.h"
+#include "core/gimptoolinfo.h"
+#include "core/gimpviewable.h"
+
+#include "vectors/gimpvectors.h"
+#include "vectors/gimpstroke.h"
+
+#include "widgets/gimpwidgets-utils.h"
+
+#include "display/gimpcanvasitem.h"
+#include "display/gimpdisplay.h"
+#include "display/gimpdisplayshell.h"
+#include "display/gimptoolgui.h"
+#include "display/gimptoolwidget.h"
+
+#include "gimptoolcontrol.h"
+#include "gimptransformgridoptions.h"
+#include "gimptransformgridtool.h"
+#include "gimptransformgridtoolundo.h"
+#include "gimptransformoptions.h"
+
+#include "gimp-intl.h"
+
+
+#define EPSILON 1e-6
+
+
+#define RESPONSE_RESET 1
+#define RESPONSE_READJUST 2
+
+#define UNDO_COMPRESS_TIME (0.5 * G_TIME_SPAN_SECOND)
+
+
+typedef struct
+{
+ GimpTransformGridTool *tg_tool;
+
+ GimpDrawable *drawable;
+ GimpDrawableFilter *filter;
+
+ GimpDrawable *root_drawable;
+
+ GeglNode *transform_node;
+ GeglNode *crop_node;
+
+ GimpMatrix3 transform;
+ GeglRectangle bounds;
+} Filter;
+
+typedef struct
+{
+ GimpTransformGridTool *tg_tool;
+ GimpDrawable *root_drawable;
+} AddFilterData;
+
+typedef struct
+{
+ gint64 time;
+ GimpTransformDirection direction;
+ TransInfo trans_infos[2];
+} UndoInfo;
+
+
+static void gimp_transform_grid_tool_finalize (GObject *object);
+
+static gboolean gimp_transform_grid_tool_initialize (GimpTool *tool,
+ GimpDisplay *display,
+ GError **error);
+static void gimp_transform_grid_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display);
+static void gimp_transform_grid_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display);
+static void gimp_transform_grid_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display);
+static void gimp_transform_grid_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_transform_grid_tool_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_transform_grid_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display);
+static const gchar * gimp_transform_grid_tool_can_undo (GimpTool *tool,
+ GimpDisplay *display);
+static const gchar * gimp_transform_grid_tool_can_redo (GimpTool *tool,
+ GimpDisplay *display);
+static gboolean gimp_transform_grid_tool_undo (GimpTool *tool,
+ GimpDisplay *display);
+static gboolean gimp_transform_grid_tool_redo (GimpTool *tool,
+ GimpDisplay *display);
+static void gimp_transform_grid_tool_options_notify (GimpTool *tool,
+ GimpToolOptions *options,
+ const GParamSpec *pspec);
+
+static void gimp_transform_grid_tool_draw (GimpDrawTool *draw_tool);
+
+static void gimp_transform_grid_tool_recalc_matrix (GimpTransformTool *tr_tool);
+static gchar * gimp_transform_grid_tool_get_undo_desc (GimpTransformTool *tr_tool);
+static GimpTransformDirection gimp_transform_grid_tool_get_direction
+ (GimpTransformTool *tr_tool);
+static GeglBuffer * gimp_transform_grid_tool_transform (GimpTransformTool *tr_tool,
+ GimpObject *object,
+ GeglBuffer *orig_buffer,
+ gint orig_offset_x,
+ gint orig_offset_y,
+ GimpColorProfile **buffer_profile,
+ gint *new_offset_x,
+ gint *new_offset_y);
+
+static void gimp_transform_grid_tool_real_apply_info (GimpTransformGridTool *tg_tool,
+ const TransInfo info);
+static gchar * gimp_transform_grid_tool_real_get_undo_desc (GimpTransformGridTool *tg_tool);
+static void gimp_transform_grid_tool_real_update_widget (GimpTransformGridTool *tg_tool);
+static void gimp_transform_grid_tool_real_widget_changed (GimpTransformGridTool *tg_tool);
+static GeglBuffer * gimp_transform_grid_tool_real_transform (GimpTransformGridTool *tg_tool,
+ GimpObject *object,
+ GeglBuffer *orig_buffer,
+ gint orig_offset_x,
+ gint orig_offset_y,
+ GimpColorProfile **buffer_profile,
+ gint *new_offset_x,
+ gint *new_offset_y);
+
+static void gimp_transform_grid_tool_widget_changed (GimpToolWidget *widget,
+ GimpTransformGridTool *tg_tool);
+static void gimp_transform_grid_tool_widget_response (GimpToolWidget *widget,
+ gint response_id,
+ GimpTransformGridTool *tg_tool);
+
+static void gimp_transform_grid_tool_filter_flush (GimpDrawableFilter *filter,
+ GimpTransformGridTool *tg_tool);
+
+static void gimp_transform_grid_tool_image_linked_items_changed
+ (GimpImage *image,
+ GimpTransformGridTool *tg_tool);
+
+static void gimp_transform_grid_tool_halt (GimpTransformGridTool *tg_tool);
+static void gimp_transform_grid_tool_commit (GimpTransformGridTool *tg_tool);
+
+static void gimp_transform_grid_tool_dialog (GimpTransformGridTool *tg_tool);
+static void gimp_transform_grid_tool_dialog_update (GimpTransformGridTool *tg_tool);
+static void gimp_transform_grid_tool_prepare (GimpTransformGridTool *tg_tool,
+ GimpDisplay *display);
+static GimpToolWidget * gimp_transform_grid_tool_get_widget (GimpTransformGridTool *tg_tool);
+static void gimp_transform_grid_tool_update_widget (GimpTransformGridTool *tg_tool);
+
+static void gimp_transform_grid_tool_response (GimpToolGui *gui,
+ gint response_id,
+ GimpTransformGridTool *tg_tool);
+
+static gboolean gimp_transform_grid_tool_composited_preview (GimpTransformGridTool *tg_tool);
+static void gimp_transform_grid_tool_update_sensitivity (GimpTransformGridTool *tg_tool);
+static void gimp_transform_grid_tool_update_preview (GimpTransformGridTool *tg_tool);
+static void gimp_transform_grid_tool_update_filters (GimpTransformGridTool *tg_tool);
+static void gimp_transform_grid_tool_hide_active_object (GimpTransformGridTool *tg_tool,
+ GimpObject *object);
+static void gimp_transform_grid_tool_show_active_object (GimpTransformGridTool *tg_tool);
+
+static void gimp_transform_grid_tool_add_filter (GimpDrawable *drawable,
+ AddFilterData *data);
+static void gimp_transform_grid_tool_remove_filter (GimpDrawable *drawable,
+ GimpTransformGridTool *tg_tool);
+
+static void gimp_transform_grid_tool_effective_mode_changed
+ (GimpLayer *layer,
+ GimpTransformGridTool *tg_tool);
+
+static Filter * filter_new (GimpTransformGridTool *tg_tool,
+ GimpDrawable *drawable,
+ GimpDrawable *root_drawable,
+ gboolean add_filter);
+static void filter_free (Filter *filter);
+
+static UndoInfo * undo_info_new (void);
+static void undo_info_free (UndoInfo *info);
+
+static gboolean trans_info_equal (const TransInfo trans_info1,
+ const TransInfo trans_info2);
+static gboolean trans_infos_equal (const TransInfo *trans_infos1,
+ const TransInfo *trans_infos2);
+
+
+G_DEFINE_TYPE (GimpTransformGridTool, gimp_transform_grid_tool, GIMP_TYPE_TRANSFORM_TOOL)
+
+#define parent_class gimp_transform_grid_tool_parent_class
+
+
+static void
+gimp_transform_grid_tool_class_init (GimpTransformGridToolClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
+ GimpDrawToolClass *draw_class = GIMP_DRAW_TOOL_CLASS (klass);
+ GimpTransformToolClass *tr_class = GIMP_TRANSFORM_TOOL_CLASS (klass);
+
+ object_class->finalize = gimp_transform_grid_tool_finalize;
+
+ tool_class->initialize = gimp_transform_grid_tool_initialize;
+ tool_class->control = gimp_transform_grid_tool_control;
+ tool_class->button_press = gimp_transform_grid_tool_button_press;
+ tool_class->button_release = gimp_transform_grid_tool_button_release;
+ tool_class->motion = gimp_transform_grid_tool_motion;
+ tool_class->modifier_key = gimp_transform_grid_tool_modifier_key;
+ tool_class->cursor_update = gimp_transform_grid_tool_cursor_update;
+ tool_class->can_undo = gimp_transform_grid_tool_can_undo;
+ tool_class->can_redo = gimp_transform_grid_tool_can_redo;
+ tool_class->undo = gimp_transform_grid_tool_undo;
+ tool_class->redo = gimp_transform_grid_tool_redo;
+ tool_class->options_notify = gimp_transform_grid_tool_options_notify;
+
+ draw_class->draw = gimp_transform_grid_tool_draw;
+
+ tr_class->recalc_matrix = gimp_transform_grid_tool_recalc_matrix;
+ tr_class->get_undo_desc = gimp_transform_grid_tool_get_undo_desc;
+ tr_class->get_direction = gimp_transform_grid_tool_get_direction;
+ tr_class->transform = gimp_transform_grid_tool_transform;
+
+ klass->info_to_matrix = NULL;
+ klass->matrix_to_info = NULL;
+ klass->apply_info = gimp_transform_grid_tool_real_apply_info;
+ klass->get_undo_desc = gimp_transform_grid_tool_real_get_undo_desc;
+ klass->dialog = NULL;
+ klass->dialog_update = NULL;
+ klass->prepare = NULL;
+ klass->readjust = NULL;
+ klass->get_widget = NULL;
+ klass->update_widget = gimp_transform_grid_tool_real_update_widget;
+ klass->widget_changed = gimp_transform_grid_tool_real_widget_changed;
+ klass->transform = gimp_transform_grid_tool_real_transform;
+
+ klass->ok_button_label = _("_Transform");
+}
+
+static void
+gimp_transform_grid_tool_init (GimpTransformGridTool *tg_tool)
+{
+ GimpTool *tool = GIMP_TOOL (tg_tool);
+
+ gimp_tool_control_set_scroll_lock (tool->control, TRUE);
+ gimp_tool_control_set_preserve (tool->control, FALSE);
+ gimp_tool_control_set_dirty_mask (tool->control,
+ GIMP_DIRTY_IMAGE_SIZE |
+ GIMP_DIRTY_IMAGE_STRUCTURE |
+ GIMP_DIRTY_DRAWABLE |
+ GIMP_DIRTY_SELECTION |
+ GIMP_DIRTY_ACTIVE_DRAWABLE);
+ gimp_tool_control_set_active_modifiers (tool->control,
+ GIMP_TOOL_ACTIVE_MODIFIERS_SAME);
+ gimp_tool_control_set_precision (tool->control,
+ GIMP_CURSOR_PRECISION_SUBPIXEL);
+ gimp_tool_control_set_cursor (tool->control,
+ GIMP_CURSOR_CROSSHAIR_SMALL);
+ gimp_tool_control_set_action_opacity (tool->control,
+ "tools/tools-transform-preview-opacity-set");
+
+ tg_tool->strokes = g_ptr_array_new ();
+}
+
+static void
+gimp_transform_grid_tool_finalize (GObject *object)
+{
+ GimpTransformGridTool *tg_tool = GIMP_TRANSFORM_GRID_TOOL (object);
+
+ g_clear_object (&tg_tool->gui);
+ g_clear_pointer (&tg_tool->strokes, g_ptr_array_unref);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static gboolean
+gimp_transform_grid_tool_initialize (GimpTool *tool,
+ GimpDisplay *display,
+ GError **error)
+{
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tool);
+ GimpTransformGridTool *tg_tool = GIMP_TRANSFORM_GRID_TOOL (tool);
+ GimpTransformGridOptions *tg_options = GIMP_TRANSFORM_GRID_TOOL_GET_OPTIONS (tool);
+ GimpImage *image = gimp_display_get_image (display);
+ GimpDrawable *drawable = gimp_image_get_active_drawable (image);
+ GimpObject *object;
+ UndoInfo *undo_info;
+
+ object = gimp_transform_tool_check_active_object (tr_tool, display, error);
+
+ if (! object)
+ return FALSE;
+
+ tool->display = display;
+ tool->drawable = drawable;
+
+ tr_tool->object = object;
+
+ if (GIMP_IS_DRAWABLE (object))
+ gimp_viewable_preview_freeze (GIMP_VIEWABLE (object));
+
+ /* Initialize the transform_grid tool dialog */
+ if (! tg_tool->gui)
+ gimp_transform_grid_tool_dialog (tg_tool);
+
+ /* Find the transform bounds for some tools (like scale,
+ * perspective) that actually need the bounds for initializing
+ */
+ gimp_transform_tool_bounds (tr_tool, display);
+
+ /* Initialize the tool-specific trans_info, and adjust the tool dialog */
+ gimp_transform_grid_tool_prepare (tg_tool, display);
+
+ /* Recalculate the tool's transformation matrix */
+ gimp_transform_tool_recalc_matrix (tr_tool, display);
+
+ /* Get the on-canvas gui */
+ tg_tool->widget = gimp_transform_grid_tool_get_widget (tg_tool);
+
+ gimp_transform_grid_tool_hide_active_object (tg_tool, object);
+
+ /* start drawing the bounding box and handles... */
+ gimp_draw_tool_start (GIMP_DRAW_TOOL (tool), display);
+
+ /* Initialize undo and redo lists */
+ undo_info = undo_info_new ();
+ tg_tool->undo_list = g_list_prepend (NULL, undo_info);
+ tg_tool->redo_list = NULL;
+
+ /* Save the current transformation info */
+ memcpy (undo_info->trans_infos, tg_tool->trans_infos,
+ sizeof (tg_tool->trans_infos));
+
+ if (tg_options->direction_chain_button)
+ gtk_widget_set_sensitive (tg_options->direction_chain_button, TRUE);
+
+ g_signal_connect (
+ image, "linked-items-changed",
+ G_CALLBACK (gimp_transform_grid_tool_image_linked_items_changed),
+ tg_tool);
+
+ return TRUE;
+}
+
+static void
+gimp_transform_grid_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display)
+{
+ GimpTransformGridTool *tg_tool = GIMP_TRANSFORM_GRID_TOOL (tool);
+
+ switch (action)
+ {
+ case GIMP_TOOL_ACTION_PAUSE:
+ case GIMP_TOOL_ACTION_RESUME:
+ break;
+
+ case GIMP_TOOL_ACTION_HALT:
+ gimp_transform_grid_tool_halt (tg_tool);
+ break;
+
+ case GIMP_TOOL_ACTION_COMMIT:
+ if (tool->display)
+ gimp_transform_grid_tool_commit (tg_tool);
+ break;
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->control (tool, action, display);
+}
+
+static void
+gimp_transform_grid_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display)
+{
+ GimpTransformGridTool *tg_tool = GIMP_TRANSFORM_GRID_TOOL (tool);
+
+ if (tg_tool->widget)
+ {
+ gimp_tool_widget_hover (tg_tool->widget, coords, state, TRUE);
+
+ if (gimp_tool_widget_button_press (tg_tool->widget, coords, time, state,
+ press_type))
+ {
+ tg_tool->grab_widget = tg_tool->widget;
+ }
+ }
+
+ gimp_tool_control_activate (tool->control);
+}
+
+static void
+gimp_transform_grid_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display)
+{
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tool);
+ GimpTransformGridTool *tg_tool = GIMP_TRANSFORM_GRID_TOOL (tool);
+
+ gimp_tool_control_halt (tool->control);
+
+ if (tg_tool->grab_widget)
+ {
+ gimp_tool_widget_button_release (tg_tool->grab_widget,
+ coords, time, state, release_type);
+ tg_tool->grab_widget = NULL;
+ }
+
+ if (release_type != GIMP_BUTTON_RELEASE_CANCEL)
+ {
+ /* We're done with an interaction, save it on the undo list */
+ gimp_transform_grid_tool_push_internal_undo (tg_tool, FALSE);
+ }
+ else
+ {
+ UndoInfo *undo_info = tg_tool->undo_list->data;
+
+ /* Restore the last saved state */
+ memcpy (tg_tool->trans_infos, undo_info->trans_infos,
+ sizeof (tg_tool->trans_infos));
+
+ /* recalculate the tool's transformation matrix */
+ gimp_transform_tool_recalc_matrix (tr_tool, display);
+ }
+}
+
+static void
+gimp_transform_grid_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpTransformGridTool *tg_tool = GIMP_TRANSFORM_GRID_TOOL (tool);
+
+ if (tg_tool->grab_widget)
+ {
+ gimp_tool_widget_motion (tg_tool->grab_widget, coords, time, state);
+ }
+}
+
+static void
+gimp_transform_grid_tool_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpTransformGridTool *tg_tool = GIMP_TRANSFORM_GRID_TOOL (tool);
+
+ if (tg_tool->widget)
+ {
+ GIMP_TOOL_CLASS (parent_class)->modifier_key (tool, key, press,
+ state, display);
+ }
+ else
+ {
+ GimpTransformGridOptions *options = GIMP_TRANSFORM_GRID_TOOL_GET_OPTIONS (tool);
+
+ if (key == gimp_get_constrain_behavior_mask ())
+ {
+ g_object_set (options,
+ "frompivot-scale", ! options->frompivot_scale,
+ "frompivot-shear", ! options->frompivot_shear,
+ "frompivot-perspective", ! options->frompivot_perspective,
+ NULL);
+ }
+ else if (key == gimp_get_extend_selection_mask ())
+ {
+ g_object_set (options,
+ "cornersnap", ! options->cornersnap,
+ "constrain-move", ! options->constrain_move,
+ "constrain-scale", ! options->constrain_scale,
+ "constrain-rotate", ! options->constrain_rotate,
+ "constrain-shear", ! options->constrain_shear,
+ "constrain-perspective", ! options->constrain_perspective,
+ NULL);
+ }
+ }
+}
+
+static void
+gimp_transform_grid_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tool);
+
+ if (display != tool->display &&
+ ! gimp_transform_tool_check_active_object (tr_tool, display, NULL))
+ {
+ gimp_tool_set_cursor (tool, display,
+ gimp_tool_control_get_cursor (tool->control),
+ gimp_tool_control_get_tool_cursor (tool->control),
+ GIMP_CURSOR_MODIFIER_BAD);
+ return;
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->cursor_update (tool, coords, state, display);
+}
+
+static const gchar *
+gimp_transform_grid_tool_can_undo (GimpTool *tool,
+ GimpDisplay *display)
+{
+ GimpTransformGridTool *tg_tool = GIMP_TRANSFORM_GRID_TOOL (tool);
+
+ if (! tg_tool->undo_list || ! tg_tool->undo_list->next)
+ return NULL;
+
+ return _("Transform Step");
+}
+
+static const gchar *
+gimp_transform_grid_tool_can_redo (GimpTool *tool,
+ GimpDisplay *display)
+{
+ GimpTransformGridTool *tg_tool = GIMP_TRANSFORM_GRID_TOOL (tool);
+
+ if (! tg_tool->redo_list)
+ return NULL;
+
+ return _("Transform Step");
+}
+
+static gboolean
+gimp_transform_grid_tool_undo (GimpTool *tool,
+ GimpDisplay *display)
+{
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tool);
+ GimpTransformGridTool *tg_tool = GIMP_TRANSFORM_GRID_TOOL (tool);
+ GimpTransformOptions *tr_options = GIMP_TRANSFORM_TOOL_GET_OPTIONS (tool);
+ UndoInfo *undo_info;
+ GimpTransformDirection direction;
+
+ undo_info = tg_tool->undo_list->data;
+ direction = undo_info->direction;
+
+ /* Move undo_info from undo_list to redo_list */
+ tg_tool->redo_list = g_list_prepend (tg_tool->redo_list, undo_info);
+ tg_tool->undo_list = g_list_remove (tg_tool->undo_list, undo_info);
+
+ undo_info = tg_tool->undo_list->data;
+
+ /* Restore the previous transformation info */
+ memcpy (tg_tool->trans_infos, undo_info->trans_infos,
+ sizeof (tg_tool->trans_infos));
+
+ /* Restore the previous transformation direction */
+ if (direction != tr_options->direction)
+ {
+ g_object_set (tr_options,
+ "direction", direction,
+ NULL);
+ }
+
+ /* recalculate the tool's transformation matrix */
+ gimp_transform_tool_recalc_matrix (tr_tool, display);
+
+ return TRUE;
+}
+
+static gboolean
+gimp_transform_grid_tool_redo (GimpTool *tool,
+ GimpDisplay *display)
+{
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tool);
+ GimpTransformGridTool *tg_tool = GIMP_TRANSFORM_GRID_TOOL (tool);
+ GimpTransformOptions *tr_options = GIMP_TRANSFORM_TOOL_GET_OPTIONS (tool);
+ UndoInfo *undo_info;
+ GimpTransformDirection direction;
+
+ undo_info = tg_tool->redo_list->data;
+ direction = undo_info->direction;
+
+ /* Move undo_info from redo_list to undo_list */
+ tg_tool->undo_list = g_list_prepend (tg_tool->undo_list, undo_info);
+ tg_tool->redo_list = g_list_remove (tg_tool->redo_list, undo_info);
+
+ /* Restore the previous transformation info */
+ memcpy (tg_tool->trans_infos, undo_info->trans_infos,
+ sizeof (tg_tool->trans_infos));
+
+ /* Restore the previous transformation direction */
+ if (direction != tr_options->direction)
+ {
+ g_object_set (tr_options,
+ "direction", direction,
+ NULL);
+ }
+
+ /* recalculate the tool's transformation matrix */
+ gimp_transform_tool_recalc_matrix (tr_tool, display);
+
+ return TRUE;
+}
+
+static void
+gimp_transform_grid_tool_options_notify (GimpTool *tool,
+ GimpToolOptions *options,
+ const GParamSpec *pspec)
+{
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tool);
+ GimpTransformGridTool *tg_tool = GIMP_TRANSFORM_GRID_TOOL (tool);
+ GimpTransformGridOptions *tg_options = GIMP_TRANSFORM_GRID_OPTIONS (options);
+
+ GIMP_TOOL_CLASS (parent_class)->options_notify (tool, options, pspec);
+
+ if (! strcmp (pspec->name, "type"))
+ {
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, tool->display);
+ return;
+ }
+
+ if (! tg_tool->widget)
+ return;
+
+ if (! strcmp (pspec->name, "direction"))
+ {
+ /* recalculate the tool's transformation matrix */
+ gimp_transform_tool_recalc_matrix (tr_tool, tool->display);
+ }
+ else if (! strcmp (pspec->name, "show-preview") ||
+ ! strcmp (pspec->name, "composited-preview"))
+ {
+ if (tg_tool->preview)
+ {
+ GimpDisplay *display;
+ GimpObject *object;
+
+ display = tool->display;
+ object = gimp_transform_tool_get_active_object (tr_tool, display);
+
+ if (object)
+ {
+ if (tg_options->show_preview &&
+ ! gimp_transform_grid_tool_composited_preview (tg_tool))
+ {
+ gimp_transform_grid_tool_hide_active_object (tg_tool, object);
+ }
+ else
+ {
+ gimp_transform_grid_tool_show_active_object (tg_tool);
+ }
+ }
+
+ gimp_transform_grid_tool_update_preview (tg_tool);
+ }
+ }
+ else if (! strcmp (pspec->name, "preview-linked") &&
+ tg_tool->filters)
+ {
+ gimp_transform_grid_tool_update_filters (tg_tool);
+ gimp_transform_grid_tool_update_preview (tg_tool);
+ }
+ else if (! strcmp (pspec->name, "interpolation") ||
+ ! strcmp (pspec->name, "clip") ||
+ ! strcmp (pspec->name, "preview-opacity"))
+ {
+ gimp_transform_grid_tool_update_preview (tg_tool);
+ }
+ else if (g_str_has_prefix (pspec->name, "constrain-") ||
+ g_str_has_prefix (pspec->name, "frompivot-") ||
+ ! strcmp (pspec->name, "fixedpivot") ||
+ ! strcmp (pspec->name, "cornersnap"))
+ {
+ gimp_transform_grid_tool_dialog_update (tg_tool);
+ }
+}
+
+static void
+gimp_transform_grid_tool_draw (GimpDrawTool *draw_tool)
+{
+ GimpTool *tool = GIMP_TOOL (draw_tool);
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (draw_tool);
+ GimpTransformGridTool *tg_tool = GIMP_TRANSFORM_GRID_TOOL (draw_tool);
+ GimpTransformGridOptions *options = GIMP_TRANSFORM_GRID_TOOL_GET_OPTIONS (tool);
+ GimpTransformOptions *tr_options = GIMP_TRANSFORM_OPTIONS (options);
+ GimpDisplayShell *shell = gimp_display_get_shell (tool->display);
+ GimpImage *image = gimp_display_get_image (tool->display);
+ GimpMatrix3 matrix = tr_tool->transform;
+ GimpCanvasItem *item;
+
+ if (tr_options->direction == GIMP_TRANSFORM_BACKWARD)
+ gimp_matrix3_invert (&matrix);
+
+ if (tr_options->type == GIMP_TRANSFORM_TYPE_LAYER ||
+ tr_options->type == GIMP_TRANSFORM_TYPE_IMAGE)
+ {
+ GimpPickable *pickable;
+
+ if (tr_options->type == GIMP_TRANSFORM_TYPE_IMAGE)
+ {
+ if (! shell->show_all)
+ pickable = GIMP_PICKABLE (image);
+ else
+ pickable = GIMP_PICKABLE (gimp_image_get_projection (image));
+ }
+ else
+ {
+ pickable = GIMP_PICKABLE (tool->drawable);
+ }
+
+ tg_tool->preview =
+ gimp_draw_tool_add_transform_preview (draw_tool,
+ pickable,
+ &matrix,
+ tr_tool->x1,
+ tr_tool->y1,
+ tr_tool->x2,
+ tr_tool->y2);
+ g_object_add_weak_pointer (G_OBJECT (tg_tool->preview),
+ (gpointer) &tg_tool->preview);
+ }
+
+ if (tr_options->type == GIMP_TRANSFORM_TYPE_SELECTION)
+ {
+ const GimpBoundSeg *segs_in;
+ const GimpBoundSeg *segs_out;
+ gint n_segs_in;
+ gint n_segs_out;
+
+ gimp_channel_boundary (gimp_image_get_mask (image),
+ &segs_in, &segs_out,
+ &n_segs_in, &n_segs_out,
+ 0, 0, 0, 0);
+
+ if (segs_in)
+ {
+ tg_tool->boundary_in =
+ gimp_draw_tool_add_boundary (draw_tool,
+ segs_in, n_segs_in,
+ &matrix,
+ 0, 0);
+ g_object_add_weak_pointer (G_OBJECT (tg_tool->boundary_in),
+ (gpointer) &tg_tool->boundary_in);
+
+ gimp_canvas_item_set_visible (tg_tool->boundary_in,
+ tr_tool->transform_valid);
+ }
+
+ if (segs_out)
+ {
+ tg_tool->boundary_out =
+ gimp_draw_tool_add_boundary (draw_tool,
+ segs_out, n_segs_out,
+ &matrix,
+ 0, 0);
+ g_object_add_weak_pointer (G_OBJECT (tg_tool->boundary_out),
+ (gpointer) &tg_tool->boundary_out);
+
+ gimp_canvas_item_set_visible (tg_tool->boundary_out,
+ tr_tool->transform_valid);
+ }
+ }
+ else if (tr_options->type == GIMP_TRANSFORM_TYPE_PATH)
+ {
+ GimpVectors *vectors = gimp_image_get_active_vectors (image);
+
+ if (vectors)
+ {
+ GimpStroke *stroke = NULL;
+
+ while ((stroke = gimp_vectors_stroke_get_next (vectors, stroke)))
+ {
+ GArray *coords;
+ gboolean closed;
+
+ coords = gimp_stroke_interpolate (stroke, 1.0, &closed);
+
+ if (coords && coords->len)
+ {
+ item =
+ gimp_draw_tool_add_strokes (draw_tool,
+ &g_array_index (coords,
+ GimpCoords, 0),
+ coords->len, &matrix, FALSE);
+
+ g_ptr_array_add (tg_tool->strokes, item);
+ g_object_weak_ref (G_OBJECT (item),
+ (GWeakNotify) g_ptr_array_remove,
+ tg_tool->strokes);
+
+ gimp_canvas_item_set_visible (item, tr_tool->transform_valid);
+ }
+
+ if (coords)
+ g_array_free (coords, TRUE);
+ }
+ }
+ }
+
+ GIMP_DRAW_TOOL_CLASS (parent_class)->draw (draw_tool);
+
+ gimp_transform_grid_tool_update_preview (tg_tool);
+}
+
+static void
+gimp_transform_grid_tool_recalc_matrix (GimpTransformTool *tr_tool)
+{
+ GimpTransformGridTool *tg_tool = GIMP_TRANSFORM_GRID_TOOL (tr_tool);
+ GimpTransformOptions *tr_options = GIMP_TRANSFORM_TOOL_GET_OPTIONS (tr_tool);
+ GimpTransformGridOptions *tg_options = GIMP_TRANSFORM_GRID_TOOL_GET_OPTIONS (tr_tool);
+
+ if (GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->info_to_matrix)
+ {
+ GimpMatrix3 forward_transform;
+ GimpMatrix3 backward_transform;
+ gboolean forward_transform_valid;
+ gboolean backward_transform_valid;
+
+ tg_tool->trans_info = tg_tool->trans_infos[GIMP_TRANSFORM_FORWARD];
+ forward_transform_valid = gimp_transform_grid_tool_info_to_matrix (
+ tg_tool, &forward_transform);
+
+ tg_tool->trans_info = tg_tool->trans_infos[GIMP_TRANSFORM_BACKWARD];
+ backward_transform_valid = gimp_transform_grid_tool_info_to_matrix (
+ tg_tool, &backward_transform);
+
+ if (GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->matrix_to_info &&
+ tg_options->direction_linked)
+ {
+ GimpMatrix3 transform = tr_tool->transform;
+
+ switch (tr_options->direction)
+ {
+ case GIMP_TRANSFORM_FORWARD:
+ if (forward_transform_valid)
+ {
+ gimp_matrix3_invert (&transform);
+
+ backward_transform = forward_transform;
+ gimp_matrix3_mult (&transform, &backward_transform);
+
+ tg_tool->trans_info =
+ tg_tool->trans_infos[GIMP_TRANSFORM_BACKWARD];
+ gimp_transform_grid_tool_matrix_to_info (tg_tool,
+ &backward_transform);
+ backward_transform_valid =
+ gimp_transform_grid_tool_info_to_matrix (
+ tg_tool, &backward_transform);
+ }
+ break;
+
+ case GIMP_TRANSFORM_BACKWARD:
+ if (backward_transform_valid)
+ {
+ forward_transform = backward_transform;
+ gimp_matrix3_mult (&transform, &forward_transform);
+
+ tg_tool->trans_info =
+ tg_tool->trans_infos[GIMP_TRANSFORM_FORWARD];
+ gimp_transform_grid_tool_matrix_to_info (tg_tool,
+ &forward_transform);
+ forward_transform_valid =
+ gimp_transform_grid_tool_info_to_matrix (
+ tg_tool, &forward_transform);
+ }
+ break;
+ }
+ }
+ else if (forward_transform_valid && backward_transform_valid)
+ {
+ tr_tool->transform = backward_transform;
+ gimp_matrix3_invert (&tr_tool->transform);
+ gimp_matrix3_mult (&forward_transform, &tr_tool->transform);
+ }
+
+ tr_tool->transform_valid = forward_transform_valid &&
+ backward_transform_valid;
+ }
+
+ tg_tool->trans_info = tg_tool->trans_infos[tr_options->direction];
+
+ gimp_transform_grid_tool_dialog_update (tg_tool);
+ gimp_transform_grid_tool_update_sensitivity (tg_tool);
+ gimp_transform_grid_tool_update_widget (tg_tool);
+ gimp_transform_grid_tool_update_preview (tg_tool);
+
+ if (tg_tool->gui)
+ gimp_tool_gui_show (tg_tool->gui);
+}
+
+static gchar *
+gimp_transform_grid_tool_get_undo_desc (GimpTransformTool *tr_tool)
+{
+ GimpTransformGridTool *tg_tool = GIMP_TRANSFORM_GRID_TOOL (tr_tool);
+ GimpTransformOptions *tr_options = GIMP_TRANSFORM_TOOL_GET_OPTIONS (tr_tool);
+ gchar *result;
+
+ if (GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->matrix_to_info)
+ {
+ TransInfo trans_info;
+
+ memcpy (&trans_info, &tg_tool->init_trans_info, sizeof (TransInfo));
+
+ tg_tool->trans_info = trans_info;
+ gimp_transform_grid_tool_matrix_to_info (tg_tool, &tr_tool->transform);
+ result = GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->get_undo_desc (
+ tg_tool);
+ }
+ else if (trans_info_equal (tg_tool->trans_infos[GIMP_TRANSFORM_BACKWARD],
+ tg_tool->init_trans_info))
+ {
+ tg_tool->trans_info = tg_tool->trans_infos[GIMP_TRANSFORM_FORWARD];
+ result = GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->get_undo_desc (
+ tg_tool);
+ }
+ else if (trans_info_equal (tg_tool->trans_infos[GIMP_TRANSFORM_FORWARD],
+ tg_tool->init_trans_info))
+ {
+ gchar *desc;
+
+ tg_tool->trans_info = tg_tool->trans_infos[GIMP_TRANSFORM_BACKWARD];
+ desc = GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->get_undo_desc (
+ tg_tool);
+
+ result = g_strdup_printf (_("%s (Corrective)"), desc);
+
+ g_free (desc);
+ }
+ else
+ {
+ result = GIMP_TRANSFORM_TOOL_CLASS (parent_class)->get_undo_desc (
+ tr_tool);
+ }
+
+ tg_tool->trans_info = tg_tool->trans_infos[tr_options->direction];
+
+ return result;
+}
+
+static GimpTransformDirection
+gimp_transform_grid_tool_get_direction (GimpTransformTool *tr_tool)
+{
+ return GIMP_TRANSFORM_FORWARD;
+}
+
+static GeglBuffer *
+gimp_transform_grid_tool_transform (GimpTransformTool *tr_tool,
+ GimpObject *object,
+ GeglBuffer *orig_buffer,
+ gint orig_offset_x,
+ gint orig_offset_y,
+ GimpColorProfile **buffer_profile,
+ gint *new_offset_x,
+ gint *new_offset_y)
+{
+ GimpTool *tool = GIMP_TOOL (tr_tool);
+ GimpTransformGridTool *tg_tool = GIMP_TRANSFORM_GRID_TOOL (tr_tool);
+ GimpDisplay *display = tool->display;
+ GimpImage *image = gimp_display_get_image (display);
+ GeglBuffer *new_buffer;
+
+ /* Send the request for the transformation to the tool...
+ */
+ new_buffer =
+ GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->transform (tg_tool,
+ object,
+ orig_buffer,
+ orig_offset_x,
+ orig_offset_y,
+ buffer_profile,
+ new_offset_x,
+ new_offset_y);
+
+ gimp_image_undo_push (image, GIMP_TYPE_TRANSFORM_GRID_TOOL_UNDO,
+ GIMP_UNDO_TRANSFORM_GRID, NULL,
+ 0,
+ "transform-tool", tg_tool,
+ NULL);
+
+ return new_buffer;
+}
+
+static void
+gimp_transform_grid_tool_real_apply_info (GimpTransformGridTool *tg_tool,
+ const TransInfo info)
+{
+ memcpy (tg_tool->trans_info, info, sizeof (TransInfo));
+}
+
+static gchar *
+gimp_transform_grid_tool_real_get_undo_desc (GimpTransformGridTool *tg_tool)
+{
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tg_tool);
+
+ return GIMP_TRANSFORM_TOOL_CLASS (parent_class)->get_undo_desc (tr_tool);
+}
+
+static void
+gimp_transform_grid_tool_real_update_widget (GimpTransformGridTool *tg_tool)
+{
+ if (GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->info_to_matrix)
+ {
+ GimpMatrix3 transform;
+
+ gimp_transform_grid_tool_info_to_matrix (tg_tool, &transform);
+
+ g_object_set (tg_tool->widget,
+ "transform", &transform,
+ NULL);
+ }
+}
+
+static void
+gimp_transform_grid_tool_real_widget_changed (GimpTransformGridTool *tg_tool)
+{
+ GimpTool *tool = GIMP_TOOL (tg_tool);
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tg_tool);
+ GimpToolWidget *widget = tg_tool->widget;
+
+ /* suppress the call to GimpTransformGridTool::update_widget() when
+ * recalculating the matrix
+ */
+ tg_tool->widget = NULL;
+
+ gimp_transform_tool_recalc_matrix (tr_tool, tool->display);
+
+ tg_tool->widget = widget;
+}
+
+static GeglBuffer *
+gimp_transform_grid_tool_real_transform (GimpTransformGridTool *tg_tool,
+ GimpObject *object,
+ GeglBuffer *orig_buffer,
+ gint orig_offset_x,
+ gint orig_offset_y,
+ GimpColorProfile **buffer_profile,
+ gint *new_offset_x,
+ gint *new_offset_y)
+{
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tg_tool);
+
+ return GIMP_TRANSFORM_TOOL_CLASS (parent_class)->transform (tr_tool,
+ object,
+ orig_buffer,
+ orig_offset_x,
+ orig_offset_y,
+ buffer_profile,
+ new_offset_x,
+ new_offset_y);
+}
+
+static void
+gimp_transform_grid_tool_widget_changed (GimpToolWidget *widget,
+ GimpTransformGridTool *tg_tool)
+{
+ if (GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->widget_changed)
+ GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->widget_changed (tg_tool);
+}
+
+static void
+gimp_transform_grid_tool_widget_response (GimpToolWidget *widget,
+ gint response_id,
+ GimpTransformGridTool *tg_tool)
+{
+ switch (response_id)
+ {
+ case GIMP_TOOL_WIDGET_RESPONSE_CONFIRM:
+ gimp_transform_grid_tool_response (NULL, GTK_RESPONSE_OK, tg_tool);
+ break;
+
+ case GIMP_TOOL_WIDGET_RESPONSE_CANCEL:
+ gimp_transform_grid_tool_response (NULL, GTK_RESPONSE_CANCEL, tg_tool);
+ break;
+
+ case GIMP_TOOL_WIDGET_RESPONSE_RESET:
+ gimp_transform_grid_tool_response (NULL, RESPONSE_RESET, tg_tool);
+ break;
+ }
+}
+
+static void
+gimp_transform_grid_tool_filter_flush (GimpDrawableFilter *filter,
+ GimpTransformGridTool *tg_tool)
+{
+ GimpTool *tool = GIMP_TOOL (tg_tool);
+ GimpImage *image = gimp_display_get_image (tool->display);
+
+ gimp_projection_flush (gimp_image_get_projection (image));
+}
+
+static void
+gimp_transform_grid_tool_image_linked_items_changed (GimpImage *image,
+ GimpTransformGridTool *tg_tool)
+{
+ if (tg_tool->filters)
+ {
+ gimp_transform_grid_tool_update_filters (tg_tool);
+ gimp_transform_grid_tool_update_preview (tg_tool);
+ }
+}
+
+static void
+gimp_transform_grid_tool_halt (GimpTransformGridTool *tg_tool)
+{
+ GimpTool *tool = GIMP_TOOL (tg_tool);
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tg_tool);
+ GimpTransformGridOptions *tg_options = GIMP_TRANSFORM_GRID_TOOL_GET_OPTIONS (tg_tool);
+
+ if (tool->display)
+ {
+ GimpImage *image = gimp_display_get_image (tool->display);
+
+ g_signal_handlers_disconnect_by_func (
+ image,
+ gimp_transform_grid_tool_image_linked_items_changed,
+ tg_tool);
+ }
+
+ if (gimp_draw_tool_is_active (GIMP_DRAW_TOOL (tg_tool)))
+ gimp_draw_tool_stop (GIMP_DRAW_TOOL (tg_tool));
+
+ gimp_draw_tool_set_widget (GIMP_DRAW_TOOL (tg_tool), NULL);
+ g_clear_object (&tg_tool->widget);
+
+ g_clear_pointer (&tg_tool->filters, g_hash_table_unref);
+ g_clear_pointer (&tg_tool->preview_drawables, g_list_free);
+
+ if (tg_tool->gui)
+ gimp_tool_gui_hide (tg_tool->gui);
+
+ if (tg_tool->redo_list)
+ {
+ g_list_free_full (tg_tool->redo_list, (GDestroyNotify) undo_info_free);
+ tg_tool->redo_list = NULL;
+ }
+
+ if (tg_tool->undo_list)
+ {
+ g_list_free_full (tg_tool->undo_list, (GDestroyNotify) undo_info_free);
+ tg_tool->undo_list = NULL;
+ }
+
+ gimp_transform_grid_tool_show_active_object (tg_tool);
+
+ if (tg_options->direction_chain_button)
+ {
+ g_object_set (tg_options,
+ "direction-linked", FALSE,
+ NULL);
+
+ gtk_widget_set_sensitive (tg_options->direction_chain_button, FALSE);
+ }
+
+ tool->display = NULL;
+ tool->drawable = NULL;
+
+ if (tr_tool->object)
+ {
+ if (GIMP_IS_DRAWABLE (tr_tool->object))
+ gimp_viewable_preview_thaw (GIMP_VIEWABLE (tr_tool->object));
+
+ tr_tool->object = NULL;
+ }
+}
+
+static void
+gimp_transform_grid_tool_commit (GimpTransformGridTool *tg_tool)
+{
+ GimpTool *tool = GIMP_TOOL (tg_tool);
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tg_tool);
+ GimpDisplay *display = tool->display;
+
+ /* undraw the tool before we muck around with the transform matrix */
+ gimp_draw_tool_stop (GIMP_DRAW_TOOL (tg_tool));
+
+ gimp_transform_tool_transform (tr_tool, display);
+}
+
+static void
+gimp_transform_grid_tool_dialog (GimpTransformGridTool *tg_tool)
+{
+ GimpTool *tool = GIMP_TOOL (tg_tool);
+ GimpToolInfo *tool_info = tool->tool_info;
+ GimpDisplayShell *shell;
+ const gchar *ok_button_label;
+
+ if (! GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->dialog)
+ return;
+
+ g_return_if_fail (tool->display != NULL);
+
+ shell = gimp_display_get_shell (tool->display);
+
+ ok_button_label = GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->ok_button_label;
+
+ tg_tool->gui = gimp_tool_gui_new (tool_info,
+ NULL, NULL, NULL, NULL,
+ gtk_widget_get_screen (GTK_WIDGET (shell)),
+ gimp_widget_get_monitor (GTK_WIDGET (shell)),
+ TRUE,
+ NULL);
+
+ gimp_tool_gui_add_button (tg_tool->gui, _("_Reset"), RESPONSE_RESET);
+ if (GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->readjust)
+ gimp_tool_gui_add_button (tg_tool->gui, _("Re_adjust"), RESPONSE_READJUST);
+ gimp_tool_gui_add_button (tg_tool->gui, _("_Cancel"), GTK_RESPONSE_CANCEL);
+ gimp_tool_gui_add_button (tg_tool->gui, ok_button_label, GTK_RESPONSE_OK);
+
+ gimp_tool_gui_set_auto_overlay (tg_tool->gui, TRUE);
+ gimp_tool_gui_set_default_response (tg_tool->gui, GTK_RESPONSE_OK);
+
+ gimp_tool_gui_set_alternative_button_order (tg_tool->gui,
+ RESPONSE_RESET,
+ RESPONSE_READJUST,
+ GTK_RESPONSE_OK,
+ GTK_RESPONSE_CANCEL,
+ -1);
+
+ g_signal_connect (tg_tool->gui, "response",
+ G_CALLBACK (gimp_transform_grid_tool_response),
+ tg_tool);
+
+ GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->dialog (tg_tool);
+}
+
+static void
+gimp_transform_grid_tool_dialog_update (GimpTransformGridTool *tg_tool)
+{
+ if (tg_tool->gui &&
+ GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->dialog_update)
+ {
+ GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->dialog_update (tg_tool);
+ }
+}
+
+static void
+gimp_transform_grid_tool_prepare (GimpTransformGridTool *tg_tool,
+ GimpDisplay *display)
+{
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tg_tool);
+
+ if (tg_tool->gui)
+ {
+ GimpObject *object = gimp_transform_tool_get_active_object (tr_tool,
+ display);
+
+ gimp_tool_gui_set_shell (tg_tool->gui, gimp_display_get_shell (display));
+ gimp_tool_gui_set_viewable (tg_tool->gui, GIMP_VIEWABLE (object));
+ }
+
+ if (GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->prepare)
+ {
+ tg_tool->trans_info = tg_tool->init_trans_info;
+ GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->prepare (tg_tool);
+
+ memcpy (tg_tool->trans_infos[GIMP_TRANSFORM_FORWARD],
+ tg_tool->init_trans_info, sizeof (TransInfo));
+ memcpy (tg_tool->trans_infos[GIMP_TRANSFORM_BACKWARD],
+ tg_tool->init_trans_info, sizeof (TransInfo));
+ }
+
+ gimp_matrix3_identity (&tr_tool->transform);
+ tr_tool->transform_valid = TRUE;
+}
+
+static GimpToolWidget *
+gimp_transform_grid_tool_get_widget (GimpTransformGridTool *tg_tool)
+{
+ static const gchar *properties[] =
+ {
+ "constrain-move",
+ "constrain-scale",
+ "constrain-rotate",
+ "constrain-shear",
+ "constrain-perspective",
+ "frompivot-scale",
+ "frompivot-shear",
+ "frompivot-perspective",
+ "cornersnap",
+ "fixedpivot"
+ };
+
+ GimpToolWidget *widget = NULL;
+
+ if (GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->get_widget)
+ {
+ GimpTransformGridOptions *options = GIMP_TRANSFORM_GRID_TOOL_GET_OPTIONS (tg_tool);
+ gint i;
+
+ widget = GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->get_widget (tg_tool);
+
+ gimp_draw_tool_set_widget (GIMP_DRAW_TOOL (tg_tool), widget);
+
+ g_object_bind_property (G_OBJECT (options), "grid-type",
+ G_OBJECT (widget), "guide-type",
+ G_BINDING_SYNC_CREATE |
+ G_BINDING_BIDIRECTIONAL);
+ g_object_bind_property (G_OBJECT (options), "grid-size",
+ G_OBJECT (widget), "n-guides",
+ G_BINDING_SYNC_CREATE |
+ G_BINDING_BIDIRECTIONAL);
+
+ for (i = 0; i < G_N_ELEMENTS (properties); i++)
+ g_object_bind_property (G_OBJECT (options), properties[i],
+ G_OBJECT (widget), properties[i],
+ G_BINDING_SYNC_CREATE |
+ G_BINDING_BIDIRECTIONAL);
+
+ g_signal_connect (widget, "changed",
+ G_CALLBACK (gimp_transform_grid_tool_widget_changed),
+ tg_tool);
+ g_signal_connect (widget, "response",
+ G_CALLBACK (gimp_transform_grid_tool_widget_response),
+ tg_tool);
+ }
+
+ return widget;
+}
+
+static void
+gimp_transform_grid_tool_update_widget (GimpTransformGridTool *tg_tool)
+{
+ if (tg_tool->widget &&
+ GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->update_widget)
+ {
+ g_signal_handlers_block_by_func (
+ tg_tool->widget,
+ G_CALLBACK (gimp_transform_grid_tool_widget_changed),
+ tg_tool);
+
+ GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->update_widget (tg_tool);
+
+ g_signal_handlers_unblock_by_func (
+ tg_tool->widget,
+ G_CALLBACK (gimp_transform_grid_tool_widget_changed),
+ tg_tool);
+ }
+}
+
+static void
+gimp_transform_grid_tool_response (GimpToolGui *gui,
+ gint response_id,
+ GimpTransformGridTool *tg_tool)
+{
+ GimpTool *tool = GIMP_TOOL (tg_tool);
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tg_tool);
+ GimpTransformOptions *tr_options = GIMP_TRANSFORM_TOOL_GET_OPTIONS (tg_tool);
+ GimpTransformGridOptions *tg_options = GIMP_TRANSFORM_GRID_TOOL_GET_OPTIONS (tg_tool);
+ GimpDisplay *display = tool->display;
+
+ /* we can get here while already committing a transformation. just return in
+ * this case. see issue #4734.
+ */
+ if (! gimp_draw_tool_is_active (GIMP_DRAW_TOOL (tg_tool)))
+ return;
+
+ switch (response_id)
+ {
+ case RESPONSE_RESET:
+ {
+ gboolean direction_linked;
+
+ /* restore the initial transformation info */
+ memcpy (tg_tool->trans_infos[GIMP_TRANSFORM_FORWARD],
+ tg_tool->init_trans_info,
+ sizeof (TransInfo));
+ memcpy (tg_tool->trans_infos[GIMP_TRANSFORM_BACKWARD],
+ tg_tool->init_trans_info,
+ sizeof (TransInfo));
+
+ /* recalculate the tool's transformation matrix */
+ direction_linked = tg_options->direction_linked;
+ tg_options->direction_linked = FALSE;
+ gimp_transform_tool_recalc_matrix (tr_tool, display);
+ tg_options->direction_linked = direction_linked;
+
+ /* push the restored info to the undo stack */
+ gimp_transform_grid_tool_push_internal_undo (tg_tool, FALSE);
+ }
+ break;
+
+ case RESPONSE_READJUST:
+ if (GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->readjust &&
+ GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->matrix_to_info &&
+ tr_tool->transform_valid)
+ {
+ TransInfo old_trans_infos[2];
+ gboolean direction_linked;
+ gboolean transform_valid;
+
+ /* save the current transformation info */
+ memcpy (old_trans_infos, tg_tool->trans_infos,
+ sizeof (old_trans_infos));
+
+ /* readjust the transformation info to view */
+ GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->readjust (tg_tool);
+
+ /* recalculate the tool's transformation matrix, preserving the
+ * overall transformation
+ */
+ direction_linked = tg_options->direction_linked;
+ tg_options->direction_linked = TRUE;
+ gimp_transform_tool_recalc_matrix (tr_tool, display);
+ tg_options->direction_linked = direction_linked;
+
+ transform_valid = tr_tool->transform_valid;
+
+ /* if the resulting transformation is invalid, or if the
+ * transformation info is already adjusted to view ...
+ */
+ if (! transform_valid ||
+ trans_infos_equal (old_trans_infos, tg_tool->trans_infos))
+ {
+ /* ... readjust the transformation info to the item bounds */
+ GimpMatrix3 transform = tr_tool->transform;
+
+ if (tr_options->direction == GIMP_TRANSFORM_BACKWARD)
+ gimp_matrix3_invert (&transform);
+
+ GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->apply_info (
+ tg_tool, tg_tool->init_trans_info);
+ GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->matrix_to_info (
+ tg_tool, &transform);
+
+ /* recalculate the tool's transformation matrix, preserving the
+ * overall transformation
+ */
+ direction_linked = tg_options->direction_linked;
+ tg_options->direction_linked = TRUE;
+ gimp_transform_tool_recalc_matrix (tr_tool, display);
+ tg_options->direction_linked = direction_linked;
+
+ if (! tr_tool->transform_valid ||
+ ! trans_infos_equal (old_trans_infos, tg_tool->trans_infos))
+ {
+ transform_valid = tr_tool->transform_valid;
+ }
+ }
+
+ if (transform_valid)
+ {
+ /* push the new info to the undo stack */
+ gimp_transform_grid_tool_push_internal_undo (tg_tool, FALSE);
+ }
+ else
+ {
+ /* restore the old transformation info */
+ memcpy (tg_tool->trans_infos, old_trans_infos,
+ sizeof (old_trans_infos));
+
+ /* recalculate the tool's transformation matrix */
+ direction_linked = tg_options->direction_linked;
+ tg_options->direction_linked = FALSE;
+ gimp_transform_tool_recalc_matrix (tr_tool, display);
+ tg_options->direction_linked = direction_linked;
+
+ gimp_tool_message_literal (tool, tool->display,
+ _("Cannot readjust the transformation"));
+ }
+ }
+ break;
+
+ case GTK_RESPONSE_OK:
+ g_return_if_fail (display != NULL);
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_COMMIT, display);
+ break;
+
+ default:
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, display);
+
+ /* update the undo actions / menu items */
+ if (display)
+ gimp_image_flush (gimp_display_get_image (display));
+ break;
+ }
+}
+
+static gboolean
+gimp_transform_grid_tool_composited_preview (GimpTransformGridTool *tg_tool)
+{
+ GimpTool *tool = GIMP_TOOL (tg_tool);
+ GimpTransformOptions *tr_options = GIMP_TRANSFORM_TOOL_GET_OPTIONS (tg_tool);
+ GimpTransformGridOptions *tg_options = GIMP_TRANSFORM_GRID_TOOL_GET_OPTIONS (tg_tool);
+ GimpImage *image = gimp_display_get_image (tool->display);
+
+ return tg_options->composited_preview &&
+ tr_options->type == GIMP_TRANSFORM_TYPE_LAYER &&
+ gimp_channel_is_empty (gimp_image_get_mask (image));
+}
+
+static void
+gimp_transform_grid_tool_update_sensitivity (GimpTransformGridTool *tg_tool)
+{
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tg_tool);
+
+ if (! tg_tool->gui)
+ return;
+
+ gimp_tool_gui_set_response_sensitive (
+ tg_tool->gui, GTK_RESPONSE_OK,
+ tr_tool->transform_valid);
+
+ gimp_tool_gui_set_response_sensitive (
+ tg_tool->gui, RESPONSE_RESET,
+ ! (trans_info_equal (tg_tool->trans_infos[GIMP_TRANSFORM_FORWARD],
+ tg_tool->init_trans_info) &&
+ trans_info_equal (tg_tool->trans_infos[GIMP_TRANSFORM_BACKWARD],
+ tg_tool->init_trans_info)));
+
+ gimp_tool_gui_set_response_sensitive (
+ tg_tool->gui, RESPONSE_READJUST,
+ tr_tool->transform_valid);
+}
+
+static void
+gimp_transform_grid_tool_update_preview (GimpTransformGridTool *tg_tool)
+{
+ GimpTool *tool = GIMP_TOOL (tg_tool);
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tg_tool);
+ GimpTransformOptions *tr_options = GIMP_TRANSFORM_TOOL_GET_OPTIONS (tg_tool);
+ GimpTransformGridOptions *tg_options = GIMP_TRANSFORM_GRID_TOOL_GET_OPTIONS (tg_tool);
+ gint i;
+
+ if (! tool->display)
+ return;
+
+ if (tg_options->show_preview &&
+ gimp_transform_grid_tool_composited_preview (tg_tool) &&
+ tr_tool->transform_valid)
+ {
+ GHashTableIter iter;
+ GimpDrawable *drawable;
+ Filter *filter;
+ gboolean flush = FALSE;
+
+ if (! tg_tool->filters)
+ {
+ tg_tool->filters = g_hash_table_new_full (
+ g_direct_hash, g_direct_equal,
+ NULL, (GDestroyNotify) filter_free);
+
+ gimp_transform_grid_tool_update_filters (tg_tool);
+ }
+
+ g_hash_table_iter_init (&iter, tg_tool->filters);
+
+ while (g_hash_table_iter_next (&iter,
+ (gpointer *) &drawable,
+ (gpointer *) &filter))
+ {
+ GimpMatrix3 transform;
+ GeglRectangle bounds;
+ gint offset_x;
+ gint offset_y;
+ gint width;
+ gint height;
+ gint x1, y1;
+ gint x2, y2;
+ gboolean update = FALSE;
+
+ if (! filter->filter)
+ continue;
+
+ gimp_item_get_offset (GIMP_ITEM (drawable), &offset_x, &offset_y);
+
+ width = gimp_item_get_width (GIMP_ITEM (drawable));
+ height = gimp_item_get_height (GIMP_ITEM (drawable));
+
+ gimp_matrix3_identity (&transform);
+ gimp_matrix3_translate (&transform, +offset_x, +offset_y);
+ gimp_matrix3_mult (&tr_tool->transform, &transform);
+ gimp_matrix3_translate (&transform, -offset_x, -offset_y);
+
+ gimp_transform_resize_boundary (&tr_tool->transform,
+ gimp_item_get_clip (
+ GIMP_ITEM (filter->root_drawable),
+ tr_options->clip),
+ offset_x, offset_y,
+ offset_x + width, offset_y + height,
+ &x1, &y1,
+ &x2, &y2);
+
+ bounds.x = x1 - offset_x;
+ bounds.y = y1 - offset_y;
+ bounds.width = x2 - x1;
+ bounds.height = y2 - y1;
+
+ if (! gimp_matrix3_equal (&transform, &filter->transform))
+ {
+ filter->transform = transform;
+
+ gimp_gegl_node_set_matrix (filter->transform_node, &transform);
+
+ update = TRUE;
+ }
+
+ if (! gegl_rectangle_equal (&bounds, &filter->bounds))
+ {
+ filter->bounds = bounds;
+
+ gegl_node_set (filter->crop_node,
+ "x", (gdouble) bounds.x,
+ "y", (gdouble) bounds.y,
+ "width", (gdouble) bounds.width,
+ "height", (gdouble) bounds.height,
+ NULL);
+
+ update = TRUE;
+ }
+
+ if (GIMP_IS_LAYER (drawable))
+ {
+ gimp_drawable_filter_set_add_alpha (
+ filter->filter,
+ tr_options->interpolation != GIMP_INTERPOLATION_NONE);
+ }
+
+ if (update)
+ {
+ if (tg_options->synchronous_preview)
+ {
+ g_signal_handlers_block_by_func (
+ filter->filter,
+ G_CALLBACK (gimp_transform_grid_tool_filter_flush),
+ tg_tool);
+ }
+
+ gimp_drawable_filter_apply (filter->filter, NULL);
+
+ if (tg_options->synchronous_preview)
+ {
+ g_signal_handlers_unblock_by_func (
+ filter->filter,
+ G_CALLBACK (gimp_transform_grid_tool_filter_flush),
+ tg_tool);
+
+ flush = TRUE;
+ }
+ }
+ }
+
+ if (flush)
+ {
+ GimpImage *image = gimp_display_get_image (tool->display);
+
+ gimp_projection_flush_now (gimp_image_get_projection (image), TRUE);
+ gimp_display_flush_now (tool->display);
+ }
+ }
+ else
+ {
+ g_clear_pointer (&tg_tool->filters, g_hash_table_unref);
+ g_clear_pointer (&tg_tool->preview_drawables, g_list_free);
+ }
+
+ if (tg_tool->preview)
+ {
+ if (tg_options->show_preview &&
+ ! gimp_transform_grid_tool_composited_preview (tg_tool) &&
+ tr_tool->transform_valid)
+ {
+ gimp_canvas_item_begin_change (tg_tool->preview);
+ gimp_canvas_item_set_visible (tg_tool->preview, TRUE);
+ g_object_set (
+ tg_tool->preview,
+ "transform", &tr_tool->transform,
+ "clip", gimp_item_get_clip (GIMP_ITEM (tool->drawable),
+ tr_options->clip),
+ "opacity", tg_options->preview_opacity,
+ NULL);
+ gimp_canvas_item_end_change (tg_tool->preview);
+ }
+ else
+ {
+ gimp_canvas_item_set_visible (tg_tool->preview, FALSE);
+ }
+ }
+
+ if (tg_tool->boundary_in)
+ {
+ gimp_canvas_item_begin_change (tg_tool->boundary_in);
+ gimp_canvas_item_set_visible (tg_tool->boundary_in,
+ tr_tool->transform_valid);
+ g_object_set (tg_tool->boundary_in,
+ "transform", &tr_tool->transform,
+ NULL);
+ gimp_canvas_item_end_change (tg_tool->boundary_in);
+ }
+
+ if (tg_tool->boundary_out)
+ {
+ gimp_canvas_item_begin_change (tg_tool->boundary_out);
+ gimp_canvas_item_set_visible (tg_tool->boundary_out,
+ tr_tool->transform_valid);
+ g_object_set (tg_tool->boundary_out,
+ "transform", &tr_tool->transform,
+ NULL);
+ gimp_canvas_item_end_change (tg_tool->boundary_out);
+ }
+
+ for (i = 0; i < tg_tool->strokes->len; i++)
+ {
+ GimpCanvasItem *item = g_ptr_array_index (tg_tool->strokes, i);
+
+ gimp_canvas_item_begin_change (item);
+ gimp_canvas_item_set_visible (item, tr_tool->transform_valid);
+ g_object_set (item,
+ "transform", &tr_tool->transform,
+ NULL);
+ gimp_canvas_item_end_change (item);
+ }
+}
+
+static void
+gimp_transform_grid_tool_update_filters (GimpTransformGridTool *tg_tool)
+{
+ GimpTool *tool = GIMP_TOOL (tg_tool);
+ GimpTransformGridOptions *options = GIMP_TRANSFORM_GRID_TOOL_GET_OPTIONS (tg_tool);
+ GHashTable *new_drawables;
+ GList *drawables;
+ GList *iter;
+ GimpDrawable *drawable;
+ GHashTableIter hash_iter;
+
+ if (! tg_tool->filters)
+ return;
+
+ if (options->preview_linked &&
+ gimp_item_get_linked (GIMP_ITEM (tool->drawable)))
+ {
+ GimpImage *image = gimp_display_get_image (tool->display);
+
+ drawables = gimp_image_item_list_get_list (image,
+ GIMP_ITEM_TYPE_LAYERS |
+ GIMP_ITEM_TYPE_CHANNELS,
+ GIMP_ITEM_SET_LINKED);
+
+ drawables = gimp_image_item_list_filter (drawables);
+ }
+ else
+ {
+ drawables = g_list_prepend (NULL, tool->drawable);
+ }
+
+ new_drawables = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+ for (iter = drawables; iter; iter = g_list_next (iter))
+ g_hash_table_add (new_drawables, iter->data);
+
+ for (iter = tg_tool->preview_drawables; iter; iter = g_list_next (iter))
+ {
+ drawable = iter->data;
+
+ if (! g_hash_table_remove (new_drawables, drawable))
+ gimp_transform_grid_tool_remove_filter (drawable, tg_tool);
+ }
+
+ g_hash_table_iter_init (&hash_iter, new_drawables);
+
+ while (g_hash_table_iter_next (&hash_iter, (gpointer *) &drawable, NULL))
+ {
+ AddFilterData data;
+
+ data.tg_tool = tg_tool;
+ data.root_drawable = drawable;
+
+ gimp_transform_grid_tool_add_filter (drawable, &data);
+ }
+
+ g_hash_table_unref (new_drawables);
+
+ g_list_free (tg_tool->preview_drawables);
+ tg_tool->preview_drawables = drawables;
+}
+
+static void
+gimp_transform_grid_tool_hide_active_object (GimpTransformGridTool *tg_tool,
+ GimpObject *object)
+{
+ GimpTransformGridOptions *options = GIMP_TRANSFORM_GRID_TOOL_GET_OPTIONS (tg_tool);
+ GimpTransformOptions *tr_options = GIMP_TRANSFORM_OPTIONS (options);
+ GimpDisplay *display = GIMP_TOOL (tg_tool)->display;
+ GimpImage *image = gimp_display_get_image (display);
+
+ if (options->show_preview)
+ {
+ /* hide only complete layers and channels, not layer masks */
+ if (tr_options->type == GIMP_TRANSFORM_TYPE_LAYER &&
+ ! options->composited_preview &&
+ GIMP_IS_DRAWABLE (object) &&
+ ! GIMP_IS_LAYER_MASK (object) &&
+ gimp_item_get_visible (GIMP_ITEM (object)) &&
+ gimp_channel_is_empty (gimp_image_get_mask (image)))
+ {
+ tg_tool->hidden_object = object;
+
+ gimp_item_set_visible (GIMP_ITEM (object), FALSE, FALSE);
+
+ gimp_projection_flush (gimp_image_get_projection (image));
+ }
+ else if (tr_options->type == GIMP_TRANSFORM_TYPE_IMAGE)
+ {
+ tg_tool->hidden_object = object;
+
+ gimp_display_shell_set_show_image (gimp_display_get_shell (display),
+ FALSE);
+ }
+ }
+}
+
+static void
+gimp_transform_grid_tool_show_active_object (GimpTransformGridTool *tg_tool)
+{
+ if (tg_tool->hidden_object)
+ {
+ GimpDisplay *display = GIMP_TOOL (tg_tool)->display;
+ GimpImage *image = gimp_display_get_image (display);
+
+ if (GIMP_IS_ITEM (tg_tool->hidden_object))
+ {
+ gimp_item_set_visible (GIMP_ITEM (tg_tool->hidden_object), TRUE,
+ FALSE);
+ }
+ else
+ {
+ g_return_if_fail (GIMP_IS_IMAGE (tg_tool->hidden_object));
+
+ gimp_display_shell_set_show_image (gimp_display_get_shell (display),
+ TRUE);
+ }
+
+ tg_tool->hidden_object = NULL;
+
+ gimp_image_flush (image);
+ }
+}
+
+static void
+gimp_transform_grid_tool_add_filter (GimpDrawable *drawable,
+ AddFilterData *data)
+{
+ Filter *filter;
+ GimpLayerMode mode = GIMP_LAYER_MODE_NORMAL;
+
+ if (GIMP_IS_LAYER (drawable))
+ {
+ gimp_layer_get_effective_mode (GIMP_LAYER (drawable),
+ &mode, NULL, NULL, NULL);
+ }
+
+ if (mode != GIMP_LAYER_MODE_PASS_THROUGH)
+ {
+ filter = filter_new (data->tg_tool, drawable, data->root_drawable, TRUE);
+ }
+ else
+ {
+ GimpContainer *container;
+
+ filter = filter_new (data->tg_tool, drawable, data->root_drawable, FALSE);
+
+ container = gimp_viewable_get_children (GIMP_VIEWABLE (drawable));
+
+ gimp_container_foreach (container,
+ (GFunc) gimp_transform_grid_tool_add_filter,
+ data);
+ }
+
+ g_hash_table_insert (data->tg_tool->filters, drawable, filter);
+
+ if (GIMP_IS_LAYER (drawable))
+ {
+ GimpLayerMask *mask = gimp_layer_get_mask (GIMP_LAYER (drawable));
+
+ if (mask)
+ gimp_transform_grid_tool_add_filter (GIMP_DRAWABLE (mask), data);
+ }
+}
+
+static void
+gimp_transform_grid_tool_remove_filter (GimpDrawable *drawable,
+ GimpTransformGridTool *tg_tool)
+{
+ Filter *filter = g_hash_table_lookup (tg_tool->filters, drawable);
+
+ if (GIMP_IS_LAYER (drawable))
+ {
+ GimpLayerMask *mask = gimp_layer_get_mask (GIMP_LAYER (drawable));
+
+ if (mask)
+ gimp_transform_grid_tool_remove_filter (GIMP_DRAWABLE (mask), tg_tool);
+ }
+
+ if (! filter->filter)
+ {
+ GimpContainer *container;
+
+ container = gimp_viewable_get_children (GIMP_VIEWABLE (drawable));
+
+ gimp_container_foreach (container,
+ (GFunc) gimp_transform_grid_tool_remove_filter,
+ tg_tool);
+ }
+
+ g_hash_table_remove (tg_tool->filters, drawable);
+}
+
+static void
+gimp_transform_grid_tool_effective_mode_changed (GimpLayer *layer,
+ GimpTransformGridTool *tg_tool)
+{
+ Filter *filter = g_hash_table_lookup (tg_tool->filters, layer);
+ GimpLayerMode mode;
+ gboolean old_pass_through;
+ gboolean new_pass_through;
+
+ gimp_layer_get_effective_mode (layer, &mode, NULL, NULL, NULL);
+
+ old_pass_through = ! filter->filter;
+ new_pass_through = mode == GIMP_LAYER_MODE_PASS_THROUGH;
+
+ if (old_pass_through != new_pass_through)
+ {
+ AddFilterData data;
+
+ data.tg_tool = tg_tool;
+ data.root_drawable = filter->root_drawable;
+
+ gimp_transform_grid_tool_remove_filter (GIMP_DRAWABLE (layer), tg_tool);
+ gimp_transform_grid_tool_add_filter (GIMP_DRAWABLE (layer), &data);
+
+ gimp_transform_grid_tool_update_preview (tg_tool);
+ }
+}
+
+static Filter *
+filter_new (GimpTransformGridTool *tg_tool,
+ GimpDrawable *drawable,
+ GimpDrawable *root_drawable,
+ gboolean add_filter)
+{
+ Filter *filter = g_slice_new0 (Filter);
+ GeglNode *node;
+ GeglNode *input_node;
+ GeglNode *output_node;
+
+ filter->tg_tool = tg_tool;
+ filter->drawable = drawable;
+ filter->root_drawable = root_drawable;
+
+ if (add_filter)
+ {
+ node = gegl_node_new ();
+
+ input_node = gegl_node_get_input_proxy (node, "input");
+ output_node = gegl_node_get_input_proxy (node, "output");
+
+ filter->transform_node = gegl_node_new_child (
+ node,
+ "operation", "gegl:transform",
+ "near-z", GIMP_TRANSFORM_NEAR_Z,
+ "sampler", GEGL_SAMPLER_NEAREST,
+ NULL);
+
+ filter->crop_node = gegl_node_new_child (
+ node,
+ "operation", "gegl:crop",
+ NULL);
+
+ gegl_node_link_many (input_node,
+ filter->transform_node,
+ filter->crop_node,
+ output_node,
+ NULL);
+
+ gimp_gegl_node_set_underlying_operation (node, filter->transform_node);
+
+ filter->filter = gimp_drawable_filter_new (
+ drawable,
+ GIMP_TRANSFORM_TOOL_GET_CLASS (tg_tool)->undo_desc,
+ node,
+ gimp_tool_get_icon_name (GIMP_TOOL (tg_tool)));
+
+ gimp_drawable_filter_set_clip (filter->filter, FALSE);
+ gimp_drawable_filter_set_override_constraints (filter->filter, TRUE);
+
+ g_signal_connect (
+ filter->filter, "flush",
+ G_CALLBACK (gimp_transform_grid_tool_filter_flush),
+ tg_tool);
+
+ g_object_unref (node);
+ }
+
+ if (GIMP_IS_GROUP_LAYER (drawable))
+ {
+ g_signal_connect (
+ drawable, "effective-mode-changed",
+ G_CALLBACK (gimp_transform_grid_tool_effective_mode_changed),
+ tg_tool);
+ }
+
+ return filter;
+}
+
+static void
+filter_free (Filter *filter)
+{
+ if (filter->filter)
+ {
+ gimp_drawable_filter_abort (filter->filter);
+
+ g_object_unref (filter->filter);
+ }
+
+ if (GIMP_IS_GROUP_LAYER (filter->drawable))
+ {
+ g_signal_handlers_disconnect_by_func (
+ filter->drawable,
+ gimp_transform_grid_tool_effective_mode_changed,
+ filter->tg_tool);
+ }
+
+ g_slice_free (Filter, filter);
+}
+
+static UndoInfo *
+undo_info_new (void)
+{
+ return g_slice_new0 (UndoInfo);
+}
+
+static void
+undo_info_free (UndoInfo *info)
+{
+ g_slice_free (UndoInfo, info);
+}
+
+static gboolean
+trans_info_equal (const TransInfo trans_info1,
+ const TransInfo trans_info2)
+{
+ gint i;
+
+ for (i = 0; i < TRANS_INFO_SIZE; i++)
+ {
+ if (fabs (trans_info1[i] - trans_info2[i]) > EPSILON)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+trans_infos_equal (const TransInfo *trans_infos1,
+ const TransInfo *trans_infos2)
+{
+ return trans_info_equal (trans_infos1[GIMP_TRANSFORM_FORWARD],
+ trans_infos2[GIMP_TRANSFORM_FORWARD]) &&
+ trans_info_equal (trans_infos1[GIMP_TRANSFORM_BACKWARD],
+ trans_infos2[GIMP_TRANSFORM_BACKWARD]);
+}
+
+gboolean
+gimp_transform_grid_tool_info_to_matrix (GimpTransformGridTool *tg_tool,
+ GimpMatrix3 *transform)
+{
+ g_return_val_if_fail (GIMP_IS_TRANSFORM_GRID_TOOL (tg_tool), FALSE);
+ g_return_val_if_fail (transform != NULL, FALSE);
+
+ if (GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->info_to_matrix)
+ {
+ return GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->info_to_matrix (
+ tg_tool, transform);
+ }
+
+ return FALSE;
+}
+
+void
+gimp_transform_grid_tool_matrix_to_info (GimpTransformGridTool *tg_tool,
+ const GimpMatrix3 *transform)
+{
+ g_return_if_fail (GIMP_IS_TRANSFORM_GRID_TOOL (tg_tool));
+ g_return_if_fail (transform != NULL);
+
+ if (GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->matrix_to_info)
+ {
+ return GIMP_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->matrix_to_info (
+ tg_tool, transform);
+ }
+}
+
+void
+gimp_transform_grid_tool_push_internal_undo (GimpTransformGridTool *tg_tool,
+ gboolean compress)
+{
+ UndoInfo *undo_info;
+
+ g_return_if_fail (GIMP_IS_TRANSFORM_GRID_TOOL (tg_tool));
+ g_return_if_fail (tg_tool->undo_list != NULL);
+
+ undo_info = tg_tool->undo_list->data;
+
+ /* push current state on the undo list and set this state as the
+ * current state, but avoid doing this if there were no changes
+ */
+ if (! trans_infos_equal (undo_info->trans_infos, tg_tool->trans_infos))
+ {
+ GimpTransformOptions *tr_options = GIMP_TRANSFORM_TOOL_GET_OPTIONS (tg_tool);
+ gint64 time = 0;
+ gboolean flush = FALSE;
+
+ if (tg_tool->undo_list->next == NULL)
+ flush = TRUE;
+
+ if (compress)
+ time = g_get_monotonic_time ();
+
+ if (! compress || time - undo_info->time >= UNDO_COMPRESS_TIME)
+ {
+ undo_info = undo_info_new ();
+
+ tg_tool->undo_list = g_list_prepend (tg_tool->undo_list, undo_info);
+ }
+
+ undo_info->time = time;
+ undo_info->direction = tr_options->direction;
+ memcpy (undo_info->trans_infos, tg_tool->trans_infos,
+ sizeof (tg_tool->trans_infos));
+
+ /* If we undid anything and started interacting, we have to
+ * discard the redo history
+ */
+ if (tg_tool->redo_list)
+ {
+ g_list_free_full (tg_tool->redo_list,
+ (GDestroyNotify) undo_info_free);
+ tg_tool->redo_list = NULL;
+
+ flush = TRUE;
+ }
+
+ gimp_transform_grid_tool_update_sensitivity (tg_tool);
+
+ /* update the undo actions / menu items */
+ if (flush)
+ {
+ gimp_image_flush (
+ gimp_display_get_image (GIMP_TOOL (tg_tool)->display));
+ }
+ }
+}