/* GIMP - The GNU Image Manipulation Program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * GFlare plug-in -- lense flare effect by using custom gradients
 * Copyright (C) 1997 Eiichi Takamori <taka@ma1.sekyou.ne.jp>
 *
 * 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/>.
 *
 *
 * A fair proportion of this code was taken from GIMP & Script-fu
 * copyrighted by Spencer Kimball and Peter Mattis, and from Gradient
 * Editor copyrighted by Federico Mena Quintero. (See copyright notice
 * below) Thanks for senior GIMP hackers!!
 *
 * GIMP - The GNU Image Manipulation Program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * Gradient editor module copyight (C) 1996-1997 Federico Mena Quintero
 * federico@nuclecu.unam.mx
 */

#include "config.h"

#include <string.h>
#include <stdlib.h>
#include <errno.h>

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#include <glib/gstdio.h>

#include <glib-object.h>

#include "libgimpcolor/gimpcolor-private.h"

#include <libgimp/gimp.h>
#include <libgimp/gimpui.h>

#include "libgimp/stdplugins-intl.h"

/* #define DEBUG */

#ifdef DEBUG
#define DEBUG_PRINT(X) g_print X
#else
#define DEBUG_PRINT(X)
#endif

#define LUMINOSITY(PIX) (GIMP_RGB_LUMINANCE (PIX[0], PIX[1], PIX[2]) + 0.5)

#define RESPONSE_RESCAN     1

#define PLUG_IN_PROC        "plug-in-gflare"
#define PLUG_IN_BINARY      "gradient-flare"
#define PLUG_IN_ROLE        "gimp-gradient-flare"

#define GRADIENT_NAME_MAX   256
#define GRADIENT_RESOLUTION 360

#define GFLARE_NAME_MAX     256
#define GFLARE_FILE_HEADER  "GIMP GFlare 0.25\n"
#define SFLARE_NUM           30

#define DLG_PREVIEW_WIDTH   256
#define DLG_PREVIEW_HEIGHT  256
#define DLG_PREVIEW_MASK    GDK_EXPOSURE_MASK | \
                            GDK_BUTTON_PRESS_MASK
#define DLG_LISTBOX_WIDTH   80
#define DLG_LISTBOX_HEIGHT  40

#define ED_PREVIEW_WIDTH    256
#define ED_PREVIEW_HEIGHT   256

#define GM_PREVIEW_WIDTH    80
#define GM_PREVIEW_HEIGHT   16

#ifndef OPAQUE
#define OPAQUE              255
#endif
#define GRAY50              128

#define GRADIENT_CACHE_SIZE 32

#define CALC_GLOW   0x01
#define CALC_RAYS   0x02
#define CALC_SFLARE 0x04

typedef struct _Preview Preview;

typedef gchar   GradientName[GRADIENT_NAME_MAX];

typedef enum
{
  GF_NORMAL = 0,
  GF_ADDITION,
  GF_OVERLAY,
  GF_SCREEN,
  GF_NUM_MODES
} GFlareMode;

typedef enum
{
  GF_CIRCLE = 0,
  GF_POLYGON,
  GF_NUM_SHAPES
} GFlareShape;

typedef struct
{
  gchar        *name;
  gchar        *filename;
  gdouble       glow_opacity;
  GFlareMode    glow_mode;
  gdouble       rays_opacity;
  GFlareMode    rays_mode;
  gdouble       sflare_opacity;
  GFlareMode    sflare_mode;
  GradientName  glow_radial;
  GradientName  glow_angular;
  GradientName  glow_angular_size;
  gdouble       glow_size;
  gdouble       glow_rotation;
  gdouble       glow_hue;
  GradientName  rays_radial;
  GradientName  rays_angular;
  GradientName  rays_angular_size;
  gdouble       rays_size;
  gdouble       rays_rotation;
  gdouble       rays_hue;
  gint          rays_nspikes;
  gdouble       rays_thickness;
  GradientName  sflare_radial;
  GradientName  sflare_sizefac;
  GradientName  sflare_probability;
  gdouble       sflare_size;
  gdouble       sflare_rotation;
  gdouble       sflare_hue;
  GFlareShape   sflare_shape;
  gint          sflare_nverts;
  guint32       sflare_seed;
  gboolean      random_seed;
} GFlare;

typedef struct
{
  FILE *fp;
  gint  error;
} GFlareFile;


typedef enum
{
  PAGE_SETTINGS,
  PAGE_SELECTOR,
  PAGE_GENERAL,
  PAGE_GLOW,
  PAGE_RAYS,
  PAGE_SFLARE
} PageNum;


typedef struct
{
  gint       init;
  GFlare    *gflare;
  GtkWidget *shell;
  Preview   *preview;
  struct
  {
    gdouble x0, y0, x1, y1;
  } pwin;
  gboolean          update_preview;
  GtkWidget        *notebook;
  GtkWidget        *sizeentry;
  GtkWidget        *asupsample_frame;
  GtkListStore     *selector_list;
  GtkTreeSelection *selection;
  gint              init_params_done;
} GFlareDialog;

typedef void (* GFlareEditorCallback) (gint updated, gpointer data);

typedef struct
{
  gint                  init;
  gint                  run;
  GFlareEditorCallback  callback;
  gpointer              calldata;
  GFlare               *target_gflare;
  GFlare               *gflare;
  GtkWidget            *shell;
  Preview              *preview;
  GtkWidget            *notebook;
  PageNum               cur_page;
  GtkWidget            *polygon_entry;
  GtkWidget            *polygon_toggle;
  gint                  init_params_done;
} GFlareEditor;

typedef struct
{
  gdouble x0;
  gdouble y0;
  gdouble x1;
  gdouble y1;
} CalcBounds;

typedef struct
{
  gint     init;
  gint     type;
  GFlare  *gflare;
  gdouble  xcenter;
  gdouble  ycenter;
  gdouble  radius;
  gdouble  rotation;
  gdouble  hue;
  gdouble  vangle;
  gdouble  vlength;

  gint        glow_opacity;
  CalcBounds  glow_bounds;
  guchar     *glow_radial;
  guchar     *glow_angular;
  guchar     *glow_angular_size;
  gdouble     glow_radius;
  gdouble     glow_rotation;

  gint        rays_opacity;
  CalcBounds  rays_bounds;
  guchar     *rays_radial;
  guchar     *rays_angular;
  guchar     *rays_angular_size;
  gdouble     rays_radius;
  gdouble     rays_rotation;
  gdouble     rays_spike_mod;
  gdouble     rays_thinness;

  gint         sflare_opacity;
  GList       *sflare_list;
  guchar      *sflare_radial;
  guchar      *sflare_sizefac;
  guchar      *sflare_probability;
  gdouble      sflare_radius;
  gdouble      sflare_rotation;
  GFlareShape  sflare_shape;
  gdouble      sflare_angle;
  gdouble      sflare_factor;
} CalcParams;

/*
 * What's the difference between (structure) CalcParams and GFlare ?
 * well, radius and lengths are actual length for CalcParams where
 * they are typically 0 to 100 for GFlares, and angles are G_PI based
 * (radian) for CalcParams where they are degree for GFlares. et cetra.
 * This is because convenience for dialog processing and for calculating.
 * these conversion is taken place in calc init routines. see below.
 */

typedef struct
{
  gdouble    xcenter;
  gdouble    ycenter;
  gdouble    radius;
  CalcBounds bounds;
} CalcSFlare;

typedef struct
{
  const Babl *format;
  gint        bpp;
  gint        is_color;
  gint        has_alpha;
  gint        x, y, w, h; /* mask bounds */
} DrawableInfo;

typedef struct _GradientMenu GradientMenu;
typedef void (* GradientMenuCallback) (const gchar *gradient_name,
                                       gpointer     data);
struct _GradientMenu
{
  GtkWidget            *preview;
  GtkWidget            *combo;
  GradientMenuCallback  callback;
  gpointer              callback_data;
  GradientName          gradient_name;
};

typedef gint (* PreviewInitFunc)   (Preview  *preview,
                                    gpointer  data);
typedef void (* PreviewRenderFunc) (Preview  *preview,
                                    guchar   *buffer,
                                    gint      y,
                                    gpointer  data);
typedef void (* PreviewDeinitFunc) (Preview  *preview,
                                    gpointer  data);

struct _Preview
{
  GtkWidget         *widget;
  gint               width;
  gint               height;
  PreviewInitFunc    init_func;
  gpointer           init_data;
  PreviewRenderFunc  render_func;
  gpointer           render_data;
  PreviewDeinitFunc  deinit_func;
  gpointer           deinit_data;
  guint              timeout_tag;
  guint              idle_tag;
  gint               init_done;
  gint               current_y;
  gint               drawn_y;
  guchar            *buffer;
  guchar            *full_image_buffer;
};

typedef struct
{
  gint               tag;
  gint               got_gradients;
  gint               current_y;
  gint               drawn_y;
  PreviewRenderFunc  render_func;
  guchar            *buffer;
} PreviewIdle;

typedef struct _GradientCacheItem  GradientCacheItem;

struct _GradientCacheItem
{
  GradientCacheItem *next;
  GradientCacheItem *prev;
  GradientName       name;
  guchar             values[4 * GRADIENT_RESOLUTION];
};


typedef void (* QueryFunc) (GtkWidget *,
                            gpointer,
                            gpointer);


typedef struct _Gflare      Gflare;
typedef struct _GflareClass GflareClass;

struct _Gflare
{
  GimpPlugIn parent_instance;
};

struct _GflareClass
{
  GimpPlugInClass parent_class;
};


#define GFLARE_TYPE  (gflare_get_type ())
#define GFLARE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GFLARE_TYPE, Gflare))

GType                   gflare_get_type         (void) G_GNUC_CONST;

static GList          * gflare_query_procedures (GimpPlugIn           *plug_in);
static GimpProcedure  * gflare_create_procedure (GimpPlugIn           *plug_in,
                                                 const gchar          *name);

static GimpValueArray * gflare_run              (GimpProcedure        *procedure,
                                                 GimpRunMode           run_mode,
                                                 GimpImage            *image,
                                                 GimpDrawable        **drawables,
                                                 GimpProcedureConfig  *config,
                                                 gpointer              run_data);

static GFlare * gflare_new_with_default (const gchar *new_name);
static GFlare * gflare_dup              (const GFlare      *src,
                                         const gchar *new_name);
static void     gflare_copy             (GFlare      *dest,
                                         const GFlare      *src);
static GFlare * gflare_load             (const gchar *filename,
                                         const gchar *name);
static void     gflare_save             (GFlare      *gflare);

static gint     gflares_list_insert     (GFlare      *gflare);
static GFlare * gflares_list_lookup     (const gchar *name);
static gint     gflares_list_index      (GFlare      *gflare);
static gint     gflares_list_remove     (GFlare      *gflare);
static void     gflares_list_load_all   (void);
static void     gflares_list_free_all   (void);

static void     calc_init_params   (GFlare  *gflare,
                                    gint     calc_type,
                                    gint     xcenter,
                                    gint     ycenter,
                                    gdouble  radius,
                                    gdouble  rotation,
                                    gdouble  hue,
                                    gdouble  vangle,
                                    gdouble  vlength);
static gint     calc_init_progress (void);
static void     calc_deinit        (void);
static void     calc_glow_pix      (guchar  *dest_pix,
                                    gdouble  x,
                                    gdouble  y);
static void     calc_rays_pix      (guchar  *dest_pix,
                                    gdouble  x,
                                    gdouble  y);
static void     calc_sflare_pix    (guchar  *dest_pix,
                                    gdouble  x,
                                    gdouble  y,
                                    guchar  *src_pix);
static void     calc_gflare_pix    (guchar  *dest_pix,
                                    gdouble  x,
                                    gdouble  y,
                                    guchar  *src_pix);

static gboolean    dlg_run                 (GimpProcedure       *procedure,
                                            GimpProcedureConfig *config);
static void        dlg_preview_calc_window (void);
static void        ed_preview_calc_window  (void);
static GtkWidget * ed_mode_menu_new        (GFlareMode *mode_var);

static Preview   * preview_new          (gint               width,
                                         gint               height,
                                         PreviewInitFunc    init_func,
                                         gpointer           init_data,
                                         PreviewRenderFunc  render_func,
                                         gpointer           render_data,
                                         PreviewDeinitFunc  deinit_func,
                                         gpointer           deinit_data);
static void        preview_free         (Preview           *preview);
static void        preview_render_start (Preview           *preview);
static void        preview_render_end   (Preview           *preview);
static void        preview_rgba_to_rgb  (guchar            *dest,
                                         gint               x,
                                         gint               y,
                                         guchar            *src);

static void             gradient_menu_init    (void);
static void             gradient_menu_rescan  (void);
static GradientMenu   * gradient_menu_new     (GradientMenuCallback callback,
                                               gpointer             callback_data,
                                               const gchar         *default_gradient_name);
static void             gradient_name_copy    (gchar               *dest,
                                               const gchar         *src);
static void             gradient_name_encode  (gchar               *dest,
                                               const gchar         *src);
static void             gradient_name_decode  (gchar               *dest,
                                               const gchar         *src);
static void             gradient_init         (void);
static void             gradient_free         (void);
static gchar         ** gradient_get_list     (gint                *num_gradients);
static void             gradient_get_values   (const gchar         *gradient_name,
                                               guchar              *values,
                                               gint                 nvalues);
static void             gradient_cache_flush  (void);

/**
***     +++ Static Functions Prototypes
**/

static void plugin_do                    (GimpProcedureConfig *config);

static void plugin_do_non_asupsample     (GeglBuffer          *src_buffer,
                                          GeglBuffer          *dest_buffer);
static void plugin_do_asupsample         (GeglBuffer          *src_buffer,
                                          GeglBuffer          *dest_buffer,
                                          gint                 asupsample_max_depth,
                                          gdouble              asupsample_threshold);
static void plugin_render_func           (gdouble              x,
                                          gdouble              y,
                                          gdouble             *color,
                                          gpointer             data);
static void plugin_put_pixel_func        (gint                 ix,
                                          gint                 iy,
                                          gdouble             *color,
                                          gpointer             data);
static void plugin_progress_func         (gint                 y1,
                                          gint                 y2,
                                          gint                 curr_y,
                                          gpointer             data);

static GFlare * gflare_new               (void);
static void gflare_free                  (GFlare              *gflare);
static void gflare_read_int              (gint                *intvar,
                                          GFlareFile          *gf);
static void gflare_read_double           (gdouble             *dblvar,
                                          GFlareFile          *gf);
static void gflare_read_gradient_name    (GradientName         name,
                                          GFlareFile          *gf);
static void gflare_read_shape            (GFlareShape         *shape,
                                          GFlareFile          *gf);
static void gflare_read_mode             (GFlareMode          *mode,
                                          GFlareFile          *gf);
static void gflare_write_gradient_name   (GradientName         name,
                                          FILE                *fp);

static gint calc_sample_one_gradient     (void);
static void calc_place_sflare            (void);
static void calc_get_gradient            (guchar              *pix,
                                          guchar              *gradient,
                                          gdouble              pos);
static gdouble fmod_positive             (gdouble              x,
                                          gdouble              m);
static void calc_paint_func              (guchar              *dest,
                                          guchar              *src1,
                                          guchar              *src2,
                                          gint                 opacity,
                                          GFlareMode           mode);
static void calc_combine                 (guchar              *dest,
                                          guchar              *src1,
                                          guchar              *src2,
                                          gint                 opacity);
static void calc_addition                (guchar              *dest,
                                          guchar              *src1,
                                          guchar              *src2);
static void calc_screen                  (guchar              *dest,
                                          guchar              *src1,
                                          guchar              *src2);
static void calc_overlay                 (guchar              *dest,
                                          guchar              *src1,
                                          guchar              *src2);

static void dlg_setup_gflare             (const gchar         *gflare_name);
static void dlg_preview_realize          (GtkWidget           *widget);
static gboolean dlg_preview_handle_event (GtkWidget           *widget,
                                          GdkEvent            *event,
                                          GimpProcedureConfig *config);
static void dlg_preview_update           (void);
static gint dlg_preview_init_func        (Preview             *preview,
                                          gpointer             data);
static void dlg_preview_render_func      (Preview             *preview,
                                          guchar              *dest,
                                          gint                 y,
                                          gpointer             data);
static void dlg_preview_deinit_func      (Preview             *preview,
                                          gpointer             data);
static void dlg_make_page_settings       (GFlareDialog        *dlg,
                                          GtkWidget           *notebook,
                                          GimpProcedureConfig *config);
static void dlg_position_entry_callback  (GtkWidget           *widget,
                                          GimpProcedureConfig *config);
static void dlg_update_preview_callback  (GtkWidget           *widget,
                                          gpointer             data);
static void dlg_make_page_selector       (GFlareDialog        *dlg,
                                          GtkWidget           *notebook,
                                          GimpProcedureConfig *config);

static void dlg_selector_setup_listbox      (void);
static void dlg_selector_list_item_callback (GtkTreeSelection *selection);

static void dlg_selector_new_callback       (GtkWidget        *widget,
                                             gpointer          data);
static void dlg_selector_new_ok_callback    (GtkWidget        *widget,
                                             const gchar      *new_name,
                                             gpointer          data);

static void dlg_selector_edit_callback      (GtkWidget        *widget,
                                             gpointer          data);
static void dlg_selector_edit_done_callback (gint              updated,
                                             gpointer          data);

static void dlg_selector_copy_callback      (GtkWidget        *widget,
                                             gpointer         data);
static void dlg_selector_copy_ok_callback   (GtkWidget        *widget,
                                             const gchar      *copy_name,
                                             gpointer          data);

static void dlg_selector_delete_callback    (GtkWidget        *widget,
                                             gpointer          data);
static void dlg_selector_do_delete_callback (GtkWidget        *widget,
                                             gboolean          delete,
                                             gpointer          data);

static void ed_run                (GtkWindow            *parent,
                                   GFlare               *target_gflare,
                                   GFlareEditorCallback  callback,
                                   gpointer              calldata);
static void ed_destroy_callback   (GtkWidget            *widget,
                                   GFlareEditor         *ed);
static void ed_response           (GtkWidget            *widget,
                                   gint                  response_id,
                                   GFlareEditor         *ed);
static void ed_make_page_general  (GFlareEditor         *ed,
                                   GtkWidget            *notebook);
static void ed_make_page_glow     (GFlareEditor         *ed,
                                   GtkWidget            *notebook);
static void ed_make_page_rays     (GFlareEditor         *ed,
                                   GtkWidget            *notebook);
static void ed_make_page_sflare   (GFlareEditor         *ed,
                                   GtkWidget            *notebook);
static void ed_put_gradient_menu  (GtkWidget            *grid,
                                   gint                  x,
                                   gint                  y,
                                   const gchar          *caption,
                                   GradientMenu         *gm);
static void ed_mode_menu_callback (GtkWidget            *widget,
                                   gpointer              data);
static void ed_gradient_menu_callback (const gchar *gradient_name,
                                       gpointer     data);
static void ed_shape_radio_callback   (GtkWidget   *widget,
                                       gpointer     data);
static void ed_ientry_callback        (GtkWidget   *widget,
                                       gpointer     data);
static void ed_page_map_callback      (GtkWidget   *widget,
                                       gpointer     data);
static void ed_preview_update         (void);
static gint ed_preview_init_func      (Preview     *preview,
                                       gpointer     data);
static void ed_preview_deinit_func    (Preview     *preview,
                                       gpointer     data);
static void ed_preview_render_func    (Preview     *preview,
                                       guchar      *buffer,
                                       gint         y,
                                       gpointer     data);
static void ed_preview_render_general (guchar      *buffer,
                                       gint         y);
static void ed_preview_render_glow    (guchar      *buffer,
                                       gint         y);
static void ed_preview_render_rays    (guchar      *buffer,
                                       gint         y);
static void ed_preview_render_sflare  (guchar      *buffer,
                                       gint         y);

static gint preview_render_start_2    (Preview *preview);
static gint preview_handle_idle       (Preview *preview);

static void gm_gradient_get_list                (void);
static void gm_gradient_combo_fill              (GradientMenu   *gm,
                                                 const gchar    *default_gradient);
static void gm_gradient_combo_callback          (GtkWidget      *widget,
                                                 gpointer        data);
static void gm_preview_draw                     (GtkWidget      *preview,
                                                 const gchar    *gradient_name);
static void gm_combo_destroy_callback           (GtkWidget      *widget,
                                                 gpointer        data);

static void gradient_get_values_internal        (const gchar    *gradient_name,
                                                 guchar         *values,
                                                 gint            nvalues);
static void gradient_get_blend                  (const guchar   *fg,
                                                 const guchar   *bg,
                                                 guchar         *values,
                                                 gint            nvalues);
static void gradient_get_random                 (guchar         *values,
                                                 gint            nvalues);
static void gradient_get_default                (const gchar    *name,
                                                 guchar         *values,
                                                 gint            nvalues);
static void gradient_get_values_external        (const gchar    *gradient_name,
                                                 guchar         *values,
                                                 gint            nvalues);
static void gradient_get_values_real_external   (const gchar    *gradient_name,
                                                 guchar         *values,
                                                 gint            nvalues,
                                                 gboolean        reverse);
static GradientCacheItem *gradient_cache_lookup (const gchar    *name,
                                                 gboolean       *found);
static void gradient_cache_zorch                (void);
static void gradient_scale_entry_update_double  (GimpLabelSpin  *entry,
                                                 gdouble        *value);
static void gradient_scale_entry_update_int     (GimpLabelSpin  *entry,
                                                 gint           *value);


G_DEFINE_TYPE (Gflare, gflare, GIMP_TYPE_PLUG_IN)

GIMP_MAIN (GFLARE_TYPE)
DEFINE_STD_SET_I18N


GFlare default_gflare =
{
  NULL,         /* name */
  NULL,         /* filename */
  100,          /* glow_opacity */
  GF_NORMAL,    /* glow_mode */
  100,          /* rays_opacity */
  GF_NORMAL,    /* rays_mode */
  100,          /* sflare_opacity */
  GF_NORMAL,    /* sflare_mode */
  "%red_grad",  /* glow_radial */
  "%white",     /* glow_angular */
  "%white",     /* glow_angular_size */
  100.0,        /* glow_size */
  0.0,          /* glow_rotation */
  0.0,          /* glow_hue */
  "%white_grad",/* rays_radial */
  "%random",    /* rays_angular */
  "%random",    /* rays_angular_size */
  100.0,        /* rays_size */
  0.0,          /* rays_rotation */
  0.0,          /* rays_hue */
  40,           /* rays_nspikes */
  20.0,         /* rays_thickness */
  "%white_grad",/* sflare_radial */
  "%random",    /* sflare_sizefac */
  "%random",    /* sflare_probability */
  40.0,         /* sflare_size */
  0.0,          /* sflare_rotation */
  0.0,          /* sflare_hue */
  GF_CIRCLE,    /* sflare_shape */
  6,            /* sflare_nverts */
  0,            /* sflare_seed */
  TRUE,         /* random_seed */
};

/* These are keywords to be written to disk files specifying flares. */
/* They are not translated since we want gflare files to be compatible
   across languages. */
static const gchar *gflare_modes[] =
{
  "NORMAL",
  "ADDITION",
  "OVERLAY",
  "SCREEN"
};

static const gchar *gflare_shapes[] =
{
  "CIRCLE",
  "POLYGON"
};

/* These are for menu entries, so they are translated. */
static const gchar *gflare_menu_modes[] =
{
  N_("Normal"),
  N_("Addition"),
  N_("Overlay"),
  N_("Screen")
};

static GimpImage          *image;
static GimpDrawable       *drawable;
static DrawableInfo        dinfo;
static GFlareDialog       *dlg = NULL;
static GFlareEditor       *ed = NULL;
static GList              *gflares_list = NULL;
static gint                num_gflares  = 0;
static gchar              *gflare_path  = NULL;
static CalcParams          calc;
static GList              *gradient_menus;
static gchar             **gradient_names = NULL;
static gint                num_gradient_names = 0;
static GradientCacheItem  *gradient_cache_head  = NULL;
static gint                gradient_cache_count = 0;


static const gchar *internal_gradients[] =
{
  "%white", "%white_grad", "%red_grad", "%blue_grad", "%yellow_grad", "%random"
};

#ifdef DEBUG
static gint     get_values_external_count = 0;
static clock_t  get_values_external_clock = 0;
#endif


static void
gflare_class_init (GflareClass *klass)
{
  GimpPlugInClass *plug_in_class = GIMP_PLUG_IN_CLASS (klass);

  plug_in_class->query_procedures = gflare_query_procedures;
  plug_in_class->create_procedure = gflare_create_procedure;
  plug_in_class->set_i18n         = STD_SET_I18N;
}

static void
gflare_init (Gflare *gflare)
{
}

static GList *
gflare_query_procedures (GimpPlugIn *plug_in)
{
  return g_list_append (NULL, g_strdup (PLUG_IN_PROC));
}

static GimpProcedure *
gflare_create_procedure (GimpPlugIn  *plug_in,
                         const gchar *name)
{
  GimpProcedure *procedure = NULL;

  if (! strcmp (name, PLUG_IN_PROC))
    {
      procedure = gimp_image_procedure_new (plug_in, name,
                                            GIMP_PDB_PROC_TYPE_PLUGIN,
                                            gflare_run, NULL, NULL);

      gimp_procedure_set_image_types (procedure, "RGB*, GRAY*");
      gimp_procedure_set_sensitivity_mask (procedure,
                                           GIMP_PROCEDURE_SENSITIVE_DRAWABLE);

      gimp_procedure_set_menu_label (procedure, _("_Gradient Flare..."));
      gimp_procedure_add_menu_path (procedure,
                                    "<Image>/Filters/Light and Shadow/[Light]");

      gimp_procedure_set_documentation (procedure,
                                        _("Produce a lense flare effect "
                                          "using gradients"),
                                        "This plug-in produces a lense flare "
                                        "effect using custom gradients. In "
                                        "interactive call, the user can edit "
                                        "their own favorite lense flare "
                                        "(GFlare) and render it. Edited "
                                        "gflare is saved automatically to "
                                        "the folder in gflare-path, if it is "
                                        "defined in gimprc. In "
                                        "non-interactive call, the user can "
                                        "only render one of GFlare "
                                        "which has been stored in "
                                        "gflare-path already.",
                                        name);
      gimp_procedure_set_attribution (procedure,
                                      "Eiichi Takamori",
                                      "Eiichi Takamori, and a lot of GIMP people",
                                      "1997");

      gimp_procedure_add_string_argument (procedure, "gflare-name",
                                          "GFlare name",
                                          "Name of the GFlare to render",
                                          "Default",
                                          G_PARAM_READWRITE);

      gimp_procedure_add_int_argument (procedure, "center-x",
                                       "Center X",
                                       "X coordinate of center of GFlare",
                                       -GIMP_MAX_IMAGE_SIZE, GIMP_MAX_IMAGE_SIZE, 128,
                                       G_PARAM_READWRITE);

      gimp_procedure_add_int_argument (procedure, "center-y",
                                       "Center Y",
                                       "Y coordinate of center of GFlare",
                                       -GIMP_MAX_IMAGE_SIZE, GIMP_MAX_IMAGE_SIZE, 128,
                                       G_PARAM_READWRITE);

      gimp_procedure_add_double_argument (procedure, "radius",
                                          _("Radi_us"),
                                          _("Radius of GFlare (pixel)"),
                                          1, GIMP_MAX_IMAGE_SIZE, 100,
                                          G_PARAM_READWRITE);

      gimp_procedure_add_double_argument (procedure, "rotation",
                                          _("Ro_tation"),
                                          _("Rotation of GFlare (degree)"),
                                          0, 360, 0,
                                          G_PARAM_READWRITE);

      gimp_procedure_add_double_argument (procedure, "hue",
                                          _("_Hue rotation"),
                                          _("Hue rotation of GFlare (degree)"),
                                          0, 360, 0,
                                          G_PARAM_READWRITE);

      gimp_procedure_add_double_argument (procedure, "vector-angle",
                                          _("Vector _angle"),
                                          _("Vector angle for second flares (degree)"),
                                          0, 360, 60,
                                          G_PARAM_READWRITE);

      gimp_procedure_add_double_argument (procedure, "vector-length",
                                          _("Vector len_gth"),
                                          _("Vector length for second flares "
                                          "(percentage of Radius)"),
                                          0, 10000, 400,
                                          G_PARAM_READWRITE);

      gimp_procedure_add_boolean_argument (procedure, "use-asupsample",
                                           _("Ada_ptive supersampling"),
                                           _("Use adaptive supersampling while rendering"),
                                           FALSE,
                                           G_PARAM_READWRITE);

      gimp_procedure_add_int_argument (procedure, "asupsample-max-depth",
                                       _("_Max depth"),
                                       _("Max depth for adaptive supersampling"),
                                       0, 10, 3,
                                       G_PARAM_READWRITE);

      gimp_procedure_add_double_argument (procedure, "asupsample-threshold",
                                          _("Threshol_d"),
                                          _("Threshold for adaptive supersampling"),
                                          0.0, 1.0, 0.2,
                                          G_PARAM_READWRITE);
    }

  return procedure;
}

static GimpValueArray *
gflare_run (GimpProcedure        *procedure,
            GimpRunMode           run_mode,
            GimpImage            *_image,
            GimpDrawable        **drawables,
            GimpProcedureConfig  *config,
            gpointer              run_data)
{
  gchar *path;

  gegl_init (NULL, NULL);

  image = _image;

  if (gimp_core_object_array_get_length ((GObject **) drawables) != 1)
    {
      GError *error = NULL;

      g_set_error (&error, GIMP_PLUG_IN_ERROR, 0,
                   _("Procedure '%s' only works with one drawable."),
                   gimp_procedure_get_name (procedure));

      return gimp_procedure_new_return_values (procedure,
                                               GIMP_PDB_CALLING_ERROR,
                                               error);
    }
  else
    {
      drawable = drawables[0];
    }

  dinfo.is_color  = gimp_drawable_is_rgb (drawable);
  dinfo.has_alpha = gimp_drawable_has_alpha (drawable);

  if (dinfo.is_color)
    {
      if (dinfo.has_alpha)
        dinfo.format = babl_format ("R'G'B'A u8");
      else
        dinfo.format = babl_format ("R'G'B' u8");
    }
  else
    {
      if (dinfo.has_alpha)
        dinfo.format = babl_format ("Y'A u8");
      else
        dinfo.format = babl_format ("Y u8");
    }

  dinfo.bpp = babl_format_get_bytes_per_pixel (dinfo.format);

  if (! gimp_drawable_mask_intersect (drawable,
                                      &dinfo.x, &dinfo.y, &dinfo.w, &dinfo.h))
    {
      return gimp_procedure_new_return_values (procedure,
                                               GIMP_PDB_SUCCESS,
                                               NULL);
    }

  /*
   *    Start gradient caching
   */

  gradient_init ();

  /*
   *    Parse gflare path from gimprc and load gflares
   */

  path = gimp_gimprc_query ("gflare-path");
  if (path)
    {
      gflare_path = g_filename_from_utf8 (path, -1, NULL, NULL, NULL);
      g_free (path);
    }
  else
    {
      GFile *gimprc    = gimp_directory_file ("gimprc", NULL);
      gchar *full_path = gimp_config_build_data_path ("gflare");
      gchar *esc_path  = g_strescape (full_path, NULL);
      g_free (full_path);

      g_message (_("No %s in gimprc:\n"
                   "You need to add an entry like\n"
                   "(%s \"%s\")\n"
                   "to your %s file."),
                 "gflare-path", "gflare-path",
                 esc_path, gimp_file_get_utf8_name (gimprc));

      g_object_unref (gimprc);
      g_free (esc_path);
    }

  gflares_list_load_all ();

  if (run_mode == GIMP_RUN_INTERACTIVE && ! dlg_run (procedure, config))
    return gimp_procedure_new_return_values (procedure,
                                             GIMP_PDB_CANCEL,
                                             NULL);

  if (gimp_drawable_is_rgb  (drawable) ||
      gimp_drawable_is_gray (drawable))
    {
      gimp_progress_init (_("Gradient Flare"));

      plugin_do (config);

      if (run_mode != GIMP_RUN_NONINTERACTIVE)
        gimp_displays_flush ();
    }
  else
    {
      GError *error =
        g_error_new_literal (0, 0,
                             _("Cannot operate on indexed color images."));

      return gimp_procedure_new_return_values (procedure,
                                               GIMP_PDB_EXECUTION_ERROR,
                                               error);
    }

  /*
   *    Deinitialization
   */
  gradient_free ();

  return gimp_procedure_new_return_values (procedure, GIMP_PDB_SUCCESS, NULL);
}

static void
plugin_do (GimpProcedureConfig *config)
{
  GeglBuffer *src_buffer;
  GeglBuffer *dest_buffer;
  GFlare     *gflare;
  gchar      *gflare_name;
  gint        xcenter;
  gint        ycenter;
  gdouble     radius;
  gdouble     rotation;
  gdouble     hue;
  gdouble     vangle;
  gdouble     vlength;
  gboolean    use_asupsample;
  gint        asupsample_max_depth;
  gdouble     asupsample_threshold;

  g_object_get (config,
                "gflare-name",          &gflare_name,
                "center-x",             &xcenter,
                "center-y",             &ycenter,
                "radius",               &radius,
                "rotation",             &rotation,
                "hue",                  &hue,
                "vector-angle",         &vangle,
                "vector-length",        &vlength,
                "use-asupsample",       &use_asupsample,
                "asupsample-max-depth", &asupsample_max_depth,
                "asupsample-threshold", &asupsample_threshold,
                NULL);

  gflare = gflares_list_lookup (gflare_name);
  if (gflare == NULL)
    {
      /* FIXME */
      g_warning ("Not found %s\n", gflare_name);
      g_free (gflare_name);
      return;
    }
  g_free (gflare_name);

  /* Initialize calc params and gradients */
  calc_init_params (gflare, CALC_GLOW | CALC_RAYS | CALC_SFLARE,
                    xcenter, ycenter,
                    radius, rotation, hue,
                    vangle, vlength);
  while (calc_init_progress ()) ;

  src_buffer  = gimp_drawable_get_buffer (drawable);
  dest_buffer = gimp_drawable_get_shadow_buffer (drawable);

  /* Render it ! */
  if (use_asupsample)
    plugin_do_asupsample (src_buffer, dest_buffer, asupsample_max_depth, asupsample_threshold);
  else
    plugin_do_non_asupsample (src_buffer, dest_buffer);

  g_object_unref (src_buffer);
  g_object_unref (dest_buffer);

  gimp_progress_update (1.0);

  /* Clean up */
  calc_deinit ();

  gimp_drawable_merge_shadow (drawable, TRUE);
  gimp_drawable_update (drawable, dinfo.x, dinfo.y, dinfo.w, dinfo.h);
}

/* these routines should be almost rewritten anyway */

static void
plugin_do_non_asupsample (GeglBuffer   *src_buffer,
                          GeglBuffer   *dest_buffer)
{
  GeglBufferIterator *iter;
  gint                progress;
  gint                max_progress;

  progress = 0;
  max_progress = dinfo.w * dinfo.h;

  iter = gegl_buffer_iterator_new (src_buffer,
                                   GEGL_RECTANGLE (dinfo.x, dinfo.y,
                                                   dinfo.w, dinfo.h), 0,
                                   dinfo.format,
                                   GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 2);

  gegl_buffer_iterator_add (iter, dest_buffer,
                            GEGL_RECTANGLE (dinfo.x, dinfo.y,
                                            dinfo.w, dinfo.h), 0,
                            dinfo.format,
                            GEGL_ACCESS_WRITE, GEGL_ABYSS_NONE);

  while (gegl_buffer_iterator_next (iter))
    {
      const guchar *src_row  = iter->items[0].data;
      guchar       *dest_row = iter->items[1].data;
      gint          row, y;

      for (row = 0, y = iter->items[0].roi.y;
           row < iter->items[0].roi.height;
           row++, y++)
        {
          const guchar *src  = src_row;
          guchar       *dest = dest_row;
          gint          col, x;

          for (col = 0, x = iter->items[0].roi.x;
               col < iter->items[0].roi.width;
               col++, x++)
            {
              guchar  src_pix[4];
              guchar  dest_pix[4];
              gint    b;

              for (b = 0; b < 3; b++)
                src_pix[b] = dinfo.is_color ? src[b] : src[0];

              src_pix[3] = dinfo.has_alpha ? src[dinfo.bpp - 1] : OPAQUE;

              calc_gflare_pix (dest_pix, x, y, src_pix);

              if (dinfo.is_color)
                {
                  for (b = 0; b < 3; b++)
                    dest[b] = dest_pix[b];
                }
              else
                {
                  dest[0] = LUMINOSITY (dest_pix);
                }

              if (dinfo.has_alpha)
                dest[dinfo.bpp - 1] = dest_pix[3];

              src  += dinfo.bpp;
              dest += dinfo.bpp;
            }

          src_row  += dinfo.bpp * iter->items[0].roi.width;
          dest_row += dinfo.bpp * iter->items[1].roi.width;
        }

      /* Update progress */
      progress += iter->items[0].roi.width * iter->items[0].roi.height;
      gimp_progress_update ((double) progress / (double) max_progress);
    }
}

static void
plugin_do_asupsample (GeglBuffer *src_buffer,
                      GeglBuffer *dest_buffer,
                      gint        asupsample_max_depth,
                      gdouble     asupsample_threshold)
{
  gimp_adaptive_supersample_area (dinfo.x, dinfo.y,
                                  dinfo.x + dinfo.w - 1, dinfo.y + dinfo.h - 1,
                                  asupsample_max_depth,
                                  asupsample_threshold,
                                  plugin_render_func,
                                  src_buffer,
                                  plugin_put_pixel_func,
                                  dest_buffer,
                                  plugin_progress_func,
                                  NULL);
}

/*
  Adaptive supersampling callback functions

  These routines may look messy, since adaptive supersampling needs
  pixel values in `double' (from 0.0 to 1.0) but calc_*_pix () returns
  guchar values. */

static void
plugin_render_func (gdouble   x,
                    gdouble   y,
                    gdouble  *color,
                    gpointer  data)
{
  GeglBuffer *src_buffer = data;
  guchar      src_pix[4];
  guchar      flare_pix[4];
  guchar      src[4];
  gint        b;
  gint        ix, iy;

  /* translate (0.5, 0.5) before convert to `int' so that it can surely
     point the center of pixel */
  ix = floor (x + 0.5);
  iy = floor (y + 0.5);

  gegl_buffer_sample (src_buffer, ix, iy, NULL, src, dinfo.format,
                      GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);

  for (b = 0; b < 3; b++)
    src_pix[b] = dinfo.is_color ? src[b] : src[0];
  src_pix[3] = dinfo.has_alpha ? src[dinfo.bpp - 1] : OPAQUE;

  calc_gflare_pix (flare_pix, x, y, src_pix);

  color[0] = flare_pix[0] / 255.0;
  color[1] = flare_pix[1] / 255.0;
  color[2] = flare_pix[2] / 255.0;
  color[3] = flare_pix[3] / 255.0;
}

static void
plugin_put_pixel_func (gint      ix,
                       gint      iy,
                       gdouble  *color,
                       gpointer  data)
{
  GeglBuffer *dest_buffer = data;
  guchar      dest[4];

  if (dinfo.is_color)
    {
      dest[0] = color[0] * 255;
      dest[1] = color[1] * 255;
      dest[2] = color[2] * 255;
    }
  else
    {
      guchar rgb[3] = {color[0] * 255, color[2] * 255, color[3] * 255};

      dest[0] = GIMP_RGB_LUMINANCE (rgb[0], rgb[1], rgb[2]);
    }

  if (dinfo.has_alpha)
    dest[dinfo.bpp - 1] = color[3] * 255;

  gegl_buffer_set (dest_buffer, GEGL_RECTANGLE (ix, iy, 1, 1), 0,
                   dinfo.format, dest, GEGL_AUTO_ROWSTRIDE);
}

static void
plugin_progress_func (gint     y1,
                      gint     y2,
                      gint     curr_y,
                      gpointer data)
{
  gimp_progress_update ((double) curr_y / (double) (y2 - y1));
}

/*************************************************************************/
/**                                                                     **/
/**             +++ GFlare Routines                                     **/
/**                                                                     **/
/*************************************************************************/

/*
 *      These code are more or less based on Quartic's gradient.c,
 *      other gimp sources, and script-fu.
 */

static GFlare *
gflare_new (void)
{
  GFlare *gflare = g_new0 (GFlare, 1);

  gflare->name     = NULL;
  gflare->filename = NULL;

  return gflare;
}

static GFlare *
gflare_new_with_default (const gchar *new_name)
{
  return gflare_dup (&default_gflare, new_name);
}

static GFlare *
gflare_dup (const GFlare *src,
            const gchar  *new_name)
{
  GFlare *dest = g_new0 (GFlare, 1);

  *dest = *src;

  dest->name = g_strdup (new_name);
  dest->filename = NULL;

  return dest;
}

static void
gflare_copy (GFlare       *dest,
             const GFlare *src)
{
  gchar *name, *filename;

  name = dest->name;
  filename = dest->filename;

  *dest = *src;

  dest->name = name;
  dest->filename =filename;
}


static void
gflare_free (GFlare *gflare)
{
  g_return_if_fail (gflare != NULL);

  g_free (gflare->name);
  g_free (gflare->filename);
  g_free (gflare);
}

GFlare *
gflare_load (const gchar *filename,
             const gchar *name)
{
  FILE          *fp;
  GFlareFile    *gf;
  GFlare        *gflare;
  gchar         header[256];

  g_return_val_if_fail (filename != NULL, NULL);

  fp = g_fopen (filename, "rb");
  if (!fp)
    {
      g_message (_("Failed to open GFlare file '%s': %s"),
                  gimp_filename_to_utf8 (filename), g_strerror (errno));
      return NULL;
    }

  if (fgets (header, sizeof(header), fp) == NULL
      || strcmp (header, GFLARE_FILE_HEADER) != 0)
    {
      g_warning (_("'%s' is not a valid GFlare file."),
                  gimp_filename_to_utf8 (filename));
      fclose (fp);
      return NULL;
    }

  gf = g_new (GFlareFile, 1);
  gf->fp = fp;
  gf->error = FALSE;

  gflare = gflare_new ();
  gflare->name = g_strdup (name);
  gflare->filename = g_strdup (filename);

  gflare_read_double   (&gflare->glow_opacity, gf);
  gflare_read_mode     (&gflare->glow_mode, gf);
  gflare_read_double   (&gflare->rays_opacity, gf);
  gflare_read_mode     (&gflare->rays_mode, gf);
  gflare_read_double   (&gflare->sflare_opacity, gf);
  gflare_read_mode     (&gflare->sflare_mode, gf);

  gflare_read_gradient_name (gflare->glow_radial, gf);
  gflare_read_gradient_name (gflare->glow_angular, gf);
  gflare_read_gradient_name (gflare->glow_angular_size, gf);
  gflare_read_double   (&gflare->glow_size, gf);
  gflare_read_double   (&gflare->glow_rotation, gf);
  gflare_read_double   (&gflare->glow_hue, gf);

  gflare_read_gradient_name (gflare->rays_radial, gf);
  gflare_read_gradient_name (gflare->rays_angular, gf);
  gflare_read_gradient_name (gflare->rays_angular_size, gf);
  gflare_read_double   (&gflare->rays_size, gf);
  gflare_read_double   (&gflare->rays_rotation, gf);
  gflare_read_double   (&gflare->rays_hue, gf);
  gflare_read_int      (&gflare->rays_nspikes, gf);
  gflare_read_double   (&gflare->rays_thickness, gf);

  gflare_read_gradient_name (gflare->sflare_radial, gf);
  gflare_read_gradient_name (gflare->sflare_sizefac, gf);
  gflare_read_gradient_name (gflare->sflare_probability, gf);
  gflare_read_double   (&gflare->sflare_size, gf);
  gflare_read_double   (&gflare->sflare_hue, gf);
  gflare_read_double   (&gflare->sflare_rotation, gf);
  gflare_read_shape    (&gflare->sflare_shape, gf);
  gflare_read_int      (&gflare->sflare_nverts, gf);
  gflare_read_int      ((gint *) &gflare->sflare_seed, gf);

  if (gflare->sflare_seed == 0)
    gflare->sflare_seed = g_random_int();

  fclose (gf->fp);

  if (gf->error)
    {
      g_warning (_("invalid formatted GFlare file: %s\n"), filename);
      g_free (gflare);
      g_free (gf);
      return NULL;
    }

  g_free (gf);

  return gflare;
}

static void
gflare_read_int (gint       *intvar,
                 GFlareFile *gf)
{
  if (gf->error)
    return;

  if (fscanf (gf->fp, "%d", intvar) != 1)
    gf->error = TRUE;
}

static void
gflare_read_double (gdouble    *dblvar,
                    GFlareFile *gf)
{
  gchar buf[31];

  if (gf->error)
    return;

  if (fscanf (gf->fp, "%30s", buf) == 1)
    *dblvar = g_ascii_strtod (buf, NULL);
  else
    gf->error = TRUE;
}

static void
gflare_read_gradient_name (GradientName  name,
                           GFlareFile   *gf)
{
  gchar tmp[1024], dec[1024];

  if (gf->error)
    return;

  /* FIXME: this is buggy */

  if (fscanf (gf->fp, "%1023s", tmp) == 1)
    {
      /* @GRADIENT_NAME */
      gradient_name_decode (dec, tmp);
      gradient_name_copy (name, dec);
    }
  else
    gf->error = TRUE;
}

static void
gflare_read_shape (GFlareShape *shape,
                   GFlareFile  *gf)
{
  gchar tmp[1024];
  gint  i;

  if (gf->error)
    return;

  if (fscanf (gf->fp, "%1023s", tmp) == 1)
    {
      for (i = 0; i < GF_NUM_SHAPES; i++)
        if (strcmp (tmp, gflare_shapes[i]) == 0)
          {
            *shape = i;
            return;
          }
    }
  gf->error = TRUE;
}

static void
gflare_read_mode (GFlareMode *mode,
                  GFlareFile *gf)
{
  gchar tmp[1024];
  gint  i;

  if (gf->error)
    return;

  if (fscanf (gf->fp, "%1023s", tmp) == 1)
    {
      for (i = 0; i < GF_NUM_MODES; i++)
        if (strcmp (tmp, gflare_modes[i]) == 0)
          {
            *mode = i;
            return;
          }
    }
  gf->error = TRUE;
}

static void
gflare_save (GFlare *gflare)
{
  FILE  *fp;
  gchar *path;
  gchar  buf[3][G_ASCII_DTOSTR_BUF_SIZE];
  static gboolean message_ok = FALSE;

  if (gflare->filename == NULL)
    {
      GList *list;

      if (gflare_path == NULL)
        {
          if (! message_ok)
            {
              GFile *gimprc = gimp_directory_file ("gimprc", NULL);
              GFile *dir    = gimp_directory_file ("gflare", NULL);
              gchar *gflare_dir;

              gflare_dir =
                g_strescape ("${gimp_dir}" G_DIR_SEPARATOR_S "gflare", NULL);

              g_message (_("GFlare '%s' is not saved. If you add a new entry "
                           "in '%s', like:\n"
                           "(gflare-path \"%s\")\n"
                           "and make a folder '%s', then you can save "
                           "your own GFlares into that folder."),
                         gflare->name,
                         gimp_file_get_utf8_name (gimprc),
                         gflare_dir,
                         gimp_file_get_utf8_name (dir));

              g_object_unref (gimprc);
              g_object_unref (dir);
              g_free (gflare_dir);

              message_ok = TRUE;
            }

          return;
        }

      list = gimp_path_parse (gflare_path, 256, FALSE, NULL);
      path = gimp_path_get_user_writable_dir (list);
      gimp_path_free (list);

      if (! path)
        path = g_strdup (gimp_directory ());

      gflare->filename = g_build_filename (path, gflare->name, NULL);

      g_free (path);
    }

  fp = g_fopen (gflare->filename, "wb");
  if (!fp)
    {
      g_message (_("Failed to write GFlare file '%s': %s"),
                  gimp_filename_to_utf8 (gflare->filename), g_strerror (errno));
      return;
    }

  fprintf (fp, "%s", GFLARE_FILE_HEADER);
  g_ascii_dtostr (buf[0],
                  G_ASCII_DTOSTR_BUF_SIZE, gflare->glow_opacity);
  fprintf (fp, "%s %s\n", buf[0], gflare_modes[gflare->glow_mode]);
  g_ascii_dtostr (buf[0],
                  G_ASCII_DTOSTR_BUF_SIZE, gflare->rays_opacity);
  fprintf (fp, "%s %s\n", buf[0], gflare_modes[gflare->rays_mode]);
  g_ascii_dtostr (buf[0],
                  G_ASCII_DTOSTR_BUF_SIZE, gflare->sflare_opacity);
  fprintf (fp, "%s %s\n", buf[0], gflare_modes[gflare->sflare_mode]);

  gflare_write_gradient_name (gflare->glow_radial, fp);
  gflare_write_gradient_name (gflare->glow_angular, fp);
  gflare_write_gradient_name (gflare->glow_angular_size, fp);
  g_ascii_dtostr (buf[0],
                  G_ASCII_DTOSTR_BUF_SIZE, gflare->glow_size);
  g_ascii_dtostr (buf[1],
                  G_ASCII_DTOSTR_BUF_SIZE, gflare->glow_rotation);
  g_ascii_dtostr (buf[2],
                  G_ASCII_DTOSTR_BUF_SIZE, gflare->glow_hue);
  fprintf (fp, "%s %s %s\n", buf[0], buf[1], buf[2]);

  gflare_write_gradient_name (gflare->rays_radial, fp);
  gflare_write_gradient_name (gflare->rays_angular, fp);
  gflare_write_gradient_name (gflare->rays_angular_size, fp);
  g_ascii_dtostr (buf[0],
                  G_ASCII_DTOSTR_BUF_SIZE, gflare->rays_size);
  g_ascii_dtostr (buf[1],
                  G_ASCII_DTOSTR_BUF_SIZE, gflare->rays_rotation);
  g_ascii_dtostr (buf[2],
                  G_ASCII_DTOSTR_BUF_SIZE, gflare->rays_hue);
  fprintf (fp, "%s %s %s\n", buf[0], buf[1], buf[2]);
  g_ascii_dtostr (buf[0],
                  G_ASCII_DTOSTR_BUF_SIZE, gflare->rays_thickness);
  fprintf (fp, "%d %s\n", gflare->rays_nspikes, buf[0]);

  gflare_write_gradient_name (gflare->sflare_radial, fp);
  gflare_write_gradient_name (gflare->sflare_sizefac, fp);
  gflare_write_gradient_name (gflare->sflare_probability, fp);
  g_ascii_dtostr (buf[0],
                  G_ASCII_DTOSTR_BUF_SIZE, gflare->sflare_size);
  g_ascii_dtostr (buf[1],
                  G_ASCII_DTOSTR_BUF_SIZE, gflare->sflare_rotation);
  g_ascii_dtostr (buf[2],
                  G_ASCII_DTOSTR_BUF_SIZE, gflare->sflare_hue);
  fprintf (fp, "%s %s %s\n", buf[0], buf[1], buf[2]);
  fprintf (fp, "%s %d %d\n",
           gflare_shapes[gflare->sflare_shape],
           gflare->sflare_nverts, gflare->sflare_seed);

  fclose (fp);
}

static void
gflare_write_gradient_name (GradientName  name,
                            FILE         *fp)
{
  gchar enc[1024];

  /* @GRADIENT_NAME */

  /* encode white spaces and control characters (if any) */
  gradient_name_encode (enc, name);

  fprintf (fp, "%s\n", enc);
}

/*************************************************************************/
/**                                                                     **/
/**             +++ GFlares List                                        **/
/**                                                                     **/
/*************************************************************************/

static gint
gflare_compare (const GFlare *flare1,
                const GFlare *flare2)
{
  return strcmp (flare1->name, flare2->name);
}

static gint
gflares_list_insert (GFlare *gflare)
{
  num_gflares++;
  gflares_list = g_list_insert_sorted (gflares_list, gflare,
                                       (GCompareFunc) gflare_compare);
  return gflares_list_index (gflare);
}

static gint
gflare_compare_name (const GFlare *flare,
                     const gchar  *name)
{
  return strcmp (flare->name, name);
}

static GFlare *
gflares_list_lookup (const gchar *name)
{
  GList *llink;
  llink = g_list_find_custom (gflares_list, name,
                              (GCompareFunc) gflare_compare_name);
  return (llink) ? llink->data : NULL;
}

static gint
gflares_list_index (GFlare *gflare)
{
  return g_list_index (gflares_list, gflare);
}

static gint
gflares_list_remove (GFlare *gflare)
{
  GList         *tmp;
  gint          n;

  n = 0;
  tmp = gflares_list;
  while (tmp)
    {
      if (tmp->data == gflare)
        {
          /* Found! */
          if (tmp->next == NULL)
          num_gflares--;
          gflares_list = g_list_remove (gflares_list, gflare);
          return n;
        }
      tmp = tmp->next;
      n++;
    }
  return -1;
}

/*
 * Load all gflares, which are founded in gflare-path-list, into gflares_list.
 */
static void
gflares_list_load_all (void)
{
  GList *path;
  GList *list;

  /*  Make sure to clear any existing gflares  */
  gflares_list_free_all ();

  path = gimp_config_path_expand_to_files (gflare_path, NULL);

  for (list = path; list; list = g_list_next (list))
    {
      GFileEnumerator *enumerator;

      enumerator = g_file_enumerate_children (list->data,
                                              G_FILE_ATTRIBUTE_STANDARD_NAME ","
                                              G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN ","
                                              G_FILE_ATTRIBUTE_STANDARD_TYPE,
                                              G_FILE_QUERY_INFO_NONE,
                                              NULL, NULL);

      if (enumerator)
        {
          GFileInfo *info;

          while ((info = g_file_enumerator_next_file (enumerator, NULL, NULL)))
            {
              GFileType file_type = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_STANDARD_TYPE);

              if (file_type == G_FILE_TYPE_REGULAR &&
                  ! g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN))
                {
                  GFlare *gflare;
                  GFile  *child;
                  gchar  *filename;
                  gchar  *basename;

                  child = g_file_enumerator_get_child (enumerator, info);

                  filename = g_file_get_path (child);
                  basename = g_file_get_basename (child);

                  gflare = gflare_load (filename, basename);

                  g_free (filename);
                  g_free (basename);

                  if (gflare)
                    gflares_list_insert (gflare);

                  g_object_unref (child);
                }

              g_object_unref (info);
            }

          g_object_unref (enumerator);
        }
    }

  g_list_free_full (path, (GDestroyNotify) g_object_unref);
}

static void
gflares_list_free_all (void)
{
  g_list_free_full (gflares_list, (GDestroyNotify) gflare_free);
  gflares_list = NULL;
}

/*************************************************************************/
/**                                                                     **/
/**             +++ Calculator                                          **/
/**                                                                     **/
/*************************************************************************/


/*
 * These routines calculates pixel values of particular gflare, at
 * specified point.  The client which wants to get benefit from these
 * calculation routines must call calc_init_params() first, and
 * iterate calling calc_init_progress() until it returns FALSE. and
 * must call calc_deinit() when job is done.
 */

static void
calc_init_params (GFlare  *gflare,
                  gint     calc_type,
                  gint     xcenter,
                  gint     ycenter,
                  gdouble  radius,
                  gdouble  rotation,
                  gdouble  hue,
                  gdouble  vangle,
                  gdouble  vlength)
{
  calc.type            = calc_type;
  calc.gflare          = gflare;
  calc.xcenter         = xcenter;
  calc.ycenter         = ycenter;
  calc.radius          = radius;
  calc.rotation        = rotation * G_PI / 180.0;
  calc.hue             = hue;
  calc.vangle          = vangle * G_PI / 180.0;
  calc.vlength         = radius * vlength / 100.0;
  calc.glow_radius     = radius * gflare->glow_size / 100.0;
  calc.rays_radius     = radius * gflare->rays_size / 100.0;
  calc.sflare_radius   = radius * gflare->sflare_size / 100.0;
  calc.glow_rotation   = (rotation + gflare->glow_rotation) * G_PI / 180.0;
  calc.rays_rotation   = (rotation + gflare->rays_rotation) * G_PI / 180.0;
  calc.sflare_rotation = (rotation + gflare->sflare_rotation) * G_PI / 180.0;
  calc.glow_opacity    = gflare->glow_opacity * 255 / 100.0;
  calc.rays_opacity    = gflare->rays_opacity * 255 / 100.0;
  calc.sflare_opacity  = gflare->sflare_opacity * 255 / 100.0;

  calc.glow_bounds.x0 = calc.xcenter - calc.glow_radius - 0.1;
  calc.glow_bounds.x1 = calc.xcenter + calc.glow_radius + 0.1;
  calc.glow_bounds.y0 = calc.ycenter - calc.glow_radius - 0.1;
  calc.glow_bounds.y1 = calc.ycenter + calc.glow_radius + 0.1;
  calc.rays_bounds.x0 = calc.xcenter - calc.rays_radius - 0.1;
  calc.rays_bounds.x1 = calc.xcenter + calc.rays_radius + 0.1;
  calc.rays_bounds.y0 = calc.ycenter - calc.rays_radius - 0.1;
  calc.rays_bounds.y1 = calc.ycenter + calc.rays_radius + 0.1;

  /* Thanks to Marcelo Malheiros for this algorithm */
  calc.rays_thinness  = log (gflare->rays_thickness / 100.0) / log(0.8);

  calc.rays_spike_mod = 1.0 / (2 * gflare->rays_nspikes);

  /*
    Initialize part of sflare
    The rest will be initialized in calc_sflare()
   */
  calc.sflare_list = NULL;
  calc.sflare_shape = gflare->sflare_shape;
  if (calc.sflare_shape == GF_POLYGON)
    {
      calc.sflare_angle = 2 * G_PI / (2 * gflare->sflare_nverts);
      calc.sflare_factor = 1.0 / cos (calc.sflare_angle);
    }

  calc.glow_radial        = NULL;
  calc.glow_angular       = NULL;
  calc.glow_angular_size  = NULL;
  calc.rays_radial        = NULL;
  calc.rays_angular       = NULL;
  calc.rays_angular_size  = NULL;
  calc.sflare_radial      = NULL;
  calc.sflare_sizefac     = NULL;
  calc.sflare_probability = NULL;

  calc.init = TRUE;
}

static int
calc_init_progress (void)
{
  if (calc_sample_one_gradient ())
    return TRUE;
  calc_place_sflare ();
  return FALSE;
}

/*
   Store samples of gradient into an array
   this routine is called during Calc initialization
   this code is very messy... :( */
static int
calc_sample_one_gradient (void)
{
  static struct
  {
    guchar **values;
    gint     name_offset;
    gint     hue_offset;
    gint     gray;
  } table[] = {
    {
      &calc.glow_radial,
      G_STRUCT_OFFSET (GFlare, glow_radial),
      G_STRUCT_OFFSET (GFlare, glow_hue),
      FALSE
    },
    {
      &calc.glow_angular,
      G_STRUCT_OFFSET (GFlare, glow_angular),
      0,
      FALSE
    },
    {
      &calc.glow_angular_size,
      G_STRUCT_OFFSET (GFlare, glow_angular_size),
      0,
      TRUE
    },
    {
      &calc.rays_radial,
      G_STRUCT_OFFSET (GFlare, rays_radial),
      G_STRUCT_OFFSET (GFlare, rays_hue),
      FALSE
    },
    {
      &calc.rays_angular,
      G_STRUCT_OFFSET (GFlare, rays_angular),
      0,
      FALSE
    },
    {
      &calc.rays_angular_size,
      G_STRUCT_OFFSET (GFlare, rays_angular_size),
      0,
      TRUE
    },
    {
      &calc.sflare_radial,
      G_STRUCT_OFFSET (GFlare, sflare_radial),
      G_STRUCT_OFFSET (GFlare, sflare_hue),
      FALSE
    },
    {
      &calc.sflare_sizefac,
      G_STRUCT_OFFSET (GFlare, sflare_sizefac),
      0,
      TRUE
    },
    {
      &calc.sflare_probability,
      G_STRUCT_OFFSET (GFlare, sflare_probability),
      0,
      TRUE
    }
  };
  GFlare        *gflare = calc.gflare;
  GradientName  *grad_name;
  guchar        *gradient;
  gdouble        hue_deg;
  gint           i, j, hue;
  const Babl    *to_hsv = babl_fish ("R'G'B'A u8", "HSVA float");
  const Babl    *to_rgb = babl_fish ("HSVA float", "R'G'B'A u8");

  for (i = 0; i < G_N_ELEMENTS (table); i++)
    {
      if (*(table[i].values) == NULL)
        {
          /* @GRADIENT_NAME */
          grad_name = (GradientName *) ((char*) gflare + table[i].name_offset);
          gradient = *(table[i].values) = g_new (guchar, 4 * GRADIENT_RESOLUTION);
          gradient_get_values (*grad_name, gradient, GRADIENT_RESOLUTION);

          /*
           * Do hue rotation, if needed
           */

          if (table[i].hue_offset != 0)
            {
              hue_deg = calc.hue + *(gdouble *) ((char*) gflare + table[i].hue_offset);
              hue = (gint) (hue_deg / 360.0 * 256.0) % 256;
              if (hue < 0)
                hue += 256;
              g_assert (0 <= hue && hue < 256);

              if (hue > 0)
                {
                  gfloat *hsva;

                  hsva = g_new0 (gfloat, sizeof (gfloat) * (GRADIENT_RESOLUTION * 4));
                  babl_process (to_hsv, gradient, hsva, GRADIENT_RESOLUTION);

                  for (j = 0; j < GRADIENT_RESOLUTION; j++)
                    {
                      hsva[j * 4] = (hsva[j * 4] + ((gdouble) hue / 255.0));

                      if (hsva[j * 4] > 1.0)
                        hsva[j * 4] -= 1.0;
                    }

                  babl_process (to_rgb, hsva, gradient, GRADIENT_RESOLUTION);

                  g_free (hsva);
                }
            }

          /*
           *    Grayfy gradient, if needed
           */

          if (table[i].gray)
            {
              for (j = 0; j < GRADIENT_RESOLUTION; j++)
                /* the first byte is enough */
                gradient[j*4] = LUMINOSITY ((gradient + j*4));

            }

          /* sampling of one gradient is done */
          return TRUE;
        }
    }
  return FALSE;
}

static void
calc_place_sflare (void)
{
  GFlare        *gflare;
  CalcSFlare    *sflare;
  gdouble       prob[GRADIENT_RESOLUTION];
  gdouble       sum, sum2;
  gdouble       pos;
  gdouble       rnd, sizefac;
  int           n;
  int           i;
  GRand        *gr;

  gr = g_rand_new ();
  if ((calc.type & CALC_SFLARE) == 0)
    return;

  gflare = calc.gflare;

  /*
    Calc cumulative probability
    */

  sum = 0.0;
  for (i = 0; i < GRADIENT_RESOLUTION; i++)
    {
      /* probability gradient was grayfied already */
      prob[i] = calc.sflare_probability[i*4];
      sum += prob[i];
    }

  if (sum == 0.0)
    sum = 1.0;

  sum2 = 0;
  for (i = 0; i < GRADIENT_RESOLUTION; i++)
    {
      sum2 += prob[i];          /* cumulation */
      prob[i] = sum2 / sum;
    }

  g_rand_set_seed (gr, gflare->sflare_seed);

  for (n = 0; n < SFLARE_NUM; n++)
    {
      sflare = g_new (CalcSFlare, 1);
      rnd = g_rand_double (gr);
      for (i = 0; i < GRADIENT_RESOLUTION; i++)
        if (prob[i] >= rnd)
          break;
      if (i >= GRADIENT_RESOLUTION)
        i = GRADIENT_RESOLUTION - 1;

      /* sizefac gradient was grayfied already */
      sizefac = calc.sflare_sizefac[i*4] / 255.0;
      sizefac = pow (sizefac, 5.0);

      pos = (double) (i - GRADIENT_RESOLUTION / 2) / GRADIENT_RESOLUTION;
      sflare->xcenter = calc.xcenter + cos (calc.vangle) * calc.vlength * pos;
      sflare->ycenter = calc.ycenter - sin (calc.vangle) * calc.vlength * pos;
      sflare->radius = sizefac * calc.sflare_radius; /* FIXME */
      sflare->bounds.x0 = sflare->xcenter - sflare->radius - 1;
      sflare->bounds.x1 = sflare->xcenter + sflare->radius + 1;
      sflare->bounds.y0 = sflare->ycenter - sflare->radius - 1;
      sflare->bounds.y1 = sflare->ycenter + sflare->radius + 1;
      calc.sflare_list = g_list_append (calc.sflare_list, sflare);
    }

  g_rand_free (gr);
}

static void
calc_deinit (void)
{
  if (!calc.init)
    {
      g_warning("calc_deinit: not initialized");
      return;
    }

  g_list_free_full (g_steal_pointer (&calc.sflare_list), (GDestroyNotify) g_free);
  calc.sflare_list = NULL;

  g_free (calc.glow_radial);
  g_free (calc.glow_angular);
  g_free (calc.glow_angular_size);
  g_free (calc.rays_radial);
  g_free (calc.rays_angular);
  g_free (calc.rays_angular_size);
  g_free (calc.sflare_radial);
  g_free (calc.sflare_sizefac);
  g_free (calc.sflare_probability);

  calc.init = FALSE;
}

/*
 *  Get sample value at specified position of a gradient
 *
 *  gradient samples are stored into array at the time of
 *  calc_sample_one_gradients (), and it is now linear interpolated.
 *
 *  INPUT:
 *      guchar  gradient[4*GRADIENT_RESOLUTION]         gradient array(RGBA)
 *      gdouble pos                                     position (0<=pos<=1)
 *  OUTPUT:
 *      guchar  pix[4]
 */
static void
calc_get_gradient (guchar *pix, guchar *gradient, gdouble pos)
{
  gint          ipos;
  gdouble       frac;
  gint          i;

  if (isnan (pos) || pos < 0.0001 || pos > 1)
    {
      pix[0] = pix[1] = pix[2] = pix[3] = 0;
      return;
    }
  pos *= GRADIENT_RESOLUTION - 1.0001;
  ipos = (gint) pos;    frac = pos - ipos;
  gradient += ipos * 4;

  for (i = 0; i < 4; i++)
    {
      pix[i] = gradient[i] * (1 - frac) + gradient[i+4] * frac;
    }
}

/* I need fmod to return always positive value */
static gdouble
fmod_positive (gdouble x, gdouble m)
{
  return x - floor (x/m) * m;
}

/*
 *  Calc glow's pixel (RGBA) value
 *  INPUT:
 *      gdouble x, y                    image coordinates
 *  OUTPUT:
 *      guchar  pix[4]
 */
static void
calc_glow_pix (guchar *dest_pix, gdouble x, gdouble y)
{
  gdouble radius, angle;
  gdouble angular_size;
  guchar  radial_pix[4], angular_pix[4], size_pix[4];
  gint    i;

  if ((calc.type & CALC_GLOW) == 0
      || x < calc.glow_bounds.x0 || x > calc.glow_bounds.x1
      || y < calc.glow_bounds.y0 || y > calc.glow_bounds.y1)
    {
      memset (dest_pix, 0, 4);
      return;
    }

  x -= calc.xcenter;
  y -= calc.ycenter;
  radius = sqrt (x*x + y*y) / calc.glow_radius;
  angle = (atan2 (-y, x) + calc.glow_rotation ) / (2 * G_PI);
  angle = fmod_positive (angle, 1.0);

  calc_get_gradient (size_pix, calc.glow_angular_size, angle);
  /* angular_size gradient was grayfied already */
  angular_size = size_pix[0] / 255.0;
  radius /= (angular_size+0.0001);      /* in case angular_size == 0.0 */
  if (radius < 0 || radius > 1)
    {
      memset (dest_pix, 0, 4);
      return;
    }

  calc_get_gradient (radial_pix, calc.glow_radial, radius);
  calc_get_gradient (angular_pix, calc.glow_angular, angle);

  for (i = 0; i < 4; i++)
    dest_pix[i] = radial_pix[i] * angular_pix[i] / 255;
}

/*
 *  Calc rays's pixel (RGBA) value
 *
 */
static void
calc_rays_pix (guchar *dest_pix, gdouble x, gdouble y)
{
  gdouble radius, angle;
  gdouble angular_size;
  gdouble spike_frac, spike_inten, spike_angle;
  guchar  radial_pix[4], angular_pix[4], size_pix[4];
  gint    i;

  if ((calc.type & CALC_RAYS) == 0
      || x < calc.rays_bounds.x0 || x > calc.rays_bounds.x1
      || y < calc.rays_bounds.y0 || y > calc.rays_bounds.y1)
    {
      memset (dest_pix, 0, 4);
      return;
    }

  x -= calc.xcenter;
  y -= calc.ycenter;
  radius = sqrt (x*x + y*y) / calc.rays_radius;
  angle = (atan2 (-y, x) + calc.rays_rotation ) / (2 * G_PI);
  angle = fmod_positive (angle, 1.0);   /* make sure 0 <= angle < 1.0 */
  spike_frac = fmod (angle, calc.rays_spike_mod * 2);
  spike_angle = angle - spike_frac + calc.rays_spike_mod;
  spike_frac = (angle - spike_angle) / calc.rays_spike_mod;
  /* spike_frac is between -1.0 and 1.0 here (except round error...) */

  spike_inten = pow (1.0 - fabs (spike_frac), calc.rays_thinness);

  calc_get_gradient (size_pix, calc.rays_angular_size, spike_angle);
  /* angular_size gradient was grayfied already */
  angular_size = size_pix[0] / 255.0;
  radius /= (angular_size+0.0001);      /* in case angular_size == 0.0 */
  if(radius < 0 || radius > 1)
    {
      memset (dest_pix, 0, 4);
      return;
    }

  calc_get_gradient (radial_pix, calc.rays_radial, radius);
  calc_get_gradient (angular_pix, calc.rays_angular, spike_angle);

  for (i = 0; i < 3; i++)
    dest_pix[i] =  radial_pix[i] * angular_pix[i] / 255;
  dest_pix[3] = spike_inten * radial_pix[3] * angular_pix[3] / 255;
}

/*
 *  Calc sflare's pixel (RGBA) value
 *
 *  the sflare (second flares) are needed to be rendered one each
 *  sequentially, onto the source image, such as like usual layer
 *  operations. So the function takes src_pix as argument.  glow, rays
 *  routines don't have src_pix as argument, because of convenience.
 *
 *  @JAPANESE
 *  sflare は複数のフレアを順に(レイヤ的に)かぶせながら描画する必要が
 *  あるので、これだけ src_pix を引数にとって paint_func を適用する。
 *  glow, rays は簡易化のためになし。
 */
void
calc_sflare_pix (guchar  *dest_pix,
                 gdouble  x,
                 gdouble  y,
                 guchar  *src_pix)
{
  GList         *list;
  CalcSFlare    *sflare;
  gdouble       sx, sy, th;
  gdouble       radius, angle;
  guchar        radial_pix[4], tmp_pix[4];

  memcpy (dest_pix, src_pix, 4);

  if ((calc.type & CALC_SFLARE) == 0)
    return;

  list = calc.sflare_list;

  while (list)
    {
      sflare = list->data;
      list = list->next;

      if (! sflare)
        break;

      if (x < sflare->bounds.x0 || x > sflare->bounds.x1
          || y < sflare->bounds.y0 || y > sflare->bounds.y1)
        continue;
      sx = x - sflare->xcenter;
      sy = y - sflare->ycenter;
      radius = sqrt (sx * sx + sy * sy) / sflare->radius;
      if (calc.sflare_shape == GF_POLYGON)
        {
          angle = atan2 (-sy, sx) - calc.vangle + calc.sflare_rotation;
          th = fmod_positive (angle, calc.sflare_angle * 2) - calc.sflare_angle;
          radius *= cos (th) * calc.sflare_factor;
        }
      if (radius < 0 || radius > 1)
        continue;

      calc_get_gradient (radial_pix, calc.sflare_radial, radius);
      memcpy (tmp_pix, dest_pix, 4);
      calc_paint_func (dest_pix, tmp_pix, radial_pix,
                       calc.sflare_opacity, calc.gflare->sflare_mode);
    }
}

static void
calc_gflare_pix (guchar *dest_pix, gdouble x, gdouble y, guchar *src_pix)
{
  GFlare        *gflare = calc.gflare;
  guchar        glow_pix[4], rays_pix[4];
  guchar        tmp_pix[4];

  memcpy (dest_pix, src_pix, 4);

  if (calc.type & CALC_GLOW)
    {
      memcpy (tmp_pix, dest_pix, 4);
      calc_glow_pix (glow_pix, x, y);
      calc_paint_func (dest_pix, tmp_pix, glow_pix,
                       calc.glow_opacity, gflare->glow_mode);
    }
  if (calc.type & CALC_RAYS)
    {
      memcpy (tmp_pix, dest_pix, 4);
      calc_rays_pix (rays_pix, x, y);
      calc_paint_func (dest_pix, tmp_pix, rays_pix,
                       calc.rays_opacity, gflare->rays_mode);
    }
  if (calc.type & CALC_SFLARE)
    {
      memcpy (tmp_pix, dest_pix, 4);
      calc_sflare_pix (dest_pix, x, y, tmp_pix);
    }
}

/*
        Paint func routines, such as Normal, Addition, ...
 */
static void
calc_paint_func (guchar *dest, guchar *src1, guchar *src2, gint opacity,
                 GFlareMode mode)
{
  guchar        buf[4], *s=buf;

  if (src2[3] == 0 || opacity <= 0)
    {
      memcpy (dest, src1, 4);
      return;
    }

  switch (mode)
    {
    case GF_NORMAL:
      s = src2;
      break;
    case GF_ADDITION:
      calc_addition (s, src1, src2);
      break;
    case GF_OVERLAY:
      calc_overlay (s, src1, src2);
      break;
    case GF_SCREEN:
      calc_screen (s, src1, src2);
      break;
    default:
      s = src2;
      break;
    }
  calc_combine (dest, src1, s, opacity);
}

static void
calc_combine (guchar *dest, guchar *src1, guchar *src2, gint opacity)
{
  gdouble       s1_a, s2_a, new_a;
  gdouble       ratio, compl_ratio;
  gint          i;

  s1_a = src1[3] / 255.0;
  s2_a = src2[3] * opacity / 65025.0;
  new_a  = s1_a + (1.0 - s1_a) * s2_a;

  if (new_a != 0.0)
    ratio = s2_a / new_a;
  else
    ratio = 0.0;

  compl_ratio = 1.0 - ratio;

  for (i = 0; i < 3; i++)
    dest[i] = src1[i] * compl_ratio + src2[i] * ratio;

  dest[3] = new_a * 255.0;
}

static void
calc_addition (guchar *dest, guchar *src1, guchar *src2)
{
  gint          tmp, i;

  for (i = 0; i < 3; i++)
    {
      tmp = src1[i] + src2[i];
      dest[i] = tmp <= 255 ? tmp: 255;
    }
  dest[3] = MIN (src1[3], src2[3]);
}

static void
calc_screen (guchar *dest, guchar *src1, guchar *src2)
{
  gint          i;

  for (i = 0; i < 3; i++)
    {
      dest[i] = 255 - ((255 - src1[i]) * (255 - src2[i])) / 255;
    }
  dest[3] = MIN (src1[3], src2[3]);
}

static void
calc_overlay (guchar *dest, guchar *src1, guchar *src2)
{
  gint          screen, mult, i;

  for (i = 0; i < 3; i++)
    {
      screen = 255 - ((255 - src1[i]) * (255 - src2[i])) / 255;
      mult = (src1[i] * src2[i]) / 255;
      dest[i] = (screen * src1[i] + mult * (255 - src1[i])) / 255;
    }
  dest[3] = MIN (src1[3], src2[3]);
}

/*************************************************************************/
/**                                                                     **/
/**                     Main Dialog                                     **/
/**                     +++ dlg                                         **/
/**                                                                     **/
/*************************************************************************/

/*
        This is gflare main dialog, one which opens in first.
 */

static gboolean
dlg_run (GimpProcedure       *procedure,
         GimpProcedureConfig *config)
{
  GeglBuffer *src_buffer;
  GtkWidget  *shell;
  GtkWidget  *hbox;
  GtkWidget  *vbox;
  GtkWidget  *frame;
  GtkWidget  *button;
  GtkWidget  *notebook;
  gboolean    run = FALSE;
  gchar      *gflare_name;

  gimp_ui_init (PLUG_IN_BINARY);

  /*
   *    Init Main Dialog
   */

  dlg = g_new0 (GFlareDialog, 1);
  dlg->init           = TRUE;
  dlg->update_preview = TRUE;

  gradient_menu_init (); /* FIXME: this should go elsewhere  */
  g_object_get (config, "gflare-name", &gflare_name, NULL);
  dlg_setup_gflare (gflare_name);
  g_free (gflare_name);

  g_assert (gflares_list != NULL);
  g_assert (dlg->gflare != NULL);
  g_assert (dlg->gflare->name != NULL);

  /*
   *    Dialog Shell
   */

  shell = dlg->shell = gimp_procedure_dialog_new (procedure,
                                                  GIMP_PROCEDURE_CONFIG (config),
                                                  _("Gradient Flare"));

  /*
   *    main hbox
   */

  hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
  gtk_container_set_border_width (GTK_CONTAINER (hbox), 12);
  gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (shell))),
                      hbox, FALSE, FALSE, 0);
  gtk_widget_show (hbox);

  /*
   *    Preview
   */

  vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
  gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 0);
  gtk_widget_show (vbox);

  frame = gtk_frame_new (NULL);
  gtk_widget_set_valign (frame, GTK_ALIGN_START);
  gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
  gtk_box_pack_start (GTK_BOX (vbox), frame, TRUE, TRUE, 0);
  gtk_widget_show (frame);

  src_buffer = gimp_drawable_get_buffer (drawable);

  dlg->preview = preview_new (DLG_PREVIEW_WIDTH, DLG_PREVIEW_HEIGHT,
                              dlg_preview_init_func, config,
                              dlg_preview_render_func, src_buffer,
                              dlg_preview_deinit_func, NULL);
  gtk_widget_set_events (GTK_WIDGET (dlg->preview->widget), DLG_PREVIEW_MASK);
  gtk_container_add (GTK_CONTAINER (frame), dlg->preview->widget);

  g_signal_connect (dlg->preview->widget, "realize",
                    G_CALLBACK (dlg_preview_realize),
                    NULL);
  g_signal_connect (dlg->preview->widget, "event",
                    G_CALLBACK (dlg_preview_handle_event),
                    config);

  dlg_preview_calc_window ();

  button = gtk_check_button_new_with_mnemonic (_("A_uto update preview"));
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button),
                                dlg->update_preview);
  gtk_box_pack_end (GTK_BOX (vbox), button, FALSE, FALSE, 0);
  gtk_widget_show (button);

  g_signal_connect (button, "toggled",
                    G_CALLBACK (dlg_update_preview_callback),
                    &dlg->update_preview);

  /*
   *    Notebook
   */

  notebook = dlg->notebook = gtk_notebook_new ();
  gtk_box_pack_start (GTK_BOX (hbox), notebook, TRUE, TRUE, 0);
  gtk_widget_show (notebook);

  dlg_make_page_settings (dlg, notebook, config);
  dlg_make_page_selector (dlg, notebook, config);

  gtk_widget_show (shell);

  /*
   *    Initialization done
   */
  dlg->init = FALSE;
  dlg_preview_update ();

  run = gimp_procedure_dialog_run (GIMP_PROCEDURE_DIALOG (shell));
  if (run)
    {
      g_object_set (config,
                    "gflare-name", dlg->gflare->name,
                    NULL);
    }

  g_object_unref (src_buffer);

  gtk_widget_destroy (shell);

  return run;
}

static void
dlg_setup_gflare (const gchar *gflare_name)
{
  dlg->gflare = gflares_list_lookup (gflare_name);

  if (!dlg->gflare)
    {
      dlg->gflare = gflares_list_lookup ("Default");
      if (!dlg->gflare)
        {
          g_warning (_("'Default' is created."));
          dlg->gflare = gflare_new_with_default (_("Default"));
          gflares_list_insert (dlg->gflare);
        }
    }
}

/***********************************/
/**     Main Dialog / Preview     **/
/***********************************/

/*
 *      Calculate preview's window, ie. translation of preview widget and
 *      drawable.
 *
 *      x0, x1, y0, y1 are drawable coord, corresponding with top left
 *      corner of preview widget, etc.
 */
void
dlg_preview_calc_window (void)
{
  gint     width  = gimp_drawable_get_width  (drawable);
  gint     height = gimp_drawable_get_height (drawable);
  gint     is_wide;
  gdouble  offx, offy;

  is_wide = ((double) DLG_PREVIEW_HEIGHT * width >=
             (double) DLG_PREVIEW_WIDTH  * height);

  if (is_wide)
    {
      offy = ((double) width * DLG_PREVIEW_HEIGHT / DLG_PREVIEW_WIDTH) / 2.0;

      dlg->pwin.x0 = 0;
      dlg->pwin.x1 = width;
      dlg->pwin.y0 = height / 2.0 - offy;
      dlg->pwin.y1 = height / 2.0 + offy;
    }
  else
    {
      offx = ((double) height * DLG_PREVIEW_WIDTH / DLG_PREVIEW_HEIGHT) / 2.0;

      dlg->pwin.x0 = width / 2.0 - offx;
      dlg->pwin.x1 = width / 2.0 + offx;
      dlg->pwin.y0 = 0;
      dlg->pwin.y1 = height;
    }
}

void
ed_preview_calc_window (void)
{
  gint     width  = gimp_drawable_get_width  (drawable);
  gint     height = gimp_drawable_get_height (drawable);
  gint     is_wide;
  gdouble  offx, offy;

  is_wide = ((double) DLG_PREVIEW_HEIGHT * width >=
             (double) DLG_PREVIEW_WIDTH  * height);

  if (is_wide)
    {
      offy = ((double) width * DLG_PREVIEW_HEIGHT / DLG_PREVIEW_WIDTH) / 2.0;

      dlg->pwin.x0 = 0;
      dlg->pwin.x1 = width;
      dlg->pwin.y0 = height / 2.0 - offy;
      dlg->pwin.y1 = height / 2.0 + offy;
    }
  else
    {
      offx = ((double) height * DLG_PREVIEW_WIDTH / DLG_PREVIEW_HEIGHT) / 2.0;

      dlg->pwin.x0 = width / 2.0 - offx;      dlg->pwin.x1 = width / 2.0 + offx;
      dlg->pwin.y0 = 0;
      dlg->pwin.y1 = height;
    }
}

static void
dlg_preview_realize (GtkWidget *widget)
{
  GdkDisplay *display = gtk_widget_get_display (widget);
  GdkCursor  *cursor  = gdk_cursor_new_for_display (display, GDK_CROSSHAIR);

  gdk_window_set_cursor (gtk_widget_get_window (widget), cursor);
  g_object_unref (cursor);
}

static gboolean
dlg_preview_handle_event (GtkWidget           *widget,
                          GdkEvent            *event,
                          GimpProcedureConfig *config)
{
  GdkEventButton *bevent;
  gint            bx, by, x, y;
  gint         xcenter;
  gint         ycenter;
  gboolean        handled = FALSE;

  g_object_get (config,
                "center-x", &xcenter,
                "center-y", &ycenter,
                NULL);

  switch (event->type)
    {
    case GDK_BUTTON_PRESS:
      bevent = (GdkEventButton *) event;
      bx = bevent->x;
      by = bevent->y;

      /* convert widget coord to drawable coord */
      x = dlg->pwin.x0 + (double) (dlg->pwin.x1 - dlg->pwin.x0)
                                        * bx / DLG_PREVIEW_WIDTH;
      y = dlg->pwin.y0 + (double) (dlg->pwin.y1 - dlg->pwin.y0)
                                        * by / DLG_PREVIEW_HEIGHT;

      if ((x != xcenter || y != ycenter))
        {
          if (x != xcenter)
            {
              xcenter = x;
              gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (dlg->sizeentry),
                                          0, x);
            }
          if (y != ycenter)
            {
              ycenter = y;
              gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (dlg->sizeentry),
                                          1, y);
            }
          dlg_preview_update ();
        }
      handled = TRUE;
      break;
    default:
      break;
    }

  g_object_set (config,
                "center-x", xcenter,
                "center-y", ycenter,
                NULL);

  return handled;
}

static void
dlg_preview_update (void)
{
  if (!dlg->init && dlg->update_preview)
    {
      dlg->init_params_done = FALSE;
      preview_render_start (dlg->preview);
    }
}

/*      preview callbacks       */
static gint
dlg_preview_init_func (Preview *preview,
                       gpointer data)
{
  GimpProcedureConfig *config = GIMP_PROCEDURE_CONFIG (data);

  /* call init_params first, and iterate init_progress while
     it returns true */
  if (dlg->init_params_done == FALSE)
    {
      gint    xcenter;
      gint    ycenter;
      gdouble radius;
      gdouble rotation;
      gdouble hue;
      gdouble vangle;
      gdouble vlength;

      g_object_get (config,
                    "center-x",      &xcenter,
                    "center-y",      &ycenter,
                    "radius",        &radius,
                    "rotation",      &rotation,
                    "hue",           &hue,
                    "vector-angle",  &vangle,
                    "vector-length", &vlength,
                    NULL);

      calc_init_params (dlg->gflare,
                        CALC_GLOW | CALC_RAYS | CALC_SFLARE,
                        xcenter, ycenter,
                        radius, rotation, hue,
                        vangle, vlength);
      dlg->init_params_done = TRUE;
      return TRUE;
    }
  return calc_init_progress ();
}

/* render preview
   do what "preview" means, ie. render lense flare effect onto drawable */
static void
dlg_preview_render_func (Preview  *preview,
                         guchar   *dest,
                         gint      y,
                         gpointer  data)
{
  GeglBuffer *src_buffer = data;
  gint        width      = gimp_drawable_get_width  (drawable);
  gint        height     = gimp_drawable_get_height (drawable);
  gint        x;
  gint        dx, dy;         /* drawable x, y */
  guchar     *src_row, *src;
  guchar      src_pix[4], dest_pix[4];
  gint        b;

  dy = (dlg->pwin.y0 +
        (gdouble) (dlg->pwin.y1 - dlg->pwin.y0) * y / DLG_PREVIEW_HEIGHT);

  if (dy < 0 || dy >= height)
    {
      memset (dest, GRAY50, 3 * DLG_PREVIEW_WIDTH);
      return;
    }

  src_row = g_new (guchar, dinfo.bpp * width);

  gegl_buffer_get (src_buffer, GEGL_RECTANGLE (0, dy, width, 1), 1.0,
                   dinfo.format, src_row,
                   GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);

  for (x = 0; x < DLG_PREVIEW_HEIGHT; x++)
    {
      dx = (dlg->pwin.x0 +
            (double) (dlg->pwin.x1 - dlg->pwin.x0) * x / DLG_PREVIEW_WIDTH);

      if (dx < 0 || dx >= width)
        {
          for (b = 0; b < 3; b++)
            *dest++ = GRAY50;
          continue;
        }

      /* Get drawable pix value */
      src = &src_row[dx * dinfo.bpp];

      for (b = 0; b < 3; b++)
        src_pix[b] = dinfo.is_color ? src[b] : src[0];
      src_pix[3] = dinfo.has_alpha ? src[dinfo.bpp - 1] : OPAQUE;

      /* Get GFlare pix value */

      calc_gflare_pix (dest_pix, dx, dy, src_pix);

      /* Draw gray check if needed */
      preview_rgba_to_rgb (dest, x, y, dest_pix);
      dest += 3;
    }

  g_free (src_row);
}

static void
dlg_preview_deinit_func (Preview *preview, gpointer data)
{
  if (dlg->init_params_done)
    {
      calc_deinit ();
      dlg->init_params_done = FALSE;
    }
}

/*****************************************/
/**     Main Dialog / Settings Page     **/
/*****************************************/

static void
dlg_make_page_settings (GFlareDialog        *dlg,
                        GtkWidget           *notebook,
                        GimpProcedureConfig *config)
{
  GtkWidget *main_vbox;
  GtkWidget *frame;
  GtkWidget *center;
  GtkWidget *chain;
  GtkWidget *scale;
  gdouble    xres, yres;
  gint       xcenter;
  gint       ycenter;

  g_object_get (config,
                "center-x", &xcenter,
                "center-y", &ycenter,
                NULL);
  gimp_image_get_resolution (image, &xres, &yres);

  center = dlg->sizeentry =
    gimp_coordinates_new (gimp_image_get_unit (image), "%a",
                          TRUE, TRUE, 75, GIMP_SIZE_ENTRY_UPDATE_SIZE,

                          FALSE, FALSE,

                          _("_X:"), xcenter, xres,
                          -GIMP_MAX_IMAGE_SIZE, GIMP_MAX_IMAGE_SIZE,
                          0, gimp_drawable_get_width (drawable),

                          _("_Y:"), ycenter, yres,
                          -GIMP_MAX_IMAGE_SIZE, GIMP_MAX_IMAGE_SIZE,
                          0, gimp_drawable_get_height (drawable));

  chain = GTK_WIDGET (GIMP_COORDINATES_CHAINBUTTON (center));

  g_signal_connect (center, "value-changed",
                    G_CALLBACK (dlg_position_entry_callback),
                    config);
  g_signal_connect (center, "refval-changed",
                    G_CALLBACK (dlg_position_entry_callback),
                    config);
  gtk_widget_hide (chain);
  gtk_widget_show (center);

  gimp_procedure_dialog_get_label (GIMP_PROCEDURE_DIALOG (dlg->shell),
                                   "center-title", _("Center"),
                                   FALSE, FALSE);
  frame = gimp_procedure_dialog_fill_frame (GIMP_PROCEDURE_DIALOG (dlg->shell),
                                            "center-frame", "center-title",
                                            FALSE, NULL);
  gtk_container_add (GTK_CONTAINER (frame), center);

  scale = gimp_procedure_dialog_get_spin_scale (GIMP_PROCEDURE_DIALOG (dlg->shell), "radius", 1.0);
  gimp_spin_scale_set_scale_limits (GIMP_SPIN_SCALE (scale), 1.0,
                                    gimp_drawable_get_width (drawable) / 2);

  gimp_procedure_dialog_get_spin_scale (GIMP_PROCEDURE_DIALOG (dlg->shell), "rotation", 1.0);
  gimp_procedure_dialog_get_spin_scale (GIMP_PROCEDURE_DIALOG (dlg->shell), "hue", 1.0);
  gimp_procedure_dialog_get_spin_scale (GIMP_PROCEDURE_DIALOG (dlg->shell), "vector-angle", 1.0);

  scale = gimp_procedure_dialog_get_spin_scale (GIMP_PROCEDURE_DIALOG (dlg->shell), "vector-length", 1.0);
  gimp_spin_scale_set_scale_limits (GIMP_SPIN_SCALE (scale), 1.0, 1000);

  gimp_procedure_dialog_fill_box (GIMP_PROCEDURE_DIALOG (dlg->shell),
                                  "parameters-box",
                                  "radius", "rotation", "hue",
                                  "vector-angle", "vector-length",
                                  NULL);
  gimp_procedure_dialog_get_label (GIMP_PROCEDURE_DIALOG (dlg->shell),
                                   "parameters-title", _("Parameters"),
                                   FALSE, FALSE);
  gimp_procedure_dialog_fill_frame (GIMP_PROCEDURE_DIALOG (dlg->shell),
                                    "parameters-frame", "parameters-title",
                                    FALSE, "parameters-box");

  g_signal_connect (config, "notify",
                    G_CALLBACK (dlg_preview_update),
                    NULL);

  /**
  ***   Asupsample settings
  ***   This code is stolen from gimp-0.99.x/app/blend.c
  **/

  gimp_procedure_dialog_get_spin_scale (GIMP_PROCEDURE_DIALOG (dlg->shell),
                                        "asupsample-max-depth", 1.0);

  gimp_procedure_dialog_get_spin_scale (GIMP_PROCEDURE_DIALOG (dlg->shell),
                                        "asupsample-threshold", 1.0);

  gimp_procedure_dialog_fill_box (GIMP_PROCEDURE_DIALOG (dlg->shell),
                                  "asupsample-box",
                                  "asupsample-max-depth", "asupsample-threshold",
                                  NULL);
  gimp_procedure_dialog_fill_frame (GIMP_PROCEDURE_DIALOG (dlg->shell),
                                    "asupsample-frame", "use-asupsample",
                                    FALSE, "asupsample-box");

  main_vbox = gimp_procedure_dialog_fill_box (GIMP_PROCEDURE_DIALOG (dlg->shell),
                                              "settings-page",
                                              "center-frame", "parameters-frame", "asupsample-frame",
                                              NULL);
  gtk_notebook_append_page (GTK_NOTEBOOK (notebook), main_vbox,
                            gtk_label_new_with_mnemonic (_("_Settings")));
  gtk_widget_show (main_vbox);
}

static void
dlg_position_entry_callback (GtkWidget           *widget,
                             GimpProcedureConfig *config)
{
  gint xcenter;
  gint ycenter;
  gint x, y;

  x = RINT (gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (widget), 0));
  y = RINT (gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (widget), 1));

  DEBUG_PRINT (("dlg_position_entry_callback\n"));

  g_object_get (config,
                "center-x", &xcenter,
                "center-y", &ycenter,
                NULL);

  if (xcenter != x || ycenter != y)
    {
      g_object_set (config,
                    "center-x", x,
                    "center-y", y,
                    NULL);

      dlg_preview_update ();
    }
}

static void
dlg_update_preview_callback (GtkWidget *widget,
                             gpointer   data)
{
  gimp_toggle_button_update (widget, data);

  dlg_preview_update ();
}

/*****************************************/
/**     Main Dialog / Selector Page     **/
/*****************************************/

static void
dlg_make_page_selector (GFlareDialog        *dlg,
                        GtkWidget           *notebook,
                        GimpProcedureConfig *config)
{
  GtkWidget         *vbox;
  GtkWidget         *hbox;
  GtkWidget         *listbox;
  GtkListStore      *list;
  GtkWidget         *listview;
  GtkCellRenderer   *renderer;
  GtkTreeViewColumn *column;
  GtkWidget         *button;
  gint               i;

  static struct
  {
    const gchar *label;
    GCallback    callback;
  }
  buttons[] =
  {
    { N_("_New"),    G_CALLBACK (dlg_selector_new_callback)    },
    { N_("_Edit"),   G_CALLBACK (dlg_selector_edit_callback)   },
    { N_("_Copy"),   G_CALLBACK (dlg_selector_copy_callback)   },
    { N_("_Delete"), G_CALLBACK (dlg_selector_delete_callback) }
  };

  DEBUG_PRINT (("dlg_make_page_selector\n"));

  vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
  gtk_container_set_border_width (GTK_CONTAINER (vbox), 12);

  /*
   *    List Box
   */

  listbox = gtk_scrolled_window_new (NULL, NULL);
  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (listbox),
                                  GTK_POLICY_AUTOMATIC,
                                  GTK_POLICY_AUTOMATIC);

  gtk_widget_set_size_request (listbox, DLG_LISTBOX_WIDTH, DLG_LISTBOX_HEIGHT);
  gtk_box_pack_start (GTK_BOX (vbox), listbox, TRUE, TRUE, 0);

  list = dlg->selector_list = gtk_list_store_new (2,
                                                  G_TYPE_STRING,
                                                  G_TYPE_POINTER);
  listview = gtk_tree_view_new_with_model (GTK_TREE_MODEL (list));
  gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (listview), FALSE);

  gtk_container_add (GTK_CONTAINER (listbox), listview);
  gtk_widget_show (listbox);
  dlg->selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (listview));
  gtk_tree_selection_set_mode (dlg->selection, GTK_SELECTION_BROWSE);
  gtk_widget_show (listview);
  g_signal_connect (dlg->selection, "changed",
                    G_CALLBACK (dlg_selector_list_item_callback),
                    NULL);

  renderer = gtk_cell_renderer_text_new ();

  /* Note: the title isn't shown, so it doesn't need to be translated. */
  column = gtk_tree_view_column_new_with_attributes ("GFlare", renderer,
                                                      "text", 0,
                                                      NULL);
  gtk_tree_view_append_column (GTK_TREE_VIEW (listview), column);

  dlg_selector_setup_listbox ();

  /*
   *    The buttons for the possible listbox operations
   */

  hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
  gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
  gtk_widget_show (hbox);

  for (i = 0; i < G_N_ELEMENTS (buttons); i++)
    {
      button = gtk_button_new_with_mnemonic (gettext (buttons[i].label));
      gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0);
      gtk_widget_show (button);

      g_signal_connect (button, "clicked",
                        buttons[i].callback,
                        config);
    }

  gtk_widget_show (vbox);

  gtk_notebook_append_page (GTK_NOTEBOOK (notebook), vbox,
                            gtk_label_new_with_mnemonic (_("S_elector")));
  gtk_widget_show (vbox);
}

/*
 *      Set up selector's listbox, according to gflares_list
 */
static void
dlg_selector_setup_listbox (void)
{
  GList  *list;
  GFlare *gflare;
  gint    n;

  list = gflares_list;
  n = 0;

  while (list)
    {
      GtkTreeIter   iter;

      gflare = list->data;

      /*
        dlg->gflare should be valid (ie. not NULL) here.
        */
      gtk_list_store_append (dlg->selector_list, &iter);

      gtk_list_store_set (dlg->selector_list, &iter,
                          0, gflare->name,
                          1, gflare,
                          -1);
      if (gflare == dlg->gflare)
         gtk_tree_selection_select_iter (dlg->selection,
                                         &iter);

      list = list->next;
      n++;
    }
}

static void
dlg_selector_list_item_callback (GtkTreeSelection *selection)
{
  GtkTreeIter iter;

  if (gtk_tree_selection_get_selected (selection, NULL, &iter))
    {
      gtk_tree_model_get (GTK_TREE_MODEL (dlg->selector_list), &iter,
                          1, &dlg->gflare, -1);
    }

  dlg_preview_update ();
}

/*
 *      "New" button in Selector page
 */
static void
dlg_selector_new_callback (GtkWidget *widget,
                           gpointer   data)
{
  GtkWidget *query_box;

  query_box = gimp_query_string_box (_("New Gradient Flare"),
                                     gtk_widget_get_toplevel (widget),
                                     gimp_standard_help_func, PLUG_IN_PROC,
                                     _("Enter a name for the new GFlare"),
                                     _("Unnamed"),
                                     NULL, NULL,
                                     dlg_selector_new_ok_callback,
                                     dlg, NULL);
  gtk_widget_show (query_box);
}

static void
dlg_selector_new_ok_callback (GtkWidget   *widget,
                              const gchar *new_name,
                              gpointer     data)
{
  GFlare      *gflare;
  GtkTreeIter  iter;
  gint         pos;

  g_return_if_fail (new_name != NULL);

  if (gflares_list_lookup (new_name))
    {
      g_message (_("The name '%s' is used already!"), new_name);
      return;
    }

  gflare = gflare_new_with_default (new_name);

  pos = gflares_list_insert (gflare);

  gtk_list_store_insert (dlg->selector_list, &iter, pos);
  gtk_list_store_set (dlg->selector_list, &iter,
                      0, gflare->name,
                      1, gflare,
                      -1);
  gtk_tree_selection_select_iter (dlg->selection, &iter);

  dlg->gflare = gflare;
  dlg_preview_update ();
}

/*
 *  "Edit" button in Selector page
 */
static void
dlg_selector_edit_callback (GtkWidget *widget,
                            gpointer   data)
{
  preview_render_end (dlg->preview);
  gtk_widget_set_sensitive (dlg->shell, FALSE);
  ed_run (GTK_WINDOW (dlg->shell),
          dlg->gflare, dlg_selector_edit_done_callback, data);
}

static void
dlg_selector_edit_done_callback (gint     updated,
                                 gpointer data)
{
  gtk_widget_set_sensitive (dlg->shell, TRUE);
  if (updated)
    {
      gflare_save (dlg->gflare);
    }
  dlg_preview_update ();
}

/*
 *  "Copy" button in Selector page
 */
static void
dlg_selector_copy_callback (GtkWidget *widget,
                            gpointer   data)
{
  GtkWidget *query_box;
  gchar     *name;

  name = g_strdup_printf ("%s copy", dlg->gflare->name);

  query_box = gimp_query_string_box (_("Copy Gradient Flare"),
                                     gtk_widget_get_toplevel (widget),
                                     gimp_standard_help_func, PLUG_IN_PROC,
                                     _("Enter a name for the copied GFlare"),
                                     name,
                                     NULL, NULL,
                                     dlg_selector_copy_ok_callback,
                                     dlg, NULL);
  g_free (name);

  gtk_widget_show (query_box);
}

static void
dlg_selector_copy_ok_callback (GtkWidget   *widget,
                               const gchar *copy_name,
                               gpointer     data)
{
  GFlare      *gflare;
  GtkTreeIter  iter;
  gint         pos;

  g_return_if_fail (copy_name != NULL);

  if (gflares_list_lookup (copy_name))
    {
      g_warning (_("The name '%s' is used already!"), copy_name);
      return;
    }

  gflare = gflare_dup (dlg->gflare, copy_name);

  pos = gflares_list_insert (gflare);
  gtk_list_store_insert (dlg->selector_list, &iter, pos);
  gtk_list_store_set (dlg->selector_list, &iter,
                      0, gflare->name,
                      1, gflare,
                      -1);
  gtk_tree_selection_select_iter (dlg->selection, &iter);

  dlg->gflare = gflare;
  gflare_save (dlg->gflare);
  dlg_preview_update ();
}

/*
 *      "Delete" button in Selector page
 */
static void
dlg_selector_delete_callback (GtkWidget *widget,
                              gpointer   data)
{
  GtkWidget *dialog;
  gchar     *str;

  if (num_gflares <= 1)
    {
      g_message (_("Cannot delete!! There must be at least one GFlare."));
      return;
    }

  gtk_widget_set_sensitive (dlg->shell, FALSE);

  str = g_strdup_printf (_("Are you sure you want to delete "
                           "\"%s\" from the list and from disk?"),
                         dlg->gflare->name);

  dialog = gimp_query_boolean_box (_("Delete Gradient Flare"),
                                   dlg->shell,
                                   gimp_standard_help_func, PLUG_IN_PROC,
                                   GIMP_ICON_DIALOG_QUESTION,
                                   str,
                                   _("_Delete"), _("_Cancel"),
                                   NULL, NULL,
                                   dlg_selector_do_delete_callback,
                                   NULL, NULL);

  g_free (str);

  gtk_widget_show (dialog);
}

static void
dlg_selector_do_delete_callback (GtkWidget *widget,
                                 gboolean   delete,
                                 gpointer   data)
{
  GFlare      *old_gflare;
  GList       *tmp;
  gint         i, new_i;
  GtkTreeIter  iter;

  gtk_widget_set_sensitive (dlg->shell, TRUE);

  if (!delete)
    return;

  i = gflares_list_index (dlg->gflare);

  if (i >= 0)
    {
      /* Remove current gflare from gflares_list and free it */
      old_gflare = dlg->gflare;
      gflares_list_remove (dlg->gflare);
      dlg->gflare = NULL;

      /* Remove from listbox */
      if (!gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (dlg->selector_list),
                                          &iter, NULL, i))
        {
          g_warning ("Unsynchronized lists. Bad things will happen!");
          return;
        }
      gtk_list_store_remove (dlg->selector_list, &iter);

      /* Calculate new position of gflare and select it */
      new_i = (i < num_gflares) ? i : num_gflares - 1;
      if ((tmp = g_list_nth (gflares_list, new_i)))
          dlg->gflare = tmp->data;
      if (!gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (dlg->selector_list),
                                          &iter, NULL, i))
        {
          g_warning ("Unsynchronized lists. Bad things will happen!");
          return;
        }
       gtk_tree_selection_select_iter (dlg->selection,
                                       &iter);

      /* Delete old one from disk and memory */
      if (old_gflare->filename)
        g_unlink (old_gflare->filename);

      gflare_free (old_gflare);

      /* Update */
      dlg_preview_update ();
    }
  else
    {
      g_warning (_("not found %s in gflares_list"), dlg->gflare->name);
    }
}

/*************************************************************************/
/**                                                                     **/
/**                     GFlare Editor                                   **/
/**                     +++ ed                                          **/
/**                                                                     **/
/*************************************************************************/

/*
        This is gflare editor dialog, one which opens by clicking
        "Edit" button on the selector page in the main dialog.
 */

static void
ed_run (GtkWindow            *parent,
        GFlare               *target_gflare,
        GFlareEditorCallback  callback,
        gpointer              calldata)
{
  GtkWidget *shell;
  GtkWidget *hbox;
  GtkWidget *frame;
  GtkWidget *notebook;

  g_return_if_fail (GIMP_IS_PROCEDURE_CONFIG (calldata));

  if (!ed)
    ed = g_new0 (GFlareEditor, 1);
  ed->init          = TRUE;
  ed->run           = FALSE;
  ed->target_gflare = target_gflare;
  ed->gflare        = gflare_dup (target_gflare, target_gflare->name);
  ed->callback      = callback;
  ed->calldata      = calldata;

  /*
   *    Dialog Shell
   */
  ed->shell =
    shell = gimp_dialog_new (_("Gradient Flare Editor"), PLUG_IN_ROLE,
                             GTK_WIDGET (parent), 0,
                             gimp_standard_help_func, PLUG_IN_PROC,

                             _("_Rescan Gradients"), RESPONSE_RESCAN,
                             _("_Cancel"),           GTK_RESPONSE_CANCEL,
                             _("_OK"),               GTK_RESPONSE_OK,

                             NULL);

  gimp_dialog_set_alternative_button_order (GTK_DIALOG (shell),
                                            RESPONSE_RESCAN,
                                            GTK_RESPONSE_OK,
                                            GTK_RESPONSE_CANCEL,
                                            -1);

  g_signal_connect (shell, "response",
                    G_CALLBACK (ed_response),
                    ed);
  g_signal_connect (shell, "destroy",
                    G_CALLBACK (ed_destroy_callback),
                    ed);

  /*
   *    main hbox
   */

  hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
  gtk_container_set_border_width (GTK_CONTAINER (hbox), 12);
  gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (shell))),
                      hbox, FALSE, FALSE, 0);
  gtk_widget_set_visible (hbox, TRUE);

  /*
   *    Preview
   */

  frame = gtk_frame_new (NULL);
  gtk_widget_set_valign (frame, GTK_ALIGN_START);
  gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
  gtk_box_pack_start (GTK_BOX (hbox), frame, FALSE, FALSE, 0);
  gtk_widget_set_visible (frame, TRUE);

  ed->preview = preview_new (ED_PREVIEW_WIDTH, ED_PREVIEW_HEIGHT,
                             ed_preview_init_func, calldata,
                             ed_preview_render_func, NULL,
                             ed_preview_deinit_func, NULL);
  gtk_widget_set_events (GTK_WIDGET (ed->preview->widget), DLG_PREVIEW_MASK);
  gtk_container_add (GTK_CONTAINER (frame), ed->preview->widget);
  ed_preview_calc_window ();

  /*
   *    Notebook
   */
  notebook = ed->notebook = gtk_notebook_new ();
  gtk_notebook_set_tab_pos (GTK_NOTEBOOK (notebook), GTK_POS_TOP);
  gtk_box_pack_start (GTK_BOX (hbox), notebook, TRUE, TRUE, 0);
  gtk_widget_set_visible (notebook, TRUE);

  ed_make_page_general (ed, notebook);
  ed_make_page_glow (ed, notebook);
  ed_make_page_rays (ed, notebook);
  ed_make_page_sflare (ed, notebook);

  gtk_widget_set_visible (shell, TRUE);

  ed->init = FALSE;
  ed_preview_update ();
}

static void
ed_destroy_callback (GtkWidget    *widget,
                     GFlareEditor *ed)
{
  preview_free (ed->preview);
  gflare_free (ed->gflare);
  if (ed->callback)
    (*ed->callback) (ed->run, ed->calldata);
}

static void
ed_response (GtkWidget    *widget,
             gint          response_id,
             GFlareEditor *ed)
{
  switch (response_id)
    {
    case RESPONSE_RESCAN:
      ed->init = TRUE;
      gradient_menu_rescan ();
      ed->init = FALSE;
      ed_preview_update ();
      gtk_widget_queue_draw (ed->notebook);
      break;

    case GTK_RESPONSE_OK:
      ed->run = TRUE;
      gflare_copy (ed->target_gflare, ed->gflare);
      gtk_widget_destroy (ed->shell);
      break;

    default:
      gtk_widget_destroy (ed->shell);
      break;
    }
}

static void
ed_make_page_general (GFlareEditor *ed,
                      GtkWidget    *notebook)
{
  GFlare    *gflare = ed->gflare;
  GtkWidget *vbox;
  GtkWidget *frame;
  GtkWidget *grid;
  GtkWidget *combo;
  GtkWidget *scale;

  vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
  gtk_container_set_border_width (GTK_CONTAINER (vbox), 12);

  /*  Glow  */

  frame = gimp_frame_new (_("Glow Paint Options"));
  gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
  gtk_widget_show (frame);

  grid = gtk_grid_new ();
  gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
  gtk_grid_set_column_spacing (GTK_GRID (grid), 6);
  gtk_container_add (GTK_CONTAINER (frame), grid);
  gtk_widget_show (grid);

  scale = gimp_scale_entry_new (_("Opacity:"), gflare->glow_opacity, 0.0, 100.0, 1);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (gradient_scale_entry_update_double),
                    &gflare->glow_opacity);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (ed_preview_update),
                    NULL);
  gtk_grid_attach (GTK_GRID (grid), scale, 0, 0, 3, 1);
  gtk_widget_show (scale);

  combo = ed_mode_menu_new (&gflare->glow_mode);
  gimp_grid_attach_aligned (GTK_GRID (grid), 0, 1,
                            _("Paint mode:"), 0.0, 0.5, combo, 1);

  /*  Rays  */

  frame = gimp_frame_new (_("Rays Paint Options"));
  gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
  gtk_widget_show (frame);

  grid = gtk_grid_new ();
  gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
  gtk_grid_set_column_spacing (GTK_GRID (grid), 6);
  gtk_container_add (GTK_CONTAINER (frame), grid);
  gtk_widget_show (grid);

  scale = gimp_scale_entry_new (_("Opacity:"), gflare->rays_opacity, 0.0, 100.0, 1);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (gradient_scale_entry_update_double),
                    &gflare->rays_opacity);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (ed_preview_update),
                    NULL);
  gtk_grid_attach (GTK_GRID (grid), scale, 0, 0, 3, 1);
  gtk_widget_show (scale);

  combo = ed_mode_menu_new (&gflare->rays_mode);
  gimp_grid_attach_aligned (GTK_GRID (grid), 0, 1,
                            _("Paint mode:"), 0.0, 0.5, combo, 1);

  /*  Rays  */

  frame = gimp_frame_new (_("Second Flares Paint Options"));
  gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
  gtk_widget_show (frame);

  grid = gtk_grid_new ();
  gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
  gtk_grid_set_column_spacing (GTK_GRID (grid), 6);
  gtk_container_add (GTK_CONTAINER (frame), grid);
  gtk_widget_show (grid);

  scale = gimp_scale_entry_new (_("Opacity:"), gflare->sflare_opacity, 0.0, 100.0, 1);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (gradient_scale_entry_update_double),
                    &gflare->sflare_opacity);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (ed_preview_update),
                    NULL);
  gtk_grid_attach (GTK_GRID (grid), scale, 0, 0, 3, 1);
  gtk_widget_show (scale);

  combo = ed_mode_menu_new (&gflare->sflare_mode);
  gimp_grid_attach_aligned (GTK_GRID (grid), 0, 1,
                            _("Paint mode:"), 0.0, 0.5, combo, 1);

  gtk_notebook_append_page (GTK_NOTEBOOK (notebook), vbox,
                            gtk_label_new_with_mnemonic (_("_General")));
  g_signal_connect (vbox, "map",
                    G_CALLBACK (ed_page_map_callback),
                    (gpointer) PAGE_GENERAL);
  gtk_widget_show (vbox);
}

static void
ed_make_page_glow (GFlareEditor *ed,
                   GtkWidget    *notebook)
{
  GFlare       *gflare = ed->gflare;
  GradientMenu *gm;
  GtkWidget    *vbox;
  GtkWidget    *frame;
  GtkWidget    *grid;
  GtkWidget    *scale;
  gint          row;

  vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
  gtk_container_set_border_width (GTK_CONTAINER (vbox), 12);

  /*
   *  Gradient Menus
   */

  frame = gimp_frame_new (_("Gradients"));
  gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
  gtk_widget_show (frame);

  grid = gtk_grid_new ();
  gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
  gtk_grid_set_column_spacing (GTK_GRID (grid), 6);
  gtk_container_add (GTK_CONTAINER (frame), grid);

  gm = gradient_menu_new ((GradientMenuCallback) &ed_gradient_menu_callback,
                          gflare->glow_radial, gflare->glow_radial);
  ed_put_gradient_menu (grid, 0, 0, _("Radial gradient:"), gm);

  gm = gradient_menu_new ((GradientMenuCallback) &ed_gradient_menu_callback,
                          gflare->glow_angular, gflare->glow_angular);
  ed_put_gradient_menu (grid, 0, 1, _("Angular gradient:"), gm);

  gm = gradient_menu_new ((GradientMenuCallback) &ed_gradient_menu_callback,
                          gflare->glow_angular_size, gflare->glow_angular_size);
  ed_put_gradient_menu (grid, 0, 2, _("Angular size gradient:"), gm);

  gtk_widget_show (grid);

  /*
   *  Scales
   */

  frame = gimp_frame_new (_("Parameters"));
  gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
  gtk_widget_show (frame);

  grid = gtk_grid_new ();
  gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
  gtk_grid_set_column_spacing (GTK_GRID (grid), 6);
  gtk_container_add (GTK_CONTAINER (frame), grid);

  row = 0;

  scale = gimp_scale_entry_new (_("Size (%):"), gflare->glow_size, 0.0, G_MAXINT, 1);
  gimp_scale_entry_set_bounds (GIMP_SCALE_ENTRY (scale), 0.0, 200.0, TRUE);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (gradient_scale_entry_update_double),
                    &gflare->glow_size);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (ed_preview_update),
                    NULL);
  gtk_grid_attach (GTK_GRID (grid), scale, 0, row++, 3, 1);
  gtk_widget_show (scale);

  scale = gimp_scale_entry_new (_("Rotation:"), gflare->glow_rotation, -180.0, 180.0, 1);
  gimp_label_spin_set_increments (GIMP_LABEL_SPIN (scale), 1.0, 15.0);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (gradient_scale_entry_update_double),
                    &gflare->glow_rotation);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (ed_preview_update),
                    NULL);
  gtk_grid_attach (GTK_GRID (grid), scale, 0, row++, 3, 1);
  gtk_widget_show (scale);

  scale = gimp_scale_entry_new (_("Hue rotation:"), gflare->glow_hue, -180.0, 180.0, 1);
  gimp_label_spin_set_increments (GIMP_LABEL_SPIN (scale), 1.0, 15.0);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (gradient_scale_entry_update_double),
                    &gflare->glow_hue);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (ed_preview_update),
                    NULL);
  gtk_grid_attach (GTK_GRID (grid), scale, 0, row++, 3, 1);
  gtk_widget_show (scale);

  gtk_widget_show (grid);

  gtk_notebook_append_page (GTK_NOTEBOOK (notebook), vbox,
                            gtk_label_new_with_mnemonic (_("G_low")));
  g_signal_connect (vbox, "map",
                    G_CALLBACK (ed_page_map_callback),
                    (gpointer) PAGE_GLOW);
  gtk_widget_show (vbox);
}

static void
ed_make_page_rays (GFlareEditor *ed,
                   GtkWidget    *notebook)
{
  GFlare       *gflare = ed->gflare;
  GradientMenu *gm;
  GtkWidget    *vbox;
  GtkWidget    *frame;
  GtkWidget    *grid;
  GtkWidget    *scale;
  gint          row;

  vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
  gtk_container_set_border_width (GTK_CONTAINER (vbox), 12);

  /*
   *  Gradient Menus
   */

  frame = gimp_frame_new (_("Gradients"));
  gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
  gtk_widget_show (frame);

  grid = gtk_grid_new ();
  gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
  gtk_grid_set_column_spacing (GTK_GRID (grid), 6);
  gtk_container_add (GTK_CONTAINER (frame), grid);

  row = 0;

  gm = gradient_menu_new ((GradientMenuCallback) &ed_gradient_menu_callback,
                          gflare->rays_radial, gflare->rays_radial);
  ed_put_gradient_menu (grid, 0, row++, _("Radial gradient:"), gm);

  gm = gradient_menu_new ((GradientMenuCallback) &ed_gradient_menu_callback,
                          gflare->rays_angular, gflare->rays_angular);
  ed_put_gradient_menu (grid, 0, row++, _("Angular gradient:"), gm);

  gm = gradient_menu_new ((GradientMenuCallback) &ed_gradient_menu_callback,
                          gflare->rays_angular_size, gflare->rays_angular_size);
  ed_put_gradient_menu (grid, 0, row++, _("Angular size gradient:"), gm);

  gtk_widget_show (grid);

  /*
   *    Scales
   */

  frame = gimp_frame_new (_("Parameters"));
  gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
  gtk_widget_show (frame);

  grid = gtk_grid_new ();
  gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
  gtk_grid_set_column_spacing (GTK_GRID (grid), 6);
  gtk_container_add (GTK_CONTAINER (frame), grid);

  row = 0;

  scale = gimp_scale_entry_new (_("Size (%):"), gflare->rays_size, 0.0, G_MAXINT, 1);
  gimp_scale_entry_set_bounds (GIMP_SCALE_ENTRY (scale), 0.0, 200.0, TRUE);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (gradient_scale_entry_update_double),
                    &gflare->rays_size);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (ed_preview_update),
                    NULL);
  gtk_grid_attach (GTK_GRID (grid), scale, 0, row++, 3, 1);
  gtk_widget_show (scale);

  scale = gimp_scale_entry_new (_("Rotation:"), gflare->rays_rotation, -180.0, 180.0, 1);
  gimp_label_spin_set_increments (GIMP_LABEL_SPIN (scale), 1.0, 15.0);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (gradient_scale_entry_update_double),
                    &gflare->rays_rotation);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (ed_preview_update),
                    NULL);
  gtk_grid_attach (GTK_GRID (grid), scale, 0, row++, 3, 1);
  gtk_widget_show (scale);

  scale = gimp_scale_entry_new (_("Hue rotation:"), gflare->rays_hue, -180.0, 180.0, 1);
  gimp_label_spin_set_increments (GIMP_LABEL_SPIN (scale), 1.0, 15.0);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (gradient_scale_entry_update_double),
                    &gflare->rays_hue);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (ed_preview_update),
                    NULL);
  gtk_grid_attach (GTK_GRID (grid), scale, 0, row++, 3, 1);
  gtk_widget_show (scale);

  scale = gimp_scale_entry_new (_("# of Spikes:"), gflare->rays_nspikes, 1, G_MAXINT, 0);
  gimp_scale_entry_set_bounds (GIMP_SCALE_ENTRY (scale), 1.0, 300.0, TRUE);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (gradient_scale_entry_update_int),
                    &gflare->rays_nspikes);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (ed_preview_update),
                    NULL);
  gtk_grid_attach (GTK_GRID (grid), scale, 0, row++, 3, 1);
  gtk_widget_show (scale);

  scale = gimp_scale_entry_new (_("Spike thickness:"), gflare->rays_thickness, 1.0, GIMP_MAX_IMAGE_SIZE, 1);
  gimp_scale_entry_set_bounds (GIMP_SCALE_ENTRY (scale), 1.0, 100.0, TRUE);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (gradient_scale_entry_update_double),
                    &gflare->rays_thickness);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (ed_preview_update),
                    NULL);
  gtk_grid_attach (GTK_GRID (grid), scale, 0, row++, 3, 1);
  gtk_widget_show (scale);

  gtk_widget_show (grid);

  gtk_notebook_append_page (GTK_NOTEBOOK (notebook), vbox,
                            gtk_label_new_with_mnemonic (_("_Rays")));
  g_signal_connect (vbox, "map",
                    G_CALLBACK (ed_page_map_callback),
                    (gpointer) PAGE_RAYS);
  gtk_widget_show (vbox);
}

static void
ed_make_page_sflare (GFlareEditor *ed,
                     GtkWidget    *notebook)
{
  GFlare       *gflare = ed->gflare;
  GradientMenu *gm;
  GtkWidget    *vbox;
  GtkWidget    *grid;
  GtkWidget    *frame;
  GtkWidget    *shape_vbox;
  GSList       *shape_group = NULL;
  GtkWidget    *polygon_hbox;
  GtkWidget    *seed_hbox;
  GtkWidget    *toggle;
  GtkWidget    *label;
  GtkWidget    *seed;
  GtkWidget    *entry;
  GtkWidget    *scale;
  gchar         buf[256];
  gint          row;

  vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
  gtk_container_set_border_width (GTK_CONTAINER (vbox), 12);

  /*
   *  Gradient Menus
   */

  frame = gimp_frame_new (_("Gradients"));
  gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
  gtk_widget_show (frame);

  grid = gtk_grid_new ();
  gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
  gtk_grid_set_column_spacing (GTK_GRID (grid), 6);
  gtk_container_add (GTK_CONTAINER (frame), grid);

  gm = gradient_menu_new ((GradientMenuCallback) &ed_gradient_menu_callback,
                          gflare->sflare_radial, gflare->sflare_radial);
  ed_put_gradient_menu (grid, 0, 0, _("Radial gradient:"), gm);

  gm = gradient_menu_new ((GradientMenuCallback) &ed_gradient_menu_callback,
                          gflare->sflare_sizefac, gflare->sflare_sizefac);
  ed_put_gradient_menu (grid, 0, 1, _("Size factor gradient:"), gm);

  gm = gradient_menu_new ((GradientMenuCallback) &ed_gradient_menu_callback,
                          gflare->sflare_probability, gflare->sflare_probability);
  ed_put_gradient_menu (grid, 0, 2, _("Probability gradient:"), gm);

  gtk_widget_show (grid);

  /*
   *    Scales
   */

  frame = gimp_frame_new (_("Parameters"));
  gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
  gtk_widget_show (frame);

  grid = gtk_grid_new ();
  gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
  gtk_grid_set_column_spacing (GTK_GRID (grid), 6);
  gtk_container_add (GTK_CONTAINER (frame), grid);

  row = 0;

  scale = gimp_scale_entry_new (_("Size (%):"), gflare->sflare_size, 0.0, G_MAXINT, 1);
  gimp_scale_entry_set_bounds (GIMP_SCALE_ENTRY (scale), 1.0, 200.0, TRUE);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (gradient_scale_entry_update_double),
                    &gflare->sflare_size);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (ed_preview_update),
                    NULL);
  gtk_grid_attach (GTK_GRID (grid), scale, 0, row++, 3, 1);
  gtk_widget_show (scale);

  scale = gimp_scale_entry_new (_("Rotation:"), gflare->sflare_rotation, -180.0, 180.0, 1);
  gimp_label_spin_set_increments (GIMP_LABEL_SPIN (scale), 1.0, 15.0);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (gradient_scale_entry_update_double),
                    &gflare->sflare_rotation);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (ed_preview_update),
                    NULL);
  gtk_grid_attach (GTK_GRID (grid), scale, 0, row++, 3, 1);
  gtk_widget_show (scale);

  scale = gimp_scale_entry_new (_("Hue rotation:"), gflare->sflare_hue, -180.0, 180.0, 1);
  gimp_label_spin_set_increments (GIMP_LABEL_SPIN (scale), 1.0, 15.0);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (gradient_scale_entry_update_double),
                    &gflare->sflare_hue);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (ed_preview_update),
                    NULL);
  gtk_grid_attach (GTK_GRID (grid), scale, 0, row++, 3, 1);
  gtk_widget_show (scale);

  gtk_widget_show (grid);

  /*
   *    Shape Radio Button Frame
   */

  frame = gimp_frame_new (_("Shape of Second Flares"));
  gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
  gtk_widget_show (frame);

  shape_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
  gtk_container_add (GTK_CONTAINER (frame), shape_vbox);
  gtk_widget_show (shape_vbox);

  toggle = gtk_radio_button_new_with_label (shape_group, _("Circle"));
  shape_group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (toggle));
  g_object_set_data (G_OBJECT (toggle), "gimp-item-data",
                     GINT_TO_POINTER (GF_CIRCLE));
  g_signal_connect (toggle, "toggled",
                    G_CALLBACK (ed_shape_radio_callback),
                    &gflare->sflare_shape);
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle),
                                gflare->sflare_shape == GF_CIRCLE);
  gtk_box_pack_start (GTK_BOX (shape_vbox), toggle, FALSE, FALSE, 0);
  gtk_widget_show (toggle);

  polygon_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
  gtk_box_pack_start (GTK_BOX (shape_vbox), polygon_hbox, FALSE, FALSE, 0);
  gtk_widget_show (polygon_hbox);

  toggle = ed->polygon_toggle =
    gtk_radio_button_new_with_label (shape_group, _("Polygon"));
  shape_group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (toggle));
  g_object_set_data (G_OBJECT (toggle), "gimp-item-data",
                     GINT_TO_POINTER (GF_POLYGON));
  g_signal_connect (toggle, "toggled",
                    G_CALLBACK (ed_shape_radio_callback),
                    &gflare->sflare_shape);
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle),
                                gflare->sflare_shape == GF_POLYGON);
  gtk_box_pack_start (GTK_BOX (polygon_hbox), toggle, FALSE, FALSE, 0);
  gtk_widget_show (toggle);

  entry = ed->polygon_entry = gtk_entry_new ();
  gtk_entry_set_width_chars (GTK_ENTRY (entry), 4);
  g_snprintf (buf, sizeof (buf), "%d", gflare->sflare_nverts);
  gtk_entry_set_text (GTK_ENTRY (entry), buf);
  g_signal_connect (entry, "changed",
                    G_CALLBACK (ed_ientry_callback),
                    &gflare->sflare_nverts);
  gtk_box_pack_start (GTK_BOX (polygon_hbox), entry, FALSE, FALSE, 0);
  gtk_widget_show (entry);

  g_object_bind_property (toggle, "active",
                          entry,  "sensitive",
                          G_BINDING_SYNC_CREATE);

  /*
   *    Random Seed Entry
   */

  seed_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
  gtk_box_pack_start (GTK_BOX (vbox), seed_hbox, FALSE, FALSE, 0);
  gtk_widget_show (seed_hbox);

  label = gtk_label_new (_("Random seed:"));
  gtk_label_set_xalign (GTK_LABEL (label), 0.0);
  gtk_box_pack_start (GTK_BOX (seed_hbox), label, FALSE, FALSE, 0);
  gtk_widget_show (label);

  seed = gimp_random_seed_new (&gflare->sflare_seed, &gflare->random_seed);
  gtk_box_pack_start (GTK_BOX (seed_hbox), seed, FALSE, TRUE, 0);
  gtk_widget_show (seed);

  g_signal_connect (GIMP_RANDOM_SEED_SPINBUTTON_ADJ (seed), "value-changed",
                    G_CALLBACK (ed_preview_update),
                    NULL);

  gtk_notebook_append_page (GTK_NOTEBOOK (notebook), vbox,
                            gtk_label_new_with_mnemonic (_("_Second Flares")));
  g_signal_connect (vbox, "map",
                    G_CALLBACK (ed_page_map_callback),
                    (gpointer) PAGE_SFLARE);
  gtk_widget_show (vbox);
}

GtkWidget *
ed_mode_menu_new (GFlareMode *mode_var)
{
  GtkWidget *combo = gimp_int_combo_box_new_array (GF_NUM_MODES,
                                                   gflare_menu_modes);

  gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (combo), *mode_var,
                              G_CALLBACK (ed_mode_menu_callback),
                              mode_var, NULL);

  return combo;
}

/*
  puts gradient menu with caption into grid
  occupies 1 row and 3 cols in grid
 */
static void
ed_put_gradient_menu (GtkWidget    *grid,
                      gint          x,
                      gint          y,
                      const gchar  *caption,
                      GradientMenu *gm)
{
  GtkWidget *label;

  label = gtk_label_new (caption);
  gtk_label_set_xalign (GTK_LABEL (label), 1.0);
  gtk_widget_show (label);

  gtk_grid_attach (GTK_GRID (grid), label, x, y, 1, 1);
  gtk_grid_attach (GTK_GRID (grid), gm->preview, x + 1, y, 1, 1);
  gtk_grid_attach (GTK_GRID (grid), gm->combo, x + 2, y, 1, 1);
}

static void
ed_mode_menu_callback (GtkWidget *widget,
                       gpointer   data)
{
  gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (widget), (gint *) data);

  ed_preview_update ();
}

static void
ed_gradient_menu_callback (const gchar *gradient_name,
                           gpointer     data)
{
  gchar *dest_string = data;

  /* @GRADIENT_NAME */
  gradient_name_copy (dest_string, gradient_name);
  ed_preview_update ();
}

static void
ed_shape_radio_callback (GtkWidget *widget,
                         gpointer   data)
{
  gimp_radio_button_update (widget, data);

  ed_preview_update ();
}

static void
ed_ientry_callback (GtkWidget *widget,
                    gpointer   data)
{
  gint new_val;

  new_val = atoi (gtk_entry_get_text (GTK_ENTRY (widget)));
  *(gint *)data = new_val;

  ed_preview_update ();
}

/*
  NOTE: This is hack, because this code depends on internal "map"
  signal of changing pages of gtknotebook.
 */
static void
ed_page_map_callback (GtkWidget *widget,
                      gpointer   data)
{
  gint page_num = GPOINTER_TO_INT (data);

  DEBUG_PRINT(("ed_page_map_callback\n"));

  ed->cur_page = page_num;
  ed_preview_update ();
}


static void
ed_preview_update (void)
{
  if (ed->init)
    return;

  ed->init_params_done = FALSE;
  preview_render_start (ed->preview);
}

static gint
ed_preview_init_func (Preview *preview, gpointer data)
{
  GimpProcedureConfig *config = GIMP_PROCEDURE_CONFIG (data);
  int                  type   = 0;

  if (ed->init_params_done == FALSE)
    {
      gdouble vangle;
      gdouble vlength;

      g_object_get (config,
                    "vector-angle",  &vangle,
                    "vector-length", &vlength,
                    NULL);
      switch (ed->cur_page)
        {
        case PAGE_GENERAL:
          type = (CALC_GLOW | CALC_RAYS | CALC_SFLARE);
          break;
        case PAGE_GLOW:
          type = CALC_GLOW;
          break;
        case PAGE_RAYS:
          type = CALC_RAYS;
          break;
        case PAGE_SFLARE:
          type = CALC_SFLARE;
          break;
        default:
          g_warning ("ed_preview_edit_func: bad page");
          break;
        }
      calc_init_params (ed->gflare, type,
                        ED_PREVIEW_WIDTH/2, ED_PREVIEW_HEIGHT/2,
                        ED_PREVIEW_WIDTH/2, 0.0, 0.0,
                        vangle, vlength);

      ed->init_params_done = TRUE;
      return TRUE;
    }
  return calc_init_progress ();
}

static void
ed_preview_deinit_func (Preview *preview, gpointer data)
{
  if (ed->init_params_done)
    {
      calc_deinit ();
      ed->init_params_done = FALSE;
    }
}

static void
ed_preview_render_func (Preview *preview,
                        guchar  *buffer,
                        gint     y,
                        gpointer data)
{
  switch (ed->cur_page)
    {
    case PAGE_GENERAL:
      ed_preview_render_general (buffer, y);
      break;
    case PAGE_GLOW:
      ed_preview_render_glow (buffer, y);
      break;
    case PAGE_RAYS:
      ed_preview_render_rays (buffer, y);
      break;
    case PAGE_SFLARE:
      ed_preview_render_sflare (buffer, y);
      break;
    default:
      g_warning ("hmm, bad page in ed_preview_render_func ()");
      break;
    }
}

static void
ed_preview_render_general (guchar *buffer, gint y)
{
  int           x, i;
  guchar        gflare_pix[4];
  static guchar src_pix[4] = {0, 0, 0, OPAQUE};
  int           gflare_a;

  for (x = 0; x < ED_PREVIEW_WIDTH; x++)
    {
      calc_gflare_pix (gflare_pix, x, y, src_pix);
      gflare_a = gflare_pix[3];

      for (i = 0; i < 3; i++)
        {
          *buffer++ = gflare_pix[i] * gflare_a / 255;
        }
    }
}

static void
ed_preview_render_glow (guchar *buffer, gint y)
{
  int           x, i;
  guchar        pix[4];

  for (x = 0; x < ED_PREVIEW_WIDTH; x++)
    {
      calc_glow_pix (pix, x, y);
      for (i = 0; i < 3; i++)
        *buffer++ = pix[i] * pix[3] / 255;
    }
}

static void
ed_preview_render_rays (guchar *buffer, gint y)
{
  int           x, i;
  guchar        pix[4];

  for (x = 0; x < ED_PREVIEW_WIDTH; x++)
    {
      calc_rays_pix (pix, x, y);
      for (i = 0; i < 3; i++)
        *buffer++ = pix[i] * pix[3] / 255;
    }
}

static void
ed_preview_render_sflare (guchar *buffer, gint y)
{
  int           x, i;
  guchar        pix[4];
  static guchar src_pix[4] = {0, 0, 0, OPAQUE};

  for (x = 0; x < ED_PREVIEW_WIDTH; x++)
    {
      calc_sflare_pix (pix, x, y, src_pix);
      for (i = 0; i < 3; i++)
        *buffer++ = pix[i] * pix[3] / 255;
    }
}

/*************************************************************************/
/**                                                                     **/
/**                     +++ Preview                                     **/
/**                                                                     **/
/*************************************************************************/

/*
        this is generic preview routines.
 */


/*
    Routines to render the preview in background
 */
static Preview *
preview_new (gint                  width,
             gint                  height,
             PreviewInitFunc       init_func,
             gpointer              init_data,
             PreviewRenderFunc     render_func,
             gpointer              render_data,
             PreviewDeinitFunc     deinit_func,
             gpointer              deinit_data)
{
  Preview *preview;

  preview = g_new0 (Preview, 1);

  preview->widget = gimp_preview_area_new ();
  gtk_widget_set_size_request (preview->widget, width, height);
  gtk_widget_show (preview->widget);

  preview->width           = width;
  preview->height          = height;
  preview->init_func       = init_func;
  preview->init_data       = init_data;
  preview->render_func     = render_func;
  preview->render_data     = render_data;
  preview->deinit_func     = deinit_func;
  preview->deinit_data     = deinit_data;
  preview->idle_tag        = 0;
  preview->buffer          = g_new (guchar, width * 3);
  preview->full_image_buffer = g_new (guchar, width * height * 3);

  return preview;
}

static void
preview_free (Preview *preview)
{
  preview_render_end (preview);
  /* not destroy preview->widget */
  g_free (preview->buffer);
  g_free (preview->full_image_buffer);
  g_free (preview);
}

/*
  Start rendering of the preview in background using an idle event.
  If already started and not yet finished, stop it first.
 */
static void
preview_render_start (Preview *preview)
{
  preview_render_end (preview);

  preview->init_done = FALSE;
  preview->current_y = 0;
  preview->drawn_y = 0;
  preview->timeout_tag = g_timeout_add (100,
                                        (GSourceFunc) preview_render_start_2,
                                        preview);
}

static gint
preview_render_start_2 (Preview *preview)
{
  preview->timeout_tag = 0;
  preview->idle_tag = g_idle_add ((GSourceFunc) preview_handle_idle, preview);
  return FALSE;
}

static void
preview_render_end (Preview *preview)
{
  if (preview->timeout_tag > 0)
    {
      g_source_remove (preview->timeout_tag);
      preview->timeout_tag = 0;
    }
  if (preview->idle_tag > 0)
    {
      if (preview->deinit_func)
        (*preview->deinit_func) (preview, preview->deinit_data);

      g_source_remove (preview->idle_tag);
      preview->idle_tag = 0;
    }
}

/*
  Handle an idle event.
  Return FALSE if done, TRUE otherwise.
 */
static gboolean
preview_handle_idle (Preview *preview)
{
  gboolean done = FALSE;

  if (preview->init_done == FALSE)
    {
      if (preview->init_func &&
          (*preview->init_func) (preview, preview->init_data))
        return TRUE;

      preview->init_done = TRUE;
    }

  if (preview->render_func)
    (*preview->render_func) (preview, preview->buffer, preview->current_y,
                             preview->render_data);
  else
    memset (preview->buffer, 0, preview->width * 3);

  memcpy (preview->full_image_buffer + preview->width * 3 * preview->current_y,
          preview->buffer,
          preview->width * 3);

  if (++preview->current_y >= preview->height)
    done = TRUE;

  if (done)
    {
      gimp_preview_area_draw (GIMP_PREVIEW_AREA (preview->widget),
                              0, 0, preview->width, preview->height,
                              GIMP_RGB_IMAGE,
                              preview->full_image_buffer,
                              preview->width * 3);

      preview->drawn_y = preview->current_y;
      preview_render_end (preview);
      return FALSE;
    }

  return TRUE;
}

/*
  Convert RGBA to RGB with rendering gray check if needed.
        (from nova.c)
  input:  guchar src[4]         RGBA pixel
  output: guchar dest[3]        RGB pixel
 */

static void
preview_rgba_to_rgb (guchar *dest,
                     gint    x,
                     gint    y,
                     guchar *src)
{
  gint src_a;
  gint check;
  gint b;

  src_a = src[3];

  if (src_a == OPAQUE)  /* full opaque */
    {
      for (b = 0; b < 3; b++)
        dest[b] = src[b];
    }
  else
    {
      if ((x % (GIMP_CHECK_SIZE) < GIMP_CHECK_SIZE_SM) ^
          (y % (GIMP_CHECK_SIZE) < GIMP_CHECK_SIZE_SM))
        check = GIMP_CHECK_LIGHT * 255;
      else
        check = GIMP_CHECK_DARK * 255;

      if (src_a == 0)   /* full transparent */
        {
          for (b = 0; b < 3; b++)
            dest[b] = check;
        }
      else
        {
          for (b = 0; b < 3; b++)
            dest[b] = (src[b] * src_a + check * (OPAQUE-src_a)) / OPAQUE;
        }
    }
}

/*************************************************************************/
/**                                                                     **/
/**                     +++ Gradient Menu                               **/
/**                     +++ gm                                          **/
/**                                                                     **/
/*************************************************************************/

static void
gradient_menu_init (void)
{
  gm_gradient_get_list ();
  gradient_menus = NULL;
}

static void
gradient_menu_rescan (void)
{
  GList         *tmp;
  GradientMenu  *gm;
  GtkTreeModel  *model;

  /* Detach and destroy menus first */
  tmp = gradient_menus;
  while (tmp)
    {
      gm  = tmp->data;
      tmp = tmp->next;

      model = gtk_combo_box_get_model (GTK_COMBO_BOX (gm->combo));
      gtk_list_store_clear (GTK_LIST_STORE (model));
    }

  gm_gradient_get_list ();

  tmp = gradient_menus;
  while (tmp)
    {
      gm  = tmp->data;
      tmp = tmp->next;

      gm_gradient_combo_fill (gm, gm->gradient_name);
    }
}

static GradientMenu *
gradient_menu_new (GradientMenuCallback callback,
                   gpointer             callback_data,
                   const gchar         *default_gradient_name)
{
  GradientMenu *gm = g_new (GradientMenu, 1);

  gm->callback = callback;
  gm->callback_data = callback_data;

  gm->preview = gimp_preview_area_new ();
  gtk_widget_set_size_request (gm->preview,
                               GM_PREVIEW_WIDTH, GM_PREVIEW_HEIGHT);

  gm->combo = g_object_new (GIMP_TYPE_INT_COMBO_BOX, NULL);

  g_signal_connect (gm->combo, "changed",
                    G_CALLBACK (gm_gradient_combo_callback),
                    gm);

  gm_gradient_combo_fill (gm, default_gradient_name);

  gtk_widget_show (gm->preview);
  gtk_widget_show (gm->combo);

  g_signal_connect (gm->combo, "destroy",
                    G_CALLBACK (gm_combo_destroy_callback),
                    gm);

  gradient_menus = g_list_append (gradient_menus, gm);

  return gm;
}

/* Local Functions */

static void
gm_gradient_get_list (void)
{
  int   i;

  if (gradient_names)
    {
      for (i = 0; i < num_gradient_names; i++)
        g_free (gradient_names[i]);
      g_free (gradient_names);
    }

  gradient_cache_flush ();      /* to make sure */
  gradient_names = gradient_get_list (&num_gradient_names);
}

static void
gm_gradient_combo_fill (GradientMenu *gm,
                        const gchar  *default_gradient)
{
  gint active = 0;
  gint i;

  for (i = 0; i < num_gradient_names; i++)
    {
      const gchar *name = gradient_names[i];

      if (strcmp (name, default_gradient) == 0)
        active = i;

      gimp_int_combo_box_append (GIMP_INT_COMBO_BOX (gm->combo),
                                 GIMP_INT_STORE_VALUE, i,
                                 GIMP_INT_STORE_LABEL, name,
                                 -1);
    }

  gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (gm->combo), active);
}

static void
gm_gradient_combo_callback (GtkWidget *widget,
                            gpointer   data)
{
  GradientMenu *gm = data;
  const gchar  *gradient_name;
  gint          index;

  if (! gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (widget), &index) ||
      index < 0 ||
      index >= num_gradient_names)
    return;

  gradient_name = gradient_names[index];

  DEBUG_PRINT(("gm_combo_callback\n"));

  gradient_name_copy (gm->gradient_name, gradient_name);

  gm_preview_draw (gm->preview, gradient_name);

  if (gm->callback)
    (* gm->callback) (gradient_name, gm->callback_data);
}

static void
gm_preview_draw (GtkWidget   *preview,
                 const gchar *gradient_name)
{
  guchar      values[GM_PREVIEW_WIDTH][4];
  gint        nvalues = GM_PREVIEW_WIDTH;
  gint        row, irow, col;
  guchar      dest_row[GM_PREVIEW_WIDTH][3];
  guchar     *dest;
  guchar     *src;
  gint        check, b;
  guchar     *dest_total_preview_buffer;
  const gint  alpha = 3;

  gradient_get_values (gradient_name, (guchar *)values, nvalues);

  dest_total_preview_buffer = g_new (guchar,
                                     GM_PREVIEW_HEIGHT * GM_PREVIEW_WIDTH * 3);

  for (row = 0; row < GM_PREVIEW_HEIGHT; row += GIMP_CHECK_SIZE_SM)
    {
      for (col = 0; col < GM_PREVIEW_WIDTH; col++)
        {
          dest = dest_row[col];
          src  = values[col];

          if (src[alpha] == OPAQUE)
            {
              /* no alpha channel or opaque -- simple way */
              for (b = 0; b < alpha; b++)
                dest[b] = src[b];
            }
          else
            {
              /* more or less transparent */
              if((col % (GIMP_CHECK_SIZE) < GIMP_CHECK_SIZE_SM) ^
                  (row % (GIMP_CHECK_SIZE) < GIMP_CHECK_SIZE_SM))
                {
                  check = GIMP_CHECK_LIGHT * 255;
                }
              else
                {
                  check = GIMP_CHECK_DARK * 255;
                }

              if (src[alpha] == 0)
                {
                  /* full transparent -- check */
                  for (b = 0; b < alpha; b++)
                    dest[b] = check;
                }
              else
                {
                  /* middlemost transparent -- mix check and src */
                  for (b = 0; b < alpha; b++)
                    dest[b] = (src[b] * src[alpha] +
                               check * (OPAQUE - src[alpha])) / OPAQUE;
                }
            }
        }

      for (irow = 0;
           irow < GIMP_CHECK_SIZE_SM && row + irow < GM_PREVIEW_HEIGHT;
           irow++)
        {
          memcpy (dest_total_preview_buffer + (row+irow) * 3 * GM_PREVIEW_WIDTH,
                  dest_row,
                  GM_PREVIEW_WIDTH * 3);
        }
    }

    gimp_preview_area_draw (GIMP_PREVIEW_AREA (preview),
                            0, 0, GM_PREVIEW_WIDTH, GM_PREVIEW_HEIGHT,
                            GIMP_RGB_IMAGE,
                            (const guchar *) dest_total_preview_buffer,
                            GM_PREVIEW_WIDTH * 3);

    g_free (dest_total_preview_buffer);
}

static void
gm_combo_destroy_callback (GtkWidget *w,
                           gpointer   data)
{
  GradientMenu *gm = data;
  gradient_menus = g_list_remove (gradient_menus, gm);
}

/*************************************************************************/
/**                                                                     **/
/**                     +++ Gradients                                   **/
/**                                                                     **/
/*************************************************************************/

/*
    Manage both internal and external gradients: list up, cache,
    sampling, etc.

    External gradients are cached.
 */


static void
gradient_name_copy (gchar       *dest,
                    const gchar *src)
{
  g_strlcpy (dest, src, GRADIENT_NAME_MAX);
}

/*
  Translate SPACE to "\\040", etc.
 */
static void
gradient_name_encode (gchar       *dest,
                      const gchar *src)
{
  gint cnt = GRADIENT_NAME_MAX - 1;

  while (*src && cnt--)
    {
      if (g_ascii_iscntrl (*src) || g_ascii_isspace (*src) || *src == '\\')
        {
          sprintf (dest, "\\%03o", *src++);
          dest += 4;
        }
      else
        *dest++ = *src++;
    }
  *dest = '\0';
}

/*
  Translate "\\040" to SPACE, etc.
 */
static void
gradient_name_decode (gchar       *dest,
                      const gchar *src)
{
  gint  cnt = GRADIENT_NAME_MAX - 1;
  guint tmp;

  while (*src && cnt--)
    {
      if (*src == '\\' && *(src+1) && *(src+2) && *(src+3))
        {
          sscanf (src+1, "%3o", &tmp);
          *dest++ = tmp;
          src += 4;
        }
      else
        *dest++ = *src++;
    }
  *dest = '\0';
}

static void
gradient_init (void)
{
  gradient_cache_head = NULL;
  gradient_cache_count = 0;
}

static void
gradient_free (void)
{
  gradient_cache_flush ();
}

static gchar **
gradient_get_list (gint *num_gradients)
{
  gchar        **gradients;
  GimpGradient **external_gradients  = NULL;
  gint           external_ngradients = 0;
  gint           i, n;

  gradient_cache_flush ();
  external_gradients = gimp_gradients_get_list (NULL);
  external_ngradients = gimp_core_object_array_get_length ((GObject **) external_gradients);

  *num_gradients = G_N_ELEMENTS (internal_gradients) + external_ngradients;
  gradients = g_new (gchar *, *num_gradients);

  n = 0;
  for (i = 0; i < G_N_ELEMENTS (internal_gradients); i++)
    {
      gradients[n++] = g_strdup (internal_gradients[i]);
    }
  for (i = 0; i < external_ngradients; i++)
    {
      gradients[n++] = gimp_resource_get_name ((GimpResource *) external_gradients[i]);
    }

  g_free (external_gradients);

  return gradients;
}

static void
gradient_get_values (const gchar *gradient_name,
                     guchar      *values,
                     gint         nvalues)
{
  /*
    Criteria to distinguish internal and external is rather simple here.
    It should be fixed later.
   */
  if (gradient_name[0] == '%')
    gradient_get_values_internal (gradient_name, values, nvalues);
  else
    gradient_get_values_external (gradient_name, values, nvalues);
}

static void
gradient_get_values_internal (const gchar *gradient_name,
                              guchar      *values,
                              gint         nvalues)
{
  const guchar white[4]        = { 255, 255, 255, 255 };
  const guchar white_trans[4]  = { 255, 255, 255, 0   };
  const guchar red_trans[4]    = { 255, 0,   0,   0   };
  const guchar blue_trans[4]   = { 0,   0,   255, 0   };
  const guchar yellow_trans[4] = { 255, 255, 0,   0   };

  /*
    The internal gradients here are example --
    What kind of internals would be useful ?
   */
  if(!strcmp(gradient_name, "%white"))
    {
      gradient_get_blend (white, white, values, nvalues);
    }
  else if(!strcmp(gradient_name, "%white_grad"))
    {
      gradient_get_blend (white, white_trans, values, nvalues);
    }
  else if (!strcmp (gradient_name, "%red_grad"))
    {
      gradient_get_blend (white, red_trans, values, nvalues);
    }
  else if (!strcmp (gradient_name, "%blue_grad"))
    {
      gradient_get_blend (white, blue_trans, values, nvalues);
    }
  else if (!strcmp (gradient_name, "%yellow_grad"))
    {
      gradient_get_blend (white, yellow_trans, values, nvalues);
    }
  else if (!strcmp (gradient_name, "%random"))
    {
      gradient_get_random (values, nvalues);
    }
  else
    {
      gradient_get_default (gradient_name, values, nvalues);
    }
}

static void
gradient_get_blend (const guchar *fg,
                    const guchar *bg,
                    guchar       *values,
                    gint          nvalues)
{
  gdouble  x;
  gint     i;
  gint     j;
  guchar  *v = values;

  for (i = 0; i < nvalues; i++)
    {
      x = (double) i / nvalues;
      for (j = 0; j < 4; j++)
        *v++ = fg[j] * (1 - x) + bg[j] * x;
    }
}

static void
gradient_get_random (guchar *values,
                     gint    nvalues)
{
  gint    i;
  gint    j;
  gint    inten;
  guchar *v = values;

  /*
    This is really simple  -- gaussian noise might be better
   */
  for (i = 0; i < nvalues; i++)
    {
      inten = g_random_int_range (0, 256);
      for (j = 0; j < 3; j++)
        *v++ = inten;
      *v++ = 255;
    }
}

static void
gradient_get_default (const gchar *name,
                      guchar      *values,
                      gint         nvalues)
{
  gdouble  e[3];
  gdouble  x;
  gint     i;
  gint     j;
  guchar  *v = values;

  /*
    Create gradient by name
   */
  name++;
  for (j = 0; j < 3; j++)
    e[j] = name[j] / 255.0;

  for (i = 0; i < nvalues; i++)
    {
      x = (double) i / nvalues;
      for (j = 0; j < 3; j++)
        *v++ = 255 * pow (x, e[j]);
      *v++ = 255;
    }
}

/*
  Caching gradients is really needed. It really takes 0.2 seconds each
  time to resample an external gradient. (And this plug-in has
  currently 6 gradient menus.)

  However, this caching routine is not too good. It picks up just
  GRADIENT_RESOLUTION samples every time, and rescales it later.  And
  cached values are stored in guchar array. No accuracy.
 */
static void
gradient_get_values_external (const gchar *gradient_name,
                              guchar      *values,
                              gint         nvalues)
{
  GradientCacheItem *ci;
  gboolean           found;
#ifdef DEBUG
  clock_t            clk = clock ();
#endif

  g_return_if_fail (nvalues >= 2);

  ci = gradient_cache_lookup (gradient_name, &found);
  if (!found)
    {
      /* FIXME: "reverse" hardcoded to FALSE. */
      gradient_get_values_real_external (gradient_name, ci->values,
                                         GRADIENT_RESOLUTION, FALSE);
    }
  if (nvalues == GRADIENT_RESOLUTION)
    {
      memcpy (values, ci->values, 4 * GRADIENT_RESOLUTION);
    }
  else
    {
      double    pos, frac;
      int       ipos;
      int       i, j;

      for (i = 0; i < nvalues; i++)
        {
          pos = ((double) i / (nvalues - 1)) * (GRADIENT_RESOLUTION - 1);
          g_assert (0 <= pos && pos <= GRADIENT_RESOLUTION - 1);
          ipos = (int) pos; frac = pos - ipos;
          if (frac == 0.0)
            {
              memcpy (&values[4 * i], &ci->values[4 * ipos], 4);
            }
          else
            for (j = 0; j < 4; j++)
              values[4 * i + j] = ci->values[4 * ipos + j] * (1 - frac)
                                + ci->values[4 * (ipos + 1) + j] * frac;
        }
    }

#ifdef DEBUG
  get_values_external_clock += clock () - clk;
  get_values_external_count ++;
#endif

}

static void
gradient_get_values_real_external (const gchar *gradient_name,
                                   guchar      *values,
                                   gint         nvalues,
                                   gboolean     reverse)
{
  GimpGradient  *gradient;
  GeglColor    **colors;
  const Babl    *format = babl_format ("R'G'B'A u8");
  gint           i;

  gradient = gimp_gradient_get_by_name (gradient_name);

  colors = gimp_gradient_get_uniform_samples (gradient, nvalues, reverse);

  for (i = 0; i < nvalues; i++)
    gegl_color_get_pixel (colors[i], format, (void *) &values[4 * i]);

  gimp_color_array_free (colors);
}

void
gradient_cache_flush (void)
{
  GradientCacheItem *ci;
  GradientCacheItem *tmp;

  ci = gradient_cache_head;
  while (ci)
    {
      tmp = ci->next;
      g_free (ci);
      ci = tmp;
    }
  gradient_cache_head = NULL;
  gradient_cache_count = 0;
}

static GradientCacheItem *
gradient_cache_lookup (const gchar *name,
                       gboolean    *found)
{
  GradientCacheItem *ci;

  ci = gradient_cache_head;
  while (ci)
    {
      if (!strcmp (ci->name, name))
        break;
      ci = ci->next;
    }
  if (ci)
    {
      *found = TRUE;
      if (!ci->prev)
        {
          g_assert (ci == gradient_cache_head);
          return ci;
        }
      ci->prev->next = ci->next;
      if (ci->next)
        ci->next->prev = ci->prev;
      ci->next = gradient_cache_head;
      gradient_cache_head->prev = ci;
      gradient_cache_head = ci;
      ci->prev = NULL;
      return ci;
    }
  else
    {
      *found = FALSE;
      while (gradient_cache_count >= GRADIENT_CACHE_SIZE)
        gradient_cache_zorch();
      ci = g_new (GradientCacheItem, 1);
      g_strlcpy (ci->name, name, GRADIENT_NAME_MAX);
      ci->next = gradient_cache_head;
      ci->prev = NULL;
      if (gradient_cache_head)
        gradient_cache_head->prev = ci;
      gradient_cache_head = ci;
      ++gradient_cache_count;
      return ci;
    }
}

static void
gradient_cache_zorch (void)
{
  GradientCacheItem *ci = gradient_cache_head;

  while (ci && ci->next)
    {
      ci = ci->next;
    }
  if (ci)
    {
      g_assert (ci->next == NULL);
      if (ci->prev)
        ci->prev->next = NULL;
      else
        gradient_cache_head = NULL;
      g_free (ci);
      --gradient_cache_count;
    }
}

static void
gradient_scale_entry_update_double (GimpLabelSpin *entry,
                                    gdouble       *value)
{
  *value = gimp_label_spin_get_value (entry);
}

static void
gradient_scale_entry_update_int (GimpLabelSpin *entry,
                                 gint          *value)
{
  *value = (gint) gimp_label_spin_get_value (entry);
}

#ifdef DEBUG
void
gradient_report (void)
{
  double total = (double) get_values_external_clock / CLOCKS_PER_SEC;

  g_printerr ("gradient_get_values_external "
              "%.2f sec. / %d times (ave %.2f sec.)\n",
              total,
              get_values_external_count,
              total / get_values_external_count);
}
#endif
