If you click on a zone filled in several visible layers, you don't necessarily want the top layer. You may want one below. With this change, as long as you hold alt, you will loop through all candidate layers from top to bottom (then looping back top when reaching the bottom). In a first alt-click, you will always end up to the top candidate.
531 lines
17 KiB
C
531 lines
17 KiB
C
/* 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 <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <gegl.h>
|
|
#include <gtk/gtk.h>
|
|
|
|
#include "libgimpwidgets/gimpwidgets.h"
|
|
|
|
#include "tools-types.h"
|
|
|
|
#include "core/gimpchannel.h"
|
|
#include "core/gimperror.h"
|
|
#include "core/gimpimage.h"
|
|
#include "core/gimpimage-pick-item.h"
|
|
#include "core/gimppickable.h"
|
|
|
|
#include "display/gimpdisplay.h"
|
|
|
|
#include "widgets/gimpwidgets-utils.h"
|
|
|
|
#include "gimpeditselectiontool.h"
|
|
#include "gimpselectiontool.h"
|
|
#include "gimpselectionoptions.h"
|
|
#include "gimptoolcontrol.h"
|
|
#include "gimptools-utils.h"
|
|
|
|
#include "gimp-intl.h"
|
|
|
|
|
|
|
|
static void gimp_selection_tool_modifier_key (GimpTool *tool,
|
|
GdkModifierType key,
|
|
gboolean press,
|
|
GdkModifierType state,
|
|
GimpDisplay *display);
|
|
static void gimp_selection_tool_oper_update (GimpTool *tool,
|
|
const GimpCoords *coords,
|
|
GdkModifierType state,
|
|
gboolean proximity,
|
|
GimpDisplay *display);
|
|
static void gimp_selection_tool_cursor_update (GimpTool *tool,
|
|
const GimpCoords *coords,
|
|
GdkModifierType state,
|
|
GimpDisplay *display);
|
|
|
|
static gboolean gimp_selection_tool_check (GimpSelectionTool *sel_tool,
|
|
GimpDisplay *display,
|
|
GError **error);
|
|
|
|
|
|
G_DEFINE_TYPE (GimpSelectionTool, gimp_selection_tool, GIMP_TYPE_DRAW_TOOL)
|
|
|
|
#define parent_class gimp_selection_tool_parent_class
|
|
|
|
|
|
static void
|
|
gimp_selection_tool_class_init (GimpSelectionToolClass *klass)
|
|
{
|
|
GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
|
|
|
|
tool_class->modifier_key = gimp_selection_tool_modifier_key;
|
|
tool_class->key_press = gimp_edit_selection_tool_key_press;
|
|
tool_class->oper_update = gimp_selection_tool_oper_update;
|
|
tool_class->cursor_update = gimp_selection_tool_cursor_update;
|
|
}
|
|
|
|
static void
|
|
gimp_selection_tool_init (GimpSelectionTool *selection_tool)
|
|
{
|
|
selection_tool->function = SELECTION_SELECT;
|
|
selection_tool->saved_operation = GIMP_CHANNEL_OP_REPLACE;
|
|
|
|
selection_tool->allow_move = TRUE;
|
|
}
|
|
|
|
static void
|
|
gimp_selection_tool_modifier_key (GimpTool *tool,
|
|
GdkModifierType key,
|
|
gboolean press,
|
|
GdkModifierType state,
|
|
GimpDisplay *display)
|
|
{
|
|
GimpSelectionTool *selection_tool = GIMP_SELECTION_TOOL (tool);
|
|
GimpSelectionOptions *options = GIMP_SELECTION_TOOL_GET_OPTIONS (tool);
|
|
GdkModifierType extend_mask;
|
|
GdkModifierType modify_mask;
|
|
|
|
extend_mask = gimp_get_extend_selection_mask ();
|
|
modify_mask = gimp_get_modify_selection_mask ();
|
|
|
|
if (key == extend_mask ||
|
|
key == modify_mask ||
|
|
key == GDK_MOD1_MASK)
|
|
{
|
|
GimpChannelOps button_op = options->operation;
|
|
|
|
if (press)
|
|
{
|
|
if (key == (state & (extend_mask |
|
|
modify_mask |
|
|
GDK_MOD1_MASK)))
|
|
{
|
|
/* first modifier pressed */
|
|
|
|
selection_tool->saved_operation = options->operation;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (! (state & (extend_mask |
|
|
modify_mask |
|
|
GDK_MOD1_MASK)))
|
|
{
|
|
/* last modifier released */
|
|
|
|
button_op = selection_tool->saved_operation;
|
|
}
|
|
}
|
|
|
|
if (state & GDK_MOD1_MASK)
|
|
{
|
|
/* if alt is down, pretend that neither
|
|
* shift nor control are down
|
|
*/
|
|
button_op = selection_tool->saved_operation;
|
|
}
|
|
else if (state & (extend_mask |
|
|
modify_mask))
|
|
{
|
|
/* else get the operation from the modifier state, but only
|
|
* if there is actually a modifier pressed, so we don't
|
|
* override the "last modifier released" assignment above
|
|
*/
|
|
button_op = gimp_modifiers_to_channel_op (state);
|
|
}
|
|
|
|
if (button_op != options->operation)
|
|
{
|
|
g_object_set (options, "operation", button_op, NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
gimp_selection_tool_oper_update (GimpTool *tool,
|
|
const GimpCoords *coords,
|
|
GdkModifierType state,
|
|
gboolean proximity,
|
|
GimpDisplay *display)
|
|
{
|
|
GimpSelectionTool *selection_tool = GIMP_SELECTION_TOOL (tool);
|
|
GimpSelectionOptions *options = GIMP_SELECTION_TOOL_GET_OPTIONS (tool);
|
|
GimpImage *image;
|
|
GimpChannel *selection;
|
|
GimpDrawable *drawable;
|
|
GimpLayer *layer;
|
|
GimpLayer *floating_sel;
|
|
GdkModifierType extend_mask;
|
|
GdkModifierType modify_mask;
|
|
gboolean move_layer = FALSE;
|
|
gboolean move_floating_sel = FALSE;
|
|
gboolean selection_empty;
|
|
|
|
image = gimp_display_get_image (display);
|
|
selection = gimp_image_get_mask (image);
|
|
drawable = gimp_image_get_active_drawable (image);
|
|
layer = gimp_image_pick_layer (image, coords->x, coords->y, NULL);
|
|
floating_sel = gimp_image_get_floating_selection (image);
|
|
|
|
extend_mask = gimp_get_extend_selection_mask ();
|
|
modify_mask = gimp_get_modify_selection_mask ();
|
|
|
|
if (drawable)
|
|
{
|
|
if (floating_sel)
|
|
{
|
|
if (layer == floating_sel)
|
|
move_floating_sel = TRUE;
|
|
}
|
|
else if (gimp_item_mask_intersect (GIMP_ITEM (drawable),
|
|
NULL, NULL, NULL, NULL))
|
|
{
|
|
move_layer = TRUE;
|
|
}
|
|
}
|
|
|
|
selection_empty = gimp_channel_is_empty (selection);
|
|
|
|
selection_tool->function = SELECTION_SELECT;
|
|
|
|
if (selection_tool->allow_move &&
|
|
(state & GDK_MOD1_MASK) && (state & modify_mask) && move_layer)
|
|
{
|
|
/* move the selection */
|
|
selection_tool->function = SELECTION_MOVE;
|
|
}
|
|
else if (selection_tool->allow_move &&
|
|
(state & GDK_MOD1_MASK) && (state & extend_mask) && move_layer)
|
|
{
|
|
/* move a copy of the selection */
|
|
selection_tool->function = SELECTION_MOVE_COPY;
|
|
}
|
|
else if (selection_tool->allow_move &&
|
|
(state & GDK_MOD1_MASK) && ! selection_empty)
|
|
{
|
|
/* move the selection mask */
|
|
selection_tool->function = SELECTION_MOVE_MASK;
|
|
}
|
|
else if (selection_tool->allow_move &&
|
|
! (state & (extend_mask | modify_mask)) &&
|
|
move_floating_sel)
|
|
{
|
|
/* move the selection */
|
|
selection_tool->function = SELECTION_MOVE;
|
|
}
|
|
else if ((state & modify_mask) || (state & extend_mask))
|
|
{
|
|
/* select */
|
|
selection_tool->function = SELECTION_SELECT;
|
|
}
|
|
else if (floating_sel)
|
|
{
|
|
/* anchor the selection */
|
|
selection_tool->function = SELECTION_ANCHOR;
|
|
}
|
|
|
|
gimp_tool_pop_status (tool, display);
|
|
|
|
if (proximity)
|
|
{
|
|
const gchar *status = NULL;
|
|
gboolean free_status = FALSE;
|
|
GdkModifierType modifiers = (extend_mask | modify_mask);
|
|
|
|
if (! selection_empty)
|
|
modifiers |= GDK_MOD1_MASK;
|
|
|
|
switch (selection_tool->function)
|
|
{
|
|
case SELECTION_SELECT:
|
|
switch (options->operation)
|
|
{
|
|
case GIMP_CHANNEL_OP_REPLACE:
|
|
if (! selection_empty)
|
|
{
|
|
status = gimp_suggest_modifiers (_("Click-Drag to replace the "
|
|
"current selection"),
|
|
modifiers & ~state,
|
|
NULL, NULL, NULL);
|
|
free_status = TRUE;
|
|
}
|
|
else
|
|
{
|
|
status = _("Click-Drag to create a new selection");
|
|
}
|
|
break;
|
|
|
|
case GIMP_CHANNEL_OP_ADD:
|
|
status = gimp_suggest_modifiers (_("Click-Drag to add to the "
|
|
"current selection"),
|
|
modifiers
|
|
& ~(state | extend_mask),
|
|
NULL, NULL, NULL);
|
|
free_status = TRUE;
|
|
break;
|
|
|
|
case GIMP_CHANNEL_OP_SUBTRACT:
|
|
status = gimp_suggest_modifiers (_("Click-Drag to subtract from the "
|
|
"current selection"),
|
|
modifiers
|
|
& ~(state | modify_mask),
|
|
NULL, NULL, NULL);
|
|
free_status = TRUE;
|
|
break;
|
|
|
|
case GIMP_CHANNEL_OP_INTERSECT:
|
|
status = gimp_suggest_modifiers (_("Click-Drag to intersect with "
|
|
"the current selection"),
|
|
modifiers & ~state,
|
|
NULL, NULL, NULL);
|
|
free_status = TRUE;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case SELECTION_MOVE_MASK:
|
|
status = gimp_suggest_modifiers (_("Click-Drag to move the "
|
|
"selection mask"),
|
|
modifiers & ~state,
|
|
NULL, NULL, NULL);
|
|
free_status = TRUE;
|
|
break;
|
|
|
|
case SELECTION_MOVE:
|
|
status = _("Click-Drag to move the selected pixels");
|
|
break;
|
|
|
|
case SELECTION_MOVE_COPY:
|
|
status = _("Click-Drag to move a copy of the selected pixels");
|
|
break;
|
|
|
|
case SELECTION_ANCHOR:
|
|
status = _("Click to anchor the floating selection");
|
|
break;
|
|
|
|
default:
|
|
g_return_if_reached ();
|
|
}
|
|
|
|
if (status)
|
|
gimp_tool_push_status (tool, display, "%s", status);
|
|
|
|
if (free_status)
|
|
g_free ((gchar *) status);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gimp_selection_tool_cursor_update (GimpTool *tool,
|
|
const GimpCoords *coords,
|
|
GdkModifierType state,
|
|
GimpDisplay *display)
|
|
{
|
|
GimpSelectionTool *selection_tool = GIMP_SELECTION_TOOL (tool);
|
|
GimpSelectionOptions *options;
|
|
GimpToolCursorType tool_cursor;
|
|
GimpCursorModifier modifier;
|
|
|
|
options = GIMP_SELECTION_TOOL_GET_OPTIONS (tool);
|
|
|
|
tool_cursor = gimp_tool_control_get_tool_cursor (tool->control);
|
|
modifier = GIMP_CURSOR_MODIFIER_NONE;
|
|
|
|
switch (selection_tool->function)
|
|
{
|
|
case SELECTION_SELECT:
|
|
switch (options->operation)
|
|
{
|
|
case GIMP_CHANNEL_OP_REPLACE:
|
|
break;
|
|
case GIMP_CHANNEL_OP_ADD:
|
|
modifier = GIMP_CURSOR_MODIFIER_PLUS;
|
|
break;
|
|
case GIMP_CHANNEL_OP_SUBTRACT:
|
|
modifier = GIMP_CURSOR_MODIFIER_MINUS;
|
|
break;
|
|
case GIMP_CHANNEL_OP_INTERSECT:
|
|
modifier = GIMP_CURSOR_MODIFIER_INTERSECT;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case SELECTION_MOVE_MASK:
|
|
modifier = GIMP_CURSOR_MODIFIER_MOVE;
|
|
break;
|
|
|
|
case SELECTION_MOVE:
|
|
case SELECTION_MOVE_COPY:
|
|
tool_cursor = GIMP_TOOL_CURSOR_MOVE;
|
|
break;
|
|
|
|
case SELECTION_ANCHOR:
|
|
modifier = GIMP_CURSOR_MODIFIER_ANCHOR;
|
|
break;
|
|
}
|
|
|
|
/* our subclass might have set a BAD modifier, in which case we leave it
|
|
* there, since it's more important than what we have to say.
|
|
*/
|
|
if (gimp_tool_control_get_cursor_modifier (tool->control) ==
|
|
GIMP_CURSOR_MODIFIER_BAD ||
|
|
! gimp_selection_tool_check (selection_tool, display, NULL))
|
|
{
|
|
modifier = GIMP_CURSOR_MODIFIER_BAD;
|
|
}
|
|
|
|
gimp_tool_set_cursor (tool, display,
|
|
gimp_tool_control_get_cursor (tool->control),
|
|
tool_cursor,
|
|
modifier);
|
|
}
|
|
|
|
static gboolean
|
|
gimp_selection_tool_check (GimpSelectionTool *sel_tool,
|
|
GimpDisplay *display,
|
|
GError **error)
|
|
{
|
|
GimpSelectionOptions *options = GIMP_SELECTION_TOOL_GET_OPTIONS (sel_tool);
|
|
GimpImage *image = gimp_display_get_image (display);
|
|
GimpDrawable *drawable = gimp_image_get_active_drawable (image);
|
|
|
|
switch (sel_tool->function)
|
|
{
|
|
case SELECTION_SELECT:
|
|
switch (options->operation)
|
|
{
|
|
case GIMP_CHANNEL_OP_ADD:
|
|
case GIMP_CHANNEL_OP_REPLACE:
|
|
break;
|
|
|
|
case GIMP_CHANNEL_OP_SUBTRACT:
|
|
if (! gimp_item_bounds (GIMP_ITEM (gimp_image_get_mask (image)),
|
|
NULL, NULL, NULL, NULL))
|
|
{
|
|
g_set_error (error, GIMP_ERROR, GIMP_FAILED,
|
|
_("Cannot subtract from an empty selection."));
|
|
|
|
return FALSE;
|
|
}
|
|
break;
|
|
|
|
case GIMP_CHANNEL_OP_INTERSECT:
|
|
if (! gimp_item_bounds (GIMP_ITEM (gimp_image_get_mask (image)),
|
|
NULL, NULL, NULL, NULL))
|
|
{
|
|
g_set_error (error, GIMP_ERROR, GIMP_FAILED,
|
|
_("Cannot intersect with an empty selection."));
|
|
|
|
return FALSE;
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case SELECTION_MOVE:
|
|
case SELECTION_MOVE_COPY:
|
|
if (gimp_viewable_get_children (GIMP_VIEWABLE (drawable)))
|
|
{
|
|
g_set_error (error, GIMP_ERROR, GIMP_FAILED,
|
|
_("Cannot modify the pixels of layer groups."));
|
|
|
|
return FALSE;
|
|
}
|
|
else if (gimp_item_is_content_locked (GIMP_ITEM (drawable)))
|
|
{
|
|
g_set_error (error, GIMP_ERROR, GIMP_FAILED,
|
|
_("The active layer's pixels are locked."));
|
|
|
|
if (error)
|
|
gimp_tools_blink_lock_box (display->gimp, GIMP_ITEM (drawable));
|
|
|
|
return FALSE;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/* public functions */
|
|
|
|
gboolean
|
|
gimp_selection_tool_start_edit (GimpSelectionTool *sel_tool,
|
|
GimpDisplay *display,
|
|
const GimpCoords *coords)
|
|
{
|
|
GimpTool *tool;
|
|
GimpSelectionOptions *options;
|
|
GError *error = NULL;
|
|
|
|
g_return_val_if_fail (GIMP_IS_SELECTION_TOOL (sel_tool), FALSE);
|
|
g_return_val_if_fail (GIMP_IS_DISPLAY (display), FALSE);
|
|
g_return_val_if_fail (coords != NULL, FALSE);
|
|
|
|
tool = GIMP_TOOL (sel_tool);
|
|
options = GIMP_SELECTION_TOOL_GET_OPTIONS (sel_tool);
|
|
|
|
g_return_val_if_fail (gimp_tool_control_is_active (tool->control) == FALSE,
|
|
FALSE);
|
|
|
|
if (! gimp_selection_tool_check (sel_tool, display, &error))
|
|
{
|
|
gimp_tool_message_literal (tool, display, error->message);
|
|
|
|
gimp_widget_blink (options->mode_box);
|
|
|
|
g_clear_error (&error);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
switch (sel_tool->function)
|
|
{
|
|
case SELECTION_MOVE_MASK:
|
|
gimp_edit_selection_tool_start (tool, display, coords,
|
|
GIMP_TRANSLATE_MODE_MASK, FALSE);
|
|
return TRUE;
|
|
|
|
case SELECTION_MOVE:
|
|
case SELECTION_MOVE_COPY:
|
|
{
|
|
GimpTranslateMode edit_mode;
|
|
|
|
if (sel_tool->function == SELECTION_MOVE)
|
|
edit_mode = GIMP_TRANSLATE_MODE_MASK_TO_LAYER;
|
|
else
|
|
edit_mode = GIMP_TRANSLATE_MODE_MASK_COPY_TO_LAYER;
|
|
|
|
gimp_edit_selection_tool_start (tool, display, coords,
|
|
edit_mode, FALSE);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|