/* 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 <string.h>

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

#include "libgimpbase/gimpbase.h"
#include "libgimpcolor/gimpcolor.h"
#include "libgimpwidgets/gimpwidgets.h"

#include "actions-types.h"

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

#include "core/gimp.h"
#include "core/gimpbrushgenerated.h"
#include "core/gimpcontext.h"
#include "core/gimpdatafactory.h"
#include "core/gimpimage.h"
#include "core/gimplist.h"
#include "core/gimppaintinfo.h"
#include "core/gimptoolinfo.h"

#include "paint/gimppaintoptions.h"

#include "widgets/gimpdialogfactory.h"
#include "widgets/gimpsessioninfo.h"
#include "widgets/gimppaletteeditor.h"
#include "widgets/gimpcolormapeditor.h"

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

#include "gimp-intl.h"


/*  local function prototypes  */

static void     context_select_object    (GimpActionSelectType  select_type,
                                          GimpContext          *context,
                                          GimpContainer        *container);
static gint     context_paint_mode_index (GimpLayerMode         paint_mode,
                                          const GimpLayerMode  *modes,
                                          gint                  n_modes);

static void     context_select_color     (GimpActionSelectType  select_type,
                                          GeglColor            *color,
                                          gboolean              use_colormap,
                                          gboolean              use_palette);

static gint     context_get_color_index  (gboolean              use_colormap,
                                          gboolean              use_palette,
                                          GeglColor            *color);
static gint     context_max_color_index  (gboolean              use_colormap,
                                          gboolean              use_palette);
static gboolean context_set_color_index  (gint                  index,
                                          gboolean              use_colormap,
                                          gboolean              use_palette,
                                          GeglColor            *color);

static GimpPaletteEditor  * context_get_palette_editor  (void);
static GimpColormapEditor * context_get_colormap_editor (void);


/*  public functions  */

void
context_colors_default_cmd_callback (GimpAction *action,
                                     GVariant   *value,
                                     gpointer    data)
{
  GimpContext *context;
  return_if_no_context (context, data);

  gimp_context_set_default_colors (context);
}

void
context_colors_swap_cmd_callback (GimpAction *action,
                                  GVariant   *value,
                                  gpointer    data)
{
  GimpContext *context;
  return_if_no_context (context, data);

  gimp_context_swap_colors (context);
}

#define SELECT_COLOR_CMD_CALLBACK(name, fgbg, use_colormap, use_palette) \
void \
context_##name##_##fgbg##ground_cmd_callback (GimpAction *action, \
                                              GVariant   *value, \
                                              gpointer    data) \
{ \
  GimpContext          *context; \
  GeglColor            *color; \
  GimpActionSelectType  select_type; \
  return_if_no_context (context, data); \
  \
  select_type = (GimpActionSelectType) g_variant_get_int32 (value); \
  \
  color = gegl_color_duplicate (gimp_context_get_##fgbg##ground (context)); \
  context_select_color (select_type, color, \
                        use_colormap, use_palette); \
  gimp_context_set_##fgbg##ground (context, color); \
  g_object_unref (color); \
}

SELECT_COLOR_CMD_CALLBACK (palette,  fore, FALSE, TRUE)
SELECT_COLOR_CMD_CALLBACK (palette,  back, FALSE, TRUE)
SELECT_COLOR_CMD_CALLBACK (colormap, fore, TRUE,  FALSE)
SELECT_COLOR_CMD_CALLBACK (colormap, back, TRUE,  FALSE)
SELECT_COLOR_CMD_CALLBACK (swatch,   fore, TRUE,  TRUE)
SELECT_COLOR_CMD_CALLBACK (swatch,   back, TRUE,  TRUE)

void
context_foreground_red_cmd_callback (GimpAction *action,
                                     GVariant   *value,
                                     gpointer    data)
{
  GimpContext          *context;
  GeglColor            *color;
  const Babl           *format;
  gdouble               pixel[4];
  GimpActionSelectType  select_type;
  return_if_no_context (context, data);

  select_type = (GimpActionSelectType) g_variant_get_int32 (value);

  color  = gegl_color_duplicate (gimp_context_get_foreground (context));
  format = gimp_context_get_rgba_format (context, color, "double", NULL);

  gegl_color_get_pixel (color, format, pixel);
  /* TODO: if value was already out-of-gamut, say we want to decrease it
   * progressively. Should the algorithm allow it to be decreased while still
   * staying out-of-gamut? Currently the function always clamps the result to
   * min/max.
   */
  pixel[0] = action_select_value (select_type,
                                  pixel[0],
                                  0.0, 1.0, 1.0,
                                  1.0 / 255.0, 0.01, 0.1, 0.0, FALSE);
  gegl_color_set_pixel (color, format, pixel);
  gimp_context_set_foreground (context, color);
  g_object_unref (color);
}

void
context_foreground_green_cmd_callback (GimpAction *action,
                                       GVariant   *value,
                                       gpointer    data)
{
  GimpContext          *context;
  GeglColor            *color;
  const Babl           *format;
  gdouble               pixel[4];
  GimpActionSelectType  select_type;
  return_if_no_context (context, data);

  select_type = (GimpActionSelectType) g_variant_get_int32 (value);

  color  = gegl_color_duplicate (gimp_context_get_foreground (context));
  format = gimp_context_get_rgba_format (context, color, "double", NULL);

  gegl_color_get_pixel (color, format, pixel);
  pixel[1] = action_select_value (select_type,
                                  pixel[1],
                                  0.0, 1.0, 1.0,
                                  1.0 / 255.0, 0.01, 0.1, 0.0, FALSE);
  gegl_color_set_pixel (color, format, pixel);
  gimp_context_set_foreground (context, color);
  g_object_unref (color);
}

void
context_foreground_blue_cmd_callback (GimpAction *action,
                                      GVariant   *value,
                                      gpointer    data)
{
  GimpContext          *context;
  GeglColor            *color;
  const Babl           *format;
  gdouble               pixel[4];
  GimpActionSelectType  select_type;
  return_if_no_context (context, data);

  select_type = (GimpActionSelectType) g_variant_get_int32 (value);

  color  = gegl_color_duplicate (gimp_context_get_foreground (context));
  format = gimp_context_get_rgba_format (context, color, "double", NULL);

  gegl_color_get_pixel (color, format, pixel);
  pixel[2] = action_select_value (select_type,
                                  pixel[2],
                                  0.0, 1.0, 1.0,
                                  1.0 / 255.0, 0.01, 0.1, 0.0, FALSE);
  gegl_color_set_pixel (color, format, pixel);
  gimp_context_set_foreground (context, color);
  g_object_unref (color);
}

void
context_background_red_cmd_callback (GimpAction *action,
                                     GVariant   *value,
                                     gpointer    data)
{
  GimpContext          *context;
  GeglColor            *color;
  const Babl           *format;
  gdouble               pixel[4];
  GimpActionSelectType  select_type;
  return_if_no_context (context, data);

  select_type = (GimpActionSelectType) g_variant_get_int32 (value);

  color  = gegl_color_duplicate (gimp_context_get_background (context));
  format = gimp_context_get_rgba_format (context, color, "double", NULL);

  gegl_color_get_pixel (color, format, pixel);
  pixel[0] = action_select_value (select_type,
                                  pixel[0],
                                  0.0, 1.0, 1.0,
                                  1.0 / 255.0, 0.01, 0.1, 0.0, FALSE);
  gegl_color_set_pixel (color, format, pixel);
  gimp_context_set_background (context, color);
  g_object_unref (color);
}

void
context_background_green_cmd_callback (GimpAction *action,
                                       GVariant   *value,
                                       gpointer    data)
{
  GimpContext          *context;
  GeglColor            *color;
  const Babl           *format;
  gdouble               pixel[4];
  GimpActionSelectType  select_type;
  return_if_no_context (context, data);

  select_type = (GimpActionSelectType) g_variant_get_int32 (value);

  color  = gegl_color_duplicate (gimp_context_get_background (context));
  format = gimp_context_get_rgba_format (context, color, "double", NULL);

  gegl_color_get_pixel (color, format, pixel);
  pixel[1] = action_select_value (select_type,
                                  pixel[1],
                                  0.0, 1.0, 1.0,
                                  1.0 / 255.0, 0.01, 0.1, 0.0, FALSE);
  gegl_color_set_pixel (color, format, pixel);
  gimp_context_set_background (context, color);
  g_object_unref (color);
}

void
context_background_blue_cmd_callback (GimpAction *action,
                                      GVariant   *value,
                                      gpointer    data)
{
  GimpContext          *context;
  GeglColor            *color;
  const Babl           *format;
  gdouble               pixel[4];
  GimpActionSelectType  select_type;
  return_if_no_context (context, data);

  select_type = (GimpActionSelectType) g_variant_get_int32 (value);

  color  = gegl_color_duplicate (gimp_context_get_background (context));
  format = gimp_context_get_rgba_format (context, color, "double", NULL);

  gegl_color_get_pixel (color, format, pixel);
  pixel[2] = action_select_value (select_type,
                              pixel[2],
                              0.0, 1.0, 1.0,
                              1.0 / 255.0, 0.01, 0.1, 0.0, FALSE);
  gegl_color_set_pixel (color, format, pixel);
  gimp_context_set_background (context, color);
  g_object_unref (color);
}

void
context_foreground_hue_cmd_callback (GimpAction *action,
                                     GVariant   *value,
                                     gpointer    data)
{
  GimpContext          *context;
  GeglColor            *color;
  gfloat                pixel[3];
  GimpActionSelectType  select_type;
  return_if_no_context (context, data);

  select_type = (GimpActionSelectType) g_variant_get_int32 (value);

  color = gegl_color_duplicate (gimp_context_get_foreground (context));
  gegl_color_get_pixel (color, babl_format_with_space ("HSV float", NULL), pixel);
  pixel[0] = (gfloat) action_select_value (select_type,
                                           pixel[0],
                                           0.0, 1.0, 1.0,
                                           1.0 / 360.0, 0.01, 0.1, 0.0, FALSE);
  gegl_color_set_pixel (color, babl_format_with_space ("HSV float", NULL), pixel);
  gimp_context_set_foreground (context, color);
  g_object_unref (color);
}

void
context_foreground_saturation_cmd_callback (GimpAction *action,
                                            GVariant   *value,
                                            gpointer    data)
{
  GimpContext          *context;
  GeglColor            *color;
  gfloat                pixel[3];
  GimpActionSelectType  select_type;
  return_if_no_context (context, data);

  select_type = (GimpActionSelectType) g_variant_get_int32 (value);

  color = gegl_color_duplicate (gimp_context_get_foreground (context));
  gegl_color_get_pixel (color, babl_format_with_space ("HSV float", NULL), pixel);
  pixel[1] = (gfloat) action_select_value (select_type,
                                           pixel[1],
                                           0.0, 1.0, 1.0,
                                           0.01, 0.01, 0.1, 0.0, FALSE);
  gegl_color_set_pixel (color, babl_format_with_space ("HSV float", NULL), pixel);
  gimp_context_set_foreground (context, color);
  g_object_unref (color);
}

void
context_foreground_value_cmd_callback (GimpAction *action,
                                       GVariant   *value,
                                       gpointer    data)
{
  GimpContext          *context;
  GeglColor            *color;
  gfloat                pixel[3];
  GimpActionSelectType  select_type;
  return_if_no_context (context, data);

  select_type = (GimpActionSelectType) g_variant_get_int32 (value);

  color = gegl_color_duplicate (gimp_context_get_foreground (context));
  gegl_color_get_pixel (color, babl_format_with_space ("HSV float", NULL), pixel);
  pixel[2] = (gfloat) action_select_value (select_type,
                                           pixel[2],
                                           0.0, 1.0, 1.0,
                                           0.01, 0.01, 0.1, 0.0, FALSE);
  gegl_color_set_pixel (color, babl_format_with_space ("HSV float", NULL), pixel);
  gimp_context_set_foreground (context, color);
  g_object_unref (color);
}

void
context_background_hue_cmd_callback (GimpAction *action,
                                     GVariant   *value,
                                     gpointer    data)
{
  GimpContext          *context;
  GeglColor            *color;
  gfloat                pixel[3];
  GimpActionSelectType  select_type;
  return_if_no_context (context, data);

  select_type = (GimpActionSelectType) g_variant_get_int32 (value);

  color = gegl_color_duplicate (gimp_context_get_background (context));
  gegl_color_get_pixel (color, babl_format_with_space ("HSV float", NULL), pixel);
  pixel[0] = (gfloat) action_select_value (select_type,
                                           pixel[0],
                                           0.0, 1.0, 1.0,
                                           1.0 / 360.0, 0.01, 0.1, 0.0, FALSE);
  gegl_color_set_pixel (color, babl_format_with_space ("HSV float", NULL), pixel);
  gimp_context_set_background (context, color);
  g_object_unref (color);
}

void
context_background_saturation_cmd_callback (GimpAction *action,
                                            GVariant   *value,
                                            gpointer    data)
{
  GimpContext          *context;
  GeglColor            *color;
  gfloat                pixel[3];
  GimpActionSelectType  select_type;
  return_if_no_context (context, data);

  select_type = (GimpActionSelectType) g_variant_get_int32 (value);

  color = gegl_color_duplicate (gimp_context_get_background (context));
  gegl_color_get_pixel (color, babl_format_with_space ("HSV float", NULL), pixel);
  pixel[1] = (gfloat) action_select_value (select_type,
                                           pixel[1],
                                           0.0, 1.0, 1.0,
                                           0.01, 0.01, 0.1, 0.0, FALSE);
  gegl_color_set_pixel (color, babl_format_with_space ("HSV float", NULL), pixel);
  gimp_context_set_background (context, color);
  g_object_unref (color);
}

void
context_background_value_cmd_callback (GimpAction *action,
                                       GVariant   *value,
                                       gpointer    data)
{
  GimpContext          *context;
  GeglColor            *color;
  gfloat                pixel[3];
  GimpActionSelectType  select_type;
  return_if_no_context (context, data);

  select_type = (GimpActionSelectType) g_variant_get_int32 (value);

  color = gegl_color_duplicate (gimp_context_get_background (context));
  gegl_color_get_pixel (color, babl_format_with_space ("HSV float", NULL), pixel);
  pixel[2] = (gfloat) action_select_value (select_type,
                                           pixel[2],
                                           0.0, 1.0, 1.0,
                                           0.01, 0.01, 0.1, 0.0, FALSE);
  gegl_color_set_pixel (color, babl_format_with_space ("HSV float", NULL), pixel);
  gimp_context_set_background (context, color);
  g_object_unref (color);
}

void
context_opacity_cmd_callback (GimpAction *action,
                              GVariant   *value,
                              gpointer    data)
{
  GimpContext          *context;
  GimpToolInfo         *tool_info;
  GimpActionSelectType  select_type;
  return_if_no_context (context, data);

  select_type = (GimpActionSelectType) g_variant_get_int32 (value);

  tool_info = gimp_context_get_tool (context);

  if (tool_info && GIMP_IS_TOOL_OPTIONS (tool_info->tool_options))
    {
      action_select_property (select_type,
                              action_data_get_display (data),
                              G_OBJECT (tool_info->tool_options),
                              "opacity",
                              1.0 / 255.0, 0.01, 0.1, 0.1, FALSE);
    }
}

void
context_paint_mode_cmd_callback (GimpAction *action,
                                 GVariant   *value,
                                 gpointer    data)
{
  GimpContext          *context;
  GimpToolInfo         *tool_info;
  GimpLayerMode        *modes;
  gint                  n_modes;
  GimpLayerMode         paint_mode;
  gint                  index;
  GimpActionSelectType  select_type;
  return_if_no_context (context, data);

  select_type = (GimpActionSelectType) g_variant_get_int32 (value);

  paint_mode = gimp_context_get_paint_mode (context);

  modes = gimp_layer_mode_get_context_array (paint_mode,
                                             GIMP_LAYER_MODE_CONTEXT_PAINT,
                                             &n_modes);
  index = context_paint_mode_index (paint_mode, modes, n_modes);
  index = action_select_value (select_type,
                               index, 0, n_modes - 1, 0,
                               0.0, 1.0, 1.0, 0.0, FALSE);
  paint_mode = modes[index];
  g_free (modes);

  gimp_context_set_paint_mode (context, paint_mode);

  tool_info = gimp_context_get_tool (context);

  if (tool_info && GIMP_IS_TOOL_OPTIONS (tool_info->tool_options))
    {
      GimpDisplay *display;
      const char  *value_desc;

      gimp_enum_get_value (GIMP_TYPE_LAYER_MODE, paint_mode,
                           NULL, NULL, &value_desc, NULL);

      display = action_data_get_display (data);

      if (value_desc && display)
        {
          action_message (display, G_OBJECT (tool_info->tool_options),
                          _("Paint Mode: %s"), value_desc);
        }
    }
}

void
context_tool_select_cmd_callback (GimpAction *action,
                                  GVariant   *value,
                                  gpointer    data)
{
  GimpContext          *context;
  GimpActionSelectType  select_type;
  return_if_no_context (context, data);

  select_type = (GimpActionSelectType) g_variant_get_int32 (value);

  context_select_object (select_type,
                         context, context->gimp->tool_info_list);
}

void
context_brush_select_cmd_callback (GimpAction *action,
                                   GVariant   *value,
                                   gpointer    data)
{
  GimpContext          *context;
  GimpActionSelectType  select_type;
  return_if_no_context (context, data);

  select_type = (GimpActionSelectType) g_variant_get_int32 (value);

  context_select_object (select_type,
                         context,
                         gimp_data_factory_get_container (context->gimp->brush_factory));
}

void
context_pattern_select_cmd_callback (GimpAction *action,
                                     GVariant   *value,
                                     gpointer    data)
{
  GimpContext          *context;
  GimpActionSelectType  select_type;
  return_if_no_context (context, data);

  select_type = (GimpActionSelectType) g_variant_get_int32 (value);

  context_select_object (select_type,
                         context,
                         gimp_data_factory_get_container (context->gimp->pattern_factory));
}

void
context_palette_select_cmd_callback (GimpAction *action,
                                     GVariant   *value,
                                     gpointer    data)
{
  GimpContext          *context;
  GimpActionSelectType  select_type;
  return_if_no_context (context, data);

  select_type = (GimpActionSelectType) g_variant_get_int32 (value);

  context_select_object (select_type,
                         context,
                         gimp_data_factory_get_container (context->gimp->palette_factory));
}

void
context_gradient_select_cmd_callback (GimpAction *action,
                                      GVariant   *value,
                                      gpointer    data)
{
  GimpContext          *context;
  GimpActionSelectType  select_type;
  return_if_no_context (context, data);

  select_type = (GimpActionSelectType) g_variant_get_int32 (value);

  context_select_object (select_type,
                         context,
                         gimp_data_factory_get_container (context->gimp->gradient_factory));
}

void
context_font_select_cmd_callback (GimpAction *action,
                                  GVariant   *value,
                                  gpointer    data)
{
  GimpContext          *context;
  GimpActionSelectType  select_type;
  return_if_no_context (context, data);

  select_type = (GimpActionSelectType) g_variant_get_int32 (value);

  context_select_object (select_type,
                         context,
                         gimp_data_factory_get_container (context->gimp->font_factory));
}

void
context_brush_spacing_cmd_callback (GimpAction *action,
                                    GVariant   *value,
                                    gpointer    data)
{
  GimpContext          *context;
  GimpBrush            *brush;
  GimpActionSelectType  select_type;
  return_if_no_context (context, data);

  select_type = (GimpActionSelectType) g_variant_get_int32 (value);

  brush = gimp_context_get_brush (context);

  if (GIMP_IS_BRUSH (brush) && gimp_data_is_writable (GIMP_DATA (brush)))
    {
      action_select_property (select_type,
                              action_data_get_display (data),
                              G_OBJECT (brush),
                              "spacing",
                              1.0, 5.0, 20.0, 0.1, FALSE);
    }
}

void
context_brush_shape_cmd_callback (GimpAction *action,
                                  GVariant   *value,
                                  gpointer    data)
{
  GimpContext             *context;
  GimpBrush               *brush;
  GimpBrushGeneratedShape  shape;
  return_if_no_context (context, data);

  shape = (GimpBrushGeneratedShape) g_variant_get_int32 (value);

  brush = gimp_context_get_brush (context);

  if (GIMP_IS_BRUSH_GENERATED (brush) &&
      gimp_data_is_writable (GIMP_DATA (brush)))
    {
      GimpBrushGenerated *generated = GIMP_BRUSH_GENERATED (brush);
      GimpDisplay        *display;
      const char         *value_desc;

      gimp_brush_generated_set_shape (generated, shape);

      gimp_enum_get_value (GIMP_TYPE_BRUSH_GENERATED_SHAPE, shape,
                           NULL, NULL, &value_desc, NULL);
      display = action_data_get_display (data);

      if (value_desc && display)
        {
          action_message (display, G_OBJECT (brush),
                          _("Brush Shape: %s"), value_desc);
        }
    }
}

void
context_brush_radius_cmd_callback (GimpAction *action,
                                   GVariant   *value,
                                   gpointer    data)
{
  GimpContext          *context;
  GimpBrush            *brush;
  GimpActionSelectType  select_type;
  return_if_no_context (context, data);

  select_type = (GimpActionSelectType) g_variant_get_int32 (value);

  brush = gimp_context_get_brush (context);

  if (GIMP_IS_BRUSH_GENERATED (brush) &&
      gimp_data_is_writable (GIMP_DATA (brush)))
    {
      GimpBrushGenerated *generated = GIMP_BRUSH_GENERATED (brush);
      GimpDisplay        *display;
      gdouble             radius;
      gdouble             min_radius;

      radius = gimp_brush_generated_get_radius (generated);

      /* If the user uses a high precision radius adjustment command
       * then we allow a minimum radius of 0.1 px, otherwise we set the
       * minimum radius to 1.0 px and adjust the radius to 1.0 px if it
       * is less than 1.0 px. This prevents irritating 0.1, 1.1, 2.1 etc
       * radius sequences when 1.0 px steps are used.
       */
      switch (select_type)
        {
        case GIMP_ACTION_SELECT_SMALL_PREVIOUS:
        case GIMP_ACTION_SELECT_SMALL_NEXT:
        case GIMP_ACTION_SELECT_PERCENT_PREVIOUS:
        case GIMP_ACTION_SELECT_PERCENT_NEXT:
          min_radius = 0.1;
          break;

        default:
          min_radius = 1.0;

          if (radius < 1.0)
            radius = 1.0;
          break;
        }

      radius = action_select_value (select_type,
                                    radius,
                                    min_radius, GIMP_BRUSH_GENERATED_MAX_RADIUS, min_radius,
                                    0.1, 1.0, 10.0, 0.05, FALSE);
      gimp_brush_generated_set_radius (generated, radius);

      display = action_data_get_display (data);

      if (display)
        {
          action_message (action_data_get_display (data), G_OBJECT (brush),
                          _("Brush Radius: %2.2f"), radius);
        }
    }
}

void
context_brush_spikes_cmd_callback (GimpAction *action,
                                   GVariant   *value,
                                   gpointer    data)
{
  GimpContext          *context;
  GimpBrush            *brush;
  GimpActionSelectType  select_type;
  return_if_no_context (context, data);

  select_type = (GimpActionSelectType) g_variant_get_int32 (value);

  brush = gimp_context_get_brush (context);

  if (GIMP_IS_BRUSH_GENERATED (brush) &&
      gimp_data_is_writable (GIMP_DATA (brush)))
    {
      action_select_property (select_type,
                              action_data_get_display (data),
                              G_OBJECT (brush),
                              "spikes",
                              0.0, 1.0, 4.0, 0.1, FALSE);
    }
}

void
context_brush_hardness_cmd_callback (GimpAction *action,
                                     GVariant   *value,
                                     gpointer    data)
{
  GimpContext          *context;
  GimpBrush            *brush;
  GimpActionSelectType  select_type;
  return_if_no_context (context, data);

  select_type = (GimpActionSelectType) g_variant_get_int32 (value);

  brush = gimp_context_get_brush (context);

  if (GIMP_IS_BRUSH_GENERATED (brush) &&
      gimp_data_is_writable (GIMP_DATA (brush)))
    {
      action_select_property (select_type,
                              action_data_get_display (data),
                              G_OBJECT (brush),
                              "hardness",
                              0.001, 0.01, 0.1, 0.1, FALSE);
    }
}

void
context_brush_aspect_cmd_callback (GimpAction *action,
                                   GVariant   *value,
                                   gpointer    data)
{
  GimpContext          *context;
  GimpBrush            *brush;
  GimpActionSelectType  select_type;
  return_if_no_context (context, data);

  select_type = (GimpActionSelectType) g_variant_get_int32 (value);

  brush = gimp_context_get_brush (context);

  if (GIMP_IS_BRUSH_GENERATED (brush) &&
      gimp_data_is_writable (GIMP_DATA (brush)))
    {
      action_select_property (select_type,
                              action_data_get_display (data),
                              G_OBJECT (brush),
                              "aspect-ratio",
                              0.1, 1.0, 4.0, 0.1, FALSE);
    }
}

void
context_brush_angle_cmd_callback (GimpAction *action,
                                  GVariant   *value,
                                  gpointer    data)
{
  GimpContext          *context;
  GimpBrush            *brush;
  GimpActionSelectType  select_type;
  return_if_no_context (context, data);

  select_type = (GimpActionSelectType) g_variant_get_int32 (value);

  brush = gimp_context_get_brush (context);

  if (GIMP_IS_BRUSH_GENERATED (brush) &&
      gimp_data_is_writable (GIMP_DATA (brush)))
    {
      GimpBrushGenerated *generated = GIMP_BRUSH_GENERATED (brush);
      GimpDisplay        *display;
      gdouble             angle;

      angle = gimp_brush_generated_get_angle (generated);

      if (select_type == GIMP_ACTION_SELECT_FIRST)
        angle = 0.0;
      else if (select_type == GIMP_ACTION_SELECT_LAST)
        angle = 90.0;
      else
        angle = action_select_value (select_type,
                                     angle,
                                     0.0, 180.0, 0.0,
                                     0.1, 1.0, 15.0, 0.1, TRUE);

      gimp_brush_generated_set_angle (generated, angle);

      display = action_data_get_display (data);

      if (display)
        {
          action_message (action_data_get_display (data), G_OBJECT (brush),
                          _("Brush Angle: %2.2f"), angle);
        }
    }
}

void
context_toggle_dynamics_cmd_callback (GimpAction *action,
                                      GVariant   *value,
                                      gpointer    data)
{
  GimpContext   *context;
  GimpPaintInfo *paint_info;
  gboolean       enabled;

  return_if_no_context (context, data);

  paint_info = gimp_context_get_paint_info (context);
  if (paint_info)
    {
      GimpPaintOptions *paint_options;
      GimpDisplay      *display;

      paint_options = paint_info->paint_options;
      enabled = gimp_paint_options_are_dynamics_enabled (paint_options);

      gimp_paint_options_enable_dynamics (paint_options, ! enabled);

      display = action_data_get_display (data);

      if (enabled)
        action_message (display, G_OBJECT (paint_options),
                        _("Dynamics disabled"));
      else
        action_message (display, G_OBJECT (paint_options),
                        _("Dynamics enabled"));
    }
}


/*  private functions  */

static void
context_select_object (GimpActionSelectType  select_type,
                       GimpContext          *context,
                       GimpContainer        *container)
{
  GimpObject *current;
  GType       child_type;

  child_type = gimp_container_get_child_type (container);

  current = gimp_context_get_by_type (context, child_type);

  current = action_select_object (select_type, container, current);

  if (current)
    gimp_context_set_by_type (context, child_type, current);
}

static gint
context_paint_mode_index (GimpLayerMode        paint_mode,
                          const GimpLayerMode *modes,
                          gint                 n_modes)
{
  gint i = 0;

  while (i < (n_modes - 1) && modes[i] != paint_mode)
    i++;

  return i;
}

static void
context_select_color (GimpActionSelectType  select_type,
                      GeglColor            *color,
                      gboolean              use_colormap,
                      gboolean              use_palette)
{
  gint index;
  gint max;

  index = context_get_color_index (use_colormap, use_palette, color);
  max   = context_max_color_index (use_colormap, use_palette);

  index = action_select_value (select_type,
                               index,
                               0, max, 0,
                               0, 1, 4, 0, FALSE);

  context_set_color_index (index, use_colormap, use_palette, color);
}

static gint
context_get_color_index (gboolean   use_colormap,
                         gboolean   use_palette,
                         GeglColor *color)
{
  if (use_colormap)
    {
      GimpColormapEditor *editor = context_get_colormap_editor ();

      if (editor)
        {
          gint index = gimp_colormap_editor_get_index (editor, color);

          if (index != -1)
            return index;
        }
    }

  if (use_palette)
    {
      GimpPaletteEditor *editor = context_get_palette_editor ();

      if (editor)
        {
          gint index = gimp_palette_editor_get_index (editor, color);

          if (index != -1)
            return index;
        }
    }

  return 0;
}

static gint
context_max_color_index (gboolean use_colormap,
                         gboolean use_palette)
{
  if (use_colormap)
    {
      GimpColormapEditor *editor = context_get_colormap_editor ();

      if (editor)
        {
          gint index = gimp_colormap_editor_max_index (editor);

          if (index != -1)
            return index;
        }
    }

  if (use_palette)
    {
      GimpPaletteEditor *editor = context_get_palette_editor ();

      if (editor)
        {
          gint index = gimp_palette_editor_max_index (editor);

          if (index != -1)
            return index;
        }
    }

  return 0;
}

static gboolean
context_set_color_index (gint       index,
                         gboolean   use_colormap,
                         gboolean   use_palette,
                         GeglColor *color)
{
  if (use_colormap)
    {
      GimpColormapEditor *editor = context_get_colormap_editor ();

      if (editor && gimp_colormap_editor_set_index (editor, index, color))
        return TRUE;
    }

  if (use_palette)
    {
      GimpPaletteEditor *editor = context_get_palette_editor ();

      if (editor && gimp_palette_editor_set_index (editor, index, color))
        return TRUE;
    }

  return FALSE;
}

static GimpPaletteEditor *
context_get_palette_editor (void)
{
  GtkWidget *widget;

  g_return_val_if_fail (GIMP_IS_DIALOG_FACTORY (gimp_dialog_factory_get_singleton ()), NULL);

  widget = gimp_dialog_factory_find_widget (gimp_dialog_factory_get_singleton (),
                                            "gimp-palette-editor");
  if (widget)
    return GIMP_PALETTE_EDITOR (gtk_bin_get_child (GTK_BIN (widget)));

  return NULL;
}

static GimpColormapEditor *
context_get_colormap_editor (void)
{
  GtkWidget *widget;

  g_return_val_if_fail (GIMP_IS_DIALOG_FACTORY (gimp_dialog_factory_get_singleton ()), NULL);

  widget = gimp_dialog_factory_find_widget (gimp_dialog_factory_get_singleton (),
                                            "gimp-indexed-palette");
  if (widget)
    return GIMP_COLORMAP_EDITOR (gtk_bin_get_child (GTK_BIN (widget)));

  return NULL;
}
