/* GIMP - The GNU Image Manipulation Program * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * 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 #include #include #include "libgimpbase/gimpbase.h" #include "libgimpmath/gimpmath.h" #include "libgimpwidgets/gimpwidgets.h" #include "tools-types.h" #include "core/gimp.h" #include "core/gimp-utils.h" #include "core/gimpboundary.h" #include "core/gimpgrouplayer.h" #include "core/gimpimage.h" #include "core/gimpimage-guides.h" #include "core/gimpimage-item-list.h" #include "core/gimpimage-undo.h" #include "core/gimpitem-linked.h" #include "core/gimplayer.h" #include "core/gimplayermask.h" #include "core/gimpprojection.h" #include "core/gimpselection.h" #include "core/gimpundostack.h" #include "widgets/gimpwidgets-utils.h" #include "display/gimpdisplay.h" #include "display/gimpdisplayshell.h" #include "display/gimpdisplayshell-appearance.h" #include "display/gimpdisplayshell-selection.h" #include "display/gimpdisplayshell-transform.h" #include "gimpdrawtool.h" #include "gimpeditselectiontool.h" #include "gimptoolcontrol.h" #include "gimptools-utils.h" #include "tool_manager.h" #include "gimp-intl.h" #define ARROW_VELOCITY 25 typedef struct _GimpEditSelectionTool GimpEditSelectionTool; typedef struct _GimpEditSelectionToolClass GimpEditSelectionToolClass; struct _GimpEditSelectionTool { GimpDrawTool parent_instance; gdouble start_x; /* Coords where button was pressed */ gdouble start_y; gint last_x; /* Last x and y coords */ gint last_y; gint current_x; /* Current x and y coords */ gint current_y; gint cuml_x; /* Cumulative changes to x and y */ gint cuml_y; gint sel_x; /* Bounding box of selection mask */ gint sel_y; /* Bounding box of selection mask */ gint sel_width; gint sel_height; gint num_segs_in; /* Num seg in selection boundary */ gint num_segs_out; /* Num seg in selection boundary */ GimpBoundSeg *segs_in; /* Pointer to the channel sel. segs */ GimpBoundSeg *segs_out; /* Pointer to the channel sel. segs */ gdouble center_x; /* Where to draw the mark of center */ gdouble center_y; GimpTranslateMode edit_mode; /* Translate the mask or layer? */ GList *live_items; /* Items that are transformed live */ GList *delayed_items; /* Items that are transformed later */ gboolean first_move; /* Don't push undos after the first */ gboolean propagate_release; gboolean constrain; /* Constrain the movement */ gdouble last_motion_x; /* Previous coords sent to _motion */ gdouble last_motion_y; }; struct _GimpEditSelectionToolClass { GimpDrawToolClass parent_class; }; static void gimp_edit_selection_tool_finalize (GObject *object); static void gimp_edit_selection_tool_button_release (GimpTool *tool, const GimpCoords *coords, guint32 time, GdkModifierType state, GimpButtonReleaseType release_type, GimpDisplay *display); static void gimp_edit_selection_tool_motion (GimpTool *tool, const GimpCoords *coords, guint32 time, GdkModifierType state, GimpDisplay *display); static void gimp_edit_selection_tool_active_modifier_key (GimpTool *tool, GdkModifierType key, gboolean press, GdkModifierType state, GimpDisplay *display); static void gimp_edit_selection_tool_draw (GimpDrawTool *tool); static GimpItem * gimp_edit_selection_tool_get_active_item (GimpEditSelectionTool *edit_select, GimpImage *image); static void gimp_edit_selection_tool_calc_coords (GimpEditSelectionTool *edit_select, GimpImage *image, gdouble x, gdouble y); static void gimp_edit_selection_tool_start_undo_group (GimpEditSelectionTool *edit_select, GimpImage *image); G_DEFINE_TYPE (GimpEditSelectionTool, gimp_edit_selection_tool, GIMP_TYPE_DRAW_TOOL) #define parent_class gimp_edit_selection_tool_parent_class static void gimp_edit_selection_tool_class_init (GimpEditSelectionToolClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass); GimpDrawToolClass *draw_class = GIMP_DRAW_TOOL_CLASS (klass); object_class->finalize = gimp_edit_selection_tool_finalize; tool_class->button_release = gimp_edit_selection_tool_button_release; tool_class->motion = gimp_edit_selection_tool_motion; tool_class->active_modifier_key = gimp_edit_selection_tool_active_modifier_key; draw_class->draw = gimp_edit_selection_tool_draw; } static void gimp_edit_selection_tool_init (GimpEditSelectionTool *edit_select) { GimpTool *tool = GIMP_TOOL (edit_select); edit_select->first_move = TRUE; gimp_tool_control_set_active_modifiers (tool->control, GIMP_TOOL_ACTIVE_MODIFIERS_SEPARATE); } static void gimp_edit_selection_tool_finalize (GObject *object) { GimpEditSelectionTool *edit_select = GIMP_EDIT_SELECTION_TOOL (object); g_clear_pointer (&edit_select->segs_in, g_free); edit_select->num_segs_in = 0; g_clear_pointer (&edit_select->segs_out, g_free); edit_select->num_segs_out = 0; g_clear_pointer (&edit_select->live_items, g_list_free); g_clear_pointer (&edit_select->delayed_items, g_list_free); G_OBJECT_CLASS (parent_class)->finalize (object); } void gimp_edit_selection_tool_start (GimpTool *parent_tool, GimpDisplay *display, const GimpCoords *coords, GimpTranslateMode edit_mode, gboolean propagate_release) { GimpEditSelectionTool *edit_select; GimpTool *tool; GimpDisplayShell *shell; GimpImage *image; GimpItem *active_item; GList *list; gint off_x, off_y; edit_select = g_object_new (GIMP_TYPE_EDIT_SELECTION_TOOL, "tool-info", parent_tool->tool_info, NULL); edit_select->propagate_release = propagate_release; tool = GIMP_TOOL (edit_select); shell = gimp_display_get_shell (display); image = gimp_display_get_image (display); /* Make a check to see if it should be a floating selection translation */ if ((edit_mode == GIMP_TRANSLATE_MODE_MASK_TO_LAYER || edit_mode == GIMP_TRANSLATE_MODE_MASK_COPY_TO_LAYER) && gimp_image_get_floating_selection (image)) { edit_mode = GIMP_TRANSLATE_MODE_FLOATING_SEL; } if (edit_mode == GIMP_TRANSLATE_MODE_LAYER) { GimpLayer *layer = gimp_image_get_active_layer (image); if (gimp_layer_is_floating_sel (layer)) edit_mode = GIMP_TRANSLATE_MODE_FLOATING_SEL; } edit_select->edit_mode = edit_mode; gimp_edit_selection_tool_start_undo_group (edit_select, image); /* Remember starting point for use in constrained movement */ edit_select->start_x = coords->x; edit_select->start_y = coords->y; active_item = gimp_edit_selection_tool_get_active_item (edit_select, image); gimp_item_get_offset (active_item, &off_x, &off_y); /* Manually set the last coords to the starting point */ edit_select->last_x = coords->x - off_x; edit_select->last_y = coords->y - off_y; edit_select->constrain = FALSE; /* Find the active item's selection bounds */ { GimpChannel *channel; const GimpBoundSeg *segs_in; const GimpBoundSeg *segs_out; if (GIMP_IS_CHANNEL (active_item)) channel = GIMP_CHANNEL (active_item); else channel = gimp_image_get_mask (image); gimp_channel_boundary (channel, &segs_in, &segs_out, &edit_select->num_segs_in, &edit_select->num_segs_out, 0, 0, 0, 0); edit_select->segs_in = g_memdup (segs_in, edit_select->num_segs_in * sizeof (GimpBoundSeg)); edit_select->segs_out = g_memdup (segs_out, edit_select->num_segs_out * sizeof (GimpBoundSeg)); if (edit_select->edit_mode == GIMP_TRANSLATE_MODE_VECTORS) { edit_select->sel_x = 0; edit_select->sel_y = 0; edit_select->sel_width = gimp_image_get_width (image); edit_select->sel_height = gimp_image_get_height (image); } else { /* find the bounding box of the selection mask - this is used * for the case of a GIMP_TRANSLATE_MODE_MASK_TO_LAYER, where * the translation will result in floating the selection mask * and translating the resulting layer */ gimp_item_mask_intersect (active_item, &edit_select->sel_x, &edit_select->sel_y, &edit_select->sel_width, &edit_select->sel_height); } } gimp_edit_selection_tool_calc_coords (edit_select, image, coords->x, coords->y); { gint x, y, w, h; switch (edit_select->edit_mode) { case GIMP_TRANSLATE_MODE_CHANNEL: case GIMP_TRANSLATE_MODE_MASK: case GIMP_TRANSLATE_MODE_LAYER_MASK: gimp_item_bounds (active_item, &x, &y, &w, &h); x += off_x; y += off_y; break; case GIMP_TRANSLATE_MODE_MASK_TO_LAYER: case GIMP_TRANSLATE_MODE_MASK_COPY_TO_LAYER: x = edit_select->sel_x + off_x; y = edit_select->sel_y + off_y; w = edit_select->sel_width; h = edit_select->sel_height; break; case GIMP_TRANSLATE_MODE_LAYER: case GIMP_TRANSLATE_MODE_FLOATING_SEL: case GIMP_TRANSLATE_MODE_VECTORS: if (gimp_item_get_linked (active_item)) { GList *linked; linked = gimp_image_item_list_get_list (image, GIMP_IS_LAYER (active_item) ? GIMP_ITEM_TYPE_LAYERS : GIMP_ITEM_TYPE_VECTORS, GIMP_ITEM_SET_LINKED); linked = gimp_image_item_list_filter (linked); gimp_image_item_list_bounds (image, linked, &x, &y, &w, &h); g_list_free (linked); } else { gimp_item_bounds (active_item, &x, &y, &w, &h); x += off_x; y += off_y; } break; } gimp_tool_control_set_snap_offsets (tool->control, x - coords->x, y - coords->y, w, h); /* Save where to draw the mark of the center */ edit_select->center_x = x + w / 2.0; edit_select->center_y = y + h / 2.0; } if (gimp_item_get_linked (active_item)) { switch (edit_select->edit_mode) { case GIMP_TRANSLATE_MODE_CHANNEL: case GIMP_TRANSLATE_MODE_LAYER: case GIMP_TRANSLATE_MODE_VECTORS: edit_select->live_items = gimp_image_item_list_get_list (image, GIMP_ITEM_TYPE_LAYERS | GIMP_ITEM_TYPE_VECTORS, GIMP_ITEM_SET_LINKED); edit_select->live_items = gimp_image_item_list_filter (edit_select->live_items); edit_select->delayed_items = gimp_image_item_list_get_list (image, GIMP_ITEM_TYPE_CHANNELS, GIMP_ITEM_SET_LINKED); edit_select->delayed_items = gimp_image_item_list_filter (edit_select->delayed_items); break; default: /* other stuff can't be linked so don't bother */ break; } } else { switch (edit_select->edit_mode) { case GIMP_TRANSLATE_MODE_VECTORS: case GIMP_TRANSLATE_MODE_LAYER: case GIMP_TRANSLATE_MODE_FLOATING_SEL: edit_select->live_items = g_list_append (NULL, active_item); break; case GIMP_TRANSLATE_MODE_CHANNEL: case GIMP_TRANSLATE_MODE_LAYER_MASK: case GIMP_TRANSLATE_MODE_MASK: edit_select->delayed_items = g_list_append (NULL, active_item); break; default: /* MASK_TO_LAYER and MASK_COPY_TO_LAYER create a live_item later */ break; } } for (list = edit_select->live_items; list; list = g_list_next (list)) { GimpItem *item = list->data; gimp_viewable_preview_freeze (GIMP_VIEWABLE (item)); gimp_item_start_transform (item, TRUE); } tool_manager_push_tool (display->gimp, tool); gimp_tool_control_activate (tool->control); tool->display = display; /* pause the current selection */ gimp_display_shell_selection_pause (shell); /* initialize the statusbar display */ gimp_tool_push_status_coords (tool, display, gimp_tool_control_get_precision (tool->control), _("Move: "), 0, ", ", 0, NULL); gimp_draw_tool_start (GIMP_DRAW_TOOL (edit_select), display); } static void gimp_edit_selection_tool_button_release (GimpTool *tool, const GimpCoords *coords, guint32 time, GdkModifierType state, GimpButtonReleaseType release_type, GimpDisplay *display) { GimpEditSelectionTool *edit_select = GIMP_EDIT_SELECTION_TOOL (tool); GimpDisplayShell *shell = gimp_display_get_shell (display); GimpImage *image = gimp_display_get_image (display); GList *list; /* resume the current selection */ gimp_display_shell_selection_resume (shell); gimp_tool_pop_status (tool, display); gimp_tool_control_halt (tool->control); /* Stop and free the selection core */ gimp_draw_tool_stop (GIMP_DRAW_TOOL (edit_select)); tool_manager_pop_tool (display->gimp); /* move the items -- whether there has been movement or not! * (to ensure that there's something on the undo stack) */ gimp_image_item_list_translate (image, edit_select->delayed_items, edit_select->cuml_x, edit_select->cuml_y, TRUE); for (list = edit_select->live_items; list; list = g_list_next (list)) { GimpItem *item = list->data; gimp_item_end_transform (item, TRUE); gimp_viewable_preview_thaw (GIMP_VIEWABLE (item)); } gimp_image_undo_group_end (image); if (release_type == GIMP_BUTTON_RELEASE_CANCEL) { /* Operation cancelled - undo the undo-group! */ gimp_image_undo (image); } gimp_image_flush (image); if (edit_select->propagate_release && tool_manager_get_active (display->gimp)) { tool_manager_button_release_active (display->gimp, coords, time, state, display); } g_object_unref (edit_select); } static void gimp_edit_selection_tool_update_motion (GimpEditSelectionTool *edit_select, gdouble new_x, gdouble new_y, GimpDisplay *display) { GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (edit_select); GimpTool *tool = GIMP_TOOL (edit_select); GimpImage *image = gimp_display_get_image (display); gint dx; gint dy; gdk_flush (); gimp_draw_tool_pause (draw_tool); if (edit_select->constrain) { gimp_constrain_line (edit_select->start_x, edit_select->start_y, &new_x, &new_y, GIMP_CONSTRAIN_LINE_45_DEGREES, 0.0, 1.0, 1.0); } gimp_edit_selection_tool_calc_coords (edit_select, image, new_x, new_y); dx = edit_select->current_x - edit_select->last_x; dy = edit_select->current_y - edit_select->last_y; /* if there has been movement, move */ if (dx != 0 || dy != 0) { GimpItem *active_item; GError *error = NULL; active_item = gimp_edit_selection_tool_get_active_item (edit_select, image); edit_select->cuml_x += dx; edit_select->cuml_y += dy; switch (edit_select->edit_mode) { case GIMP_TRANSLATE_MODE_LAYER_MASK: case GIMP_TRANSLATE_MODE_MASK: case GIMP_TRANSLATE_MODE_VECTORS: case GIMP_TRANSLATE_MODE_CHANNEL: edit_select->last_x = edit_select->current_x; edit_select->last_y = edit_select->current_y; /* fallthru */ case GIMP_TRANSLATE_MODE_LAYER: gimp_image_item_list_translate (image, edit_select->live_items, dx, dy, edit_select->first_move); break; case GIMP_TRANSLATE_MODE_MASK_TO_LAYER: case GIMP_TRANSLATE_MODE_MASK_COPY_TO_LAYER: if (! gimp_selection_float (GIMP_SELECTION (gimp_image_get_mask (image)), GIMP_DRAWABLE (active_item), gimp_get_user_context (display->gimp), edit_select->edit_mode == GIMP_TRANSLATE_MODE_MASK_TO_LAYER, 0, 0, &error)) { /* no region to float, abort safely */ gimp_message_literal (display->gimp, G_OBJECT (display), GIMP_MESSAGE_WARNING, error->message); g_clear_error (&error); gimp_draw_tool_resume (draw_tool); return; } edit_select->last_x -= edit_select->sel_x; edit_select->last_y -= edit_select->sel_y; edit_select->sel_x = 0; edit_select->sel_y = 0; edit_select->edit_mode = GIMP_TRANSLATE_MODE_FLOATING_SEL; active_item = gimp_edit_selection_tool_get_active_item (edit_select, image); edit_select->live_items = g_list_prepend (NULL, active_item); gimp_viewable_preview_freeze (GIMP_VIEWABLE (active_item)); gimp_item_start_transform (active_item, TRUE); /* fallthru */ case GIMP_TRANSLATE_MODE_FLOATING_SEL: gimp_image_item_list_translate (image, edit_select->live_items, dx, dy, edit_select->first_move); break; } edit_select->first_move = FALSE; } gimp_projection_flush (gimp_image_get_projection (image)); gimp_tool_pop_status (tool, display); gimp_tool_push_status_coords (tool, display, gimp_tool_control_get_precision (tool->control), _("Move: "), edit_select->cuml_x, ", ", edit_select->cuml_y, NULL); gimp_draw_tool_resume (draw_tool); } static void gimp_edit_selection_tool_motion (GimpTool *tool, const GimpCoords *coords, guint32 time, GdkModifierType state, GimpDisplay *display) { GimpEditSelectionTool *edit_select = GIMP_EDIT_SELECTION_TOOL (tool); edit_select->last_motion_x = coords->x; edit_select->last_motion_y = coords->y; gimp_edit_selection_tool_update_motion (edit_select, coords->x, coords->y, display); } static void gimp_edit_selection_tool_active_modifier_key (GimpTool *tool, GdkModifierType key, gboolean press, GdkModifierType state, GimpDisplay *display) { GimpEditSelectionTool *edit_select = GIMP_EDIT_SELECTION_TOOL (tool); edit_select->constrain = (state & gimp_get_constrain_behavior_mask () ? TRUE : FALSE); /* If we didn't came here due to a mouse release, immediately update * the position of the thing we move. */ if (state & GDK_BUTTON1_MASK) { gimp_edit_selection_tool_update_motion (edit_select, edit_select->last_motion_x, edit_select->last_motion_y, display); } } static void gimp_edit_selection_tool_draw (GimpDrawTool *draw_tool) { GimpEditSelectionTool *edit_select = GIMP_EDIT_SELECTION_TOOL (draw_tool); GimpDisplay *display = GIMP_TOOL (draw_tool)->display; GimpImage *image = gimp_display_get_image (display); GimpItem *active_item; gint off_x; gint off_y; active_item = gimp_edit_selection_tool_get_active_item (edit_select, image); gimp_item_get_offset (active_item, &off_x, &off_y); switch (edit_select->edit_mode) { case GIMP_TRANSLATE_MODE_CHANNEL: case GIMP_TRANSLATE_MODE_LAYER_MASK: case GIMP_TRANSLATE_MODE_MASK: { gboolean floating_sel = FALSE; if (edit_select->edit_mode == GIMP_TRANSLATE_MODE_MASK) { GimpLayer *layer = gimp_image_get_active_layer (image); if (layer) floating_sel = gimp_layer_is_floating_sel (layer); } if (! floating_sel && edit_select->segs_in) { gimp_draw_tool_add_boundary (draw_tool, edit_select->segs_in, edit_select->num_segs_in, NULL, edit_select->cuml_x + off_x, edit_select->cuml_y + off_y); } if (edit_select->segs_out) { gimp_draw_tool_add_boundary (draw_tool, edit_select->segs_out, edit_select->num_segs_out, NULL, edit_select->cuml_x + off_x, edit_select->cuml_y + off_y); } else if (edit_select->edit_mode != GIMP_TRANSLATE_MODE_MASK) { gimp_draw_tool_add_rectangle (draw_tool, FALSE, edit_select->cuml_x + off_x, edit_select->cuml_y + off_y, gimp_item_get_width (active_item), gimp_item_get_height (active_item)); } } break; case GIMP_TRANSLATE_MODE_MASK_TO_LAYER: case GIMP_TRANSLATE_MODE_MASK_COPY_TO_LAYER: gimp_draw_tool_add_rectangle (draw_tool, FALSE, edit_select->sel_x + off_x, edit_select->sel_y + off_y, edit_select->sel_width, edit_select->sel_height); break; case GIMP_TRANSLATE_MODE_LAYER: case GIMP_TRANSLATE_MODE_VECTORS: { gint x, y, w, h; if (gimp_item_get_linked (active_item)) { GList *linked; linked = gimp_image_item_list_get_list (image, GIMP_IS_LAYER (active_item) ? GIMP_ITEM_TYPE_LAYERS : GIMP_ITEM_TYPE_VECTORS, GIMP_ITEM_SET_LINKED); linked = gimp_image_item_list_filter (linked); gimp_image_item_list_bounds (image, linked, &x, &y, &w, &h); g_list_free (linked); } else { gimp_item_bounds (active_item, &x, &y, &w, &h); x += off_x; y += off_y; } gimp_draw_tool_add_rectangle (draw_tool, FALSE, x, y, w, h); } break; case GIMP_TRANSLATE_MODE_FLOATING_SEL: if (edit_select->segs_in) { gimp_draw_tool_add_boundary (draw_tool, edit_select->segs_in, edit_select->num_segs_in, NULL, edit_select->cuml_x, edit_select->cuml_y); } break; } /* Mark the center because we snap to it */ gimp_draw_tool_add_handle (draw_tool, GIMP_HANDLE_CROSS, edit_select->center_x + edit_select->cuml_x, edit_select->center_y + edit_select->cuml_y, GIMP_TOOL_HANDLE_SIZE_SMALL, GIMP_TOOL_HANDLE_SIZE_SMALL, GIMP_HANDLE_ANCHOR_CENTER); GIMP_DRAW_TOOL_CLASS (parent_class)->draw (draw_tool); } static GimpItem * gimp_edit_selection_tool_get_active_item (GimpEditSelectionTool *edit_select, GimpImage *image) { GimpItem *active_item; switch (edit_select->edit_mode) { case GIMP_TRANSLATE_MODE_VECTORS: active_item = GIMP_ITEM (gimp_image_get_active_vectors (image)); break; case GIMP_TRANSLATE_MODE_LAYER: active_item = GIMP_ITEM (gimp_image_get_active_layer (image)); break; case GIMP_TRANSLATE_MODE_MASK: active_item = GIMP_ITEM (gimp_image_get_mask (image)); break; default: active_item = GIMP_ITEM (gimp_image_get_active_drawable (image)); break; } return active_item; } static void gimp_edit_selection_tool_calc_coords (GimpEditSelectionTool *edit_select, GimpImage *image, gdouble x, gdouble y) { GimpItem *active_item; gint off_x, off_y; gdouble x1, y1; gdouble dx, dy; active_item = gimp_edit_selection_tool_get_active_item (edit_select, image); gimp_item_get_offset (active_item, &off_x, &off_y); dx = (x - off_x) - edit_select->last_x; dy = (y - off_y) - edit_select->last_y; x1 = edit_select->sel_x + dx; y1 = edit_select->sel_y + dy; edit_select->current_x = ((gint) floor (x1) - (edit_select->sel_x - edit_select->last_x)); edit_select->current_y = ((gint) floor (y1) - (edit_select->sel_y - edit_select->last_y)); } static void gimp_edit_selection_tool_start_undo_group (GimpEditSelectionTool *edit_select, GimpImage *image) { GimpItem *active_item; const gchar *undo_desc = NULL; active_item = gimp_edit_selection_tool_get_active_item (edit_select, image); switch (edit_select->edit_mode) { case GIMP_TRANSLATE_MODE_VECTORS: case GIMP_TRANSLATE_MODE_CHANNEL: case GIMP_TRANSLATE_MODE_LAYER_MASK: case GIMP_TRANSLATE_MODE_MASK: case GIMP_TRANSLATE_MODE_LAYER: undo_desc = GIMP_ITEM_GET_CLASS (active_item)->translate_desc; break; case GIMP_TRANSLATE_MODE_MASK_TO_LAYER: case GIMP_TRANSLATE_MODE_MASK_COPY_TO_LAYER: case GIMP_TRANSLATE_MODE_FLOATING_SEL: undo_desc = _("Move Floating Selection"); break; default: g_return_if_reached (); } gimp_image_undo_group_start (image, edit_select->edit_mode == GIMP_TRANSLATE_MODE_MASK ? GIMP_UNDO_GROUP_MASK : GIMP_UNDO_GROUP_ITEM_DISPLACE, undo_desc); } static gint process_event_queue_keys (GdkEventKey *kevent, ... /* GdkKeyType, GdkModifierType, value ... 0 */) { #define FILTER_MAX_KEYS 50 va_list argp; GdkEvent *event; GList *event_list = NULL; GList *list; guint keys[FILTER_MAX_KEYS]; GdkModifierType modifiers[FILTER_MAX_KEYS]; gint values[FILTER_MAX_KEYS]; gint i = 0; gint n_keys = 0; gint value = 0; gboolean done = FALSE; GtkWidget *orig_widget; va_start (argp, kevent); while (n_keys < FILTER_MAX_KEYS && (keys[n_keys] = va_arg (argp, guint)) != 0) { modifiers[n_keys] = va_arg (argp, GdkModifierType); values[n_keys] = va_arg (argp, gint); n_keys++; } va_end (argp); for (i = 0; i < n_keys; i++) if (kevent->keyval == keys[i] && (kevent->state & modifiers[i]) == modifiers[i]) value += values[i]; orig_widget = gtk_get_event_widget ((GdkEvent *) kevent); while (gdk_events_pending () > 0 && ! done) { gboolean discard_event = FALSE; event = gdk_event_get (); if (! event || orig_widget != gtk_get_event_widget (event)) { done = TRUE; } else { if (event->any.type == GDK_KEY_PRESS) { for (i = 0; i < n_keys; i++) if (event->key.keyval == keys[i] && (event->key.state & modifiers[i]) == modifiers[i]) { discard_event = TRUE; value += values[i]; } if (! discard_event) done = TRUE; } /* should there be more types here? */ else if (event->any.type != GDK_KEY_RELEASE && event->any.type != GDK_MOTION_NOTIFY && event->any.type != GDK_EXPOSE) done = FALSE; } if (! event) ; /* Do nothing */ else if (! discard_event) event_list = g_list_prepend (event_list, event); else gdk_event_free (event); } event_list = g_list_reverse (event_list); /* unget the unused events and free the list */ for (list = event_list; list; list = g_list_next (list)) { gdk_event_put ((GdkEvent *) list->data); gdk_event_free ((GdkEvent *) list->data); } g_list_free (event_list); return value; #undef FILTER_MAX_KEYS } gboolean gimp_edit_selection_tool_key_press (GimpTool *tool, GdkEventKey *kevent, GimpDisplay *display) { GimpTransformType translate_type; if (kevent->state & GDK_MOD1_MASK) { translate_type = GIMP_TRANSFORM_TYPE_SELECTION; } else if (kevent->state & gimp_get_toggle_behavior_mask ()) { translate_type = GIMP_TRANSFORM_TYPE_PATH; } else { translate_type = GIMP_TRANSFORM_TYPE_LAYER; } return gimp_edit_selection_tool_translate (tool, kevent, translate_type, display, NULL); } gboolean gimp_edit_selection_tool_translate (GimpTool *tool, GdkEventKey *kevent, GimpTransformType translate_type, GimpDisplay *display, GtkWidget *type_box) { gint inc_x = 0; gint inc_y = 0; GimpUndo *undo; gboolean push_undo = TRUE; GimpImage *image = gimp_display_get_image (display); GimpItem *item = NULL; GimpTranslateMode edit_mode = GIMP_TRANSLATE_MODE_MASK; GimpUndoType undo_type = GIMP_UNDO_GROUP_MASK; const gchar *undo_desc = NULL; GdkModifierType extend_mask = gimp_get_extend_selection_mask (); const gchar *null_message = NULL; const gchar *locked_message = NULL; gint velocity; /* bail out early if it is not an arrow key event */ if (kevent->keyval != GDK_KEY_Left && kevent->keyval != GDK_KEY_Right && kevent->keyval != GDK_KEY_Up && kevent->keyval != GDK_KEY_Down) return FALSE; /* adapt arrow velocity to the zoom factor when holding */ velocity = (ARROW_VELOCITY / gimp_zoom_model_get_factor (gimp_display_get_shell (display)->zoom)); velocity = MAX (1.0, velocity); /* check the event queue for key events with the same modifier mask * as the current event, allowing only extend_mask to vary between * them. */ inc_x = process_event_queue_keys (kevent, GDK_KEY_Left, kevent->state | extend_mask, -1 * velocity, GDK_KEY_Left, kevent->state & ~extend_mask, -1, GDK_KEY_Right, kevent->state | extend_mask, 1 * velocity, GDK_KEY_Right, kevent->state & ~extend_mask, 1, 0); inc_y = process_event_queue_keys (kevent, GDK_KEY_Up, kevent->state | extend_mask, -1 * velocity, GDK_KEY_Up, kevent->state & ~extend_mask, -1, GDK_KEY_Down, kevent->state | extend_mask, 1 * velocity, GDK_KEY_Down, kevent->state & ~extend_mask, 1, 0); switch (translate_type) { case GIMP_TRANSFORM_TYPE_SELECTION: item = GIMP_ITEM (gimp_image_get_mask (image)); if (gimp_channel_is_empty (GIMP_CHANNEL (item))) item = NULL; edit_mode = GIMP_TRANSLATE_MODE_MASK; undo_type = GIMP_UNDO_GROUP_MASK; if (! item) { /* cannot happen, don't translate this message */ null_message = "There is no selection to move."; } else if (gimp_item_is_position_locked (item)) { /* cannot happen, don't translate this message */ locked_message = "The selection's position is locked."; } break; case GIMP_TRANSFORM_TYPE_PATH: item = GIMP_ITEM (gimp_image_get_active_vectors (image)); edit_mode = GIMP_TRANSLATE_MODE_VECTORS; undo_type = GIMP_UNDO_GROUP_ITEM_DISPLACE; if (! item) { null_message = _("There is no path to move."); } else if (gimp_item_is_position_locked (item)) { locked_message = _("The active path's position is locked."); } break; case GIMP_TRANSFORM_TYPE_LAYER: item = GIMP_ITEM (gimp_image_get_active_drawable (image)); undo_type = GIMP_UNDO_GROUP_ITEM_DISPLACE; if (! item) { null_message = _("There is no layer to move."); } else if (GIMP_IS_LAYER_MASK (item)) { edit_mode = GIMP_TRANSLATE_MODE_LAYER_MASK; if (gimp_item_is_position_locked (item)) { locked_message = _("The active layer's position is locked."); } else if (gimp_item_is_content_locked (item)) { locked_message = _("The active layer's pixels are locked."); } } else if (GIMP_IS_CHANNEL (item)) { edit_mode = GIMP_TRANSLATE_MODE_CHANNEL; if (gimp_item_is_position_locked (item)) { locked_message = _("The active channel's position is locked."); } else if (gimp_item_is_content_locked (item)) { locked_message = _("The active channel's pixels are locked."); } } else if (gimp_layer_is_floating_sel (GIMP_LAYER (item))) { edit_mode = GIMP_TRANSLATE_MODE_FLOATING_SEL; if (gimp_item_is_position_locked (item)) { locked_message = _("The active layer's position is locked."); } } else { edit_mode = GIMP_TRANSLATE_MODE_LAYER; if (gimp_item_is_position_locked (item)) { locked_message = _("The active layer's position is locked."); } } break; case GIMP_TRANSFORM_TYPE_IMAGE: g_return_val_if_reached (FALSE); } if (! item) { gimp_tool_message_literal (tool, display, null_message); if (type_box) gimp_widget_blink (type_box); return TRUE; } else if (locked_message) { gimp_tool_message_literal (tool, display, locked_message); gimp_tools_blink_lock_box (display->gimp, item); return TRUE; } if (inc_x == 0 && inc_y == 0) return TRUE; switch (edit_mode) { case GIMP_TRANSLATE_MODE_FLOATING_SEL: undo_desc = _("Move Floating Selection"); break; default: undo_desc = GIMP_ITEM_GET_CLASS (item)->translate_desc; break; } /* compress undo */ undo = gimp_image_undo_can_compress (image, GIMP_TYPE_UNDO_STACK, undo_type); if (undo && g_object_get_data (G_OBJECT (undo), "edit-selection-tool") == (gpointer) tool && g_object_get_data (G_OBJECT (undo), "edit-selection-item") == (gpointer) item && g_object_get_data (G_OBJECT (undo), "edit-selection-type") == GINT_TO_POINTER (edit_mode)) { push_undo = FALSE; } if (push_undo) { if (gimp_image_undo_group_start (image, undo_type, undo_desc)) { undo = gimp_image_undo_can_compress (image, GIMP_TYPE_UNDO_STACK, undo_type); if (undo) { g_object_set_data (G_OBJECT (undo), "edit-selection-tool", tool); g_object_set_data (G_OBJECT (undo), "edit-selection-item", item); g_object_set_data (G_OBJECT (undo), "edit-selection-type", GINT_TO_POINTER (edit_mode)); } } } switch (edit_mode) { case GIMP_TRANSLATE_MODE_LAYER_MASK: case GIMP_TRANSLATE_MODE_MASK: gimp_item_translate (item, inc_x, inc_y, push_undo); break; case GIMP_TRANSLATE_MODE_MASK_TO_LAYER: case GIMP_TRANSLATE_MODE_MASK_COPY_TO_LAYER: /* this won't happen */ break; case GIMP_TRANSLATE_MODE_VECTORS: case GIMP_TRANSLATE_MODE_CHANNEL: case GIMP_TRANSLATE_MODE_LAYER: if (gimp_item_get_linked (item)) { gimp_item_linked_translate (item, inc_x, inc_y, push_undo); } else { gimp_item_translate (item, inc_x, inc_y, push_undo); } break; case GIMP_TRANSLATE_MODE_FLOATING_SEL: gimp_item_translate (item, inc_x, inc_y, push_undo); break; } if (push_undo) gimp_image_undo_group_end (image); else gimp_undo_refresh_preview (undo, gimp_get_user_context (display->gimp)); gimp_image_flush (image); return TRUE; }