/* 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 "config/gimpguiconfig.h"

#include "core/gimp.h"
#include "core/gimpasync.h"
#include "core/gimpcancelable.h"
#include "core/gimpcontainer.h"
#include "core/gimpdrawable-bucket-fill.h"
#include "core/gimpdrawable-edit.h"
#include "core/gimpdrawablefilter.h"
#include "core/gimperror.h"
#include "core/gimpfilloptions.h"
#include "core/gimpimage.h"
#include "core/gimpimageproxy.h"
#include "core/gimpitem.h"
#include "core/gimplineart.h"
#include "core/gimppickable.h"
#include "core/gimppickable-contiguous-region.h"
#include "core/gimpprogress.h"
#include "core/gimpprojection.h"
#include "core/gimptoolinfo.h"
#include "core/gimpwaitable.h"

#include "gegl/gimp-gegl-nodes.h"

#include "operations/layer-modes/gimp-layer-modes.h"

#include "widgets/gimphelp-ids.h"
#include "widgets/gimpwidgets-utils.h"

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

#include "gimpbucketfilloptions.h"
#include "gimpbucketfilltool.h"
#include "gimpcoloroptions.h"
#include "gimptoolcontrol.h"
#include "gimptools-utils.h"

#include "gimp-intl.h"


struct _GimpBucketFillToolPrivate
{
  GimpLineArt        *line_art;
  GimpImage          *line_art_image;
  GimpDisplayShell   *line_art_shell;
  GList              *line_art_bindings;

  /* For preview */
  GeglNode           *graph;
  GeglNode           *fill_node;
  GeglNode           *offset_node;

  GeglBuffer         *fill_mask;

  GimpDrawableFilter *filter;

  /* Temp property save */
  GimpBucketFillMode  fill_mode;
  GimpBucketFillArea  fill_area;
};


/*  local function prototypes  */

static void     gimp_bucket_fill_tool_constructed      (GObject               *object);
static void     gimp_bucket_fill_tool_finalize         (GObject               *object);

static void     gimp_bucket_fill_tool_button_press     (GimpTool              *tool,
                                                        const GimpCoords      *coords,
                                                        guint32                time,
                                                        GdkModifierType        state,
                                                        GimpButtonPressType    press_type,
                                                        GimpDisplay           *display);
static void     gimp_bucket_fill_tool_motion           (GimpTool              *tool,
                                                        const GimpCoords      *coords,
                                                        guint32                time,
                                                        GdkModifierType        state,
                                                        GimpDisplay           *display);
static void     gimp_bucket_fill_tool_button_release   (GimpTool              *tool,
                                                        const GimpCoords      *coords,
                                                        guint32                time,
                                                        GdkModifierType        state,
                                                        GimpButtonReleaseType  release_type,
                                                        GimpDisplay           *display);
static void     gimp_bucket_fill_tool_modifier_key     (GimpTool              *tool,
                                                        GdkModifierType        key,
                                                        gboolean               press,
                                                        GdkModifierType        state,
                                                        GimpDisplay           *display);
static void     gimp_bucket_fill_tool_cursor_update    (GimpTool              *tool,
                                                        const GimpCoords      *coords,
                                                        GdkModifierType        state,
                                                        GimpDisplay           *display);
static void     gimp_bucket_fill_tool_options_notify   (GimpTool              *tool,
                                                        GimpToolOptions       *options,
                                                        const GParamSpec      *pspec);

static void gimp_bucket_fill_tool_line_art_computing_start (GimpBucketFillTool *tool);
static void gimp_bucket_fill_tool_line_art_computing_end   (GimpBucketFillTool *tool);

static gboolean gimp_bucket_fill_tool_coords_in_active_pickable
                                                       (GimpBucketFillTool    *tool,
                                                        GimpDisplay           *display,
                                                        const GimpCoords      *coords);

static void     gimp_bucket_fill_tool_start            (GimpBucketFillTool    *tool,
                                                        const GimpCoords      *coords,
                                                        GimpDisplay           *display);
static void     gimp_bucket_fill_tool_preview          (GimpBucketFillTool    *tool,
                                                        const GimpCoords      *coords,
                                                        GimpDisplay           *display,
                                                        GimpFillOptions       *fill_options);
static void     gimp_bucket_fill_tool_commit           (GimpBucketFillTool    *tool);
static void     gimp_bucket_fill_tool_halt             (GimpBucketFillTool    *tool);
static void     gimp_bucket_fill_tool_filter_flush     (GimpDrawableFilter    *filter,
                                                        GimpTool              *tool);
static void     gimp_bucket_fill_tool_create_graph     (GimpBucketFillTool    *tool);

static void     gimp_bucket_fill_tool_reset_line_art   (GimpBucketFillTool    *tool);


G_DEFINE_TYPE_WITH_PRIVATE (GimpBucketFillTool, gimp_bucket_fill_tool,
                            GIMP_TYPE_COLOR_TOOL)

#define parent_class gimp_bucket_fill_tool_parent_class


void
gimp_bucket_fill_tool_register (GimpToolRegisterCallback  callback,
                                gpointer                  data)
{
  (* callback) (GIMP_TYPE_BUCKET_FILL_TOOL,
                GIMP_TYPE_BUCKET_FILL_OPTIONS,
                gimp_bucket_fill_options_gui,
                GIMP_CONTEXT_PROP_MASK_FOREGROUND |
                GIMP_CONTEXT_PROP_MASK_BACKGROUND |
                GIMP_CONTEXT_PROP_MASK_OPACITY    |
                GIMP_CONTEXT_PROP_MASK_PAINT_MODE |
                GIMP_CONTEXT_PROP_MASK_PATTERN,
                "gimp-bucket-fill-tool",
                _("Bucket Fill"),
                _("Bucket Fill Tool: Fill selected area with a color or pattern"),
                N_("_Bucket Fill"), "<shift>B",
                NULL, GIMP_HELP_TOOL_BUCKET_FILL,
                GIMP_ICON_TOOL_BUCKET_FILL,
                data);
}

static void
gimp_bucket_fill_tool_class_init (GimpBucketFillToolClass *klass)
{
  GObjectClass  *object_class = G_OBJECT_CLASS (klass);
  GimpToolClass *tool_class   = GIMP_TOOL_CLASS (klass);

  object_class->constructed  = gimp_bucket_fill_tool_constructed;
  object_class->finalize     = gimp_bucket_fill_tool_finalize;

  tool_class->button_press   = gimp_bucket_fill_tool_button_press;
  tool_class->motion         = gimp_bucket_fill_tool_motion;
  tool_class->button_release = gimp_bucket_fill_tool_button_release;
  tool_class->modifier_key   = gimp_bucket_fill_tool_modifier_key;
  tool_class->cursor_update  = gimp_bucket_fill_tool_cursor_update;
  tool_class->options_notify = gimp_bucket_fill_tool_options_notify;
}

static void
gimp_bucket_fill_tool_init (GimpBucketFillTool *bucket_fill_tool)
{
  GimpTool *tool = GIMP_TOOL (bucket_fill_tool);

  gimp_tool_control_set_scroll_lock     (tool->control, TRUE);
  gimp_tool_control_set_wants_click     (tool->control, TRUE);
  gimp_tool_control_set_tool_cursor     (tool->control,
                                         GIMP_TOOL_CURSOR_BUCKET_FILL);
  gimp_tool_control_set_action_opacity  (tool->control,
                                         "context-opacity-set");
  gimp_tool_control_set_action_object_1 (tool->control,
                                         "context-pattern-select-set");

  bucket_fill_tool->priv =
    gimp_bucket_fill_tool_get_instance_private (bucket_fill_tool);
}

static void
gimp_bucket_fill_tool_constructed (GObject *object)
{
  GimpTool              *tool        = GIMP_TOOL (object);
  GimpBucketFillTool    *bucket_tool = GIMP_BUCKET_FILL_TOOL (object);
  GimpBucketFillOptions *options     = GIMP_BUCKET_FILL_TOOL_GET_OPTIONS (tool);
  Gimp                  *gimp        = GIMP_CONTEXT (options)->gimp;
  GimpContext           *context     = gimp_get_user_context (gimp);
  GList                 *bindings    = NULL;
  GBinding              *binding;
  GimpLineArt           *line_art;

  G_OBJECT_CLASS (parent_class)->constructed (object);

  gimp_tool_control_set_motion_mode (tool->control,
                                     options->fill_area == GIMP_BUCKET_FILL_LINE_ART ?
                                     GIMP_MOTION_MODE_EXACT : GIMP_MOTION_MODE_COMPRESS);

  line_art = gimp_context_take_line_art (context);
  binding = g_object_bind_property (options,  "fill-transparent",
                                    line_art, "select-transparent",
                                    G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
  bindings = g_list_prepend (bindings, binding);
  binding = g_object_bind_property (options,  "line-art-threshold",
                                    line_art, "threshold",
                                    G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
  bindings = g_list_prepend (bindings, binding);
  binding = g_object_bind_property (options,  "line-art-max-grow",
                                    line_art, "max-grow",
                                    G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
  bindings = g_list_prepend (bindings, binding);
  binding = g_object_bind_property (options,  "line-art-automatic-closure",
                                    line_art, "automatic-closure",
                                    G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
  bindings = g_list_prepend (bindings, binding);
  binding = g_object_bind_property (options,  "line-art-max-gap-length",
                                    line_art, "spline-max-length",
                                    G_BINDING_SYNC_CREATE | G_BINDING_DEFAULT);
  bindings = g_list_prepend (bindings, binding);
  binding = g_object_bind_property (options,  "line-art-max-gap-length",
                                    line_art, "segment-max-length",
                                    G_BINDING_SYNC_CREATE | G_BINDING_DEFAULT);
  bindings = g_list_prepend (bindings, binding);
  g_signal_connect_swapped (line_art, "computing-start",
                            G_CALLBACK (gimp_bucket_fill_tool_line_art_computing_start),
                            tool);
  g_signal_connect_swapped (line_art, "computing-end",
                            G_CALLBACK (gimp_bucket_fill_tool_line_art_computing_end),
                            tool);
  gimp_line_art_bind_gap_length (line_art, TRUE);
  bucket_tool->priv->line_art = line_art;
  bucket_tool->priv->line_art_bindings = bindings;

  gimp_bucket_fill_tool_reset_line_art (bucket_tool);

  g_signal_connect_swapped (options, "notify::line-art-source",
                            G_CALLBACK (gimp_bucket_fill_tool_reset_line_art),
                            tool);
  g_signal_connect_swapped (context, "display-changed",
                            G_CALLBACK (gimp_bucket_fill_tool_reset_line_art),
                            tool);

  GIMP_COLOR_TOOL (tool)->pick_target =
    (options->fill_mode == GIMP_BUCKET_FILL_BG) ?
    GIMP_COLOR_PICK_TARGET_BACKGROUND : GIMP_COLOR_PICK_TARGET_FOREGROUND;
}

static void
gimp_bucket_fill_tool_finalize (GObject *object)
{
  GimpBucketFillTool    *tool     = GIMP_BUCKET_FILL_TOOL (object);
  GimpBucketFillOptions *options  = GIMP_BUCKET_FILL_TOOL_GET_OPTIONS (tool);
  Gimp                  *gimp     = GIMP_CONTEXT (options)->gimp;
  GimpContext           *context  = gimp_get_user_context (gimp);

  if (tool->priv->line_art_image)
    {
      g_signal_handlers_disconnect_by_data (gimp_image_get_layers (tool->priv->line_art_image), tool);
      g_signal_handlers_disconnect_by_data (tool->priv->line_art_image, tool);
      tool->priv->line_art_image = NULL;
    }
  if (tool->priv->line_art_shell)
    {
      g_signal_handlers_disconnect_by_func (
        tool->priv->line_art_shell,
        gimp_bucket_fill_tool_reset_line_art,
        tool);
      tool->priv->line_art_shell = NULL;
    }

  /* We don't free the line art object, but gives temporary ownership to
   * the user context which will free it if a timer runs out.
   *
   * This way, we allow people to not suffer a new computational delay
   * if for instance they just needed to switch tools for a few seconds
   * while the source layer stays the same.
   */
  g_signal_handlers_disconnect_by_data (tool->priv->line_art, tool);
  g_list_free_full (tool->priv->line_art_bindings,
                    (GDestroyNotify) g_binding_unbind);
  gimp_context_store_line_art (context, tool->priv->line_art);
  tool->priv->line_art = NULL;

  g_signal_handlers_disconnect_by_data (options, tool);
  g_signal_handlers_disconnect_by_data (context, tool);

  G_OBJECT_CLASS (parent_class)->finalize (object);
}

static gboolean
gimp_bucket_fill_tool_coords_in_active_pickable (GimpBucketFillTool *tool,
                                                 GimpDisplay        *display,
                                                 const GimpCoords   *coords)
{
  GimpBucketFillOptions *options       = GIMP_BUCKET_FILL_TOOL_GET_OPTIONS (tool);
  GimpDisplayShell      *shell         = gimp_display_get_shell (display);
  GimpImage             *image         = gimp_display_get_image (display);
  gboolean               sample_merged = FALSE;

  switch (options->fill_area)
    {
    case GIMP_BUCKET_FILL_SELECTION:
      break;

    case GIMP_BUCKET_FILL_SIMILAR_COLORS:
      sample_merged = options->sample_merged;
      break;

    case GIMP_BUCKET_FILL_LINE_ART:
      sample_merged = options->line_art_source ==
                      GIMP_LINE_ART_SOURCE_SAMPLE_MERGED;
      break;
    }

  return gimp_image_coords_in_active_pickable (image, coords,
                                               shell->show_all, sample_merged,
                                               TRUE);
}

static void
gimp_bucket_fill_tool_start (GimpBucketFillTool *tool,
                             const GimpCoords   *coords,
                             GimpDisplay        *display)
{
  GimpBucketFillOptions *options   = GIMP_BUCKET_FILL_TOOL_GET_OPTIONS (tool);
  GimpContext           *context   = GIMP_CONTEXT (options);
  GimpImage             *image     = gimp_display_get_image (display);
  GList                 *drawables = gimp_image_get_selected_drawables (image);

  g_return_if_fail (! tool->priv->filter);
  g_return_if_fail (g_list_length (drawables) == 1);

  gimp_line_art_freeze (tool->priv->line_art);

  GIMP_TOOL (tool)->display  = display;
  g_list_free (GIMP_TOOL (tool)->drawables);
  GIMP_TOOL (tool)->drawables = drawables;

  gimp_bucket_fill_tool_create_graph (tool);

  tool->priv->filter = gimp_drawable_filter_new (drawables->data, _("Bucket fill"),
                                                 tool->priv->graph,
                                                 GIMP_ICON_TOOL_BUCKET_FILL);
  g_object_set (tool->priv->filter, "temporary", TRUE, NULL);

  gimp_drawable_filter_set_region (tool->priv->filter,
                                   GIMP_FILTER_REGION_DRAWABLE);

  /* We only set these here, and don't need to update it since we assume
   * the settings can't change while the fill started.
   */
  gimp_drawable_filter_set_mode (tool->priv->filter,
                                 gimp_context_get_paint_mode (context),
                                 GIMP_LAYER_COLOR_SPACE_AUTO,
                                 GIMP_LAYER_COLOR_SPACE_AUTO,
                                 gimp_layer_mode_get_paint_composite_mode (gimp_context_get_paint_mode (context)));
  gimp_drawable_filter_set_opacity (tool->priv->filter,
                                    gimp_context_get_opacity (context));

  g_signal_connect (tool->priv->filter, "flush",
                    G_CALLBACK (gimp_bucket_fill_tool_filter_flush),
                    tool);
}

static void
gimp_bucket_fill_tool_preview (GimpBucketFillTool *tool,
                               const GimpCoords   *coords,
                               GimpDisplay        *display,
                               GimpFillOptions    *fill_options)
{
  GimpBucketFillOptions *options   = GIMP_BUCKET_FILL_TOOL_GET_OPTIONS (tool);
  GimpDisplayShell      *shell     = gimp_display_get_shell (display);
  GimpImage             *image     = gimp_display_get_image (display);
  GList                 *drawables = gimp_image_get_selected_drawables (image);
  GimpDrawable          *drawable;

  g_return_if_fail (g_list_length (drawables) == 1);

  drawable = drawables->data;
  g_list_free (drawables);

  if (tool->priv->filter)
    {
      GeglBuffer *fill     = NULL;
      gdouble     x        = coords->x;
      gdouble     y        = coords->y;

      if (options->fill_area == GIMP_BUCKET_FILL_SIMILAR_COLORS)
        {
          if (! options->sample_merged)
            {
              gint off_x, off_y;

              gimp_item_get_offset (GIMP_ITEM (drawable), &off_x, &off_y);

              x -= (gdouble) off_x;
              y -= (gdouble) off_y;
            }
          fill = gimp_drawable_get_bucket_fill_buffer (drawable,
                                                       fill_options,
                                                       options->fill_transparent,
                                                       options->fill_criterion,
                                                       options->threshold / 255.0,
                                                       shell->show_all,
                                                       options->sample_merged,
                                                       options->diagonal_neighbors,
                                                       x, y,
                                                       &tool->priv->fill_mask,
                                                       &x, &y, NULL, NULL);
        }
      else
        {
          gint source_off_x = 0;
          gint source_off_y = 0;

          if (options->line_art_source != GIMP_LINE_ART_SOURCE_SAMPLE_MERGED)
            {
              GimpPickable *input;

              input = gimp_line_art_get_input (tool->priv->line_art);
              g_return_if_fail (GIMP_IS_ITEM (input));

              gimp_item_get_offset (GIMP_ITEM (input), &source_off_x, &source_off_y);

              x -= (gdouble) source_off_x;
              y -= (gdouble) source_off_y;
            }
          fill = gimp_drawable_get_line_art_fill_buffer (drawable,
                                                         tool->priv->line_art,
                                                         fill_options,
                                                         options->line_art_source ==
                                                         GIMP_LINE_ART_SOURCE_SAMPLE_MERGED,
                                                         options->fill_as_line_art &&
                                                         options->fill_mode != GIMP_BUCKET_FILL_PATTERN,
                                                         options->fill_as_line_art_threshold / 255.0,
                                                         options->line_art_stroke,
                                                         options->stroke_options,
                                                         x, y,
                                                         &tool->priv->fill_mask,
                                                         &x, &y, NULL, NULL);
          if (options->line_art_source != GIMP_LINE_ART_SOURCE_SAMPLE_MERGED)
            {
              gint off_x, off_y;

              gimp_item_get_offset (GIMP_ITEM (drawable), &off_x, &off_y);

              x -= (gdouble) off_x - (gdouble) source_off_x;
              y -= (gdouble) off_y - (gdouble) source_off_y;
            }
        }
      if (fill)
        {
          gegl_node_set (tool->priv->fill_node,
                         "buffer", fill,
                         NULL);
          gegl_node_set (tool->priv->offset_node,
                         "x", x,
                         "y", y,
                         NULL);
          gimp_drawable_filter_apply (tool->priv->filter, NULL);
          g_object_unref (fill);
        }
    }
}

static void
gimp_bucket_fill_tool_commit (GimpBucketFillTool *tool)
{
  if (tool->priv->filter)
    {
      gimp_drawable_filter_commit (tool->priv->filter, FALSE,
                                   GIMP_PROGRESS (tool), FALSE);
      gimp_image_flush (gimp_display_get_image (GIMP_TOOL (tool)->display));
    }
}

static void
gimp_bucket_fill_tool_halt (GimpBucketFillTool *tool)
{
  if (tool->priv->graph)
    {
      g_clear_object (&tool->priv->graph);
      tool->priv->fill_node   = NULL;
      tool->priv->offset_node = NULL;
    }

  if (tool->priv->filter)
    {
      gimp_drawable_filter_abort (tool->priv->filter);
      g_clear_object (&tool->priv->filter);
    }

  g_clear_object (&tool->priv->fill_mask);

  if (gimp_line_art_is_frozen (tool->priv->line_art))
    gimp_line_art_thaw (tool->priv->line_art);

  GIMP_TOOL (tool)->display   = NULL;
  g_list_free (GIMP_TOOL (tool)->drawables);
  GIMP_TOOL (tool)->drawables = NULL;
}

static void
gimp_bucket_fill_tool_filter_flush (GimpDrawableFilter *filter,
                                    GimpTool           *tool)
{
  GimpImage *image = gimp_display_get_image (tool->display);

  gimp_projection_flush (gimp_image_get_projection (image));
}

static void
gimp_bucket_fill_tool_create_graph (GimpBucketFillTool *tool)
{
  GeglNode *graph;
  GeglNode *output;
  GeglNode *fill_node;
  GeglNode *offset_node;

  g_return_if_fail (! tool->priv->graph     &&
                    ! tool->priv->fill_node &&
                    ! tool->priv->offset_node);

  graph = gegl_node_new ();

  fill_node = gegl_node_new_child (graph,
                                   "operation", "gegl:buffer-source",
                                   NULL);
  offset_node = gegl_node_new_child (graph,
                                     "operation", "gegl:translate",
                                     NULL);
  output = gegl_node_get_output_proxy (graph, "output");
  gegl_node_link_many (fill_node, offset_node, output, NULL);

  tool->priv->graph       = graph;
  tool->priv->fill_node   = fill_node;
  tool->priv->offset_node = offset_node;
}

static void
gimp_bucket_fill_tool_button_press (GimpTool            *tool,
                                    const GimpCoords    *coords,
                                    guint32              time,
                                    GdkModifierType      state,
                                    GimpButtonPressType  press_type,
                                    GimpDisplay         *display)
{
  GimpBucketFillTool    *bucket_tool = GIMP_BUCKET_FILL_TOOL (tool);
  GimpBucketFillOptions *options     = GIMP_BUCKET_FILL_TOOL_GET_OPTIONS (tool);
  GimpGuiConfig         *config      = GIMP_GUI_CONFIG (display->gimp->config);
  GimpImage             *image       = gimp_display_get_image (display);
  GimpItem              *locked_item = NULL;
  GList                 *drawables   = gimp_image_get_selected_drawables (image);
  GimpDrawable          *drawable;

  if (gimp_color_tool_is_enabled (GIMP_COLOR_TOOL (tool)))
    {
      GIMP_TOOL_CLASS (parent_class)->button_press (tool, coords, time, state,
                                                    press_type, display);
      return;
    }

  if (g_list_length (drawables) != 1)
    {
      if (g_list_length (drawables) > 1)
        gimp_tool_message_literal (tool, display,
                                   _("Cannot fill multiple layers. Select only one layer."));
      else
        gimp_tool_message_literal (tool, display, _("No selected drawables."));

      g_list_free (drawables);
      return;
    }

  drawable = drawables->data;
  g_list_free (drawables);

  if (gimp_viewable_get_children (GIMP_VIEWABLE (drawable)))
    {
      gimp_tool_message_literal (tool, display,
                                 _("Cannot modify the pixels of layer groups."));
      return;
    }

  if (! gimp_item_is_visible (GIMP_ITEM (drawable)) &&
      ! config->edit_non_visible)
    {
      gimp_tool_message_literal (tool, display,
                                 _("The active layer is not visible."));
      return;
    }

  if (gimp_item_is_content_locked (GIMP_ITEM (drawable), &locked_item))
    {
      gimp_tool_message_literal (tool, display,
                                 _("The selected layer's pixels are locked."));
      gimp_tools_blink_lock_box (display->gimp, locked_item);
      return;
    }

  if (options->fill_area == GIMP_BUCKET_FILL_LINE_ART &&
      ! gimp_line_art_get_input (bucket_tool->priv->line_art))
    {
      gimp_tool_message_literal (tool, display,
                                 _("No valid line art source selected."));
      gimp_blink_dockable (display->gimp, "gimp-tool-options", "line-art-source", NULL, NULL);
      return;
    }

  if (press_type == GIMP_BUTTON_PRESS_NORMAL &&
      gimp_bucket_fill_tool_coords_in_active_pickable (bucket_tool,
                                                       display, coords))
    {
      GimpContext     *context  = GIMP_CONTEXT (options);
      GimpFillOptions *fill_options;
      GError          *error = NULL;

      fill_options = gimp_fill_options_new (image->gimp, NULL, FALSE);

      if (gimp_fill_options_set_by_fill_mode (fill_options, context,
                                              options->fill_mode,
                                              &error))
        {
          gimp_fill_options_set_antialias (fill_options, options->antialias);
          gimp_fill_options_set_feather (fill_options, options->feather,
                                         options->feather_radius);

          gimp_context_set_opacity (GIMP_CONTEXT (fill_options),
                                    gimp_context_get_opacity (context));
          gimp_context_set_paint_mode (GIMP_CONTEXT (fill_options),
                                       gimp_context_get_paint_mode (context));

          if (options->fill_area == GIMP_BUCKET_FILL_SELECTION)
            {
              gimp_drawable_edit_fill (drawable, fill_options, NULL);
              gimp_image_flush (image);
            }
          else /* GIMP_BUCKET_FILL_SIMILAR_COLORS || GIMP_BUCKET_FILL_LINE_ART */
            {
              gimp_bucket_fill_tool_start (bucket_tool, coords, display);
              gimp_bucket_fill_tool_preview (bucket_tool, coords, display,
                                             fill_options);
            }
        }
      else
        {
          gimp_message_literal (display->gimp, G_OBJECT (display),
                                GIMP_MESSAGE_WARNING, error->message);
          g_clear_error (&error);
        }

      g_object_unref (fill_options);
    }

  GIMP_TOOL_CLASS (parent_class)->button_press (tool, coords, time, state,
                                                press_type, display);
}

static void
gimp_bucket_fill_tool_motion (GimpTool         *tool,
                              const GimpCoords *coords,
                              guint32           time,
                              GdkModifierType   state,
                              GimpDisplay      *display)
{
  GimpBucketFillTool    *bucket_tool = GIMP_BUCKET_FILL_TOOL (tool);
  GimpBucketFillOptions *options     = GIMP_BUCKET_FILL_TOOL_GET_OPTIONS (tool);
  GimpImage             *image       = gimp_display_get_image (display);

  GIMP_TOOL_CLASS (parent_class)->motion (tool, coords, time, state, display);

  if (gimp_color_tool_is_enabled (GIMP_COLOR_TOOL (tool)))
    return;

  if (gimp_bucket_fill_tool_coords_in_active_pickable (bucket_tool,
                                                       display, coords) &&
      /* Fill selection only needs to happen once. */
      options->fill_area != GIMP_BUCKET_FILL_SELECTION)
    {
      GimpContext     *context  = GIMP_CONTEXT (options);
      GimpFillOptions *fill_options;
      GError          *error = NULL;

      fill_options = gimp_fill_options_new (image->gimp, NULL, FALSE);

      if (gimp_fill_options_set_by_fill_mode (fill_options, context,
                                              options->fill_mode,
                                              &error))
        {
          gimp_fill_options_set_antialias (fill_options, options->antialias);
          gimp_fill_options_set_feather (fill_options, options->feather,
                                         options->feather_radius);

          gimp_context_set_opacity (GIMP_CONTEXT (fill_options),
                                    gimp_context_get_opacity (context));
          gimp_context_set_paint_mode (GIMP_CONTEXT (fill_options),
                                       gimp_context_get_paint_mode (context));

          gimp_bucket_fill_tool_preview (bucket_tool, coords, display,
                                         fill_options);
        }
      else
        {
          gimp_message_literal (display->gimp, G_OBJECT (display),
                                GIMP_MESSAGE_WARNING, error->message);
          g_clear_error (&error);
        }

      g_object_unref (fill_options);
    }
}

static void
gimp_bucket_fill_tool_button_release (GimpTool              *tool,
                                      const GimpCoords      *coords,
                                      guint32                time,
                                      GdkModifierType        state,
                                      GimpButtonReleaseType  release_type,
                                      GimpDisplay           *display)
{
  GimpBucketFillTool    *bucket_tool = GIMP_BUCKET_FILL_TOOL (tool);
  GimpBucketFillOptions *options = GIMP_BUCKET_FILL_TOOL_GET_OPTIONS (tool);

  if (gimp_color_tool_is_enabled (GIMP_COLOR_TOOL (tool)))
    {
      GIMP_TOOL_CLASS (parent_class)->button_release (tool, coords, time,
                                                      state, release_type,
                                                      display);
      return;
    }

  if (release_type != GIMP_BUTTON_RELEASE_CANCEL)
    gimp_bucket_fill_tool_commit (bucket_tool);

  if (options->fill_area != GIMP_BUCKET_FILL_SELECTION)
    gimp_bucket_fill_tool_halt (bucket_tool);

  GIMP_TOOL_CLASS (parent_class)->button_release (tool, coords, time, state,
                                                  release_type, display);
}

static void
gimp_bucket_fill_tool_modifier_key (GimpTool        *tool,
                                    GdkModifierType  key,
                                    gboolean         press,
                                    GdkModifierType  state,
                                    GimpDisplay     *display)
{
  GimpBucketFillOptions *options = GIMP_BUCKET_FILL_TOOL_GET_OPTIONS (tool);

  if (key == GDK_MOD1_MASK)
    {
      if (press)
        {
          GIMP_BUCKET_FILL_TOOL (tool)->priv->fill_mode = options->fill_mode;

          switch (options->fill_mode)
            {
            case GIMP_BUCKET_FILL_FG:
              g_object_set (options, "fill-mode", GIMP_BUCKET_FILL_BG, NULL);
              break;

            default:
              /* GIMP_BUCKET_FILL_BG || GIMP_BUCKET_FILL_PATTERN */
              g_object_set (options, "fill-mode", GIMP_BUCKET_FILL_FG, NULL);
              break;

              break;
            }
        }
      else /* release */
        {
          g_object_set (options, "fill-mode",
                        GIMP_BUCKET_FILL_TOOL (tool)->priv->fill_mode,
                        NULL);
        }
    }
  else if (key == gimp_get_toggle_behavior_mask ())
    {
      GimpToolInfo *info = gimp_get_tool_info (display->gimp,
                                               "gimp-color-picker-tool");

      if (! gimp_color_tool_is_enabled (GIMP_COLOR_TOOL (tool)))
        {
          switch (GIMP_COLOR_TOOL (tool)->pick_target)
            {
            case GIMP_COLOR_PICK_TARGET_BACKGROUND:
              gimp_tool_push_status (tool, display,
                                     _("Click in any image to pick the "
                                       "background color"));
              break;

            case GIMP_COLOR_PICK_TARGET_FOREGROUND:
            default:
              gimp_tool_push_status (tool, display,
                                     _("Click in any image to pick the "
                                       "foreground color"));
              break;
            }

          GIMP_TOOL (tool)->display = display;
          gimp_color_tool_enable (GIMP_COLOR_TOOL (tool),
                                  GIMP_COLOR_OPTIONS (info->tool_options));
        }
      else
        {
          gimp_tool_pop_status (tool, display);
          gimp_color_tool_disable (GIMP_COLOR_TOOL (tool));
          GIMP_TOOL (tool)->display = NULL;
        }
    }
  else if (key == gimp_get_extend_selection_mask ())
    {
      if (press)
        {
          GIMP_BUCKET_FILL_TOOL (tool)->priv->fill_area = options->fill_area;

          switch (options->fill_area)
            {
            case GIMP_BUCKET_FILL_SIMILAR_COLORS:
              g_object_set (options,
                            "fill-area", GIMP_BUCKET_FILL_SELECTION,
                            NULL);
              break;

            default:
              /* GIMP_BUCKET_FILL_SELECTION || GIMP_BUCKET_FILL_LINE_ART */
              g_object_set (options,
                            "fill-area", GIMP_BUCKET_FILL_SIMILAR_COLORS,
                            NULL);
              break;
            }
        }
      else /* release */
        {
          g_object_set (options, "fill-area",
                        GIMP_BUCKET_FILL_TOOL (tool)->priv->fill_area,
                        NULL);
        }
    }
}

static void
gimp_bucket_fill_tool_cursor_update (GimpTool         *tool,
                                     const GimpCoords *coords,
                                     GdkModifierType   state,
                                     GimpDisplay      *display)
{
  GimpBucketFillTool    *bucket_tool = GIMP_BUCKET_FILL_TOOL (tool);
  GimpBucketFillOptions *options     = GIMP_BUCKET_FILL_TOOL_GET_OPTIONS (tool);
  GimpGuiConfig         *config      = GIMP_GUI_CONFIG (display->gimp->config);
  GimpCursorModifier     modifier    = GIMP_CURSOR_MODIFIER_BAD;
  GimpImage             *image       = gimp_display_get_image (display);

  if (gimp_bucket_fill_tool_coords_in_active_pickable (bucket_tool,
                                                       display, coords))
    {
      GList        *drawables = gimp_image_get_selected_drawables (image);
      GimpDrawable *drawable  = NULL;

      if (g_list_length (drawables) == 1)
        drawable = drawables->data;

      if (drawable                                                   &&
          ! gimp_viewable_get_children (GIMP_VIEWABLE (drawable))    &&
          ! gimp_item_is_content_locked (GIMP_ITEM (drawable), NULL) &&
          (gimp_item_is_visible (GIMP_ITEM (drawable)) ||
           config->edit_non_visible))
        {
          switch (options->fill_mode)
            {
            case GIMP_BUCKET_FILL_FG:
              modifier = GIMP_CURSOR_MODIFIER_FOREGROUND;
              break;

            case GIMP_BUCKET_FILL_BG:
              modifier = GIMP_CURSOR_MODIFIER_BACKGROUND;
              break;

            case GIMP_BUCKET_FILL_PATTERN:
              modifier = GIMP_CURSOR_MODIFIER_PATTERN;
              break;
            }
        }

      g_list_free (drawables);
    }

  gimp_tool_control_set_cursor_modifier (tool->control, modifier);

  GIMP_TOOL_CLASS (parent_class)->cursor_update (tool, coords, state, display);
}

static void
gimp_bucket_fill_tool_options_notify (GimpTool         *tool,
                                      GimpToolOptions  *options,
                                      const GParamSpec *pspec)
{
  GimpBucketFillTool    *bucket_tool    = GIMP_BUCKET_FILL_TOOL (tool);
  GimpBucketFillOptions *bucket_options = GIMP_BUCKET_FILL_OPTIONS (options);

  GIMP_TOOL_CLASS (parent_class)->options_notify (tool, options, pspec);

  if (! strcmp (pspec->name, "fill-area"))
    {
      /* We want more motion events when the tool is used in a paint tool
       * fashion. Unfortunately we only set exact mode in line art fill,
       * because we can't as easily remove events from the similar color
       * mode just because a point has already been selected  (unless
       * threshold were 0, but that's an edge case).
       */
      gimp_tool_control_set_motion_mode (tool->control,
                                         bucket_options->fill_area == GIMP_BUCKET_FILL_LINE_ART ?
                                         GIMP_MOTION_MODE_EXACT : GIMP_MOTION_MODE_COMPRESS);

      gimp_bucket_fill_tool_reset_line_art (bucket_tool);
    }
  else if (! strcmp (pspec->name, "fill-mode"))
    {
      if (gimp_color_tool_is_enabled (GIMP_COLOR_TOOL (tool)))
        gimp_tool_pop_status (tool, tool->display);

      switch (bucket_options->fill_mode)
        {
        case GIMP_BUCKET_FILL_BG:
          GIMP_COLOR_TOOL (tool)->pick_target = GIMP_COLOR_PICK_TARGET_BACKGROUND;
          if (gimp_color_tool_is_enabled (GIMP_COLOR_TOOL (tool)))
            gimp_tool_push_status (tool, tool->display,
                                   _("Click in any image to pick the "
                                     "background color"));
          break;

        case GIMP_BUCKET_FILL_FG:
        default:
          GIMP_COLOR_TOOL (tool)->pick_target = GIMP_COLOR_PICK_TARGET_FOREGROUND;
          if (gimp_color_tool_is_enabled (GIMP_COLOR_TOOL (tool)))
            gimp_tool_push_status (tool, tool->display,
                                   _("Click in any image to pick the "
                                     "foreground color"));
          break;
        }
    }
}

static void
gimp_bucket_fill_tool_line_art_computing_start (GimpBucketFillTool *tool)
{
  GimpBucketFillOptions *options = GIMP_BUCKET_FILL_TOOL_GET_OPTIONS (tool);

  gtk_widget_show (options->line_art_busy_box);
}

static void
gimp_bucket_fill_tool_line_art_computing_end (GimpBucketFillTool *tool)
{
  GimpBucketFillOptions *options = GIMP_BUCKET_FILL_TOOL_GET_OPTIONS (tool);

  gtk_widget_hide (options->line_art_busy_box);
}

static void
gimp_bucket_fill_tool_reset_line_art (GimpBucketFillTool *tool)
{
  GimpBucketFillOptions *options  = GIMP_BUCKET_FILL_TOOL_GET_OPTIONS (tool);
  GimpLineArt           *line_art = tool->priv->line_art;
  GimpDisplayShell      *shell    = NULL;
  GimpImage             *image    = NULL;

  if (options->fill_area == GIMP_BUCKET_FILL_LINE_ART)
    {
      GimpContext *context;
      GimpDisplay *display;

      context = gimp_get_user_context (GIMP_CONTEXT (options)->gimp);
      display = gimp_context_get_display (context);

      if (display)
        {
          shell = gimp_display_get_shell (display);
          image = gimp_display_get_image (display);
        }
    }

  if (image != tool->priv->line_art_image)
    {
      if (tool->priv->line_art_image)
        {
          g_signal_handlers_disconnect_by_data (gimp_image_get_layers (tool->priv->line_art_image), tool);
          g_signal_handlers_disconnect_by_data (tool->priv->line_art_image, tool);
        }

      tool->priv->line_art_image = image;

      if (image)
        {
          g_signal_connect_swapped (image, "selected-layers-changed",
                                    G_CALLBACK (gimp_bucket_fill_tool_reset_line_art),
                                    tool);
          g_signal_connect_swapped (image, "selected-channels-changed",
                                    G_CALLBACK (gimp_bucket_fill_tool_reset_line_art),
                                    tool);

          g_signal_connect_swapped (gimp_image_get_layers (image), "add",
                                    G_CALLBACK (gimp_bucket_fill_tool_reset_line_art),
                                    tool);
          g_signal_connect_swapped (gimp_image_get_layers (image), "remove",
                                    G_CALLBACK (gimp_bucket_fill_tool_reset_line_art),
                                    tool);
          g_signal_connect_swapped (gimp_image_get_layers (image), "reorder",
                                    G_CALLBACK (gimp_bucket_fill_tool_reset_line_art),
                                    tool);
        }
    }

  if (shell != tool->priv->line_art_shell)
    {
      if (tool->priv->line_art_shell)
        {
          g_signal_handlers_disconnect_by_func (
            tool->priv->line_art_shell,
            gimp_bucket_fill_tool_reset_line_art,
            tool);
        }

      tool->priv->line_art_shell = shell;

      if (shell)
        {
          g_signal_connect_swapped (shell, "notify::show-all",
                                    G_CALLBACK (gimp_bucket_fill_tool_reset_line_art),
                                    tool);
        }
    }

  if (image)
    {
      GList        *drawables = gimp_image_get_selected_drawables (image);
      GimpDrawable *drawable  = NULL;

      if (g_list_length (drawables) == 1)
        {
          drawable = drawables->data;
          if (gimp_viewable_get_children (GIMP_VIEWABLE (drawable)))
            drawable = NULL;
        }
      g_list_free (drawables);

      if (options->line_art_source == GIMP_LINE_ART_SOURCE_SAMPLE_MERGED)
        {
          GimpImageProxy *image_proxy = gimp_image_proxy_new (image);

          gimp_image_proxy_set_show_all (image_proxy, shell->show_all);

          gimp_line_art_set_input (line_art, GIMP_PICKABLE (image_proxy));

          g_object_unref (image_proxy);
        }
      else if (drawable)
        {
          GimpItem      *parent;
          GimpContainer *container;
          GimpObject    *neighbour = NULL;
          GimpPickable  *source    = NULL;
          gint           index;

          parent = gimp_item_get_parent (GIMP_ITEM (drawable));
          if (parent)
            container = gimp_viewable_get_children (GIMP_VIEWABLE (parent));
          else
            container = gimp_image_get_layers (image);

          index = gimp_item_get_index (GIMP_ITEM (drawable));

          if (options->line_art_source == GIMP_LINE_ART_SOURCE_ACTIVE_LAYER)
            source = GIMP_PICKABLE (drawable);
          else if (options->line_art_source == GIMP_LINE_ART_SOURCE_LOWER_LAYER)
            neighbour = gimp_container_get_child_by_index (container, index + 1);
          else if (options->line_art_source == GIMP_LINE_ART_SOURCE_UPPER_LAYER)
            neighbour = gimp_container_get_child_by_index (container, index - 1);

          source = neighbour ? GIMP_PICKABLE (neighbour) : source;
          gimp_line_art_set_input (line_art, source);
        }
      else
        {
          gimp_line_art_set_input (line_art, NULL);
        }
    }
  else
    {
      gimp_line_art_set_input (line_art, NULL);
    }
}
