/* PIKA - Photo and Image Kooker Application
 * a rebranding of The GNU Image Manipulation Program (created with heckimp)
 * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
 *
 * Original copyright, applying to most contents (license remains unchanged): 
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * pikacontext.c
 * Copyright (C) 1999-2010 Michael Natterer
 *
 * 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 <cairo.h>
#include <gegl.h>
#include <gdk-pixbuf/gdk-pixbuf.h>

#include "libpikabase/pikabase.h"
#include "libpikacolor/pikacolor.h"
#include "libpikaconfig/pikaconfig.h"

#include "core-types.h"

#include "config/pikacoreconfig.h"

#include "pika.h"
#include "pika-memsize.h"
#include "pikabrush.h"
#include "pikabuffer.h"
#include "pikacontainer.h"
#include "pikacontext.h"
#include "pikadatafactory.h"
#include "pikadisplay.h"
#include "pikadynamics.h"
#include "pikaimagefile.h"
#include "pikagradient.h"
#include "pikaimage.h"
#include "pikalineart.h"
#include "pikamybrush.h"
#include "pikapaintinfo.h"
#include "pikapalette.h"
#include "pikapattern.h"
#include "pikatemplate.h"
#include "pikatoolinfo.h"
#include "pikatoolpreset.h"

#include "text/pikafont.h"

#include "pika-intl.h"


#define RGBA_EPSILON 1e-10

typedef void (* PikaContextCopyPropFunc) (PikaContext *src,
                                          PikaContext *dest);

#define context_find_defined(context, prop)                              \
  while (!(((context)->defined_props) & (1 << (prop))) && (context)->parent) \
    (context) = (context)->parent

#define COPY_NAME(src, dest, member)            \
  g_free (dest->member);                        \
  dest->member = g_strdup (src->member)


/*  local function prototypes  */

static void    pika_context_config_iface_init (PikaConfigInterface   *iface);

static void       pika_context_constructed    (GObject               *object);
static void       pika_context_dispose        (GObject               *object);
static void       pika_context_finalize       (GObject               *object);
static void       pika_context_set_property   (GObject               *object,
                                               guint                  property_id,
                                               const GValue          *value,
                                               GParamSpec            *pspec);
static void       pika_context_get_property   (GObject               *object,
                                               guint                  property_id,
                                               GValue                *value,
                                               GParamSpec            *pspec);
static gint64     pika_context_get_memsize    (PikaObject            *object,
                                               gint64                *gui_size);

static gboolean   pika_context_serialize            (PikaConfig       *config,
                                                     PikaConfigWriter *writer,
                                                     gpointer          data);
static gboolean   pika_context_deserialize          (PikaConfig       *config,
                                                     GScanner         *scanner,
                                                     gint              nest_level,
                                                     gpointer          data);
static gboolean   pika_context_serialize_property   (PikaConfig       *config,
                                                     guint             property_id,
                                                     const GValue     *value,
                                                     GParamSpec       *pspec,
                                                     PikaConfigWriter *writer);
static gboolean   pika_context_deserialize_property (PikaConfig       *config,
                                                     guint             property_id,
                                                     GValue           *value,
                                                     GParamSpec       *pspec,
                                                     GScanner         *scanner,
                                                     GTokenType       *expected);
static PikaConfig * pika_context_duplicate          (PikaConfig       *config);
static gboolean     pika_context_copy               (PikaConfig       *src,
                                                     PikaConfig       *dest,
                                                     GParamFlags       flags);

/*  image  */
static void pika_context_image_removed       (PikaContainer    *container,
                                              PikaImage        *image,
                                              PikaContext      *context);
static void pika_context_real_set_image      (PikaContext      *context,
                                              PikaImage        *image);

/*  display  */
static void pika_context_display_removed     (PikaContainer    *container,
                                              PikaDisplay      *display,
                                              PikaContext      *context);
static void pika_context_real_set_display    (PikaContext      *context,
                                              PikaDisplay      *display);

/*  tool  */
static void pika_context_tool_dirty          (PikaToolInfo     *tool_info,
                                              PikaContext      *context);
static void pika_context_tool_removed        (PikaContainer    *container,
                                              PikaToolInfo     *tool_info,
                                              PikaContext      *context);
static void pika_context_tool_list_thaw      (PikaContainer    *container,
                                              PikaContext      *context);
static void pika_context_real_set_tool       (PikaContext      *context,
                                              PikaToolInfo     *tool_info);

/*  paint info  */
static void pika_context_paint_info_dirty    (PikaPaintInfo    *paint_info,
                                              PikaContext      *context);
static void pika_context_paint_info_removed  (PikaContainer    *container,
                                              PikaPaintInfo    *paint_info,
                                              PikaContext      *context);
static void pika_context_paint_info_list_thaw(PikaContainer    *container,
                                              PikaContext      *context);
static void pika_context_real_set_paint_info (PikaContext      *context,
                                              PikaPaintInfo    *paint_info);

/*  foreground  */
static void pika_context_real_set_foreground (PikaContext      *context,
                                              const PikaRGB    *color);

/*  background  */
static void pika_context_real_set_background (PikaContext      *context,
                                              const PikaRGB    *color);

/*  opacity  */
static void pika_context_real_set_opacity    (PikaContext      *context,
                                              gdouble           opacity);

/*  paint mode  */
static void pika_context_real_set_paint_mode (PikaContext      *context,
                                              PikaLayerMode     paint_mode);

/*  brush  */
static void pika_context_brush_dirty         (PikaBrush        *brush,
                                              PikaContext      *context);
static void pika_context_brush_removed       (PikaContainer    *brush_list,
                                              PikaBrush        *brush,
                                              PikaContext      *context);
static void pika_context_brush_list_thaw     (PikaContainer    *container,
                                              PikaContext      *context);
static void pika_context_real_set_brush      (PikaContext      *context,
                                              PikaBrush        *brush);

/*  dynamics  */

static void pika_context_dynamics_dirty      (PikaDynamics     *dynamics,
                                              PikaContext      *context);
static void pika_context_dynamics_removed    (PikaContainer    *container,
                                              PikaDynamics     *dynamics,
                                              PikaContext      *context);
static void pika_context_dynamics_list_thaw  (PikaContainer    *container,
                                              PikaContext      *context);
static void pika_context_real_set_dynamics   (PikaContext      *context,
                                              PikaDynamics     *dynamics);

/*  mybrush  */
static void pika_context_mybrush_dirty       (PikaMybrush      *brush,
                                              PikaContext      *context);
static void pika_context_mybrush_removed     (PikaContainer    *brush_list,
                                              PikaMybrush      *brush,
                                              PikaContext      *context);
static void pika_context_mybrush_list_thaw   (PikaContainer    *container,
                                              PikaContext      *context);
static void pika_context_real_set_mybrush    (PikaContext      *context,
                                              PikaMybrush      *brush);

/*  pattern  */
static void pika_context_pattern_dirty       (PikaPattern      *pattern,
                                              PikaContext      *context);
static void pika_context_pattern_removed     (PikaContainer    *container,
                                              PikaPattern      *pattern,
                                              PikaContext      *context);
static void pika_context_pattern_list_thaw   (PikaContainer    *container,
                                              PikaContext      *context);
static void pika_context_real_set_pattern    (PikaContext      *context,
                                              PikaPattern      *pattern);

/*  gradient  */
static void pika_context_gradient_dirty      (PikaGradient     *gradient,
                                              PikaContext      *context);
static void pika_context_gradient_removed    (PikaContainer    *container,
                                              PikaGradient     *gradient,
                                              PikaContext      *context);
static void pika_context_gradient_list_thaw  (PikaContainer    *container,
                                              PikaContext      *context);
static void pika_context_real_set_gradient   (PikaContext      *context,
                                              PikaGradient     *gradient);

/*  palette  */
static void pika_context_palette_dirty       (PikaPalette      *palette,
                                              PikaContext      *context);
static void pika_context_palette_removed     (PikaContainer    *container,
                                              PikaPalette      *palette,
                                              PikaContext      *context);
static void pika_context_palette_list_thaw   (PikaContainer    *container,
                                              PikaContext      *context);
static void pika_context_real_set_palette    (PikaContext      *context,
                                              PikaPalette      *palette);

/*  font  */
static void pika_context_font_dirty          (PikaFont         *font,
                                              PikaContext      *context);
static void pika_context_font_removed        (PikaContainer    *container,
                                              PikaFont         *font,
                                              PikaContext      *context);
static void pika_context_font_list_thaw      (PikaContainer    *container,
                                              PikaContext      *context);
static void pika_context_real_set_font       (PikaContext      *context,
                                              PikaFont         *font);

/*  tool preset  */
static void pika_context_tool_preset_dirty     (PikaToolPreset   *tool_preset,
                                                PikaContext      *context);
static void pika_context_tool_preset_removed   (PikaContainer    *container,
                                                PikaToolPreset   *tool_preset,
                                                PikaContext      *context);
static void pika_context_tool_preset_list_thaw (PikaContainer    *container,
                                                PikaContext      *context);
static void pika_context_real_set_tool_preset  (PikaContext      *context,
                                                PikaToolPreset   *tool_preset);

/*  buffer  */
static void pika_context_buffer_dirty        (PikaBuffer       *buffer,
                                              PikaContext      *context);
static void pika_context_buffer_removed      (PikaContainer    *container,
                                              PikaBuffer       *buffer,
                                              PikaContext      *context);
static void pika_context_buffer_list_thaw    (PikaContainer    *container,
                                              PikaContext      *context);
static void pika_context_real_set_buffer     (PikaContext      *context,
                                              PikaBuffer       *buffer);

/*  imagefile  */
static void pika_context_imagefile_dirty     (PikaImagefile    *imagefile,
                                              PikaContext      *context);
static void pika_context_imagefile_removed   (PikaContainer    *container,
                                              PikaImagefile    *imagefile,
                                              PikaContext      *context);
static void pika_context_imagefile_list_thaw (PikaContainer    *container,
                                              PikaContext      *context);
static void pika_context_real_set_imagefile  (PikaContext      *context,
                                              PikaImagefile    *imagefile);

/*  template  */
static void pika_context_template_dirty      (PikaTemplate     *template,
                                              PikaContext      *context);
static void pika_context_template_removed    (PikaContainer    *container,
                                              PikaTemplate     *template,
                                              PikaContext      *context);
static void pika_context_template_list_thaw  (PikaContainer    *container,
                                              PikaContext      *context);
static void pika_context_real_set_template   (PikaContext      *context,
                                              PikaTemplate     *template);


/*  line art  */
static gboolean pika_context_free_line_art   (PikaContext      *context);


/*  utilities  */
static gpointer pika_context_find_object     (PikaContext      *context,
                                              PikaContainer    *container,
                                              const gchar      *object_name,
                                              gpointer          standard_object);


/*  properties & signals  */

enum
{
  PIKA_CONTEXT_PROP_0,
  PIKA_CONTEXT_PROP_PIKA

  /*  remaining values are in core-enums.h  (PikaContextPropType)  */
};

enum
{
  DUMMY_0,
  DUMMY_1,
  IMAGE_CHANGED,
  DISPLAY_CHANGED,
  TOOL_CHANGED,
  PAINT_INFO_CHANGED,
  FOREGROUND_CHANGED,
  BACKGROUND_CHANGED,
  OPACITY_CHANGED,
  PAINT_MODE_CHANGED,
  BRUSH_CHANGED,
  DYNAMICS_CHANGED,
  MYBRUSH_CHANGED,
  PATTERN_CHANGED,
  GRADIENT_CHANGED,
  PALETTE_CHANGED,
  FONT_CHANGED,
  TOOL_PRESET_CHANGED,
  BUFFER_CHANGED,
  IMAGEFILE_CHANGED,
  TEMPLATE_CHANGED,
  PROP_NAME_CHANGED,
  LAST_SIGNAL
};

static const gchar * const pika_context_prop_names[] =
{
  NULL, /* PROP_0 */
  "pika",
  "image",
  "display",
  "tool",
  "paint-info",
  "foreground",
  "background",
  "opacity",
  "paint-mode",
  "brush",
  "dynamics",
  "mybrush",
  "pattern",
  "gradient",
  "palette",
  "font",
  "tool-preset",
  "buffer",
  "imagefile",
  "template"
};

static GType pika_context_prop_types[] =
{
  G_TYPE_NONE, /* PROP_0    */
  G_TYPE_NONE, /* PROP_PIKA */
  0,
  G_TYPE_NONE,
  0,
  0,
  G_TYPE_NONE,
  G_TYPE_NONE,
  G_TYPE_NONE,
  G_TYPE_NONE,
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0
};


G_DEFINE_TYPE_WITH_CODE (PikaContext, pika_context, PIKA_TYPE_VIEWABLE,
                         G_IMPLEMENT_INTERFACE (PIKA_TYPE_CONFIG,
                                                pika_context_config_iface_init))

#define parent_class pika_context_parent_class

static PikaConfigInterface *parent_config_iface = NULL;

static guint pika_context_signals[LAST_SIGNAL] = { 0 };


static void
pika_context_class_init (PikaContextClass *klass)
{
  GObjectClass    *object_class      = G_OBJECT_CLASS (klass);
  PikaObjectClass *pika_object_class = PIKA_OBJECT_CLASS (klass);
  PikaRGB          black;
  PikaRGB          white;

  pika_rgba_set (&black, 0.0, 0.0, 0.0, PIKA_OPACITY_OPAQUE);
  pika_rgba_set (&white, 1.0, 1.0, 1.0, PIKA_OPACITY_OPAQUE);

  pika_context_signals[IMAGE_CHANGED] =
    g_signal_new ("image-changed",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (PikaContextClass, image_changed),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 1,
                  PIKA_TYPE_IMAGE);

  pika_context_signals[DISPLAY_CHANGED] =
    g_signal_new ("display-changed",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (PikaContextClass, display_changed),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 1,
                  PIKA_TYPE_DISPLAY);

  pika_context_signals[TOOL_CHANGED] =
    g_signal_new ("tool-changed",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (PikaContextClass, tool_changed),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 1,
                  PIKA_TYPE_TOOL_INFO);

  pika_context_signals[PAINT_INFO_CHANGED] =
    g_signal_new ("paint-info-changed",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (PikaContextClass, paint_info_changed),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 1,
                  PIKA_TYPE_PAINT_INFO);

  pika_context_signals[FOREGROUND_CHANGED] =
    g_signal_new ("foreground-changed",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (PikaContextClass, foreground_changed),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 1,
                  PIKA_TYPE_RGB | G_SIGNAL_TYPE_STATIC_SCOPE);

  pika_context_signals[BACKGROUND_CHANGED] =
    g_signal_new ("background-changed",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (PikaContextClass, background_changed),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 1,
                  PIKA_TYPE_RGB | G_SIGNAL_TYPE_STATIC_SCOPE);

  pika_context_signals[OPACITY_CHANGED] =
    g_signal_new ("opacity-changed",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (PikaContextClass, opacity_changed),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 1,
                  G_TYPE_DOUBLE);

  pika_context_signals[PAINT_MODE_CHANGED] =
    g_signal_new ("paint-mode-changed",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (PikaContextClass, paint_mode_changed),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 1,
                  PIKA_TYPE_LAYER_MODE);

  pika_context_signals[BRUSH_CHANGED] =
    g_signal_new ("brush-changed",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (PikaContextClass, brush_changed),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 1,
                  PIKA_TYPE_BRUSH);

  pika_context_signals[DYNAMICS_CHANGED] =
    g_signal_new ("dynamics-changed",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (PikaContextClass, dynamics_changed),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 1,
                  PIKA_TYPE_DYNAMICS);

  pika_context_signals[MYBRUSH_CHANGED] =
    g_signal_new ("mybrush-changed",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (PikaContextClass, mybrush_changed),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 1,
                  PIKA_TYPE_MYBRUSH);

  pika_context_signals[PATTERN_CHANGED] =
    g_signal_new ("pattern-changed",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (PikaContextClass, pattern_changed),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 1,
                  PIKA_TYPE_PATTERN);

  pika_context_signals[GRADIENT_CHANGED] =
    g_signal_new ("gradient-changed",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (PikaContextClass, gradient_changed),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 1,
                  PIKA_TYPE_GRADIENT);

  pika_context_signals[PALETTE_CHANGED] =
    g_signal_new ("palette-changed",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (PikaContextClass, palette_changed),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 1,
                  PIKA_TYPE_PALETTE);

  pika_context_signals[FONT_CHANGED] =
    g_signal_new ("font-changed",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (PikaContextClass, font_changed),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 1,
                  PIKA_TYPE_FONT);

  pika_context_signals[TOOL_PRESET_CHANGED] =
    g_signal_new ("tool-preset-changed",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (PikaContextClass, tool_preset_changed),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 1,
                  PIKA_TYPE_TOOL_PRESET);

  pika_context_signals[BUFFER_CHANGED] =
    g_signal_new ("buffer-changed",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (PikaContextClass, buffer_changed),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 1,
                  PIKA_TYPE_BUFFER);

  pika_context_signals[IMAGEFILE_CHANGED] =
    g_signal_new ("imagefile-changed",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (PikaContextClass, imagefile_changed),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 1,
                  PIKA_TYPE_IMAGEFILE);

  pika_context_signals[TEMPLATE_CHANGED] =
    g_signal_new ("template-changed",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (PikaContextClass, template_changed),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 1,
                  PIKA_TYPE_TEMPLATE);

  pika_context_signals[PROP_NAME_CHANGED] =
    g_signal_new ("prop-name-changed",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (PikaContextClass, prop_name_changed),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 1,
                  G_TYPE_INT);

  object_class->constructed      = pika_context_constructed;
  object_class->set_property     = pika_context_set_property;
  object_class->get_property     = pika_context_get_property;
  object_class->dispose          = pika_context_dispose;
  object_class->finalize         = pika_context_finalize;

  pika_object_class->get_memsize = pika_context_get_memsize;

  klass->image_changed           = NULL;
  klass->display_changed         = NULL;
  klass->tool_changed            = NULL;
  klass->paint_info_changed      = NULL;
  klass->foreground_changed      = NULL;
  klass->background_changed      = NULL;
  klass->opacity_changed         = NULL;
  klass->paint_mode_changed      = NULL;
  klass->brush_changed           = NULL;
  klass->dynamics_changed        = NULL;
  klass->mybrush_changed         = NULL;
  klass->pattern_changed         = NULL;
  klass->gradient_changed        = NULL;
  klass->palette_changed         = NULL;
  klass->font_changed            = NULL;
  klass->tool_preset_changed     = NULL;
  klass->buffer_changed          = NULL;
  klass->imagefile_changed       = NULL;
  klass->template_changed        = NULL;
  klass->prop_name_changed       = NULL;

  pika_context_prop_types[PIKA_CONTEXT_PROP_IMAGE]       = PIKA_TYPE_IMAGE;
  pika_context_prop_types[PIKA_CONTEXT_PROP_TOOL]        = PIKA_TYPE_TOOL_INFO;
  pika_context_prop_types[PIKA_CONTEXT_PROP_PAINT_INFO]  = PIKA_TYPE_PAINT_INFO;
  pika_context_prop_types[PIKA_CONTEXT_PROP_BRUSH]       = PIKA_TYPE_BRUSH;
  pika_context_prop_types[PIKA_CONTEXT_PROP_DYNAMICS]    = PIKA_TYPE_DYNAMICS;
  pika_context_prop_types[PIKA_CONTEXT_PROP_MYBRUSH]     = PIKA_TYPE_MYBRUSH;
  pika_context_prop_types[PIKA_CONTEXT_PROP_PATTERN]     = PIKA_TYPE_PATTERN;
  pika_context_prop_types[PIKA_CONTEXT_PROP_GRADIENT]    = PIKA_TYPE_GRADIENT;
  pika_context_prop_types[PIKA_CONTEXT_PROP_PALETTE]     = PIKA_TYPE_PALETTE;
  pika_context_prop_types[PIKA_CONTEXT_PROP_FONT]        = PIKA_TYPE_FONT;
  pika_context_prop_types[PIKA_CONTEXT_PROP_TOOL_PRESET] = PIKA_TYPE_TOOL_PRESET;
  pika_context_prop_types[PIKA_CONTEXT_PROP_BUFFER]      = PIKA_TYPE_BUFFER;
  pika_context_prop_types[PIKA_CONTEXT_PROP_IMAGEFILE]   = PIKA_TYPE_IMAGEFILE;
  pika_context_prop_types[PIKA_CONTEXT_PROP_TEMPLATE]    = PIKA_TYPE_TEMPLATE;

  g_object_class_install_property (object_class, PIKA_CONTEXT_PROP_PIKA,
                                   g_param_spec_object ("pika",
                                                        NULL, NULL,
                                                        PIKA_TYPE_PIKA,
                                                        PIKA_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT_ONLY));

  g_object_class_install_property (object_class, PIKA_CONTEXT_PROP_IMAGE,
                                   g_param_spec_object (pika_context_prop_names[PIKA_CONTEXT_PROP_IMAGE],
                                                        NULL, NULL,
                                                        PIKA_TYPE_IMAGE,
                                                        PIKA_PARAM_READWRITE));

  g_object_class_install_property (object_class, PIKA_CONTEXT_PROP_DISPLAY,
                                   g_param_spec_object (pika_context_prop_names[PIKA_CONTEXT_PROP_DISPLAY],
                                                        NULL, NULL,
                                                        PIKA_TYPE_DISPLAY,
                                                        PIKA_PARAM_READWRITE));

  PIKA_CONFIG_PROP_OBJECT (object_class, PIKA_CONTEXT_PROP_TOOL,
                           pika_context_prop_names[PIKA_CONTEXT_PROP_TOOL],
                           NULL, NULL,
                           PIKA_TYPE_TOOL_INFO,
                           PIKA_PARAM_STATIC_STRINGS);

  PIKA_CONFIG_PROP_OBJECT (object_class, PIKA_CONTEXT_PROP_PAINT_INFO,
                           pika_context_prop_names[PIKA_CONTEXT_PROP_PAINT_INFO],
                           NULL, NULL,
                           PIKA_TYPE_PAINT_INFO,
                           PIKA_PARAM_STATIC_STRINGS);

  PIKA_CONFIG_PROP_RGB (object_class, PIKA_CONTEXT_PROP_FOREGROUND,
                        pika_context_prop_names[PIKA_CONTEXT_PROP_FOREGROUND],
                        _("Foreground"),
                        _("Foreground color"),
                         FALSE, &black,
                        PIKA_PARAM_STATIC_STRINGS);

  PIKA_CONFIG_PROP_RGB (object_class, PIKA_CONTEXT_PROP_BACKGROUND,
                        pika_context_prop_names[PIKA_CONTEXT_PROP_BACKGROUND],
                        _("Background"),
                        _("Background color"),
                        FALSE, &white,
                        PIKA_PARAM_STATIC_STRINGS);

  PIKA_CONFIG_PROP_DOUBLE (object_class, PIKA_CONTEXT_PROP_OPACITY,
                           pika_context_prop_names[PIKA_CONTEXT_PROP_OPACITY],
                           _("Opacity"),
                           _("Opacity"),
                           PIKA_OPACITY_TRANSPARENT,
                           PIKA_OPACITY_OPAQUE,
                           PIKA_OPACITY_OPAQUE,
                           PIKA_PARAM_STATIC_STRINGS);

  PIKA_CONFIG_PROP_ENUM (object_class, PIKA_CONTEXT_PROP_PAINT_MODE,
                         pika_context_prop_names[PIKA_CONTEXT_PROP_PAINT_MODE],
                         _("Paint Mode"),
                         _("Paint Mode"),
                         PIKA_TYPE_LAYER_MODE,
                         PIKA_LAYER_MODE_NORMAL,
                         PIKA_PARAM_STATIC_STRINGS);

  PIKA_CONFIG_PROP_OBJECT (object_class, PIKA_CONTEXT_PROP_BRUSH,
                           pika_context_prop_names[PIKA_CONTEXT_PROP_BRUSH],
                           _("Brush"),
                           _("Brush"),
                           PIKA_TYPE_BRUSH,
                           PIKA_PARAM_STATIC_STRINGS);

  PIKA_CONFIG_PROP_OBJECT (object_class, PIKA_CONTEXT_PROP_DYNAMICS,
                           pika_context_prop_names[PIKA_CONTEXT_PROP_DYNAMICS],
                           _("Dynamics"),
                           _("Paint dynamics"),
                           PIKA_TYPE_DYNAMICS,
                           PIKA_PARAM_STATIC_STRINGS);

  PIKA_CONFIG_PROP_OBJECT (object_class, PIKA_CONTEXT_PROP_MYBRUSH,
                           pika_context_prop_names[PIKA_CONTEXT_PROP_MYBRUSH],
                           _("MyPaint Brush"),
                           _("MyPaint Brush"),
                           PIKA_TYPE_MYBRUSH,
                           PIKA_PARAM_STATIC_STRINGS);

  PIKA_CONFIG_PROP_OBJECT (object_class, PIKA_CONTEXT_PROP_PATTERN,
                           pika_context_prop_names[PIKA_CONTEXT_PROP_PATTERN],
                           _("Pattern"),
                           _("Pattern"),
                           PIKA_TYPE_PATTERN,
                           PIKA_PARAM_STATIC_STRINGS);

  PIKA_CONFIG_PROP_OBJECT (object_class, PIKA_CONTEXT_PROP_GRADIENT,
                           pika_context_prop_names[PIKA_CONTEXT_PROP_GRADIENT],
                           _("Gradient"),
                           _("Gradient"),
                           PIKA_TYPE_GRADIENT,
                           PIKA_PARAM_STATIC_STRINGS);

  PIKA_CONFIG_PROP_OBJECT (object_class, PIKA_CONTEXT_PROP_PALETTE,
                           pika_context_prop_names[PIKA_CONTEXT_PROP_PALETTE],
                           _("Palette"),
                           _("Palette"),
                           PIKA_TYPE_PALETTE,
                           PIKA_PARAM_STATIC_STRINGS);

  PIKA_CONFIG_PROP_OBJECT (object_class, PIKA_CONTEXT_PROP_FONT,
                           pika_context_prop_names[PIKA_CONTEXT_PROP_FONT],
                           _("Font"),
                           _("Font"),
                           PIKA_TYPE_FONT,
                           PIKA_PARAM_STATIC_STRINGS);

  PIKA_CONFIG_PROP_OBJECT (object_class, PIKA_CONTEXT_PROP_TOOL_PRESET,
                           pika_context_prop_names[PIKA_CONTEXT_PROP_TOOL_PRESET],
                           _("Tool Preset"),
                           _("Tool Preset"),
                           PIKA_TYPE_TOOL_PRESET,
                           PIKA_PARAM_STATIC_STRINGS);

  g_object_class_install_property (object_class, PIKA_CONTEXT_PROP_BUFFER,
                                   g_param_spec_object (pika_context_prop_names[PIKA_CONTEXT_PROP_BUFFER],
                                                        NULL, NULL,
                                                        PIKA_TYPE_BUFFER,
                                                        PIKA_PARAM_READWRITE));

  g_object_class_install_property (object_class, PIKA_CONTEXT_PROP_IMAGEFILE,
                                   g_param_spec_object (pika_context_prop_names[PIKA_CONTEXT_PROP_IMAGEFILE],
                                                        NULL, NULL,
                                                        PIKA_TYPE_IMAGEFILE,
                                                        PIKA_PARAM_READWRITE));

  g_object_class_install_property (object_class, PIKA_CONTEXT_PROP_TEMPLATE,
                                   g_param_spec_object (pika_context_prop_names[PIKA_CONTEXT_PROP_TEMPLATE],
                                                        NULL, NULL,
                                                        PIKA_TYPE_TEMPLATE,
                                                        PIKA_PARAM_READWRITE));
}

static void
pika_context_init (PikaContext *context)
{
  context->pika            = NULL;

  context->parent          = NULL;

  context->defined_props   = PIKA_CONTEXT_PROP_MASK_ALL;
  context->serialize_props = PIKA_CONTEXT_PROP_MASK_ALL;

  context->image           = NULL;
  context->display         = NULL;

  context->tool_info       = NULL;
  context->tool_name       = NULL;

  context->paint_info      = NULL;
  context->paint_name      = NULL;

  context->brush           = NULL;
  context->brush_name      = NULL;

  context->dynamics        = NULL;
  context->dynamics_name   = NULL;

  context->mybrush         = NULL;
  context->mybrush_name    = NULL;

  context->pattern         = NULL;
  context->pattern_name    = NULL;

  context->gradient        = NULL;
  context->gradient_name   = NULL;

  context->palette         = NULL;
  context->palette_name    = NULL;

  context->font            = NULL;
  context->font_name       = NULL;

  context->tool_preset      = NULL;
  context->tool_preset_name = NULL;

  context->buffer          = NULL;
  context->buffer_name     = NULL;

  context->imagefile       = NULL;
  context->imagefile_name  = NULL;

  context->template        = NULL;
  context->template_name   = NULL;

  context->line_art            = NULL;
  context->line_art_timeout_id = 0;
}

static void
pika_context_config_iface_init (PikaConfigInterface *iface)
{
  parent_config_iface = g_type_interface_peek_parent (iface);

  if (! parent_config_iface)
    parent_config_iface = g_type_default_interface_peek (PIKA_TYPE_CONFIG);

  iface->serialize            = pika_context_serialize;
  iface->deserialize          = pika_context_deserialize;
  iface->serialize_property   = pika_context_serialize_property;
  iface->deserialize_property = pika_context_deserialize_property;
  iface->duplicate            = pika_context_duplicate;
  iface->copy                 = pika_context_copy;
}

static void
pika_context_constructed (GObject *object)
{
  Pika          *pika;
  PikaContainer *container;

  G_OBJECT_CLASS (parent_class)->constructed (object);

  pika = PIKA_CONTEXT (object)->pika;

  pika_assert (PIKA_IS_PIKA (pika));

  pika->context_list = g_list_prepend (pika->context_list, object);

  g_signal_connect_object (pika->images, "remove",
                           G_CALLBACK (pika_context_image_removed),
                           object, 0);
  g_signal_connect_object (pika->displays, "remove",
                           G_CALLBACK (pika_context_display_removed),
                           object, 0);

  g_signal_connect_object (pika->tool_info_list, "remove",
                           G_CALLBACK (pika_context_tool_removed),
                           object, 0);
  g_signal_connect_object (pika->tool_info_list, "thaw",
                           G_CALLBACK (pika_context_tool_list_thaw),
                           object, 0);

  g_signal_connect_object (pika->paint_info_list, "remove",
                           G_CALLBACK (pika_context_paint_info_removed),
                           object, 0);
  g_signal_connect_object (pika->paint_info_list, "thaw",
                           G_CALLBACK (pika_context_paint_info_list_thaw),
                           object, 0);

  container = pika_data_factory_get_container (pika->brush_factory);
  g_signal_connect_object (container, "remove",
                           G_CALLBACK (pika_context_brush_removed),
                           object, 0);
  g_signal_connect_object (container, "thaw",
                           G_CALLBACK (pika_context_brush_list_thaw),
                           object, 0);

  container = pika_data_factory_get_container (pika->dynamics_factory);
  g_signal_connect_object (container, "remove",
                           G_CALLBACK (pika_context_dynamics_removed),
                           object, 0);
  g_signal_connect_object (container, "thaw",
                           G_CALLBACK (pika_context_dynamics_list_thaw),
                           object, 0);

  container = pika_data_factory_get_container (pika->mybrush_factory);
  g_signal_connect_object (container, "remove",
                           G_CALLBACK (pika_context_mybrush_removed),
                           object, 0);
  g_signal_connect_object (container, "thaw",
                           G_CALLBACK (pika_context_mybrush_list_thaw),
                           object, 0);

  container = pika_data_factory_get_container (pika->pattern_factory);
  g_signal_connect_object (container, "remove",
                           G_CALLBACK (pika_context_pattern_removed),
                           object, 0);
  g_signal_connect_object (container, "thaw",
                           G_CALLBACK (pika_context_pattern_list_thaw),
                           object, 0);

  container = pika_data_factory_get_container (pika->gradient_factory);
  g_signal_connect_object (container, "remove",
                           G_CALLBACK (pika_context_gradient_removed),
                           object, 0);
  g_signal_connect_object (container, "thaw",
                           G_CALLBACK (pika_context_gradient_list_thaw),
                           object, 0);

  container = pika_data_factory_get_container (pika->palette_factory);
  g_signal_connect_object (container, "remove",
                           G_CALLBACK (pika_context_palette_removed),
                           object, 0);
  g_signal_connect_object (container, "thaw",
                           G_CALLBACK (pika_context_palette_list_thaw),
                           object, 0);

  container = pika_data_factory_get_container (pika->font_factory);
  g_signal_connect_object (container, "remove",
                           G_CALLBACK (pika_context_font_removed),
                           object, 0);
  g_signal_connect_object (container, "thaw",
                           G_CALLBACK (pika_context_font_list_thaw),
                           object, 0);

  container = pika_data_factory_get_container (pika->tool_preset_factory);
  g_signal_connect_object (container, "remove",
                           G_CALLBACK (pika_context_tool_preset_removed),
                           object, 0);
  g_signal_connect_object (container, "thaw",
                           G_CALLBACK (pika_context_tool_preset_list_thaw),
                           object, 0);

  g_signal_connect_object (pika->named_buffers, "remove",
                           G_CALLBACK (pika_context_buffer_removed),
                           object, 0);
  g_signal_connect_object (pika->named_buffers, "thaw",
                           G_CALLBACK (pika_context_buffer_list_thaw),
                           object, 0);

  g_signal_connect_object (pika->documents, "remove",
                           G_CALLBACK (pika_context_imagefile_removed),
                           object, 0);
  g_signal_connect_object (pika->documents, "thaw",
                           G_CALLBACK (pika_context_imagefile_list_thaw),
                           object, 0);

  g_signal_connect_object (pika->templates, "remove",
                           G_CALLBACK (pika_context_template_removed),
                           object, 0);
  g_signal_connect_object (pika->templates, "thaw",
                           G_CALLBACK (pika_context_template_list_thaw),
                           object, 0);

  pika_context_set_paint_info (PIKA_CONTEXT (object),
                               pika_paint_info_get_standard (pika));
}

static void
pika_context_dispose (GObject *object)
{
  PikaContext *context = PIKA_CONTEXT (object);

  pika_context_set_parent (context, NULL);

  if (context->pika)
    {
      context->pika->context_list = g_list_remove (context->pika->context_list,
                                                   context);
      context->pika = NULL;
    }

  g_clear_object (&context->tool_info);
  g_clear_object (&context->paint_info);
  g_clear_object (&context->brush);
  g_clear_object (&context->dynamics);
  g_clear_object (&context->mybrush);
  g_clear_object (&context->pattern);
  g_clear_object (&context->gradient);
  g_clear_object (&context->palette);
  g_clear_object (&context->font);
  g_clear_object (&context->tool_preset);
  g_clear_object (&context->buffer);
  g_clear_object (&context->imagefile);
  g_clear_object (&context->template);

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

static void
pika_context_finalize (GObject *object)
{
  PikaContext *context = PIKA_CONTEXT (object);

  context->parent  = NULL;
  context->image   = NULL;
  context->display = NULL;

  g_clear_pointer (&context->tool_name,        g_free);
  g_clear_pointer (&context->paint_name,       g_free);
  g_clear_pointer (&context->brush_name,       g_free);
  g_clear_pointer (&context->dynamics_name,    g_free);
  g_clear_pointer (&context->mybrush_name,     g_free);
  g_clear_pointer (&context->pattern_name,     g_free);
  g_clear_pointer (&context->gradient_name,    g_free);
  g_clear_pointer (&context->palette_name,     g_free);
  g_clear_pointer (&context->font_name,        g_free);
  g_clear_pointer (&context->tool_preset_name, g_free);
  g_clear_pointer (&context->buffer_name,      g_free);
  g_clear_pointer (&context->imagefile_name,   g_free);
  g_clear_pointer (&context->template_name,    g_free);

  g_clear_object (&context->line_art);

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

static void
pika_context_set_property (GObject      *object,
                           guint         property_id,
                           const GValue *value,
                           GParamSpec   *pspec)
{
  PikaContext *context = PIKA_CONTEXT (object);

  switch (property_id)
    {
    case PIKA_CONTEXT_PROP_PIKA:
      context->pika = g_value_get_object (value);
      break;
    case PIKA_CONTEXT_PROP_IMAGE:
      pika_context_set_image (context, g_value_get_object (value));
      break;
    case PIKA_CONTEXT_PROP_DISPLAY:
      pika_context_set_display (context, g_value_get_object (value));
      break;
    case PIKA_CONTEXT_PROP_TOOL:
      pika_context_set_tool (context, g_value_get_object (value));
      break;
    case PIKA_CONTEXT_PROP_PAINT_INFO:
      pika_context_set_paint_info (context, g_value_get_object (value));
      break;
    case PIKA_CONTEXT_PROP_FOREGROUND:
      pika_context_set_foreground (context, g_value_get_boxed (value));
      break;
    case PIKA_CONTEXT_PROP_BACKGROUND:
      pika_context_set_background (context, g_value_get_boxed (value));
      break;
    case PIKA_CONTEXT_PROP_OPACITY:
      pika_context_set_opacity (context, g_value_get_double (value));
      break;
    case PIKA_CONTEXT_PROP_PAINT_MODE:
      pika_context_set_paint_mode (context, g_value_get_enum (value));
      break;
    case PIKA_CONTEXT_PROP_BRUSH:
      pika_context_set_brush (context, g_value_get_object (value));
      break;
    case PIKA_CONTEXT_PROP_DYNAMICS:
      pika_context_set_dynamics (context, g_value_get_object (value));
      break;
    case PIKA_CONTEXT_PROP_MYBRUSH:
      pika_context_set_mybrush (context, g_value_get_object (value));
      break;
    case PIKA_CONTEXT_PROP_PATTERN:
      pika_context_set_pattern (context, g_value_get_object (value));
      break;
    case PIKA_CONTEXT_PROP_GRADIENT:
      pika_context_set_gradient (context, g_value_get_object (value));
      break;
    case PIKA_CONTEXT_PROP_PALETTE:
      pika_context_set_palette (context, g_value_get_object (value));
      break;
    case PIKA_CONTEXT_PROP_FONT:
      pika_context_set_font (context, g_value_get_object (value));
      break;
    case PIKA_CONTEXT_PROP_TOOL_PRESET:
      pika_context_set_tool_preset (context, g_value_get_object (value));
      break;
    case PIKA_CONTEXT_PROP_BUFFER:
      pika_context_set_buffer (context, g_value_get_object (value));
      break;
    case PIKA_CONTEXT_PROP_IMAGEFILE:
      pika_context_set_imagefile (context, g_value_get_object (value));
      break;
    case PIKA_CONTEXT_PROP_TEMPLATE:
      pika_context_set_template (context, g_value_get_object (value));
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
pika_context_get_property (GObject    *object,
                           guint       property_id,
                           GValue     *value,
                           GParamSpec *pspec)
{
  PikaContext *context = PIKA_CONTEXT (object);

  switch (property_id)
    {
    case PIKA_CONTEXT_PROP_PIKA:
      g_value_set_object (value, context->pika);
      break;
    case PIKA_CONTEXT_PROP_IMAGE:
      g_value_set_object (value, pika_context_get_image (context));
      break;
    case PIKA_CONTEXT_PROP_DISPLAY:
      g_value_set_object (value, pika_context_get_display (context));
      break;
    case PIKA_CONTEXT_PROP_TOOL:
      g_value_set_object (value, pika_context_get_tool (context));
      break;
    case PIKA_CONTEXT_PROP_PAINT_INFO:
      g_value_set_object (value, pika_context_get_paint_info (context));
      break;
    case PIKA_CONTEXT_PROP_FOREGROUND:
      {
        PikaRGB color;

        pika_context_get_foreground (context, &color);
        g_value_set_boxed (value, &color);
      }
      break;
    case PIKA_CONTEXT_PROP_BACKGROUND:
      {
        PikaRGB color;

        pika_context_get_background (context, &color);
        g_value_set_boxed (value, &color);
      }
      break;
    case PIKA_CONTEXT_PROP_OPACITY:
      g_value_set_double (value, pika_context_get_opacity (context));
      break;
    case PIKA_CONTEXT_PROP_PAINT_MODE:
      g_value_set_enum (value, pika_context_get_paint_mode (context));
      break;
    case PIKA_CONTEXT_PROP_BRUSH:
      g_value_set_object (value, pika_context_get_brush (context));
      break;
    case PIKA_CONTEXT_PROP_DYNAMICS:
      g_value_set_object (value, pika_context_get_dynamics (context));
      break;
    case PIKA_CONTEXT_PROP_MYBRUSH:
      g_value_set_object (value, pika_context_get_mybrush (context));
      break;
    case PIKA_CONTEXT_PROP_PATTERN:
      g_value_set_object (value, pika_context_get_pattern (context));
      break;
    case PIKA_CONTEXT_PROP_GRADIENT:
      g_value_set_object (value, pika_context_get_gradient (context));
      break;
    case PIKA_CONTEXT_PROP_PALETTE:
      g_value_set_object (value, pika_context_get_palette (context));
      break;
    case PIKA_CONTEXT_PROP_FONT:
      g_value_set_object (value, pika_context_get_font (context));
      break;
    case PIKA_CONTEXT_PROP_TOOL_PRESET:
      g_value_set_object (value, pika_context_get_tool_preset (context));
      break;
    case PIKA_CONTEXT_PROP_BUFFER:
      g_value_set_object (value, pika_context_get_buffer (context));
      break;
    case PIKA_CONTEXT_PROP_IMAGEFILE:
      g_value_set_object (value, pika_context_get_imagefile (context));
      break;
    case PIKA_CONTEXT_PROP_TEMPLATE:
      g_value_set_object (value, pika_context_get_template (context));
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static gint64
pika_context_get_memsize (PikaObject *object,
                          gint64     *gui_size)
{
  PikaContext *context = PIKA_CONTEXT (object);
  gint64       memsize = 0;

  memsize += pika_string_get_memsize (context->tool_name);
  memsize += pika_string_get_memsize (context->paint_name);
  memsize += pika_string_get_memsize (context->brush_name);
  memsize += pika_string_get_memsize (context->dynamics_name);
  memsize += pika_string_get_memsize (context->mybrush_name);
  memsize += pika_string_get_memsize (context->pattern_name);
  memsize += pika_string_get_memsize (context->palette_name);
  memsize += pika_string_get_memsize (context->font_name);
  memsize += pika_string_get_memsize (context->tool_preset_name);
  memsize += pika_string_get_memsize (context->buffer_name);
  memsize += pika_string_get_memsize (context->imagefile_name);
  memsize += pika_string_get_memsize (context->template_name);

  return memsize + PIKA_OBJECT_CLASS (parent_class)->get_memsize (object,
                                                                  gui_size);
}

static gboolean
pika_context_serialize (PikaConfig       *config,
                        PikaConfigWriter *writer,
                        gpointer          data)
{
  return pika_config_serialize_changed_properties (config, writer);
}

static gboolean
pika_context_deserialize (PikaConfig *config,
                          GScanner   *scanner,
                          gint        nest_level,
                          gpointer    data)
{
  PikaContext   *context        = PIKA_CONTEXT (config);
  PikaLayerMode  old_paint_mode = context->paint_mode;
  gboolean       success;

  success = pika_config_deserialize_properties (config, scanner, nest_level);

  if (context->paint_mode != old_paint_mode)
    {
      if (context->paint_mode == PIKA_LAYER_MODE_OVERLAY_LEGACY)
        g_object_set (context,
                      "paint-mode", PIKA_LAYER_MODE_SOFTLIGHT_LEGACY,
                      NULL);
    }

  return success;
}

static gboolean
pika_context_serialize_property (PikaConfig       *config,
                                 guint             property_id,
                                 const GValue     *value,
                                 GParamSpec       *pspec,
                                 PikaConfigWriter *writer)
{
  PikaContext *context = PIKA_CONTEXT (config);
  PikaObject  *serialize_obj;

  /*  serialize nothing if the property is not in serialize_props  */
  if (! ((1 << property_id) & context->serialize_props))
    return TRUE;

  switch (property_id)
    {
    case PIKA_CONTEXT_PROP_TOOL:
    case PIKA_CONTEXT_PROP_PAINT_INFO:
    case PIKA_CONTEXT_PROP_BRUSH:
    case PIKA_CONTEXT_PROP_DYNAMICS:
    case PIKA_CONTEXT_PROP_MYBRUSH:
    case PIKA_CONTEXT_PROP_PATTERN:
    case PIKA_CONTEXT_PROP_GRADIENT:
    case PIKA_CONTEXT_PROP_PALETTE:
    case PIKA_CONTEXT_PROP_FONT:
    case PIKA_CONTEXT_PROP_TOOL_PRESET:
      serialize_obj = g_value_get_object (value);
      break;

    default:
      return FALSE;
    }

  pika_config_writer_open (writer, pspec->name);

  if (serialize_obj)
    pika_config_writer_string (writer, pika_object_get_name (serialize_obj));
  else
    pika_config_writer_print (writer, "NULL", 4);

  pika_config_writer_close (writer);

  return TRUE;
}

static gboolean
pika_context_deserialize_property (PikaConfig *object,
                                   guint       property_id,
                                   GValue     *value,
                                   GParamSpec *pspec,
                                   GScanner   *scanner,
                                   GTokenType *expected)
{
  PikaContext   *context = PIKA_CONTEXT (object);
  PikaContainer *container;
  gpointer       standard;
  gchar        **name_loc;
  gchar         *object_name;

  switch (property_id)
    {
    case PIKA_CONTEXT_PROP_TOOL:
      container = context->pika->tool_info_list;
      standard  = pika_tool_info_get_standard (context->pika);
      name_loc  = &context->tool_name;
      break;

    case PIKA_CONTEXT_PROP_PAINT_INFO:
      container = context->pika->paint_info_list;
      standard  = pika_paint_info_get_standard (context->pika);
      name_loc  = &context->paint_name;
      break;

    case PIKA_CONTEXT_PROP_BRUSH:
      container = pika_data_factory_get_container (context->pika->brush_factory);
      standard  = pika_brush_get_standard (context);
      name_loc  = &context->brush_name;
      break;

    case PIKA_CONTEXT_PROP_DYNAMICS:
      container = pika_data_factory_get_container (context->pika->dynamics_factory);
      standard  = pika_dynamics_get_standard (context);
      name_loc  = &context->dynamics_name;
      break;

    case PIKA_CONTEXT_PROP_MYBRUSH:
      container = pika_data_factory_get_container (context->pika->mybrush_factory);
      standard  = pika_mybrush_get_standard (context);
      name_loc  = &context->mybrush_name;
      break;

    case PIKA_CONTEXT_PROP_PATTERN:
      container = pika_data_factory_get_container (context->pika->pattern_factory);
      standard  = pika_pattern_get_standard (context);
      name_loc  = &context->pattern_name;
      break;

    case PIKA_CONTEXT_PROP_GRADIENT:
      container = pika_data_factory_get_container (context->pika->gradient_factory);
      standard  = pika_gradient_get_standard (context);
      name_loc  = &context->gradient_name;
      break;

    case PIKA_CONTEXT_PROP_PALETTE:
      container = pika_data_factory_get_container (context->pika->palette_factory);
      standard  = pika_palette_get_standard (context);
      name_loc  = &context->palette_name;
      break;

    case PIKA_CONTEXT_PROP_FONT:
      container = pika_data_factory_get_container (context->pika->font_factory);
      standard  = pika_font_get_standard ();
      name_loc  = &context->font_name;
      break;

    case PIKA_CONTEXT_PROP_TOOL_PRESET:
      container = pika_data_factory_get_container (context->pika->tool_preset_factory);
      standard  = NULL;
      name_loc  = &context->tool_preset_name;
      break;

    default:
      return FALSE;
    }

  if (pika_scanner_parse_identifier (scanner, "NULL"))
    {
      g_value_set_object (value, NULL);
    }
  else if (pika_scanner_parse_string (scanner, &object_name))
    {
      PikaObject *deserialize_obj;

      if (! object_name)
        object_name = g_strdup ("");

      deserialize_obj = pika_container_get_child_by_name (container,
                                                          object_name);

      if (! deserialize_obj)
        {
          g_value_set_object (value, standard);

          g_free (*name_loc);
          *name_loc = g_strdup (object_name);
        }
      else
        {
          g_value_set_object (value, deserialize_obj);
        }

      g_free (object_name);
    }
  else
    {
      *expected = G_TOKEN_STRING;
    }

  return TRUE;
}

static PikaConfig *
pika_context_duplicate (PikaConfig *config)
{
  PikaContext *context = PIKA_CONTEXT (config);
  PikaContext *new;

  new = PIKA_CONTEXT (parent_config_iface->duplicate (config));

  COPY_NAME (context, new, tool_name);
  COPY_NAME (context, new, paint_name);
  COPY_NAME (context, new, brush_name);
  COPY_NAME (context, new, dynamics_name);
  COPY_NAME (context, new, mybrush_name);
  COPY_NAME (context, new, pattern_name);
  COPY_NAME (context, new, gradient_name);
  COPY_NAME (context, new, palette_name);
  COPY_NAME (context, new, font_name);
  COPY_NAME (context, new, tool_preset_name);
  COPY_NAME (context, new, buffer_name);
  COPY_NAME (context, new, imagefile_name);
  COPY_NAME (context, new, template_name);

  return PIKA_CONFIG (new);
}

static gboolean
pika_context_copy (PikaConfig  *src,
                   PikaConfig  *dest,
                   GParamFlags  flags)
{
  PikaContext *src_context  = PIKA_CONTEXT (src);
  PikaContext *dest_context = PIKA_CONTEXT (dest);
  gboolean     success      = parent_config_iface->copy (src, dest, flags);

  COPY_NAME (src_context, dest_context, tool_name);
  COPY_NAME (src_context, dest_context, paint_name);
  COPY_NAME (src_context, dest_context, brush_name);
  COPY_NAME (src_context, dest_context, dynamics_name);
  COPY_NAME (src_context, dest_context, mybrush_name);
  COPY_NAME (src_context, dest_context, pattern_name);
  COPY_NAME (src_context, dest_context, gradient_name);
  COPY_NAME (src_context, dest_context, palette_name);
  COPY_NAME (src_context, dest_context, font_name);
  COPY_NAME (src_context, dest_context, tool_preset_name);
  COPY_NAME (src_context, dest_context, buffer_name);
  COPY_NAME (src_context, dest_context, imagefile_name);
  COPY_NAME (src_context, dest_context, template_name);

  return success;
}


/*****************************************************************************/
/*  public functions  ********************************************************/

PikaContext *
pika_context_new (Pika        *pika,
                  const gchar *name,
                  PikaContext *template)
{
  PikaContext *context;

  g_return_val_if_fail (PIKA_IS_PIKA (pika), NULL);
  g_return_val_if_fail (name != NULL, NULL);
  g_return_val_if_fail (template == NULL || PIKA_IS_CONTEXT (template), NULL);

  context = g_object_new (PIKA_TYPE_CONTEXT,
                          "name", name,
                          "pika", pika,
                          NULL);

  if (template)
    {
      context->defined_props = template->defined_props;

      pika_context_copy_properties (template, context,
                                    PIKA_CONTEXT_PROP_MASK_ALL);
    }

  return context;
}

PikaContext *
pika_context_get_parent (PikaContext *context)
{
  g_return_val_if_fail (PIKA_IS_CONTEXT (context), NULL);

  return context->parent;
}

static void
pika_context_parent_notify (PikaContext *parent,
                            GParamSpec  *pspec,
                            PikaContext *context)
{
  if (pspec->owner_type == PIKA_TYPE_CONTEXT)
    {
      PikaContextPropType prop = pspec->param_id;

      /*  copy from parent if the changed property is undefined;
       *  ignore properties that are not context properties, for
       *  example notifications on the context's "pika" property
       */
      if ((prop >= PIKA_CONTEXT_PROP_FIRST) &&
          (prop <= PIKA_CONTEXT_PROP_LAST)  &&
          ! ((1 << prop) & context->defined_props))
        {
          pika_context_copy_property (parent, context, prop);
        }
    }
}

void
pika_context_set_parent (PikaContext *context,
                         PikaContext *parent)
{
  g_return_if_fail (PIKA_IS_CONTEXT (context));
  g_return_if_fail (parent == NULL || PIKA_IS_CONTEXT (parent));
  g_return_if_fail (parent == NULL || parent->parent != context);
  g_return_if_fail (context != parent);

  if (context->parent == parent)
    return;

  if (context->parent)
    {
      g_signal_handlers_disconnect_by_func (context->parent,
                                            pika_context_parent_notify,
                                            context);
    }

  g_set_weak_pointer (&context->parent, parent);

  if (parent)
    {
      /*  copy all undefined properties from the new parent  */
      pika_context_copy_properties (parent, context,
                                    ~context->defined_props &
                                    PIKA_CONTEXT_PROP_MASK_ALL);

      g_signal_connect_object (parent, "notify",
                               G_CALLBACK (pika_context_parent_notify),
                               context,
                               0);
    }
}


/*  define / undefinine context properties  */

void
pika_context_define_property (PikaContext         *context,
                              PikaContextPropType  prop,
                              gboolean             defined)
{
  PikaContextPropMask mask;

  g_return_if_fail (PIKA_IS_CONTEXT (context));
  g_return_if_fail ((prop >= PIKA_CONTEXT_PROP_FIRST) &&
                    (prop <= PIKA_CONTEXT_PROP_LAST));

  mask = (1 << prop);

  if (defined)
    {
      if (! (context->defined_props & mask))
        {
          context->defined_props |= mask;
        }
    }
  else
    {
      if (context->defined_props & mask)
        {
          context->defined_props &= ~mask;

          if (context->parent)
            pika_context_copy_property (context->parent, context, prop);
        }
    }
}

gboolean
pika_context_property_defined (PikaContext         *context,
                               PikaContextPropType  prop)
{
  g_return_val_if_fail (PIKA_IS_CONTEXT (context), FALSE);

  return (context->defined_props & (1 << prop)) ? TRUE : FALSE;
}

void
pika_context_define_properties (PikaContext         *context,
                                PikaContextPropMask  prop_mask,
                                gboolean             defined)
{
  PikaContextPropType prop;

  g_return_if_fail (PIKA_IS_CONTEXT (context));

  for (prop = PIKA_CONTEXT_PROP_FIRST; prop <= PIKA_CONTEXT_PROP_LAST; prop++)
    if ((1 << prop) & prop_mask)
      pika_context_define_property (context, prop, defined);
}


/*  specify which context properties will be serialized  */

void
pika_context_set_serialize_properties (PikaContext         *context,
                                       PikaContextPropMask  props_mask)
{
  g_return_if_fail (PIKA_IS_CONTEXT (context));

  context->serialize_props = props_mask;
}

PikaContextPropMask
pika_context_get_serialize_properties (PikaContext *context)
{
  g_return_val_if_fail (PIKA_IS_CONTEXT (context), 0);

  return context->serialize_props;
}


/*  copying context properties  */

void
pika_context_copy_property (PikaContext         *src,
                            PikaContext         *dest,
                            PikaContextPropType  prop)
{
  g_return_if_fail (PIKA_IS_CONTEXT (src));
  g_return_if_fail (PIKA_IS_CONTEXT (dest));
  g_return_if_fail ((prop >= PIKA_CONTEXT_PROP_FIRST) &&
                    (prop <= PIKA_CONTEXT_PROP_LAST));

  switch (prop)
    {
    case PIKA_CONTEXT_PROP_IMAGE:
      pika_context_real_set_image (dest, src->image);
      break;

    case PIKA_CONTEXT_PROP_DISPLAY:
      pika_context_real_set_display (dest, src->display);
      break;

    case PIKA_CONTEXT_PROP_TOOL:
      pika_context_real_set_tool (dest, src->tool_info);
      COPY_NAME (src, dest, tool_name);
      break;

    case PIKA_CONTEXT_PROP_PAINT_INFO:
      pika_context_real_set_paint_info (dest, src->paint_info);
      COPY_NAME (src, dest, paint_name);
      break;

    case PIKA_CONTEXT_PROP_FOREGROUND:
      pika_context_real_set_foreground (dest, &src->foreground);
      break;

    case PIKA_CONTEXT_PROP_BACKGROUND:
      pika_context_real_set_background (dest, &src->background);
      break;

    case PIKA_CONTEXT_PROP_OPACITY:
      pika_context_real_set_opacity (dest, src->opacity);
      break;

    case PIKA_CONTEXT_PROP_PAINT_MODE:
      pika_context_real_set_paint_mode (dest, src->paint_mode);
      break;

    case PIKA_CONTEXT_PROP_BRUSH:
      pika_context_real_set_brush (dest, src->brush);
      COPY_NAME (src, dest, brush_name);
      break;

    case PIKA_CONTEXT_PROP_DYNAMICS:
      pika_context_real_set_dynamics (dest, src->dynamics);
      COPY_NAME (src, dest, dynamics_name);
      break;

    case PIKA_CONTEXT_PROP_MYBRUSH:
      pika_context_real_set_mybrush (dest, src->mybrush);
      COPY_NAME (src, dest, mybrush_name);
      break;

    case PIKA_CONTEXT_PROP_PATTERN:
      pika_context_real_set_pattern (dest, src->pattern);
      COPY_NAME (src, dest, pattern_name);
      break;

    case PIKA_CONTEXT_PROP_GRADIENT:
      pika_context_real_set_gradient (dest, src->gradient);
      COPY_NAME (src, dest, gradient_name);
      break;

    case PIKA_CONTEXT_PROP_PALETTE:
      pika_context_real_set_palette (dest, src->palette);
      COPY_NAME (src, dest, palette_name);
      break;

    case PIKA_CONTEXT_PROP_FONT:
      pika_context_real_set_font (dest, src->font);
      COPY_NAME (src, dest, font_name);
      break;

    case PIKA_CONTEXT_PROP_TOOL_PRESET:
      pika_context_real_set_tool_preset (dest, src->tool_preset);
      COPY_NAME (src, dest, tool_preset_name);
      break;

    case PIKA_CONTEXT_PROP_BUFFER:
      pika_context_real_set_buffer (dest, src->buffer);
      COPY_NAME (src, dest, buffer_name);
      break;

    case PIKA_CONTEXT_PROP_IMAGEFILE:
      pika_context_real_set_imagefile (dest, src->imagefile);
      COPY_NAME (src, dest, imagefile_name);
      break;

    case PIKA_CONTEXT_PROP_TEMPLATE:
      pika_context_real_set_template (dest, src->template);
      COPY_NAME (src, dest, template_name);
      break;

    default:
      break;
    }
}

void
pika_context_copy_properties (PikaContext         *src,
                              PikaContext         *dest,
                              PikaContextPropMask  prop_mask)
{
  PikaContextPropType prop;

  g_return_if_fail (PIKA_IS_CONTEXT (src));
  g_return_if_fail (PIKA_IS_CONTEXT (dest));

  for (prop = PIKA_CONTEXT_PROP_FIRST; prop <= PIKA_CONTEXT_PROP_LAST; prop++)
    if ((1 << prop) & prop_mask)
      pika_context_copy_property (src, dest, prop);
}


/*  attribute access functions  */

/*****************************************************************************/
/*  manipulate by GType  *****************************************************/

PikaContextPropType
pika_context_type_to_property (GType type)
{
  PikaContextPropType prop;

  for (prop = PIKA_CONTEXT_PROP_FIRST; prop <= PIKA_CONTEXT_PROP_LAST; prop++)
    {
      if (g_type_is_a (type, pika_context_prop_types[prop]))
        return prop;
    }

  return -1;
}

const gchar *
pika_context_type_to_prop_name (GType type)
{
  PikaContextPropType prop;

  for (prop = PIKA_CONTEXT_PROP_FIRST; prop <= PIKA_CONTEXT_PROP_LAST; prop++)
    {
      if (g_type_is_a (type, pika_context_prop_types[prop]))
        return pika_context_prop_names[prop];
    }

  return NULL;
}

const gchar *
pika_context_type_to_signal_name (GType type)
{
  PikaContextPropType prop;

  for (prop = PIKA_CONTEXT_PROP_FIRST; prop <= PIKA_CONTEXT_PROP_LAST; prop++)
    {
      if (g_type_is_a (type, pika_context_prop_types[prop]))
        return g_signal_name (pika_context_signals[prop]);
    }

  return NULL;
}

PikaObject *
pika_context_get_by_type (PikaContext *context,
                          GType        type)
{
  PikaContextPropType  prop;
  PikaObject          *object = NULL;

  g_return_val_if_fail (PIKA_IS_CONTEXT (context), NULL);

  prop = pika_context_type_to_property (type);

  g_return_val_if_fail (prop != -1, NULL);

  g_object_get (context,
                pika_context_prop_names[prop], &object,
                NULL);

  /*  g_object_get() refs the object, this function however is a getter,
   *  which usually doesn't ref it's return value
   */
  if (object)
    g_object_unref (object);

  return object;
}

void
pika_context_set_by_type (PikaContext *context,
                          GType        type,
                          PikaObject  *object)
{
  PikaContextPropType  prop;
  GParamSpec          *pspec;
  GValue               value = G_VALUE_INIT;

  g_return_if_fail (PIKA_IS_CONTEXT (context));
  g_return_if_fail (object == NULL || G_IS_OBJECT (object));

  prop = pika_context_type_to_property (type);

  g_return_if_fail (prop != -1);

  pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (context),
                                        pika_context_prop_names[prop]);

  g_return_if_fail (pspec != NULL);

  g_value_init (&value, pspec->value_type);
  g_value_set_object (&value, object);

  /*  we use pika_context_set_property() (which in turn only calls
   *  pika_context_set_foo() functions) instead of the much more obvious
   *  g_object_set(); this avoids g_object_freeze_notify()/thaw_notify()
   *  around the g_object_set() and makes PikaContext callbacks being
   *  called in a much more predictable order. See bug #731279.
   */
  pika_context_set_property (G_OBJECT (context),
                             pspec->param_id,
                             (const GValue *) &value,
                             pspec);

  g_value_unset (&value);
}

void
pika_context_changed_by_type (PikaContext *context,
                              GType        type)
{
  PikaContextPropType  prop;
  PikaObject          *object;

  g_return_if_fail (PIKA_IS_CONTEXT (context));

  prop = pika_context_type_to_property (type);

  g_return_if_fail (prop != -1);

  object = pika_context_get_by_type (context, type);

  g_signal_emit (context,
                 pika_context_signals[prop], 0,
                 object);
}


/*****************************************************************************/
/*  image  *******************************************************************/

PikaImage *
pika_context_get_image (PikaContext *context)
{
  g_return_val_if_fail (PIKA_IS_CONTEXT (context), NULL);

  return context->image;
}

void
pika_context_set_image (PikaContext *context,
                        PikaImage   *image)
{
  g_return_if_fail (PIKA_IS_CONTEXT (context));
  g_return_if_fail (image == NULL || PIKA_IS_IMAGE (image));

  context_find_defined (context, PIKA_CONTEXT_PROP_IMAGE);

  pika_context_real_set_image (context, image);
}

void
pika_context_image_changed (PikaContext *context)
{
  g_return_if_fail (PIKA_IS_CONTEXT (context));

  g_signal_emit (context,
                 pika_context_signals[IMAGE_CHANGED], 0,
                 context->image);
}

static void
pika_context_image_removed (PikaContainer *container,
                            PikaImage     *image,
                            PikaContext   *context)
{
  if (context->image == image)
    pika_context_real_set_image (context, NULL);
}

static void
pika_context_real_set_image (PikaContext *context,
                             PikaImage   *image)
{
  if (context->image == image)
    return;

  context->image = image;

  g_object_notify (G_OBJECT (context), "image");
  pika_context_image_changed (context);
}


/*****************************************************************************/
/*  display  *****************************************************************/

PikaDisplay *
pika_context_get_display (PikaContext *context)
{
  g_return_val_if_fail (PIKA_IS_CONTEXT (context), NULL);

  return context->display;
}

void
pika_context_set_display (PikaContext *context,
                          PikaDisplay *display)
{
  g_return_if_fail (PIKA_IS_CONTEXT (context));
  g_return_if_fail (display == NULL || PIKA_IS_DISPLAY (display));

  context_find_defined (context, PIKA_CONTEXT_PROP_DISPLAY);

  pika_context_real_set_display (context, display);
}

void
pika_context_display_changed (PikaContext *context)
{
  g_return_if_fail (PIKA_IS_CONTEXT (context));

  g_signal_emit (context,
                 pika_context_signals[DISPLAY_CHANGED], 0,
                 context->display);
}

static void
pika_context_display_removed (PikaContainer *container,
                              PikaDisplay   *display,
                              PikaContext   *context)
{
  if (context->display == display)
    pika_context_real_set_display (context, NULL);
}

static void
pika_context_real_set_display (PikaContext *context,
                               PikaDisplay *display)
{
  PikaDisplay *old_display;

  if (context->display == display)
    {
      /*  make sure that setting a display *always* sets the image
       *  to that display's image, even if the display already
       *  matches
       */
      if (display)
        {
          PikaImage *image;

          g_object_get (display, "image", &image, NULL);

          pika_context_real_set_image (context, image);

          if (image)
            g_object_unref (image);
        }

      return;
    }

  old_display = context->display;

  context->display = display;

  if (context->display)
    {
      PikaImage *image;

      g_object_get (display, "image", &image, NULL);

      pika_context_real_set_image (context, image);

      if (image)
        g_object_unref (image);
    }
  else if (old_display)
    {
      pika_context_real_set_image (context, NULL);
    }

  g_object_notify (G_OBJECT (context), "display");
  pika_context_display_changed (context);
}


/*****************************************************************************/
/*  tool  ********************************************************************/

PikaToolInfo *
pika_context_get_tool (PikaContext *context)
{
  g_return_val_if_fail (PIKA_IS_CONTEXT (context), NULL);

  return context->tool_info;
}

void
pika_context_set_tool (PikaContext  *context,
                       PikaToolInfo *tool_info)
{
  g_return_if_fail (PIKA_IS_CONTEXT (context));
  g_return_if_fail (tool_info == NULL || PIKA_IS_TOOL_INFO (tool_info));

  context_find_defined (context, PIKA_CONTEXT_PROP_TOOL);

  pika_context_real_set_tool (context, tool_info);
}

void
pika_context_tool_changed (PikaContext *context)
{
  g_return_if_fail (PIKA_IS_CONTEXT (context));

  g_signal_emit (context,
                 pika_context_signals[TOOL_CHANGED], 0,
                 context->tool_info);
}

static void
pika_context_tool_dirty (PikaToolInfo *tool_info,
                         PikaContext  *context)
{
  g_free (context->tool_name);
  context->tool_name = g_strdup (pika_object_get_name (tool_info));

  g_signal_emit (context, pika_context_signals[PROP_NAME_CHANGED], 0,
                 PIKA_CONTEXT_PROP_TOOL);
}

static void
pika_context_tool_list_thaw (PikaContainer *container,
                             PikaContext   *context)
{
  PikaToolInfo *tool_info;

  if (! context->tool_name)
    context->tool_name = g_strdup ("pika-paintbrush-tool");

  tool_info = pika_context_find_object (context, container,
                                        context->tool_name,
                                        pika_tool_info_get_standard (context->pika));

  pika_context_real_set_tool (context, tool_info);
}

static void
pika_context_tool_removed (PikaContainer *container,
                           PikaToolInfo  *tool_info,
                           PikaContext   *context)
{
  if (tool_info == context->tool_info)
    {
      g_signal_handlers_disconnect_by_func (context->tool_info,
                                            pika_context_tool_dirty,
                                            context);
      g_clear_object (&context->tool_info);

      if (! pika_container_frozen (container))
        pika_context_tool_list_thaw (container, context);
    }
}

static void
pika_context_real_set_tool (PikaContext  *context,
                            PikaToolInfo *tool_info)
{
  if (context->tool_info == tool_info)
    return;

  if (context->tool_name &&
      tool_info != pika_tool_info_get_standard (context->pika))
    {
      g_clear_pointer (&context->tool_name, g_free);
    }

  if (context->tool_info)
    g_signal_handlers_disconnect_by_func (context->tool_info,
                                          pika_context_tool_dirty,
                                          context);

  g_set_object (&context->tool_info, tool_info);

  if (tool_info)
    {
      g_signal_connect_object (tool_info, "name-changed",
                               G_CALLBACK (pika_context_tool_dirty),
                               context,
                               0);

      if (tool_info != pika_tool_info_get_standard (context->pika))
        context->tool_name = g_strdup (pika_object_get_name (tool_info));

      if (tool_info->paint_info)
        pika_context_real_set_paint_info (context, tool_info->paint_info);
    }

  g_object_notify (G_OBJECT (context), "tool");
  pika_context_tool_changed (context);
}


/*****************************************************************************/
/*  paint info  **************************************************************/

PikaPaintInfo *
pika_context_get_paint_info (PikaContext *context)
{
  g_return_val_if_fail (PIKA_IS_CONTEXT (context), NULL);

  return context->paint_info;
}

void
pika_context_set_paint_info (PikaContext   *context,
                             PikaPaintInfo *paint_info)
{
  g_return_if_fail (PIKA_IS_CONTEXT (context));
  g_return_if_fail (paint_info == NULL || PIKA_IS_PAINT_INFO (paint_info));

  context_find_defined (context, PIKA_CONTEXT_PROP_PAINT_INFO);

  pika_context_real_set_paint_info (context, paint_info);
}

void
pika_context_paint_info_changed (PikaContext *context)
{
  g_return_if_fail (PIKA_IS_CONTEXT (context));

  g_signal_emit (context,
                 pika_context_signals[PAINT_INFO_CHANGED], 0,
                 context->paint_info);
}

static void
pika_context_paint_info_dirty (PikaPaintInfo *paint_info,
                               PikaContext   *context)
{
  g_free (context->paint_name);
  context->paint_name = g_strdup (pika_object_get_name (paint_info));

  g_signal_emit (context, pika_context_signals[PROP_NAME_CHANGED], 0,
                 PIKA_CONTEXT_PROP_PAINT_INFO);
}

/*  the global paint info list is there again after refresh  */
static void
pika_context_paint_info_list_thaw (PikaContainer *container,
                                   PikaContext   *context)
{
  PikaPaintInfo *paint_info;

  if (! context->paint_name)
    context->paint_name = g_strdup ("pika-paintbrush");

  paint_info = pika_context_find_object (context, container,
                                         context->paint_name,
                                         pika_paint_info_get_standard (context->pika));

  pika_context_real_set_paint_info (context, paint_info);
}

static void
pika_context_paint_info_removed (PikaContainer *container,
                                 PikaPaintInfo *paint_info,
                                 PikaContext   *context)
{
  if (paint_info == context->paint_info)
    {
      g_signal_handlers_disconnect_by_func (context->paint_info,
                                            pika_context_paint_info_dirty,
                                            context);
      g_clear_object (&context->paint_info);

      if (! pika_container_frozen (container))
        pika_context_paint_info_list_thaw (container, context);
    }
}

static void
pika_context_real_set_paint_info (PikaContext   *context,
                                  PikaPaintInfo *paint_info)
{
  if (context->paint_info == paint_info)
    return;

  if (context->paint_name &&
      paint_info != pika_paint_info_get_standard (context->pika))
    {
      g_clear_pointer (&context->paint_name, g_free);
    }

  if (context->paint_info)
    g_signal_handlers_disconnect_by_func (context->paint_info,
                                          pika_context_paint_info_dirty,
                                          context);

  g_set_object (&context->paint_info, paint_info);

  if (paint_info)
    {
      g_signal_connect_object (paint_info, "name-changed",
                               G_CALLBACK (pika_context_paint_info_dirty),
                               context,
                               0);

      if (paint_info != pika_paint_info_get_standard (context->pika))
        context->paint_name = g_strdup (pika_object_get_name (paint_info));
    }

  g_object_notify (G_OBJECT (context), "paint-info");
  pika_context_paint_info_changed (context);
}


/*****************************************************************************/
/*  foreground color  ********************************************************/

void
pika_context_get_foreground (PikaContext *context,
                             PikaRGB     *color)
{
  g_return_if_fail (PIKA_IS_CONTEXT (context));
  g_return_if_fail (color != NULL);

  *color = context->foreground;
}

void
pika_context_set_foreground (PikaContext   *context,
                             const PikaRGB *color)
{
  g_return_if_fail (PIKA_IS_CONTEXT (context));
  g_return_if_fail (color != NULL);

  context_find_defined (context, PIKA_CONTEXT_PROP_FOREGROUND);

  pika_context_real_set_foreground (context, color);
}

void
pika_context_foreground_changed (PikaContext *context)
{
  g_return_if_fail (PIKA_IS_CONTEXT (context));

  g_signal_emit (context,
                 pika_context_signals[FOREGROUND_CHANGED], 0,
                 &context->foreground);
}

static void
pika_context_real_set_foreground (PikaContext   *context,
                                  const PikaRGB *color)
{
  if (pika_rgba_distance (&context->foreground, color) < RGBA_EPSILON)
    return;

  context->foreground = *color;
  pika_rgb_set_alpha (&context->foreground, PIKA_OPACITY_OPAQUE);

  g_object_notify (G_OBJECT (context), "foreground");
  pika_context_foreground_changed (context);
}


/*****************************************************************************/
/*  background color  ********************************************************/

void
pika_context_get_background (PikaContext *context,
                             PikaRGB     *color)
{
  g_return_if_fail (PIKA_IS_CONTEXT (context));

  g_return_if_fail (color != NULL);

  *color = context->background;
}

void
pika_context_set_background (PikaContext   *context,
                             const PikaRGB *color)
{
  g_return_if_fail (PIKA_IS_CONTEXT (context));
  g_return_if_fail (color != NULL);

  context_find_defined (context, PIKA_CONTEXT_PROP_BACKGROUND);

  pika_context_real_set_background (context, color);
}

void
pika_context_background_changed (PikaContext *context)
{
  g_return_if_fail (PIKA_IS_CONTEXT (context));

  g_signal_emit (context,
                 pika_context_signals[BACKGROUND_CHANGED], 0,
                 &context->background);
}

static void
pika_context_real_set_background (PikaContext   *context,
                                  const PikaRGB *color)
{
  if (pika_rgba_distance (&context->background, color) < RGBA_EPSILON)
    return;

  context->background = *color;
  pika_rgb_set_alpha (&context->background, PIKA_OPACITY_OPAQUE);

  g_object_notify (G_OBJECT (context), "background");
  pika_context_background_changed (context);
}


/*****************************************************************************/
/*  color utility functions  *************************************************/

void
pika_context_set_default_colors (PikaContext *context)
{
  PikaContext *bg_context;
  PikaRGB      fg;
  PikaRGB      bg;

  g_return_if_fail (PIKA_IS_CONTEXT (context));

  bg_context = context;

  context_find_defined (context, PIKA_CONTEXT_PROP_FOREGROUND);
  context_find_defined (bg_context, PIKA_CONTEXT_PROP_BACKGROUND);

  pika_rgba_set (&fg, 0.0, 0.0, 0.0, PIKA_OPACITY_OPAQUE);
  pika_rgba_set (&bg, 1.0, 1.0, 1.0, PIKA_OPACITY_OPAQUE);

  pika_context_real_set_foreground (context, &fg);
  pika_context_real_set_background (bg_context, &bg);
}

void
pika_context_swap_colors (PikaContext *context)
{
  PikaContext *bg_context;
  PikaRGB      fg;
  PikaRGB      bg;

  g_return_if_fail (PIKA_IS_CONTEXT (context));

  bg_context = context;

  context_find_defined (context, PIKA_CONTEXT_PROP_FOREGROUND);
  context_find_defined (bg_context, PIKA_CONTEXT_PROP_BACKGROUND);

  pika_context_get_foreground (context, &fg);
  pika_context_get_background (bg_context, &bg);

  pika_context_real_set_foreground (context, &bg);
  pika_context_real_set_background (bg_context, &fg);
}


/*****************************************************************************/
/*  opacity  *****************************************************************/

gdouble
pika_context_get_opacity (PikaContext *context)
{
  g_return_val_if_fail (PIKA_IS_CONTEXT (context), PIKA_OPACITY_OPAQUE);

  return context->opacity;
}

void
pika_context_set_opacity (PikaContext *context,
                          gdouble      opacity)
{
  g_return_if_fail (PIKA_IS_CONTEXT (context));

  context_find_defined (context, PIKA_CONTEXT_PROP_OPACITY);

  pika_context_real_set_opacity (context, opacity);
}

void
pika_context_opacity_changed (PikaContext *context)
{
  g_return_if_fail (PIKA_IS_CONTEXT (context));

  g_signal_emit (context,
                 pika_context_signals[OPACITY_CHANGED], 0,
                 context->opacity);
}

static void
pika_context_real_set_opacity (PikaContext *context,
                               gdouble      opacity)
{
  if (context->opacity == opacity)
    return;

  context->opacity = opacity;

  g_object_notify (G_OBJECT (context), "opacity");
  pika_context_opacity_changed (context);
}


/*****************************************************************************/
/*  paint mode  **************************************************************/

PikaLayerMode
pika_context_get_paint_mode (PikaContext *context)
{
  g_return_val_if_fail (PIKA_IS_CONTEXT (context), PIKA_LAYER_MODE_NORMAL);

  return context->paint_mode;
}

void
pika_context_set_paint_mode (PikaContext   *context,
                             PikaLayerMode  paint_mode)
{
  g_return_if_fail (PIKA_IS_CONTEXT (context));

  context_find_defined (context, PIKA_CONTEXT_PROP_PAINT_MODE);

  pika_context_real_set_paint_mode (context, paint_mode);
}

void
pika_context_paint_mode_changed (PikaContext *context)
{
  g_return_if_fail (PIKA_IS_CONTEXT (context));

  g_signal_emit (context,
                 pika_context_signals[PAINT_MODE_CHANGED], 0,
                 context->paint_mode);
}

static void
pika_context_real_set_paint_mode (PikaContext   *context,
                                  PikaLayerMode  paint_mode)
{
  if (context->paint_mode == paint_mode)
    return;

  context->paint_mode = paint_mode;

  g_object_notify (G_OBJECT (context), "paint-mode");
  pika_context_paint_mode_changed (context);
}


/*****************************************************************************/
/*  brush  *******************************************************************/

PikaBrush *
pika_context_get_brush (PikaContext *context)
{
  g_return_val_if_fail (PIKA_IS_CONTEXT (context), NULL);

  return context->brush;
}

void
pika_context_set_brush (PikaContext *context,
                        PikaBrush   *brush)
{
  g_return_if_fail (PIKA_IS_CONTEXT (context));
  g_return_if_fail (brush == NULL || PIKA_IS_BRUSH (brush));

  context_find_defined (context, PIKA_CONTEXT_PROP_BRUSH);

  pika_context_real_set_brush (context, brush);
}

void
pika_context_brush_changed (PikaContext *context)
{
  g_return_if_fail (PIKA_IS_CONTEXT (context));

  g_signal_emit (context,
                 pika_context_signals[BRUSH_CHANGED], 0,
                 context->brush);
}

static void
pika_context_brush_dirty (PikaBrush   *brush,
                          PikaContext *context)
{
  g_free (context->brush_name);
  context->brush_name = g_strdup (pika_object_get_name (brush));

  g_signal_emit (context, pika_context_signals[PROP_NAME_CHANGED], 0,
                 PIKA_CONTEXT_PROP_BRUSH);
}

static void
pika_context_brush_list_thaw (PikaContainer *container,
                              PikaContext   *context)
{
  PikaBrush *brush;

  if (! context->brush_name)
    context->brush_name = g_strdup (context->pika->config->default_brush);

  brush = pika_context_find_object (context, container,
                                    context->brush_name,
                                    pika_brush_get_standard (context));

  pika_context_real_set_brush (context, brush);
}

/*  the active brush disappeared  */
static void
pika_context_brush_removed (PikaContainer *container,
                            PikaBrush     *brush,
                            PikaContext   *context)
{
  if (brush == context->brush)
    {
      g_signal_handlers_disconnect_by_func (context->brush,
                                            pika_context_brush_dirty,
                                            context);
      g_clear_object (&context->brush);

      if (! pika_container_frozen (container))
        pika_context_brush_list_thaw (container, context);
    }
}

static void
pika_context_real_set_brush (PikaContext *context,
                             PikaBrush   *brush)
{
  if (context->brush == brush)
    return;

  if (context->brush_name &&
      brush != PIKA_BRUSH (pika_brush_get_standard (context)))
    {
      g_clear_pointer (&context->brush_name, g_free);
    }

  if (context->brush)
    g_signal_handlers_disconnect_by_func (context->brush,
                                          pika_context_brush_dirty,
                                          context);

  g_set_object (&context->brush, brush);

  if (brush)
    {
      g_signal_connect_object (brush, "name-changed",
                               G_CALLBACK (pika_context_brush_dirty),
                               context,
                               0);

      if (brush != PIKA_BRUSH (pika_brush_get_standard (context)))
        context->brush_name = g_strdup (pika_object_get_name (brush));
    }

  g_object_notify (G_OBJECT (context), "brush");
  pika_context_brush_changed (context);
}


/*****************************************************************************/
/*  dynamics *****************************************************************/

PikaDynamics *
pika_context_get_dynamics (PikaContext *context)
{
  g_return_val_if_fail (PIKA_IS_CONTEXT (context), NULL);

  return context->dynamics;
}

void
pika_context_set_dynamics (PikaContext  *context,
                           PikaDynamics *dynamics)
{
  g_return_if_fail (PIKA_IS_CONTEXT (context));
  g_return_if_fail (dynamics == NULL || PIKA_IS_DYNAMICS (dynamics));

  context_find_defined (context, PIKA_CONTEXT_PROP_DYNAMICS);

  pika_context_real_set_dynamics (context, dynamics);
}

void
pika_context_dynamics_changed (PikaContext *context)
{
  g_return_if_fail (PIKA_IS_CONTEXT (context));

  g_signal_emit (context,
                 pika_context_signals[DYNAMICS_CHANGED], 0,
                 context->dynamics);
}

static void
pika_context_dynamics_dirty (PikaDynamics *dynamics,
                             PikaContext  *context)
{
  g_free (context->dynamics_name);
  context->dynamics_name = g_strdup (pika_object_get_name (dynamics));

  g_signal_emit (context, pika_context_signals[PROP_NAME_CHANGED], 0,
                 PIKA_CONTEXT_PROP_DYNAMICS);
}

static void
pika_context_dynamics_removed (PikaContainer *container,
                               PikaDynamics  *dynamics,
                               PikaContext   *context)
{
  if (dynamics == context->dynamics)
    {
      g_signal_handlers_disconnect_by_func (context->dynamics,
                                            pika_context_dynamics_dirty,
                                            context);
      g_clear_object (&context->dynamics);

      if (! pika_container_frozen (container))
        pika_context_dynamics_list_thaw (container, context);
    }
}

static void
pika_context_dynamics_list_thaw (PikaContainer *container,
                                 PikaContext   *context)
{
  PikaDynamics *dynamics;

  if (! context->dynamics_name)
    context->dynamics_name = g_strdup (context->pika->config->default_dynamics);

  dynamics = pika_context_find_object (context, container,
                                       context->dynamics_name,
                                       pika_dynamics_get_standard (context));

  pika_context_real_set_dynamics (context, dynamics);
}

static void
pika_context_real_set_dynamics (PikaContext  *context,
                                PikaDynamics *dynamics)
{
  if (context->dynamics == dynamics)
    return;

  if (context->dynamics_name &&
      dynamics != PIKA_DYNAMICS (pika_dynamics_get_standard (context)))
    {
      g_clear_pointer (&context->dynamics_name, g_free);
    }

  if (context->dynamics)
    g_signal_handlers_disconnect_by_func (context->dynamics,
                                          pika_context_dynamics_dirty,
                                          context);

  g_set_object (&context->dynamics, dynamics);

  if (dynamics)
    {
      g_signal_connect_object (dynamics, "name-changed",
                               G_CALLBACK (pika_context_dynamics_dirty),
                               context,
                               0);

      if (dynamics != PIKA_DYNAMICS (pika_dynamics_get_standard (context)))
        context->dynamics_name = g_strdup (pika_object_get_name (dynamics));
    }

  g_object_notify (G_OBJECT (context), "dynamics");
  pika_context_dynamics_changed (context);
}


/*****************************************************************************/
/*  mybrush  *****************************************************************/

PikaMybrush *
pika_context_get_mybrush (PikaContext *context)
{
  g_return_val_if_fail (PIKA_IS_CONTEXT (context), NULL);

  return context->mybrush;
}

void
pika_context_set_mybrush (PikaContext *context,
                          PikaMybrush *brush)
{
  g_return_if_fail (PIKA_IS_CONTEXT (context));
  g_return_if_fail (brush == NULL || PIKA_IS_MYBRUSH (brush));

  context_find_defined (context, PIKA_CONTEXT_PROP_MYBRUSH);

  pika_context_real_set_mybrush (context, brush);
}

void
pika_context_mybrush_changed (PikaContext *context)
{
  g_return_if_fail (PIKA_IS_CONTEXT (context));

  g_signal_emit (context,
                 pika_context_signals[MYBRUSH_CHANGED], 0,
                 context->mybrush);
}

static void
pika_context_mybrush_dirty (PikaMybrush *brush,
                            PikaContext *context)
{
  g_free (context->mybrush_name);
  context->mybrush_name = g_strdup (pika_object_get_name (brush));

  g_signal_emit (context, pika_context_signals[PROP_NAME_CHANGED], 0,
                 PIKA_CONTEXT_PROP_MYBRUSH);
}

static void
pika_context_mybrush_list_thaw (PikaContainer *container,
                                PikaContext   *context)
{
  PikaMybrush *brush;

  if (! context->mybrush_name)
    context->mybrush_name = g_strdup (context->pika->config->default_mypaint_brush);

  brush = pika_context_find_object (context, container,
                                    context->mybrush_name,
                                    pika_mybrush_get_standard (context));

  pika_context_real_set_mybrush (context, brush);
}

static void
pika_context_mybrush_removed (PikaContainer *container,
                              PikaMybrush   *brush,
                              PikaContext   *context)
{
  if (brush == context->mybrush)
    {
      g_signal_handlers_disconnect_by_func (context->mybrush,
                                            pika_context_mybrush_dirty,
                                            context);
      g_clear_object (&context->mybrush);

      if (! pika_container_frozen (container))
        pika_context_mybrush_list_thaw (container, context);
    }
}

static void
pika_context_real_set_mybrush (PikaContext *context,
                               PikaMybrush *brush)
{
  if (context->mybrush == brush)
    return;

  if (context->mybrush_name &&
      brush != PIKA_MYBRUSH (pika_mybrush_get_standard (context)))
    {
      g_clear_pointer (&context->mybrush_name, g_free);
    }

  if (context->mybrush)
    g_signal_handlers_disconnect_by_func (context->mybrush,
                                          pika_context_mybrush_dirty,
                                          context);

  g_set_object (&context->mybrush, brush);

  if (brush)
    {
      g_signal_connect_object (brush, "name-changed",
                               G_CALLBACK (pika_context_mybrush_dirty),
                               context,
                               0);

      if (brush != PIKA_MYBRUSH (pika_mybrush_get_standard (context)))
        context->mybrush_name = g_strdup (pika_object_get_name (brush));
    }

  g_object_notify (G_OBJECT (context), "mybrush");
  pika_context_mybrush_changed (context);
}


/*****************************************************************************/
/*  pattern  *****************************************************************/

PikaPattern *
pika_context_get_pattern (PikaContext *context)
{
  g_return_val_if_fail (PIKA_IS_CONTEXT (context), NULL);

  return context->pattern;
}

void
pika_context_set_pattern (PikaContext *context,
                          PikaPattern *pattern)
{
  g_return_if_fail (PIKA_IS_CONTEXT (context));
  g_return_if_fail (pattern == NULL || PIKA_IS_PATTERN (pattern));

  context_find_defined (context, PIKA_CONTEXT_PROP_PATTERN);

  pika_context_real_set_pattern (context, pattern);
}

void
pika_context_pattern_changed (PikaContext *context)
{
  g_return_if_fail (PIKA_IS_CONTEXT (context));

  g_signal_emit (context,
                 pika_context_signals[PATTERN_CHANGED], 0,
                 context->pattern);
}

static void
pika_context_pattern_dirty (PikaPattern *pattern,
                            PikaContext *context)
{
  g_free (context->pattern_name);
  context->pattern_name = g_strdup (pika_object_get_name (pattern));

  g_signal_emit (context, pika_context_signals[PROP_NAME_CHANGED], 0,
                 PIKA_CONTEXT_PROP_PATTERN);
}

static void
pika_context_pattern_list_thaw (PikaContainer *container,
                                PikaContext   *context)
{
  PikaPattern *pattern;

  if (! context->pattern_name)
    context->pattern_name = g_strdup (context->pika->config->default_pattern);

  pattern = pika_context_find_object (context, container,
                                      context->pattern_name,
                                      pika_pattern_get_standard (context));

  pika_context_real_set_pattern (context, pattern);
}

static void
pika_context_pattern_removed (PikaContainer *container,
                              PikaPattern   *pattern,
                              PikaContext   *context)
{
  if (pattern == context->pattern)
    {
      g_signal_handlers_disconnect_by_func (context->pattern,
                                            pika_context_pattern_dirty,
                                            context);
      g_clear_object (&context->pattern);

      if (! pika_container_frozen (container))
        pika_context_pattern_list_thaw (container, context);
    }
}

static void
pika_context_real_set_pattern (PikaContext *context,
                               PikaPattern *pattern)
{
  if (context->pattern == pattern)
    return;

  if (context->pattern_name &&
      pattern != PIKA_PATTERN (pika_pattern_get_standard (context)))
    {
      g_clear_pointer (&context->pattern_name, g_free);
    }

  if (context->pattern)
    g_signal_handlers_disconnect_by_func (context->pattern,
                                          pika_context_pattern_dirty,
                                          context);

  g_set_object (&context->pattern, pattern);

  if (pattern)
    {
      g_signal_connect_object (pattern, "name-changed",
                               G_CALLBACK (pika_context_pattern_dirty),
                               context,
                               0);

      if (pattern != PIKA_PATTERN (pika_pattern_get_standard (context)))
        context->pattern_name = g_strdup (pika_object_get_name (pattern));
    }

  g_object_notify (G_OBJECT (context), "pattern");
  pika_context_pattern_changed (context);
}


/*****************************************************************************/
/*  gradient  ****************************************************************/

PikaGradient *
pika_context_get_gradient (PikaContext *context)
{
  g_return_val_if_fail (PIKA_IS_CONTEXT (context), NULL);

  return context->gradient;
}

void
pika_context_set_gradient (PikaContext  *context,
                           PikaGradient *gradient)
{
  g_return_if_fail (PIKA_IS_CONTEXT (context));
  g_return_if_fail (gradient == NULL || PIKA_IS_GRADIENT (gradient));

  context_find_defined (context, PIKA_CONTEXT_PROP_GRADIENT);

  pika_context_real_set_gradient (context, gradient);
}

void
pika_context_gradient_changed (PikaContext *context)
{
  g_return_if_fail (PIKA_IS_CONTEXT (context));

  g_signal_emit (context,
                 pika_context_signals[GRADIENT_CHANGED], 0,
                 context->gradient);
}

static void
pika_context_gradient_dirty (PikaGradient *gradient,
                             PikaContext  *context)
{
  g_free (context->gradient_name);
  context->gradient_name = g_strdup (pika_object_get_name (gradient));

  g_signal_emit (context, pika_context_signals[PROP_NAME_CHANGED], 0,
                 PIKA_CONTEXT_PROP_GRADIENT);
}

static void
pika_context_gradient_list_thaw (PikaContainer *container,
                                 PikaContext   *context)
{
  PikaGradient *gradient;

  if (! context->gradient_name)
    context->gradient_name = g_strdup (context->pika->config->default_gradient);

  gradient = pika_context_find_object (context, container,
                                       context->gradient_name,
                                       pika_gradient_get_standard (context));

  pika_context_real_set_gradient (context, gradient);
}

static void
pika_context_gradient_removed (PikaContainer *container,
                               PikaGradient  *gradient,
                               PikaContext   *context)
{
  if (gradient == context->gradient)
    {
      g_signal_handlers_disconnect_by_func (context->gradient,
                                            pika_context_gradient_dirty,
                                            context);
      g_clear_object (&context->gradient);

      if (! pika_container_frozen (container))
        pika_context_gradient_list_thaw (container, context);
    }
}

static void
pika_context_real_set_gradient (PikaContext  *context,
                                PikaGradient *gradient)
{
  if (context->gradient == gradient)
    return;

  if (context->gradient_name &&
      gradient != PIKA_GRADIENT (pika_gradient_get_standard (context)))
    {
      g_clear_pointer (&context->gradient_name, g_free);
    }

  if (context->gradient)
    g_signal_handlers_disconnect_by_func (context->gradient,
                                          pika_context_gradient_dirty,
                                          context);

  g_set_object (&context->gradient, gradient);

  if (gradient)
    {
      g_signal_connect_object (gradient, "name-changed",
                               G_CALLBACK (pika_context_gradient_dirty),
                               context,
                               0);

      if (gradient != PIKA_GRADIENT (pika_gradient_get_standard (context)))
        context->gradient_name = g_strdup (pika_object_get_name (gradient));
    }

  g_object_notify (G_OBJECT (context), "gradient");
  pika_context_gradient_changed (context);
}


/*****************************************************************************/
/*  palette  *****************************************************************/

PikaPalette *
pika_context_get_palette (PikaContext *context)
{
  g_return_val_if_fail (PIKA_IS_CONTEXT (context), NULL);

  return context->palette;
}

void
pika_context_set_palette (PikaContext *context,
                          PikaPalette *palette)
{
  g_return_if_fail (PIKA_IS_CONTEXT (context));
  g_return_if_fail (palette == NULL || PIKA_IS_PALETTE (palette));

  context_find_defined (context, PIKA_CONTEXT_PROP_PALETTE);

  pika_context_real_set_palette (context, palette);
}

void
pika_context_palette_changed (PikaContext *context)
{
  g_return_if_fail (PIKA_IS_CONTEXT (context));

  g_signal_emit (context,
                 pika_context_signals[PALETTE_CHANGED], 0,
                 context->palette);
}

static void
pika_context_palette_dirty (PikaPalette *palette,
                            PikaContext *context)
{
  g_free (context->palette_name);
  context->palette_name = g_strdup (pika_object_get_name (palette));

  g_signal_emit (context, pika_context_signals[PROP_NAME_CHANGED], 0,
                 PIKA_CONTEXT_PROP_PALETTE);
}

static void
pika_context_palette_list_thaw (PikaContainer *container,
                                PikaContext   *context)
{
  PikaPalette *palette;

  if (! context->palette_name)
    context->palette_name = g_strdup (context->pika->config->default_palette);

  palette = pika_context_find_object (context, container,
                                      context->palette_name,
                                      pika_palette_get_standard (context));

  pika_context_real_set_palette (context, palette);
}

static void
pika_context_palette_removed (PikaContainer *container,
                              PikaPalette   *palette,
                              PikaContext   *context)
{
  if (palette == context->palette)
    {
      g_signal_handlers_disconnect_by_func (context->palette,
                                            pika_context_palette_dirty,
                                            context);
      g_clear_object (&context->palette);

      if (! pika_container_frozen (container))
        pika_context_palette_list_thaw (container, context);
    }
}

static void
pika_context_real_set_palette (PikaContext *context,
                               PikaPalette *palette)
{
  if (context->palette == palette)
    return;

  if (context->palette_name &&
      palette != PIKA_PALETTE (pika_palette_get_standard (context)))
    {
      g_clear_pointer (&context->palette_name, g_free);
    }

  if (context->palette)
    g_signal_handlers_disconnect_by_func (context->palette,
                                          pika_context_palette_dirty,
                                          context);

  g_set_object (&context->palette, palette);

  if (palette)
    {
      g_signal_connect_object (palette, "name-changed",
                               G_CALLBACK (pika_context_palette_dirty),
                               context,
                               0);

      if (palette != PIKA_PALETTE (pika_palette_get_standard (context)))
        context->palette_name = g_strdup (pika_object_get_name (palette));
    }

  g_object_notify (G_OBJECT (context), "palette");
  pika_context_palette_changed (context);
}


/*****************************************************************************/
/*  font     *****************************************************************/

PikaFont *
pika_context_get_font (PikaContext *context)
{
  g_return_val_if_fail (PIKA_IS_CONTEXT (context), NULL);

  return context->font;
}

void
pika_context_set_font (PikaContext *context,
                       PikaFont    *font)
{
  g_return_if_fail (PIKA_IS_CONTEXT (context));
  g_return_if_fail (font == NULL || PIKA_IS_FONT (font));

  context_find_defined (context, PIKA_CONTEXT_PROP_FONT);

  pika_context_real_set_font (context, font);
}

const gchar *
pika_context_get_font_name (PikaContext *context)
{
  g_return_val_if_fail (PIKA_IS_CONTEXT (context), NULL);

  return context->font_name;
}

void
pika_context_set_font_name (PikaContext *context,
                            const gchar *name)
{
  PikaContainer *container;
  PikaObject    *font;

  g_return_if_fail (PIKA_IS_CONTEXT (context));

  container = pika_data_factory_get_container (context->pika->font_factory);
  font      = pika_container_search (container,
                                     (PikaContainerSearchFunc) pika_font_match_by_lookup_name,
                                     (gpointer) name);

  if (font)
    {
      pika_context_set_font (context, PIKA_FONT (font));
    }
  else
    {
      /* No font with this name exists, use the standard font, but
       * keep the intended name around
       */
      pika_context_set_font (context, PIKA_FONT (pika_font_get_standard ()));

      g_free (context->font_name);
      context->font_name = g_strdup (name);
    }
}

void
pika_context_font_changed (PikaContext *context)
{
  g_return_if_fail (PIKA_IS_CONTEXT (context));

  g_signal_emit (context,
                 pika_context_signals[FONT_CHANGED], 0,
                 context->font);
}

static void
pika_context_font_dirty (PikaFont    *font,
                         PikaContext *context)
{
  g_free (context->font_name);
  context->font_name = g_strdup (pika_object_get_name (font));

  g_signal_emit (context, pika_context_signals[PROP_NAME_CHANGED], 0,
                 PIKA_CONTEXT_PROP_FONT);
}

static void
pika_context_font_list_thaw (PikaContainer *container,
                             PikaContext   *context)
{
  PikaFont *font;

  if (! context->font_name)
    context->font_name = g_strdup (context->pika->config->default_font);

  font = pika_context_find_object (context, container,
                                   context->font_name,
                                   pika_font_get_standard ());

  pika_context_real_set_font (context, font);
}

static void
pika_context_font_removed (PikaContainer *container,
                           PikaFont      *font,
                           PikaContext   *context)
{
  if (font == context->font)
    {
      g_signal_handlers_disconnect_by_func (context->font,
                                            pika_context_font_dirty,
                                            context);
      g_clear_object (&context->font);

      if (! pika_container_frozen (container))
        pika_context_font_list_thaw (container, context);
    }
}

static void
pika_context_real_set_font (PikaContext *context,
                            PikaFont    *font)
{
  if (context->font == font)
    return;

  if (context->font_name &&
      font != PIKA_FONT (pika_font_get_standard ()))
    {
      g_clear_pointer (&context->font_name, g_free);
    }

  if (context->font)
    g_signal_handlers_disconnect_by_func (context->font,
                                          pika_context_font_dirty,
                                          context);

  g_set_object (&context->font, font);

  if (font)
    {
      g_signal_connect_object (font, "name-changed",
                               G_CALLBACK (pika_context_font_dirty),
                               context,
                               0);

      if (font != PIKA_FONT (pika_font_get_standard ()))
        context->font_name = g_strdup (pika_font_get_lookup_name (font));
    }

  g_object_notify (G_OBJECT (context), "font");
  pika_context_font_changed (context);
}


/********************************************************************************/
/*  tool preset *****************************************************************/

PikaToolPreset *
pika_context_get_tool_preset (PikaContext *context)
{
  g_return_val_if_fail (PIKA_IS_CONTEXT (context), NULL);

  return context->tool_preset;
}

void
pika_context_set_tool_preset (PikaContext    *context,
                              PikaToolPreset *tool_preset)
{
  g_return_if_fail (PIKA_IS_CONTEXT (context));
  g_return_if_fail (tool_preset == NULL || PIKA_IS_TOOL_PRESET (tool_preset));

  context_find_defined (context, PIKA_CONTEXT_PROP_TOOL_PRESET);

  pika_context_real_set_tool_preset (context, tool_preset);
}

void
pika_context_tool_preset_changed (PikaContext *context)
{
  g_return_if_fail (PIKA_IS_CONTEXT (context));

  g_signal_emit (context,
                 pika_context_signals[TOOL_PRESET_CHANGED], 0,
                 context->tool_preset);
}

static void
pika_context_tool_preset_dirty (PikaToolPreset *tool_preset,
                                PikaContext    *context)
{
  g_free (context->tool_preset_name);
  context->tool_preset_name = g_strdup (pika_object_get_name (tool_preset));

  g_signal_emit (context, pika_context_signals[PROP_NAME_CHANGED], 0,
                 PIKA_CONTEXT_PROP_TOOL_PRESET);
}

static void
pika_context_tool_preset_removed (PikaContainer  *container,
                                  PikaToolPreset *tool_preset,
                                  PikaContext    *context)
{
  if (tool_preset == context->tool_preset)
    {
      g_signal_handlers_disconnect_by_func (context->tool_preset,
                                            pika_context_tool_preset_dirty,
                                            context);
      g_clear_object (&context->tool_preset);

      if (! pika_container_frozen (container))
        pika_context_tool_preset_list_thaw (container, context);
    }
}

static void
pika_context_tool_preset_list_thaw (PikaContainer *container,
                                    PikaContext   *context)
{
  PikaToolPreset *tool_preset;

  tool_preset = pika_context_find_object (context, container,
                                          context->tool_preset_name,
                                          NULL);

  pika_context_real_set_tool_preset (context, tool_preset);
}

static void
pika_context_real_set_tool_preset (PikaContext    *context,
                                   PikaToolPreset *tool_preset)
{
  if (context->tool_preset == tool_preset)
    return;

  if (context->tool_preset_name)
    {
      g_clear_pointer (&context->tool_preset_name, g_free);
    }

  if (context->tool_preset)
    g_signal_handlers_disconnect_by_func (context->tool_preset,
                                          pika_context_tool_preset_dirty,
                                          context);

  g_set_object (&context->tool_preset, tool_preset);

  if (tool_preset)
    {
      g_signal_connect_object (tool_preset, "name-changed",
                               G_CALLBACK (pika_context_tool_preset_dirty),
                               context,
                               0);

      context->tool_preset_name = g_strdup (pika_object_get_name (tool_preset));
    }

  g_object_notify (G_OBJECT (context), "tool-preset");
  pika_context_tool_preset_changed (context);
}


/*****************************************************************************/
/*  buffer  ******************************************************************/

PikaBuffer *
pika_context_get_buffer (PikaContext *context)
{
  g_return_val_if_fail (PIKA_IS_CONTEXT (context), NULL);

  return context->buffer;
}

void
pika_context_set_buffer (PikaContext *context,
                         PikaBuffer *buffer)
{
  g_return_if_fail (PIKA_IS_CONTEXT (context));
  g_return_if_fail (buffer == NULL || PIKA_IS_BUFFER (buffer));

  context_find_defined (context, PIKA_CONTEXT_PROP_BUFFER);

  pika_context_real_set_buffer (context, buffer);
}

void
pika_context_buffer_changed (PikaContext *context)
{
  g_return_if_fail (PIKA_IS_CONTEXT (context));

  g_signal_emit (context,
                 pika_context_signals[BUFFER_CHANGED], 0,
                 context->buffer);
}

static void
pika_context_buffer_dirty (PikaBuffer  *buffer,
                           PikaContext *context)
{
  g_free (context->buffer_name);
  context->buffer_name = g_strdup (pika_object_get_name (buffer));

  g_signal_emit (context, pika_context_signals[PROP_NAME_CHANGED], 0,
                 PIKA_CONTEXT_PROP_BUFFER);
}

static void
pika_context_buffer_list_thaw (PikaContainer *container,
                               PikaContext   *context)
{
  PikaBuffer *buffer;

  buffer = pika_context_find_object (context, container,
                                     context->buffer_name,
                                     NULL);

  if (buffer)
    {
      pika_context_real_set_buffer (context, buffer);
    }
  else
    {
      g_object_notify (G_OBJECT (context), "buffer");
      pika_context_buffer_changed (context);
    }
}

static void
pika_context_buffer_removed (PikaContainer *container,
                             PikaBuffer    *buffer,
                             PikaContext   *context)
{
  if (buffer == context->buffer)
    {
      g_signal_handlers_disconnect_by_func (context->buffer,
                                            pika_context_buffer_dirty,
                                            context);
      g_clear_object (&context->buffer);

      if (! pika_container_frozen (container))
        pika_context_buffer_list_thaw (container, context);
    }
}

static void
pika_context_real_set_buffer (PikaContext *context,
                              PikaBuffer  *buffer)
{
  if (context->buffer == buffer)
    return;

  if (context->buffer_name)
    {
      g_clear_pointer (&context->buffer_name, g_free);
    }

  if (context->buffer)
    g_signal_handlers_disconnect_by_func (context->buffer,
                                          pika_context_buffer_dirty,
                                          context);

  g_set_object (&context->buffer, buffer);

  if (buffer)
    {
      g_signal_connect_object (buffer, "name-changed",
                               G_CALLBACK (pika_context_buffer_dirty),
                               context,
                               0);

      context->buffer_name = g_strdup (pika_object_get_name (buffer));
    }

  g_object_notify (G_OBJECT (context), "buffer");
  pika_context_buffer_changed (context);
}


/*****************************************************************************/
/*  imagefile  ***************************************************************/

PikaImagefile *
pika_context_get_imagefile (PikaContext *context)
{
  g_return_val_if_fail (PIKA_IS_CONTEXT (context), NULL);

  return context->imagefile;
}

void
pika_context_set_imagefile (PikaContext   *context,
                            PikaImagefile *imagefile)
{
  g_return_if_fail (PIKA_IS_CONTEXT (context));
  g_return_if_fail (imagefile == NULL || PIKA_IS_IMAGEFILE (imagefile));

  context_find_defined (context, PIKA_CONTEXT_PROP_IMAGEFILE);

  pika_context_real_set_imagefile (context, imagefile);
}

void
pika_context_imagefile_changed (PikaContext *context)
{
  g_return_if_fail (PIKA_IS_CONTEXT (context));

  g_signal_emit (context,
                 pika_context_signals[IMAGEFILE_CHANGED], 0,
                 context->imagefile);
}

static void
pika_context_imagefile_dirty (PikaImagefile *imagefile,
                              PikaContext   *context)
{
  g_free (context->imagefile_name);
  context->imagefile_name = g_strdup (pika_object_get_name (imagefile));

  g_signal_emit (context, pika_context_signals[PROP_NAME_CHANGED], 0,
                 PIKA_CONTEXT_PROP_IMAGEFILE);
}

static void
pika_context_imagefile_list_thaw (PikaContainer *container,
                                  PikaContext   *context)
{
  PikaImagefile *imagefile;

  imagefile = pika_context_find_object (context, container,
                                        context->imagefile_name,
                                        NULL);

  if (imagefile)
    {
      pika_context_real_set_imagefile (context, imagefile);
    }
  else
    {
      g_object_notify (G_OBJECT (context), "imagefile");
      pika_context_imagefile_changed (context);
    }
}

static void
pika_context_imagefile_removed (PikaContainer *container,
                                PikaImagefile *imagefile,
                                PikaContext   *context)
{
  if (imagefile == context->imagefile)
    {
      g_signal_handlers_disconnect_by_func (context->imagefile,
                                            pika_context_imagefile_dirty,
                                            context);
      g_clear_object (&context->imagefile);

      if (! pika_container_frozen (container))
        pika_context_imagefile_list_thaw (container, context);
    }
}

static void
pika_context_real_set_imagefile (PikaContext   *context,
                                 PikaImagefile *imagefile)
{
  if (context->imagefile == imagefile)
    return;

  if (context->imagefile_name)
    {
      g_clear_pointer (&context->imagefile_name, g_free);
    }

  if (context->imagefile)
    g_signal_handlers_disconnect_by_func (context->imagefile,
                                          pika_context_imagefile_dirty,
                                          context);

  g_set_object (&context->imagefile, imagefile);

  if (imagefile)
    {
      g_signal_connect_object (imagefile, "name-changed",
                               G_CALLBACK (pika_context_imagefile_dirty),
                               context,
                               0);

      context->imagefile_name = g_strdup (pika_object_get_name (imagefile));
    }

  g_object_notify (G_OBJECT (context), "imagefile");
  pika_context_imagefile_changed (context);
}


/*****************************************************************************/
/*  template  ***************************************************************/

PikaTemplate *
pika_context_get_template (PikaContext *context)
{
  g_return_val_if_fail (PIKA_IS_CONTEXT (context), NULL);

  return context->template;
}

void
pika_context_set_template (PikaContext  *context,
                           PikaTemplate *template)
{
  g_return_if_fail (PIKA_IS_CONTEXT (context));
  g_return_if_fail (template == NULL || PIKA_IS_TEMPLATE (template));

  context_find_defined (context, PIKA_CONTEXT_PROP_TEMPLATE);

  pika_context_real_set_template (context, template);
}

void
pika_context_template_changed (PikaContext *context)
{
  g_return_if_fail (PIKA_IS_CONTEXT (context));

  g_signal_emit (context,
                 pika_context_signals[TEMPLATE_CHANGED], 0,
                 context->template);
}

static void
pika_context_template_dirty (PikaTemplate *template,
                             PikaContext  *context)
{
  g_free (context->template_name);
  context->template_name = g_strdup (pika_object_get_name (template));

  g_signal_emit (context, pika_context_signals[PROP_NAME_CHANGED], 0,
                 PIKA_CONTEXT_PROP_TEMPLATE);
}

static void
pika_context_template_list_thaw (PikaContainer *container,
                                 PikaContext   *context)
{
  PikaTemplate *template;

  template = pika_context_find_object (context, container,
                                       context->template_name,
                                       NULL);

  if (template)
    {
      pika_context_real_set_template (context, template);
    }
  else
    {
      g_object_notify (G_OBJECT (context), "template");
      pika_context_template_changed (context);
    }
}

static void
pika_context_template_removed (PikaContainer *container,
                               PikaTemplate  *template,
                               PikaContext   *context)
{
  if (template == context->template)
    {
      g_signal_handlers_disconnect_by_func (context->template,
                                            pika_context_template_dirty,
                                            context);
      g_clear_object (&context->template);

      if (! pika_container_frozen (container))
        pika_context_template_list_thaw (container, context);
    }
}

static void
pika_context_real_set_template (PikaContext  *context,
                                PikaTemplate *template)
{
  if (context->template == template)
    return;

  if (context->template_name)
    {
      g_clear_pointer (&context->template_name, g_free);
    }

  if (context->template)
    g_signal_handlers_disconnect_by_func (context->template,
                                          pika_context_template_dirty,
                                          context);

  g_set_object (&context->template, template);

  if (template)
    {
      g_signal_connect_object (template, "name-changed",
                               G_CALLBACK (pika_context_template_dirty),
                               context,
                               0);

      context->template_name = g_strdup (pika_object_get_name (template));
    }

  g_object_notify (G_OBJECT (context), "template");
  pika_context_template_changed (context);
}


/*****************************************************************************/
/*  Line Art  ****************************************************************/

PikaLineArt *
pika_context_take_line_art (PikaContext *context)
{
  PikaLineArt *line_art;

  g_return_val_if_fail (PIKA_IS_CONTEXT (context), NULL);

  if (context->line_art)
    {
      g_source_remove (context->line_art_timeout_id);
      context->line_art_timeout_id = 0;

      line_art = context->line_art;
      context->line_art = NULL;
    }
  else
    {
      line_art = pika_line_art_new ();
    }

  return line_art;
}

/*
 * pika_context_store_line_art:
 * @context:
 * @line_art:
 *
 * The @context takes ownership of @line_art until the next time it is
 * requested with pika_context_take_line_art() or until 3 minutes have
 * passed.
 * This function allows to temporarily store the computed line art data
 * in case it is needed very soon again, so that not to free and
 * recompute all the time the data when quickly switching tools.
 */
void
pika_context_store_line_art (PikaContext *context,
                             PikaLineArt *line_art)
{
  g_return_if_fail (PIKA_IS_CONTEXT (context));
  g_return_if_fail (PIKA_IS_LINE_ART (line_art));

  if (context->line_art)
    {
      g_source_remove (context->line_art_timeout_id);
      context->line_art_timeout_id = 0;
    }

  context->line_art            = line_art;
  context->line_art_timeout_id = g_timeout_add (180000,
                                                (GSourceFunc) pika_context_free_line_art,
                                                context);
}

static gboolean
pika_context_free_line_art (PikaContext *context)
{
  g_clear_object (&context->line_art);

  context->line_art_timeout_id = 0;

  return G_SOURCE_REMOVE;
}


/*****************************************************************************/
/*  utility functions  *******************************************************/

static gpointer
pika_context_find_object (PikaContext   *context,
                          PikaContainer *container,
                          const gchar   *object_name,
                          gpointer       standard_object)
{
  PikaObject *object = NULL;

  if (object_name)
    object = pika_container_get_child_by_name (container, object_name);

  if (! object && ! pika_container_is_empty (container))
    object = pika_container_get_child_by_index (container, 0);

  if (! object)
    object = standard_object;

  return object;
}
