/* 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 <http://www.gnu.org/licenses/>.
 */

#include "config.h"

#include <string.h>

#include <gegl.h>
#include <gtk/gtk.h>

#include "libgimpmath/gimpmath.h"
#include "libgimpbase/gimpbase.h"
#include "libgimpwidgets/gimpwidgets.h"

#include "actions-types.h"

#include "config/gimpcoreconfig.h"

#include "core/gimp.h"
#include "core/gimpchannel.h"
#include "core/gimpcontext.h"
#include "core/gimpdrawable-fill.h"
#include "core/gimpgrouplayer.h"
#include "core/gimpimage.h"
#include "core/gimpimage-merge.h"
#include "core/gimpimage-undo.h"
#include "core/gimpimage-undo-push.h"
#include "core/gimpitemundo.h"
#include "core/gimplayer-floating-sel.h"
#include "core/gimplayer-new.h"
#include "core/gimppickable.h"
#include "core/gimppickable-auto-shrink.h"
#include "core/gimptoolinfo.h"
#include "core/gimpundostack.h"
#include "core/gimpprogress.h"

#include "text/gimptext.h"
#include "text/gimptext-vectors.h"
#include "text/gimptextlayer.h"

#include "vectors/gimpvectors-warp.h"

#include "widgets/gimpaction.h"
#include "widgets/gimpdock.h"
#include "widgets/gimphelp-ids.h"
#include "widgets/gimpprogressdialog.h"

#include "display/gimpdisplay.h"
#include "display/gimpdisplayshell.h"
#include "display/gimpimagewindow.h"

#include "tools/gimptexttool.h"
#include "tools/tool_manager.h"

#include "dialogs/layer-add-mask-dialog.h"
#include "dialogs/layer-options-dialog.h"
#include "dialogs/resize-dialog.h"
#include "dialogs/scale-dialog.h"

#include "actions.h"
#include "layers-commands.h"

#include "gimp-intl.h"


static const GimpLayerModeEffects layer_modes[] =
{
  GIMP_NORMAL_MODE,
  GIMP_DISSOLVE_MODE,
  GIMP_MULTIPLY_MODE,
  GIMP_DIVIDE_MODE,
  GIMP_SCREEN_MODE,
  GIMP_NEW_OVERLAY_MODE,
  GIMP_DODGE_MODE,
  GIMP_BURN_MODE,
  GIMP_HARDLIGHT_MODE,
  GIMP_SOFTLIGHT_MODE,
  GIMP_GRAIN_EXTRACT_MODE,
  GIMP_GRAIN_MERGE_MODE,
  GIMP_DIFFERENCE_MODE,
  GIMP_ADDITION_MODE,
  GIMP_SUBTRACT_MODE,
  GIMP_DARKEN_ONLY_MODE,
  GIMP_LIGHTEN_ONLY_MODE,
  GIMP_HUE_MODE,
  GIMP_SATURATION_MODE,
  GIMP_COLOR_MODE,
  GIMP_VALUE_MODE,
  GIMP_LCH_HUE_MODE,
  GIMP_LCH_CHROMA_MODE,
  GIMP_LCH_COLOR_MODE,
  GIMP_LCH_LIGHTNESS_MODE
};


/*  local function prototypes  */

static void   layers_new_layer_response    (GtkWidget             *widget,
                                            gint                   response_id,
                                            LayerOptionsDialog    *dialog);
static void   layers_edit_layer_response   (GtkWidget             *widget,
                                            gint                   response_id,
                                            LayerOptionsDialog    *dialog);
static void   layers_add_mask_response     (GtkWidget             *widget,
                                            gint                   response_id,
                                            LayerAddMaskDialog    *dialog);

static void   layers_scale_layer_callback  (GtkWidget             *dialog,
                                            GimpViewable          *viewable,
                                            gint                   width,
                                            gint                   height,
                                            GimpUnit               unit,
                                            GimpInterpolationType  interpolation,
                                            gdouble                xresolution,
                                            gdouble                yresolution,
                                            GimpUnit               resolution_unit,
                                            gpointer               data);
static void   layers_resize_layer_callback (GtkWidget             *dialog,
                                            GimpViewable          *viewable,
                                            gint                   width,
                                            gint                   height,
                                            GimpUnit               unit,
                                            gint                   offset_x,
                                            gint                   offset_y,
                                            GimpItemSet            unused,
                                            gboolean               unused2,
                                            gpointer               data);

static gint   layers_mode_index            (GimpLayerModeEffects   layer_mode);


/*  private variables  */

static GimpFillType           layer_fill_type     = GIMP_FILL_TRANSPARENT;
static gchar                 *layer_name          = NULL;
static GimpUnit               layer_resize_unit   = GIMP_UNIT_PIXEL;
static GimpUnit               layer_scale_unit    = GIMP_UNIT_PIXEL;
static GimpInterpolationType  layer_scale_interp  = -1;
static GimpAddMaskType        layer_add_mask_type = GIMP_ADD_MASK_WHITE;
static gboolean               layer_mask_invert   = FALSE;


/*  public functions  */

void
layers_text_tool_cmd_callback (GtkAction *action,
                               gpointer   data)
{
  GimpImage *image;
  GimpLayer *layer;
  GtkWidget *widget;
  GimpTool  *active_tool;
  return_if_no_layer (image, layer, data);
  return_if_no_widget (widget, data);

  if (! gimp_item_is_text_layer (GIMP_ITEM (layer)))
    {
      layers_edit_attributes_cmd_callback (action, data);
      return;
    }

  active_tool = tool_manager_get_active (image->gimp);

  if (! GIMP_IS_TEXT_TOOL (active_tool))
    {
      GimpToolInfo *tool_info = gimp_get_tool_info (image->gimp,
                                                    "gimp-text-tool");

      if (GIMP_IS_TOOL_INFO (tool_info))
        {
          gimp_context_set_tool (action_data_get_context (data), tool_info);
          active_tool = tool_manager_get_active (image->gimp);
        }
    }

  if (GIMP_IS_TEXT_TOOL (active_tool))
    gimp_text_tool_set_layer (GIMP_TEXT_TOOL (active_tool), layer);
}

void
layers_edit_attributes_cmd_callback (GtkAction *action,
                                     gpointer   data)
{
  LayerOptionsDialog *dialog;
  GimpImage          *image;
  GimpLayer          *layer;
  GtkWidget          *widget;
  return_if_no_layer (image, layer, data);
  return_if_no_widget (widget, data);

  dialog = layer_options_dialog_new (gimp_item_get_image (GIMP_ITEM (layer)),
                                     layer,
                                     action_data_get_context (data),
                                     widget,
                                     gimp_object_get_name (layer),
                                     layer_fill_type,
                                     _("Layer Attributes"),
                                     "gimp-layer-edit",
                                     "gtk-edit",
                                     _("Edit Layer Attributes"),
                                     GIMP_HELP_LAYER_EDIT);

  g_signal_connect (dialog->dialog, "response",
                    G_CALLBACK (layers_edit_layer_response),
                    dialog);

  gtk_widget_show (dialog->dialog);
}

void
layers_new_cmd_callback (GtkAction *action,
                         gpointer   data)
{
  LayerOptionsDialog *dialog;
  GimpImage          *image;
  GtkWidget          *widget;
  GimpLayer          *floating_sel;
  return_if_no_image (image, data);
  return_if_no_widget (widget, data);

  /*  If there is a floating selection, the new command transforms
   *  the current fs into a new layer
   */
  if ((floating_sel = gimp_image_get_floating_selection (image)))
    {
      GError *error = NULL;

      if (! floating_sel_to_layer (floating_sel, &error))
        {
          gimp_message_literal (image->gimp,
                                G_OBJECT (widget), GIMP_MESSAGE_WARNING,
                                error->message);
          g_clear_error (&error);
          return;
        }

      gimp_image_flush (image);
      return;
    }

  dialog = layer_options_dialog_new (image, NULL,
                                     action_data_get_context (data),
                                     widget,
                                     layer_name ? layer_name : _("Layer"),
                                     layer_fill_type,
                                     _("New Layer"),
                                     "gimp-layer-new",
                                     GIMP_STOCK_LAYER,
                                     _("Create a New Layer"),
                                     GIMP_HELP_LAYER_NEW);

  g_signal_connect (dialog->dialog, "response",
                    G_CALLBACK (layers_new_layer_response),
                    dialog);

  gtk_widget_show (dialog->dialog);
}

void
layers_new_last_vals_cmd_callback (GtkAction *action,
                                   gpointer   data)
{
  GimpImage            *image;
  GtkWidget            *widget;
  GimpLayer            *floating_sel;
  GimpLayer            *new_layer;
  gint                  width, height;
  gint                  off_x, off_y;
  gdouble               opacity;
  GimpLayerModeEffects  mode;
  return_if_no_image (image, data);
  return_if_no_widget (widget, data);

  /*  If there is a floating selection, the new command transforms
   *  the current fs into a new layer
   */
  if ((floating_sel = gimp_image_get_floating_selection (image)))
    {
      GError *error = NULL;

      if (! floating_sel_to_layer (floating_sel, &error))
        {
          gimp_message_literal (image->gimp, G_OBJECT (widget),
                                GIMP_MESSAGE_WARNING, error->message);
          g_clear_error (&error);
          return;
        }

      gimp_image_flush (image);
      return;
    }

  if (GIMP_IS_LAYER (GIMP_ACTION (action)->viewable))
    {
      GimpLayer *template = GIMP_LAYER (GIMP_ACTION (action)->viewable);

      gimp_item_get_offset (GIMP_ITEM (template), &off_x, &off_y);
      width   = gimp_item_get_width  (GIMP_ITEM (template));
      height  = gimp_item_get_height (GIMP_ITEM (template));
      opacity = gimp_layer_get_opacity (template);
      mode    = gimp_layer_get_mode (template);
    }
  else
    {
      width   = gimp_image_get_width (image);
      height  = gimp_image_get_height (image);
      off_x   = 0;
      off_y   = 0;
      opacity = 1.0;
      mode    = GIMP_NORMAL_MODE;
    }

  gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_EDIT_PASTE,
                               _("New Layer"));

  new_layer = gimp_layer_new (image, width, height,
                              gimp_image_get_layer_format (image, TRUE),
                              layer_name,
                              opacity, mode);

  gimp_drawable_fill (GIMP_DRAWABLE (new_layer),
                      action_data_get_context (data),
                      layer_fill_type);
  gimp_item_translate (GIMP_ITEM (new_layer), off_x, off_y, FALSE);

  gimp_image_add_layer (image, new_layer,
                        GIMP_IMAGE_ACTIVE_PARENT, -1, TRUE);

  gimp_image_undo_group_end (image);

  gimp_image_flush (image);
}

void
layers_new_from_visible_cmd_callback (GtkAction *action,
                                      gpointer   data)
{
  GimpImage    *image;
  GimpLayer    *layer;
  GimpPickable *pickable;
  return_if_no_image (image, data);

  pickable = GIMP_PICKABLE (image);

  gimp_pickable_flush (pickable);

  layer = gimp_layer_new_from_gegl_buffer (gimp_pickable_get_buffer (pickable),
                                           image,
                                           gimp_image_get_layer_format (image,
                                                                        TRUE),
                                           _("Visible"),
                                           GIMP_OPACITY_OPAQUE,
                                           GIMP_NORMAL_MODE,
                                           NULL /* same image */);

  gimp_image_add_layer (image, layer,
                        GIMP_IMAGE_ACTIVE_PARENT, -1, TRUE);

  gimp_image_flush (image);
}

void
layers_new_group_cmd_callback (GtkAction *action,
                               gpointer   data)
{
  GimpImage *image;
  GimpLayer *layer;
  return_if_no_image (image, data);

  layer = gimp_group_layer_new (image);

  gimp_image_add_layer (image, layer,
                        GIMP_IMAGE_ACTIVE_PARENT, -1, TRUE);

  gimp_image_flush (image);
}

void
layers_select_cmd_callback (GtkAction *action,
                            gint       value,
                            gpointer   data)
{
  GimpImage     *image;
  GimpLayer     *layer;
  GimpContainer *container;
  GimpLayer     *new_layer;
  return_if_no_image (image, data);

  layer = gimp_image_get_active_layer (image);

  if (layer)
    container = gimp_item_get_container (GIMP_ITEM (layer));
  else
    container = gimp_image_get_layers (image);

  new_layer = (GimpLayer *) action_select_object ((GimpActionSelectType) value,
                                                  container,
                                                  (GimpObject *) layer);

  if (new_layer && new_layer != layer)
    {
      gimp_image_set_active_layer (image, new_layer);
      gimp_image_flush (image);
    }
}

void
layers_raise_cmd_callback (GtkAction *action,
                           gpointer   data)
{
  GimpImage *image;
  GimpLayer *layer;
  return_if_no_layer (image, layer, data);

  gimp_image_raise_item (image, GIMP_ITEM (layer), NULL);
  gimp_image_flush (image);
}

void
layers_raise_to_top_cmd_callback (GtkAction *action,
                                  gpointer   data)
{
  GimpImage *image;
  GimpLayer *layer;
  return_if_no_layer (image, layer, data);

  gimp_image_raise_item_to_top (image, GIMP_ITEM (layer));
  gimp_image_flush (image);
}

void
layers_lower_cmd_callback (GtkAction *action,
                           gpointer   data)
{
  GimpImage *image;
  GimpLayer *layer;
  return_if_no_layer (image, layer, data);

  gimp_image_lower_item (image, GIMP_ITEM (layer), NULL);
  gimp_image_flush (image);
}

void
layers_lower_to_bottom_cmd_callback (GtkAction *action,
                                     gpointer   data)
{
  GimpImage *image;
  GimpLayer *layer;
  return_if_no_layer (image, layer, data);

  gimp_image_lower_item_to_bottom (image, GIMP_ITEM (layer));
  gimp_image_flush (image);
}

void
layers_duplicate_cmd_callback (GtkAction *action,
                               gpointer   data)
{
  GimpImage *image;
  GimpLayer *layer;
  GimpLayer *new_layer;
  return_if_no_layer (image, layer, data);

  new_layer = GIMP_LAYER (gimp_item_duplicate (GIMP_ITEM (layer),
                                               G_TYPE_FROM_INSTANCE (layer)));

  /*  use the actual parent here, not GIMP_IMAGE_ACTIVE_PARENT because
   *  the latter would add a duplicated group inside itself instead of
   *  above it
   */
  gimp_image_add_layer (image, new_layer,
                        gimp_layer_get_parent (layer), -1,
                        TRUE);

  gimp_image_flush (image);
}

void
layers_anchor_cmd_callback (GtkAction *action,
                            gpointer   data)
{
  GimpImage *image;
  GimpLayer *layer;
  return_if_no_layer (image, layer, data);

  if (gimp_layer_is_floating_sel (layer))
    {
      floating_sel_anchor (layer);
      gimp_image_flush (image);
    }
}

void
layers_merge_down_cmd_callback (GtkAction *action,
                                gpointer   data)
{
  GimpImage *image;
  GimpLayer *layer;
  return_if_no_layer (image, layer, data);

  gimp_image_merge_down (image, layer, action_data_get_context (data),
                         GIMP_EXPAND_AS_NECESSARY, NULL);
  gimp_image_flush (image);
}

void
layers_merge_group_cmd_callback (GtkAction *action,
                                 gpointer   data)
{
  GimpImage *image;
  GimpLayer *layer;
  return_if_no_layer (image, layer, data);

  gimp_image_merge_group_layer (image, GIMP_GROUP_LAYER (layer));
  gimp_image_flush (image);
}

void
layers_delete_cmd_callback (GtkAction *action,
                            gpointer   data)
{
  GimpImage *image;
  GimpLayer *layer;
  return_if_no_layer (image, layer, data);

  gimp_image_remove_layer (image, layer, TRUE, NULL);
  gimp_image_flush (image);
}

void
layers_text_discard_cmd_callback (GtkAction *action,
                                  gpointer   data)
{
  GimpImage *image;
  GimpLayer *layer;
  return_if_no_layer (image, layer, data);

  if (GIMP_IS_TEXT_LAYER (layer))
    gimp_text_layer_discard (GIMP_TEXT_LAYER (layer));
}

void
layers_text_to_vectors_cmd_callback (GtkAction *action,
                                     gpointer   data)
{
  GimpImage *image;
  GimpLayer *layer;
  return_if_no_layer (image, layer, data);

  if (GIMP_IS_TEXT_LAYER (layer))
    {
      GimpVectors *vectors;
      gint         x, y;

      vectors = gimp_text_vectors_new (image, GIMP_TEXT_LAYER (layer)->text);

      gimp_item_get_offset (GIMP_ITEM (layer), &x, &y);
      gimp_item_translate (GIMP_ITEM (vectors), x, y, FALSE);

      gimp_image_add_vectors (image, vectors,
                              GIMP_IMAGE_ACTIVE_PARENT, -1, TRUE);

      gimp_image_flush (image);
    }
}

void
layers_text_along_vectors_cmd_callback (GtkAction *action,
                                        gpointer   data)
{
  GimpImage   *image;
  GimpLayer   *layer;
  GimpVectors *vectors;
  return_if_no_layer (image, layer, data);
  return_if_no_vectors (image, vectors, data);

  if (GIMP_IS_TEXT_LAYER (layer))
    {
      GimpVectors *new_vectors;

      new_vectors = gimp_text_vectors_new (image, GIMP_TEXT_LAYER (layer)->text);

      gimp_vectors_warp_vectors (vectors, new_vectors,
                                 0.5 * gimp_item_get_height (GIMP_ITEM (layer)));

      gimp_item_set_visible (GIMP_ITEM (new_vectors), TRUE, FALSE);

      gimp_image_add_vectors (image, new_vectors,
                              GIMP_IMAGE_ACTIVE_PARENT, -1, TRUE);

      gimp_image_flush (image);
    }
}

void
layers_resize_cmd_callback (GtkAction *action,
                            gpointer   data)
{
  GimpDisplay *display = NULL;
  GimpImage   *image;
  GimpLayer   *layer;
  GtkWidget   *widget;
  GtkWidget   *dialog;
  return_if_no_layer (image, layer, data);
  return_if_no_widget (widget, data);

  if (GIMP_IS_IMAGE_WINDOW (data))
    display = action_data_get_display (data);

  if (layer_resize_unit != GIMP_UNIT_PERCENT && display)
    layer_resize_unit = gimp_display_get_shell (display)->unit;

  dialog = resize_dialog_new (GIMP_VIEWABLE (layer),
                              action_data_get_context (data),
                              _("Set Layer Boundary Size"), "gimp-layer-resize",
                              widget,
                              gimp_standard_help_func, GIMP_HELP_LAYER_RESIZE,
                              layer_resize_unit,
                              layers_resize_layer_callback,
                              action_data_get_context (data));

  gtk_widget_show (dialog);
}

void
layers_resize_to_image_cmd_callback (GtkAction *action,
                                     gpointer   data)
{
  GimpImage *image;
  GimpLayer *layer;
  return_if_no_layer (image, layer, data);

  gimp_layer_resize_to_image (layer, action_data_get_context (data));
  gimp_image_flush (image);
}

void
layers_scale_cmd_callback (GtkAction *action,
                           gpointer   data)
{
  GimpDisplay *display = NULL;
  GimpImage   *image;
  GimpLayer   *layer;
  GtkWidget   *widget;
  GtkWidget   *dialog;
  return_if_no_layer (image, layer, data);
  return_if_no_widget (widget, data);

  if (GIMP_IS_IMAGE_WINDOW (data))
    display = action_data_get_display (data);

  if (layer_scale_unit != GIMP_UNIT_PERCENT && display)
    layer_scale_unit = gimp_display_get_shell (display)->unit;

  if (layer_scale_interp == -1)
    layer_scale_interp = image->gimp->config->interpolation_type;

  dialog = scale_dialog_new (GIMP_VIEWABLE (layer),
                             action_data_get_context (data),
                             _("Scale Layer"), "gimp-layer-scale",
                             widget,
                             gimp_standard_help_func, GIMP_HELP_LAYER_SCALE,
                             layer_scale_unit,
                             layer_scale_interp,
                             layers_scale_layer_callback,
                             display);

  gtk_widget_show (dialog);
}

void
layers_crop_to_selection_cmd_callback (GtkAction *action,
                                       gpointer   data)
{
  GimpImage *image;
  GimpLayer *layer;
  GtkWidget *widget;
  gint       x, y;
  gint       width, height;
  gint       off_x, off_y;
  return_if_no_layer (image, layer, data);
  return_if_no_widget (widget, data);

  if (! gimp_item_bounds (GIMP_ITEM (gimp_image_get_mask (image)),
                          &x, &y, &width, &height))
    {
      gimp_message_literal (image->gimp,
                            G_OBJECT (widget), GIMP_MESSAGE_WARNING,
                            _("Cannot crop because the current selection is empty."));
      return;
    }

  gimp_item_get_offset (GIMP_ITEM (layer), &off_x, &off_y);
  off_x -= x;
  off_y -= y;

  gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_ITEM_RESIZE,
                               _("Crop Layer to Selection"));

  gimp_item_resize (GIMP_ITEM (layer), action_data_get_context (data),
                    width, height, off_x, off_y);

  gimp_image_undo_group_end (image);

  gimp_image_flush (image);
}

void
layers_crop_to_content_cmd_callback (GtkAction *action,
                                     gpointer   data)
{
  GimpImage *image;
  GimpLayer *layer;
  GtkWidget *widget;
  gint       x1, y1, x2, y2;
  return_if_no_layer (image, layer, data);
  return_if_no_widget (widget, data);

  switch (gimp_pickable_auto_shrink (GIMP_PICKABLE (layer),
                                     0, 0,
                                     gimp_item_get_width  (GIMP_ITEM (layer)),
                                     gimp_item_get_height (GIMP_ITEM (layer)),
                                     &x1, &y1, &x2, &y2))
    {
    case GIMP_AUTO_SHRINK_SHRINK:
      gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_ITEM_RESIZE,
                                   _("Crop Layer to Content"));

      gimp_item_resize (GIMP_ITEM (layer), action_data_get_context (data),
                        x2 - x1, y2 - y1, -x1, -y1);

      gimp_image_undo_group_end (image);
      gimp_image_flush (image);
      break;

    case GIMP_AUTO_SHRINK_EMPTY:
      gimp_message_literal (image->gimp,
                            G_OBJECT (widget), GIMP_MESSAGE_INFO,
                            _("Cannot crop because the active layer has no content."));
      break;

    case GIMP_AUTO_SHRINK_UNSHRINKABLE:
      gimp_message_literal (image->gimp,
                            G_OBJECT (widget), GIMP_MESSAGE_INFO,
                            _("Cannot crop because the active layer is already cropped to its content."));
      break;
    }
}

void
layers_mask_add_cmd_callback (GtkAction *action,
                              gpointer   data)
{
  LayerAddMaskDialog *dialog;
  GimpImage          *image;
  GimpLayer          *layer;
  GtkWidget          *widget;
  return_if_no_layer (image, layer, data);
  return_if_no_widget (widget, data);

  dialog = layer_add_mask_dialog_new (layer, action_data_get_context (data),
                                      widget,
                                      layer_add_mask_type, layer_mask_invert);

  g_signal_connect (dialog->dialog, "response",
                    G_CALLBACK (layers_add_mask_response),
                    dialog);

  gtk_widget_show (dialog->dialog);
}

void
layers_mask_apply_cmd_callback (GtkAction *action,
                                gint       value,
                                gpointer   data)
{
  GimpImage         *image;
  GimpLayer         *layer;
  GimpMaskApplyMode  mode;
  return_if_no_layer (image, layer, data);

  mode = (GimpMaskApplyMode) value;

  if (gimp_layer_get_mask (layer))
    {
      gimp_layer_apply_mask (layer, mode, TRUE);
      gimp_image_flush (image);
    }
}

void
layers_mask_edit_cmd_callback (GtkAction *action,
                               gpointer   data)
{
  GimpImage     *image;
  GimpLayer     *layer;
  GimpLayerMask *mask;
  return_if_no_layer (image, layer, data);

  mask = gimp_layer_get_mask (layer);

  if (mask)
    {
      gboolean active;

      active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));

      gimp_layer_set_edit_mask (layer, active);
      gimp_image_flush (image);
    }
}

void
layers_mask_show_cmd_callback (GtkAction *action,
                               gpointer   data)
{
  GimpImage     *image;
  GimpLayer     *layer;
  GimpLayerMask *mask;
  return_if_no_layer (image, layer, data);

  mask = gimp_layer_get_mask (layer);

  if (mask)
    {
      gboolean active;

      active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));

      gimp_layer_set_show_mask (layer, active, TRUE);
      gimp_image_flush (image);
    }
}

void
layers_mask_disable_cmd_callback (GtkAction *action,
                                  gpointer   data)
{
  GimpImage     *image;
  GimpLayer     *layer;
  GimpLayerMask *mask;
  return_if_no_layer (image, layer, data);

  mask = gimp_layer_get_mask (layer);

  if (mask)
    {
      gboolean active;

      active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));

      gimp_layer_set_apply_mask (layer, ! active, TRUE);
      gimp_image_flush (image);
    }
}

void
layers_mask_to_selection_cmd_callback (GtkAction *action,
                                       gint       value,
                                       gpointer   data)
{
  GimpImage     *image;
  GimpLayer     *layer;
  GimpLayerMask *mask;
  return_if_no_layer (image, layer, data);

  mask = gimp_layer_get_mask (layer);

  if (mask)
    {
      gimp_item_to_selection (GIMP_ITEM (mask),
                              (GimpChannelOps) value,
                              TRUE, FALSE, 0.0, 0.0);
      gimp_image_flush (image);
    }
}

void
layers_alpha_add_cmd_callback (GtkAction *action,
                               gpointer   data)
{
  GimpImage *image;
  GimpLayer *layer;
  return_if_no_layer (image, layer, data);

  if (! gimp_drawable_has_alpha (GIMP_DRAWABLE (layer)))
    {
      gimp_layer_add_alpha (layer);
      gimp_image_flush (image);
    }
}

void
layers_alpha_remove_cmd_callback (GtkAction *action,
                                  gpointer   data)
{
  GimpImage *image;
  GimpLayer *layer;
  return_if_no_layer (image, layer, data);

  if (gimp_drawable_has_alpha (GIMP_DRAWABLE (layer)))
    {
      gimp_layer_flatten (layer, action_data_get_context (data));
      gimp_image_flush (image);
    }
}

void
layers_alpha_to_selection_cmd_callback (GtkAction *action,
                                        gint       value,
                                        gpointer   data)
{
  GimpImage *image;
  GimpLayer *layer;
  return_if_no_layer (image, layer, data);

  gimp_item_to_selection (GIMP_ITEM (layer),
                          (GimpChannelOps) value,
                          TRUE, FALSE, 0.0, 0.0);
  gimp_image_flush (image);
}

void
layers_opacity_cmd_callback (GtkAction *action,
                             gint       value,
                             gpointer   data)
{
  GimpImage      *image;
  GimpLayer      *layer;
  gdouble         opacity;
  GimpUndo       *undo;
  gboolean        push_undo = TRUE;
  return_if_no_layer (image, layer, data);

  undo = gimp_image_undo_can_compress (image, GIMP_TYPE_ITEM_UNDO,
                                       GIMP_UNDO_LAYER_OPACITY);

  if (undo && GIMP_ITEM_UNDO (undo)->item == GIMP_ITEM (layer))
    push_undo = FALSE;

  opacity = action_select_value ((GimpActionSelectType) value,
                                 gimp_layer_get_opacity (layer),
                                 0.0, 1.0, 1.0,
                                 1.0 / 255.0, 0.01, 0.1, 0.0, FALSE);
  gimp_layer_set_opacity (layer, opacity, push_undo);
  gimp_image_flush (image);
}

void
layers_mode_cmd_callback (GtkAction *action,
                          gint       value,
                          gpointer   data)
{
  GimpImage            *image;
  GimpLayer            *layer;
  GimpLayerModeEffects  layer_mode;
  gint                  index;
  GimpUndo             *undo;
  gboolean              push_undo = TRUE;
  return_if_no_layer (image, layer, data);

  undo = gimp_image_undo_can_compress (image, GIMP_TYPE_ITEM_UNDO,
                                       GIMP_UNDO_LAYER_MODE);

  if (undo && GIMP_ITEM_UNDO (undo)->item == GIMP_ITEM (layer))
    push_undo = FALSE;

  layer_mode = gimp_layer_get_mode (layer);

  index = action_select_value ((GimpActionSelectType) value,
                               layers_mode_index (layer_mode),
                               0, G_N_ELEMENTS (layer_modes) - 1, 0,
                               0.0, 1.0, 1.0, 0.0, FALSE);
  gimp_layer_set_mode (layer, layer_modes[index], push_undo);
  gimp_image_flush (image);
}

void
layers_lock_alpha_cmd_callback (GtkAction *action,
                                gpointer   data)
{
  GimpImage *image;
  GimpLayer *layer;
  gboolean   lock_alpha;
  return_if_no_layer (image, layer, data);

  lock_alpha = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));

  if (lock_alpha != gimp_layer_get_lock_alpha (layer))
    {
      GimpUndo *undo;
      gboolean  push_undo = TRUE;

      undo = gimp_image_undo_can_compress (image, GIMP_TYPE_ITEM_UNDO,
                                           GIMP_UNDO_LAYER_LOCK_ALPHA);

      if (undo && GIMP_ITEM_UNDO (undo)->item == GIMP_ITEM (layer))
        push_undo = FALSE;

      gimp_layer_set_lock_alpha (layer, lock_alpha, push_undo);
      gimp_image_flush (image);
    }
}


/*  private functions  */

static void
layers_new_layer_response (GtkWidget          *widget,
                           gint                response_id,
                           LayerOptionsDialog *dialog)
{
  if (response_id == GTK_RESPONSE_OK)
    {
      GimpLayer *layer;

      if (layer_name)
        g_free (layer_name);

      layer_name =
        g_strdup (gtk_entry_get_text (GTK_ENTRY (dialog->name_entry)));

      layer_fill_type = dialog->fill_type;

      dialog->xsize =
        RINT (gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (dialog->size_se),
                                          0));
      dialog->ysize =
        RINT (gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (dialog->size_se),
                                          1));

      layer = gimp_layer_new (dialog->image,
                              dialog->xsize,
                              dialog->ysize,
                              gimp_image_get_layer_format (dialog->image, TRUE),
                              layer_name,
                              GIMP_OPACITY_OPAQUE, GIMP_NORMAL_MODE);

      if (layer)
        {
          gimp_drawable_fill (GIMP_DRAWABLE (layer),
                              dialog->context,
                              layer_fill_type);

          gimp_image_add_layer (dialog->image, layer,
                                GIMP_IMAGE_ACTIVE_PARENT, -1, TRUE);

          gimp_image_flush (dialog->image);
        }
      else
        {
          g_warning ("%s: could not allocate new layer", G_STRFUNC);
        }
    }

  gtk_widget_destroy (dialog->dialog);
}

static void
layers_edit_layer_response (GtkWidget          *widget,
                            gint                response_id,
                            LayerOptionsDialog *dialog)
{
  if (response_id == GTK_RESPONSE_OK)
    {
      GimpLayer   *layer = dialog->layer;
      const gchar *new_name;

      new_name = gtk_entry_get_text (GTK_ENTRY (dialog->name_entry));

      if (strcmp (new_name, gimp_object_get_name (layer)))
        {
          GError *error = NULL;

          if (gimp_item_rename (GIMP_ITEM (layer), new_name, &error))
            {
              gimp_image_flush (dialog->image);
            }
          else
            {
              gimp_message_literal (dialog->image->gimp,
                                    G_OBJECT (widget), GIMP_MESSAGE_WARNING,
                                    error->message);
              g_clear_error (&error);

              return;
            }
        }

      if (dialog->rename_toggle &&
          gimp_item_is_text_layer (GIMP_ITEM (layer)))
        {
          g_object_set (layer,
                        "auto-rename",
                        gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->rename_toggle)),
                        NULL);
        }
    }

  gtk_widget_destroy (dialog->dialog);
}

static void
layers_add_mask_response (GtkWidget          *widget,
                          gint                response_id,
                          LayerAddMaskDialog *dialog)
{
  if (response_id == GTK_RESPONSE_OK)
    {
      GimpLayer     *layer = dialog->layer;
      GimpImage     *image = gimp_item_get_image (GIMP_ITEM (layer));
      GimpLayerMask *mask;

      if (dialog->add_mask_type == GIMP_ADD_MASK_CHANNEL &&
          ! dialog->channel)
        {
          gimp_message_literal (image->gimp,
                                G_OBJECT (widget), GIMP_MESSAGE_WARNING,
                                _("Please select a channel first"));
          return;
        }

      layer_add_mask_type = dialog->add_mask_type;
      layer_mask_invert   = dialog->invert;

      gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_LAYER_ADD_MASK,
                                   _("Add Layer Mask"));

      mask = gimp_layer_create_mask (layer, layer_add_mask_type,
                                     dialog->channel);

      if (layer_mask_invert)
        gimp_channel_invert (GIMP_CHANNEL (mask), FALSE);

      gimp_layer_add_mask (layer, mask, TRUE, NULL);

      gimp_image_undo_group_end (image);

      gimp_image_flush (image);
    }

  gtk_widget_destroy (dialog->dialog);
}

static void
layers_scale_layer_callback (GtkWidget             *dialog,
                             GimpViewable          *viewable,
                             gint                   width,
                             gint                   height,
                             GimpUnit               unit,
                             GimpInterpolationType  interpolation,
                             gdouble                xresolution,    /* unused */
                             gdouble                yresolution,    /* unused */
                             GimpUnit               resolution_unit,/* unused */
                             gpointer               data)
{
  GimpDisplay *display = GIMP_DISPLAY (data);

  layer_scale_unit   = unit;
  layer_scale_interp = interpolation;

  if (width > 0 && height > 0)
    {
      GimpItem     *item = GIMP_ITEM (viewable);
      GimpProgress *progress;
      GtkWidget    *progress_dialog = NULL;

      gtk_widget_destroy (dialog);

      if (width  == gimp_item_get_width  (item) &&
          height == gimp_item_get_height (item))
        return;

      if (display)
        {
          progress = GIMP_PROGRESS (display);
        }
      else
        {
          progress_dialog = gimp_progress_dialog_new ();
          progress = GIMP_PROGRESS (progress_dialog);
        }

      progress = gimp_progress_start (progress, FALSE, _("Scaling"));

      gimp_item_scale_by_origin (item,
                                 width, height, interpolation,
                                 progress, TRUE);

      if (progress)
        gimp_progress_end (progress);

      if (progress_dialog)
        gtk_widget_destroy (progress_dialog);

      gimp_image_flush (gimp_item_get_image (item));
    }
  else
    {
      g_warning ("Scale Error: "
                 "Both width and height must be greater than zero.");
    }
}

static void
layers_resize_layer_callback (GtkWidget    *dialog,
                              GimpViewable *viewable,
                              gint          width,
                              gint          height,
                              GimpUnit      unit,
                              gint          offset_x,
                              gint          offset_y,
                              GimpItemSet   unused,
                              gboolean      unused2,
                              gpointer      data)
{
  GimpContext *context = GIMP_CONTEXT (data);

  layer_resize_unit = unit;

  if (width > 0 && height > 0)
    {
      GimpItem *item = GIMP_ITEM (viewable);

      gtk_widget_destroy (dialog);

      if (width  == gimp_item_get_width  (item) &&
          height == gimp_item_get_height (item))
        return;

      gimp_item_resize (item, context,
                        width, height, offset_x, offset_y);

      gimp_image_flush (gimp_item_get_image (item));
    }
  else
    {
      g_warning ("Resize Error: "
                 "Both width and height must be greater than zero.");
    }
}

static gint
layers_mode_index (GimpLayerModeEffects layer_mode)
{
  gint i = 0;

  while (i < (G_N_ELEMENTS (layer_modes) - 1) && layer_modes[i] != layer_mode)
    i++;

  return i;
}
