gimp/app/tools/gimpcroptool.c

755 lines
26 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/gimp.h"
#include "core/gimpimage.h"
#include "core/gimpimage-crop.h"
#include "core/gimpimage-undo.h"
#include "core/gimpitem.h"
#include "core/gimptoolinfo.h"
#include "widgets/gimphelp-ids.h"
#include "display/gimpdisplay.h"
#include "display/gimpdisplayshell.h"
#include "display/gimptoolrectangle.h"
#include "gimpcropoptions.h"
#include "gimpcroptool.h"
#include "gimprectangleoptions.h"
#include "gimptoolcontrol.h"
#include "gimptools-utils.h"
#include "gimp-intl.h"
static void gimp_crop_tool_constructed (GObject *object);
static void gimp_crop_tool_dispose (GObject *object);
static void gimp_crop_tool_control (GimpTool *tool,
GimpToolAction action,
GimpDisplay *display);
static void gimp_crop_tool_button_press (GimpTool *tool,
const GimpCoords *coords,
guint32 time,
GdkModifierType state,
GimpButtonPressType press_type,
GimpDisplay *display);
static void gimp_crop_tool_button_release (GimpTool *tool,
const GimpCoords *coords,
guint32 time,
GdkModifierType state,
GimpButtonReleaseType release_type,
GimpDisplay *display);
static void gimp_crop_tool_motion (GimpTool *tool,
const GimpCoords *coords,
guint32 time,
GdkModifierType state,
GimpDisplay *display);
static void gimp_crop_tool_options_notify (GimpTool *tool,
GimpToolOptions *options,
const GParamSpec *pspec);
static void gimp_crop_tool_rectangle_changed (GimpToolWidget *rectangle,
GimpCropTool *crop_tool);
static void gimp_crop_tool_rectangle_response (GimpToolWidget *rectangle,
gint response_id,
GimpCropTool *crop_tool);
static void gimp_crop_tool_rectangle_change_complete (GimpToolRectangle *rectangle,
GimpCropTool *crop_tool);
static void gimp_crop_tool_start (GimpCropTool *crop_tool,
GimpDisplay *display);
static void gimp_crop_tool_commit (GimpCropTool *crop_tool);
static void gimp_crop_tool_halt (GimpCropTool *crop_tool);
static void gimp_crop_tool_update_option_defaults (GimpCropTool *crop_tool,
gboolean ignore_pending);
static GimpRectangleConstraint
gimp_crop_tool_get_constraint (GimpCropTool *crop_tool);
static void gimp_crop_tool_image_changed (GimpCropTool *crop_tool,
GimpImage *image,
GimpContext *context);
static void gimp_crop_tool_image_size_changed (GimpCropTool *crop_tool);
static void gimp_crop_tool_image_selected_layers_changed (GimpCropTool *crop_tool);
static void gimp_crop_tool_layer_size_changed (GimpCropTool *crop_tool);
static void gimp_crop_tool_auto_shrink (GimpCropTool *crop_tool);
G_DEFINE_TYPE (GimpCropTool, gimp_crop_tool, GIMP_TYPE_DRAW_TOOL)
#define parent_class gimp_crop_tool_parent_class
/* public functions */
void
gimp_crop_tool_register (GimpToolRegisterCallback callback,
gpointer data)
{
(* callback) (GIMP_TYPE_CROP_TOOL,
GIMP_TYPE_CROP_OPTIONS,
gimp_crop_options_gui,
GIMP_CONTEXT_PROP_MASK_FOREGROUND |
GIMP_CONTEXT_PROP_MASK_BACKGROUND |
GIMP_CONTEXT_PROP_MASK_PATTERN,
"gimp-crop-tool",
_("Crop"),
_("Crop Tool: Remove edge areas from image or layer"),
N_("_Crop"), "<shift>C",
NULL, GIMP_HELP_TOOL_CROP,
GIMP_ICON_TOOL_CROP,
data);
}
static void
gimp_crop_tool_class_init (GimpCropToolClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
object_class->constructed = gimp_crop_tool_constructed;
object_class->dispose = gimp_crop_tool_dispose;
tool_class->control = gimp_crop_tool_control;
tool_class->button_press = gimp_crop_tool_button_press;
tool_class->button_release = gimp_crop_tool_button_release;
tool_class->motion = gimp_crop_tool_motion;
tool_class->options_notify = gimp_crop_tool_options_notify;
}
static void
gimp_crop_tool_init (GimpCropTool *crop_tool)
{
GimpTool *tool = GIMP_TOOL (crop_tool);
gimp_tool_control_set_wants_click (tool->control, TRUE);
gimp_tool_control_set_active_modifiers (tool->control,
GIMP_TOOL_ACTIVE_MODIFIERS_SEPARATE);
gimp_tool_control_set_precision (tool->control,
GIMP_CURSOR_PRECISION_PIXEL_BORDER);
gimp_tool_control_set_cursor (tool->control,
GIMP_CURSOR_CROSSHAIR_SMALL);
gimp_tool_control_set_tool_cursor (tool->control,
GIMP_TOOL_CURSOR_CROP);
gimp_draw_tool_set_default_status (GIMP_DRAW_TOOL (tool),
_("Click-Drag to draw a crop rectangle"));
}
static void
gimp_crop_tool_constructed (GObject *object)
{
GimpCropTool *crop_tool = GIMP_CROP_TOOL (object);
GimpContext *context;
GimpToolInfo *tool_info;
G_OBJECT_CLASS (parent_class)->constructed (object);
tool_info = GIMP_TOOL (crop_tool)->tool_info;
context = gimp_get_user_context (tool_info->gimp);
g_signal_connect_object (context, "image-changed",
G_CALLBACK (gimp_crop_tool_image_changed),
crop_tool,
G_CONNECT_SWAPPED);
/* Make sure we are connected to "size-changed" for the initial
* image.
*/
gimp_crop_tool_image_changed (crop_tool,
gimp_context_get_image (context),
context);
}
static void
gimp_crop_tool_dispose (GObject *object)
{
GimpCropTool *crop_tool = GIMP_CROP_TOOL (object);
/* Clean up current_image and current_layers. */
gimp_crop_tool_image_changed (crop_tool, NULL, NULL);
G_OBJECT_CLASS (parent_class)->dispose (object);
}
static void
gimp_crop_tool_control (GimpTool *tool,
GimpToolAction action,
GimpDisplay *display)
{
GimpCropTool *crop_tool = GIMP_CROP_TOOL (tool);
switch (action)
{
case GIMP_TOOL_ACTION_PAUSE:
case GIMP_TOOL_ACTION_RESUME:
break;
case GIMP_TOOL_ACTION_HALT:
gimp_crop_tool_halt (crop_tool);
break;
case GIMP_TOOL_ACTION_COMMIT:
gimp_crop_tool_commit (crop_tool);
break;
}
GIMP_TOOL_CLASS (parent_class)->control (tool, action, display);
}
static void
gimp_crop_tool_button_press (GimpTool *tool,
const GimpCoords *coords,
guint32 time,
GdkModifierType state,
GimpButtonPressType press_type,
GimpDisplay *display)
{
GimpCropTool *crop_tool = GIMP_CROP_TOOL (tool);
if (tool->display && display != tool->display)
gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, tool->display);
if (! tool->display)
{
gimp_crop_tool_start (crop_tool, display);
gimp_tool_widget_hover (crop_tool->widget, coords, state, TRUE);
/* HACK: force CREATING on a newly created rectangle; otherwise,
* property bindings would cause the rectangle to start with the
* size from tool options.
*/
gimp_tool_rectangle_set_function (GIMP_TOOL_RECTANGLE (crop_tool->widget),
GIMP_TOOL_RECTANGLE_CREATING);
}
if (gimp_tool_widget_button_press (crop_tool->widget, coords, time, state,
press_type))
{
crop_tool->grab_widget = crop_tool->widget;
}
gimp_tool_control_activate (tool->control);
}
static void
gimp_crop_tool_button_release (GimpTool *tool,
const GimpCoords *coords,
guint32 time,
GdkModifierType state,
GimpButtonReleaseType release_type,
GimpDisplay *display)
{
GimpCropTool *crop_tool = GIMP_CROP_TOOL (tool);
gimp_tool_control_halt (tool->control);
if (crop_tool->grab_widget)
{
gimp_tool_widget_button_release (crop_tool->grab_widget,
coords, time, state, release_type);
crop_tool->grab_widget = NULL;
}
gimp_tool_push_status (tool, display, _("Click or press Enter to crop"));
}
static void
gimp_crop_tool_motion (GimpTool *tool,
const GimpCoords *coords,
guint32 time,
GdkModifierType state,
GimpDisplay *display)
{
GimpCropTool *crop_tool = GIMP_CROP_TOOL (tool);
if (crop_tool->grab_widget)
{
gimp_tool_widget_motion (crop_tool->grab_widget, coords, time, state);
}
}
static void
gimp_crop_tool_options_notify (GimpTool *tool,
GimpToolOptions *options,
const GParamSpec *pspec)
{
GimpCropTool *crop_tool = GIMP_CROP_TOOL (tool);
if (! strcmp (pspec->name, "layer-only") ||
! strcmp (pspec->name, "allow-growing"))
{
if (crop_tool->widget)
{
gimp_tool_rectangle_set_constraint (GIMP_TOOL_RECTANGLE (crop_tool->widget),
gimp_crop_tool_get_constraint (crop_tool));
}
else
{
gimp_crop_tool_update_option_defaults (crop_tool, FALSE);
}
}
}
static void
gimp_crop_tool_rectangle_changed (GimpToolWidget *rectangle,
GimpCropTool *crop_tool)
{
}
static void
gimp_crop_tool_rectangle_response (GimpToolWidget *rectangle,
gint response_id,
GimpCropTool *crop_tool)
{
GimpTool *tool = GIMP_TOOL (crop_tool);
switch (response_id)
{
case GIMP_TOOL_WIDGET_RESPONSE_CONFIRM:
gimp_tool_control (tool, GIMP_TOOL_ACTION_COMMIT, tool->display);
break;
case GIMP_TOOL_WIDGET_RESPONSE_CANCEL:
gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, tool->display);
break;
}
}
static void
gimp_crop_tool_rectangle_change_complete (GimpToolRectangle *rectangle,
GimpCropTool *crop_tool)
{
gimp_crop_tool_update_option_defaults (crop_tool, FALSE);
}
static void
gimp_crop_tool_start (GimpCropTool *crop_tool,
GimpDisplay *display)
{
static const gchar *properties[] =
{
"highlight",
"highlight-opacity",
"guide",
"x",
"y",
"width",
"height",
"fixed-rule-active",
"fixed-rule",
"desired-fixed-width",
"desired-fixed-height",
"desired-fixed-size-width",
"desired-fixed-size-height",
"aspect-numerator",
"aspect-denominator",
"fixed-center"
};
GimpTool *tool = GIMP_TOOL (crop_tool);
GimpDisplayShell *shell = gimp_display_get_shell (display);
GimpCropOptions *options = GIMP_CROP_TOOL_GET_OPTIONS (crop_tool);
GimpToolWidget *widget;
gint i;
tool->display = display;
crop_tool->widget = widget = gimp_tool_rectangle_new (shell);
g_object_set (widget,
"status-title", _("Crop to: "),
NULL);
gimp_draw_tool_set_widget (GIMP_DRAW_TOOL (tool), widget);
for (i = 0; i < G_N_ELEMENTS (properties); i++)
{
GBinding *binding =
g_object_bind_property (G_OBJECT (options), properties[i],
G_OBJECT (widget), properties[i],
G_BINDING_SYNC_CREATE |
G_BINDING_BIDIRECTIONAL);
crop_tool->bindings = g_list_prepend (crop_tool->bindings, binding);
}
gimp_rectangle_options_connect (GIMP_RECTANGLE_OPTIONS (options),
gimp_display_get_image (shell->display),
G_CALLBACK (gimp_crop_tool_auto_shrink),
crop_tool);
gimp_tool_rectangle_set_constraint (GIMP_TOOL_RECTANGLE (widget),
gimp_crop_tool_get_constraint (crop_tool));
g_signal_connect (widget, "changed",
G_CALLBACK (gimp_crop_tool_rectangle_changed),
crop_tool);
g_signal_connect (widget, "response",
G_CALLBACK (gimp_crop_tool_rectangle_response),
crop_tool);
g_signal_connect (widget, "change-complete",
G_CALLBACK (gimp_crop_tool_rectangle_change_complete),
crop_tool);
gimp_draw_tool_start (GIMP_DRAW_TOOL (tool), display);
}
static void
gimp_crop_tool_commit (GimpCropTool *crop_tool)
{
GimpTool *tool = GIMP_TOOL (crop_tool);
if (tool->display)
{
GimpCropOptions *options = GIMP_CROP_TOOL_GET_OPTIONS (tool);
GimpImage *image = gimp_display_get_image (tool->display);
gdouble x, y;
gdouble x2, y2;
gint w, h;
gimp_tool_rectangle_get_public_rect (GIMP_TOOL_RECTANGLE (crop_tool->widget),
&x, &y, &x2, &y2);
w = x2 - x;
h = y2 - y;
gimp_tool_pop_status (tool, tool->display);
/* if rectangle exists, crop it */
if (w > 0 && h > 0)
{
if (options->layer_only)
{
GList *layers = gimp_image_get_selected_layers (image);
GList *iter;
gint off_x, off_y;
gchar *undo_text;
if (! layers)
{
gimp_tool_message_literal (tool, tool->display,
_("There are no selected layers to crop."));
return;
}
for (iter = layers; iter; iter = iter->next)
if (! gimp_item_is_content_locked (GIMP_ITEM (iter->data)))
break;
if (iter == NULL)
{
gimp_tool_message_literal (tool, tool->display,
_("All selected layers' pixels are locked."));
gimp_tools_blink_lock_box (tool->display->gimp, GIMP_ITEM (layers->data));
return;
}
undo_text = ngettext ("Resize Layer", "Resize %d layers",
g_list_length (layers));
undo_text = g_strdup_printf (undo_text, g_list_length (layers));
gimp_image_undo_group_start (image,
GIMP_UNDO_GROUP_IMAGE_CROP,
undo_text);
g_free (undo_text);
for (iter = layers; iter; iter = iter->next)
{
gimp_item_get_offset (GIMP_ITEM (iter->data), &off_x, &off_y);
off_x -= x;
off_y -= y;
gimp_item_resize (GIMP_ITEM (iter->data),
GIMP_CONTEXT (options), options->fill_type,
w, h, off_x, off_y);
}
gimp_image_undo_group_end (image);
}
else
{
gimp_image_crop (image,
GIMP_CONTEXT (options), GIMP_FILL_TRANSPARENT,
x, y, w, h, options->delete_pixels);
}
gimp_image_flush (image);
}
}
}
static void
gimp_crop_tool_halt (GimpCropTool *crop_tool)
{
GimpTool *tool = GIMP_TOOL (crop_tool);
GimpCropOptions *options = GIMP_CROP_TOOL_GET_OPTIONS (crop_tool);
if (tool->display)
{
GimpDisplayShell *shell = gimp_display_get_shell (tool->display);
gimp_display_shell_set_highlight (shell, NULL, 0.0);
gimp_rectangle_options_disconnect (GIMP_RECTANGLE_OPTIONS (options),
G_CALLBACK (gimp_crop_tool_auto_shrink),
crop_tool);
}
if (gimp_draw_tool_is_active (GIMP_DRAW_TOOL (tool)))
gimp_draw_tool_stop (GIMP_DRAW_TOOL (tool));
/* disconnect bindings manually so they are really gone *now*, we
* might be in the middle of a signal emission that keeps the
* widget and its bindings alive.
*/
g_list_free_full (crop_tool->bindings, (GDestroyNotify) g_object_unref);
crop_tool->bindings = NULL;
gimp_draw_tool_set_widget (GIMP_DRAW_TOOL (tool), NULL);
g_clear_object (&crop_tool->widget);
tool->display = NULL;
g_list_free (tool->drawables);
tool->drawables = NULL;
gimp_crop_tool_update_option_defaults (crop_tool, TRUE);
}
/**
* gimp_crop_tool_update_option_defaults:
* @crop_tool:
* @ignore_pending: %TRUE to ignore any pending crop rectangle.
*
* Sets the default Fixed: Aspect ratio and Fixed: Size option
* properties.
*/
static void
gimp_crop_tool_update_option_defaults (GimpCropTool *crop_tool,
gboolean ignore_pending)
{
GimpTool *tool = GIMP_TOOL (crop_tool);
GimpToolRectangle *rectangle = GIMP_TOOL_RECTANGLE (crop_tool->widget);
GimpRectangleOptions *options;
options = GIMP_RECTANGLE_OPTIONS (GIMP_TOOL_GET_OPTIONS (tool));
if (rectangle && ! ignore_pending)
{
/* There is a pending rectangle and we should not ignore it, so
* set default Fixed: Aspect ratio to the same as the current
* pending rectangle width/height.
*/
gimp_tool_rectangle_pending_size_set (rectangle,
G_OBJECT (options),
"default-aspect-numerator",
"default-aspect-denominator");
g_object_set (G_OBJECT (options),
"use-string-current", TRUE,
NULL);
}
else
{
/* There is no pending rectangle, set default Fixed: Aspect
* ratio to that of the current image/layer.
*/
if (! rectangle)
{
/* ugly hack: if we don't have a widget, construct a temporary one
* so that we can use it to call
* gimp_tool_rectangle_constraint_size_set().
*/
GimpContext *context = gimp_get_user_context (tool->tool_info->gimp);
GimpDisplay *display = gimp_context_get_display (context);
if (display)
{
GimpDisplayShell *shell = gimp_display_get_shell (display);
rectangle = GIMP_TOOL_RECTANGLE (gimp_tool_rectangle_new (shell));
gimp_tool_rectangle_set_constraint (
rectangle, gimp_crop_tool_get_constraint (crop_tool));
}
}
if (rectangle)
{
gimp_tool_rectangle_constraint_size_set (rectangle,
G_OBJECT (options),
"default-aspect-numerator",
"default-aspect-denominator");
if (! crop_tool->widget)
g_object_unref (rectangle);
}
g_object_set (G_OBJECT (options),
"use-string-current", FALSE,
NULL);
}
}
static GimpRectangleConstraint
gimp_crop_tool_get_constraint (GimpCropTool *crop_tool)
{
GimpCropOptions *crop_options = GIMP_CROP_TOOL_GET_OPTIONS (crop_tool);
if (crop_options->allow_growing)
{
return GIMP_RECTANGLE_CONSTRAIN_NONE;
}
else
{
return crop_options->layer_only ? GIMP_RECTANGLE_CONSTRAIN_DRAWABLE :
GIMP_RECTANGLE_CONSTRAIN_IMAGE;
}
}
static void
gimp_crop_tool_image_changed (GimpCropTool *crop_tool,
GimpImage *image,
GimpContext *context)
{
if (crop_tool->current_image)
{
g_signal_handlers_disconnect_by_func (crop_tool->current_image,
gimp_crop_tool_image_size_changed,
NULL);
g_signal_handlers_disconnect_by_func (crop_tool->current_image,
gimp_crop_tool_image_selected_layers_changed,
NULL);
g_object_remove_weak_pointer (G_OBJECT (crop_tool->current_image),
(gpointer) &crop_tool->current_image);
}
crop_tool->current_image = image;
if (crop_tool->current_image)
{
g_object_add_weak_pointer (G_OBJECT (crop_tool->current_image),
(gpointer) &crop_tool->current_image);
g_signal_connect_object (crop_tool->current_image, "size-changed",
G_CALLBACK (gimp_crop_tool_image_size_changed),
crop_tool,
G_CONNECT_SWAPPED);
g_signal_connect_object (crop_tool->current_image, "selected-layers-changed",
G_CALLBACK (gimp_crop_tool_image_selected_layers_changed),
crop_tool,
G_CONNECT_SWAPPED);
}
/* Make sure we are connected to "size-changed" for the initial
* layer.
*/
gimp_crop_tool_image_selected_layers_changed (crop_tool);
gimp_crop_tool_update_option_defaults (GIMP_CROP_TOOL (crop_tool), FALSE);
}
static void
gimp_crop_tool_image_size_changed (GimpCropTool *crop_tool)
{
gimp_crop_tool_update_option_defaults (crop_tool, FALSE);
}
static void
gimp_crop_tool_image_selected_layers_changed (GimpCropTool *crop_tool)
{
GList *iter;
if (crop_tool->current_layers)
{
for (iter = crop_tool->current_layers; iter; iter = iter->next)
{
if (iter->data)
{
g_signal_handlers_disconnect_by_func (iter->data,
gimp_crop_tool_layer_size_changed,
NULL);
g_object_remove_weak_pointer (G_OBJECT (iter->data),
(gpointer) &iter->data);
}
}
g_list_free (crop_tool->current_layers);
crop_tool->current_layers = NULL;
}
if (crop_tool->current_image)
{
crop_tool->current_layers = gimp_image_get_selected_layers (crop_tool->current_image);
crop_tool->current_layers = g_list_copy (crop_tool->current_layers);
}
else
{
crop_tool->current_layers = NULL;
}
if (crop_tool->current_layers)
{
for (iter = crop_tool->current_layers; iter; iter = iter->next)
{
g_object_add_weak_pointer (G_OBJECT (iter->data),
(gpointer) &iter->data);
g_signal_connect_object (iter->data, "size-changed",
G_CALLBACK (gimp_crop_tool_layer_size_changed),
crop_tool,
G_CONNECT_SWAPPED);
}
}
gimp_crop_tool_update_option_defaults (crop_tool, FALSE);
}
static void
gimp_crop_tool_layer_size_changed (GimpCropTool *crop_tool)
{
gimp_crop_tool_update_option_defaults (crop_tool, FALSE);
}
static void
gimp_crop_tool_auto_shrink (GimpCropTool *crop_tool)
{
gboolean shrink_merged ;
g_object_get (gimp_tool_get_options (GIMP_TOOL (crop_tool)),
"shrink-merged", &shrink_merged,
NULL);
gimp_tool_rectangle_auto_shrink (GIMP_TOOL_RECTANGLE (crop_tool->widget),
shrink_merged);
}