/* Plug-in to load and export .gih (PIKA Brush Pipe) files.
 *
 * Copyright (C) 1999 Tor Lillqvist
 * Copyright (C) 2000 Jens Lautenbacher, Sven Neumann
 *
 * 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/>.
 */

  /* Example of how to call file_gih_save from script-fu:

  (let ((ranks (cons-array 1 'byte)))
    (aset ranks 0 12)
    (file-gih-save 1
                   img
                   drawable
                   "foo.gih"
                   "foo.gih"
                   100
                   "test brush"
                   125
                   125
                   3
                   4
                   1
                   ranks
                   1
                   '("random")))
  */


#include "config.h"

#include <libpika/pika.h>
#include <libpika/pikaui.h>
#include <libpikabase/pikaparasiteio.h>

#include "libpika/stdplugins-intl.h"


#define SAVE_PROC      "file-gih-save"
#define PLUG_IN_BINARY "file-gih"
#define PLUG_IN_ROLE   "pika-file-gih"


typedef struct
{
  GtkWidget *rank_entry[PIKA_PIXPIPE_MAXDIM];
  GtkWidget *mode_entry[PIKA_PIXPIPE_MAXDIM];
} SizeAdjustmentData;


typedef struct _Gih      Gih;
typedef struct _GihClass GihClass;

struct _Gih
{
  PikaPlugIn      parent_instance;
};

struct _GihClass
{
  PikaPlugInClass parent_class;
};


#define GIH_TYPE  (gih_get_type ())
#define GIH(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIH_TYPE, Gih))

GType                   gih_get_type          (void) G_GNUC_CONST;

static GList          * gih_query_procedures  (PikaPlugIn           *plug_in);
static PikaProcedure  * gih_create_procedure  (PikaPlugIn           *plug_in,
                                               const gchar          *name);

static PikaValueArray * gih_save              (PikaProcedure        *procedure,
                                               PikaRunMode           run_mode,
                                               PikaImage            *image,
                                               gint                  n_drawables,
                                               PikaDrawable        **drawables,
                                               GFile                *file,
                                               PikaMetadata         *metadata,
                                               PikaProcedureConfig  *config,
                                               gpointer              run_data);

static void             gih_remove_guides     (PikaProcedureConfig  *config,
                                               PikaImage            *image);
static void             gih_cell_width_notify (PikaProcedureConfig  *config,
                                               const GParamSpec     *pspec,
                                               PikaProcedureDialog  *dialog);

static gboolean         gih_save_dialog       (PikaImage            *image,
                                               PikaProcedure        *procedure,
                                               PikaProcedureConfig  *config);


G_DEFINE_TYPE (Gih, gih, PIKA_TYPE_PLUG_IN)

PIKA_MAIN (GIH_TYPE)
DEFINE_STD_SET_I18N


static const gchar * const selection_modes[] = { "incremental",
                                                 "angular",
                                                 "random",
                                                 "velocity",
                                                 "pressure",
                                                 "xtilt",
                                                 "ytilt" };


static void
gih_class_init (GihClass *klass)
{
  PikaPlugInClass *plug_in_class = PIKA_PLUG_IN_CLASS (klass);

  plug_in_class->query_procedures = gih_query_procedures;
  plug_in_class->create_procedure = gih_create_procedure;
  plug_in_class->set_i18n         = STD_SET_I18N;
}

static void
gih_init (Gih *gih)
{
}

static GList *
gih_query_procedures (PikaPlugIn *plug_in)
{
  return g_list_append (NULL, g_strdup (SAVE_PROC));
}

static PikaProcedure *
gih_create_procedure (PikaPlugIn  *plug_in,
                      const gchar *name)
{
  PikaProcedure *procedure = NULL;

  if (! strcmp (name, SAVE_PROC))
    {
      procedure = pika_save_procedure_new (plug_in, name,
                                           PIKA_PDB_PROC_TYPE_PLUGIN,
                                           FALSE, gih_save, NULL, NULL);

      pika_procedure_set_image_types (procedure, "RGB*, GRAY*");

      pika_procedure_set_menu_label (procedure, _("PIKA brush (animated)"));
      pika_procedure_set_icon_name (procedure, PIKA_ICON_BRUSH);

      pika_procedure_set_documentation (procedure,
                                        "exports images in PIKA brush pipe "
                                        "format",
                                        "This plug-in exports an image in "
                                        "the PIKA brush pipe format. For a "
                                        "colored brush pipe, RGBA layers are "
                                        "used, otherwise the layers should be "
                                        "grayscale masks. The image can be "
                                        "multi-layered, and additionally the "
                                        "layers can be divided into a "
                                        "rectangular array of brushes.",
                                        SAVE_PROC);
      pika_procedure_set_attribution (procedure,
                                      "Tor Lillqvist",
                                      "Tor Lillqvist",
                                      "1999");

      pika_file_procedure_set_mime_types (PIKA_FILE_PROCEDURE (procedure),
                                          "image/x-pika-gih");
      pika_file_procedure_set_format_name (PIKA_FILE_PROCEDURE (procedure),
                                           _("Brush Pipe"));
      pika_file_procedure_set_extensions (PIKA_FILE_PROCEDURE (procedure),
                                          "gih");
      pika_file_procedure_set_handles_remote (PIKA_FILE_PROCEDURE (procedure),
                                              TRUE);

      PIKA_PROC_ARG_INT (procedure, "spacing",
                         _("Spacing (_percent)"),
                         _("Spacing of the brush"),
                         1, 1000, 20,
                         PIKA_PARAM_READWRITE);

      PIKA_PROC_ARG_STRING (procedure, "description",
                            _("_Description"),
                            _("Short description of the GIH brush pipe"),
                            "PIKA Brush Pipe",
                            PIKA_PARAM_READWRITE);

      PIKA_PROC_ARG_INT (procedure, "cell-width",
                         _("Cell _width"),
                         _("Width of the brush cells in pixels"),
                         1, 1000, 1,
                         PIKA_PARAM_READWRITE);

      PIKA_PROC_ARG_INT (procedure, "cell-height",
                         _("Cell _height"),
                         _("Height of the brush cells in pixels"),
                         1, 1000, 1,
                         PIKA_PARAM_READWRITE);

      PIKA_PROC_ARG_INT (procedure, "num-cells",
                         _("_Number of cells"),
                         _("Number of cells to cut up"),
                         1, 1000, 1,
                         PIKA_PARAM_READWRITE);

      PIKA_PROC_ARG_BYTES (procedure, "ranks",
                           _("_Rank"),
                           _("Ranks of the dimensions"),
                           PIKA_PARAM_READWRITE);

      PIKA_PROC_ARG_STRV (procedure, "selection-modes",
                          "Selection modes",
                          "Selection modes",
                          PIKA_PARAM_READWRITE);

      /* Auxiliary arguments. Only useful for the GUI, to pass info around. */

      PIKA_PROC_AUX_ARG_STRING (procedure, "info-text",
                                _("Display as"),
                                _("Describe how the layers will be split"),
                                "", PIKA_PARAM_READWRITE);

      PIKA_PROC_AUX_ARG_INT (procedure, "dimension",
                             _("D_imension"),
                             _("How many dimensions the animated brush has"),
                             1, 1000, 1,
                             PIKA_PARAM_READWRITE);

      PIKA_PROC_AUX_ARG_INT32_ARRAY (procedure, "guides",
                                     "Guides",
                                     "Guides to show how the layers will be split in cells",
                                     PIKA_PARAM_READWRITE);
    }

  return procedure;
}

static PikaValueArray *
gih_save (PikaProcedure        *procedure,
          PikaRunMode           run_mode,
          PikaImage            *image,
          gint                  n_drawables,
          PikaDrawable        **drawables,
          GFile                *file,
          PikaMetadata         *metadata,
          PikaProcedureConfig  *config,
          gpointer              run_data)
{
  PikaPDBStatusType  status = PIKA_PDB_SUCCESS;
  PikaExportReturn   export = PIKA_EXPORT_CANCEL;
  PikaParasite      *parasite;
  PikaImage         *orig_image;
  GError            *error  = NULL;

  PikaPixPipeParams  gihparams   = { 0, };
  gchar             *description = NULL;
  gint               spacing;
  GBytes            *rank_bytes;
  const guint8      *rank;
  gchar            **selection;

  orig_image = image;

  if (run_mode == PIKA_RUN_INTERACTIVE)
    {
      GParamSpec *spec;
      gint        image_width;
      gint        image_height;
      gint        cell_width;
      gint        cell_height;

      export = pika_export_image (&image, &n_drawables, &drawables, "GIH",
                                  PIKA_EXPORT_CAN_HANDLE_RGB   |
                                  PIKA_EXPORT_CAN_HANDLE_GRAY  |
                                  PIKA_EXPORT_CAN_HANDLE_ALPHA |
                                  PIKA_EXPORT_CAN_HANDLE_LAYERS);

      if (export == PIKA_EXPORT_CANCEL)
        return pika_procedure_new_return_values (procedure,
                                                 PIKA_PDB_CANCEL,
                                                 NULL);

      /*  Possibly retrieve data  */
      parasite = pika_image_get_parasite (orig_image,
                                          "pika-brush-pipe-name");
      if (parasite)
        {
          gchar   *parasite_data;
          guint32  parasite_size;

          parasite_data = (gchar *) pika_parasite_get_data (parasite, &parasite_size);

          g_object_set (config, "description", parasite_data , NULL);

          pika_parasite_free (parasite);
        }
      else
        {
          gchar *name = g_path_get_basename (pika_file_get_utf8_name (file));

          if (g_str_has_suffix (name, ".gih"))
            name[strlen (name) - 4] = '\0';

          if (strlen (name))
            g_object_set (config, "description", name, NULL);

          g_free (name);
        }

      g_object_get (config,
                    "cell-width",  &cell_width,
                    "cell-height", &cell_height,
                    NULL);

      image_width  = pika_image_get_width (image);
      image_height = pika_image_get_height (image);

      spec = g_object_class_find_property (G_OBJECT_GET_CLASS (config), "cell-width");
      G_PARAM_SPEC_INT (spec)->default_value = image_width;
      G_PARAM_SPEC_INT (spec)->maximum       = image_width;

      spec = g_object_class_find_property (G_OBJECT_GET_CLASS (config), "cell-height");
      G_PARAM_SPEC_INT (spec)->default_value = image_height;
      G_PARAM_SPEC_INT (spec)->maximum       = image_height;

      if (cell_width == 1 && cell_height == 1)
        {
          g_object_set (config,
                        "cell-width",  image_width,
                        "cell-height", image_height,
                        NULL);

          spec = g_object_class_find_property (G_OBJECT_GET_CLASS (config), "num-cells");
          G_PARAM_SPEC_INT (spec)->maximum = 1;
        }

      pika_ui_init (PLUG_IN_BINARY);

      if (! gih_save_dialog (image, procedure, config))
        {
          status = PIKA_PDB_CANCEL;
          goto out;
        }
    }

  pika_pixpipe_params_init (&gihparams);

  g_object_get (config,
                "spacing",         &spacing,
                "description",     &description,
                "cell-width",      &gihparams.cellwidth,
                "cell-height",     &gihparams.cellheight,
                "num-cells",       &gihparams.ncells,
                "ranks",           &rank_bytes,
                "selection-modes", &selection,
                NULL);

  gihparams.cols = pika_image_get_width (image) / gihparams.cellwidth;
  gihparams.rows = pika_image_get_height (image) / gihparams.cellheight;

  rank           = g_bytes_get_data (rank_bytes, NULL);
  gihparams.dim  = g_bytes_get_size (rank_bytes);

  for (gint i = 0; i < gihparams.dim; i++)
    {
      gihparams.rank[i]      = rank[i];
      gihparams.selection[i] = g_strdup (selection[i]);
    }

  g_strfreev (selection);
  g_bytes_unref (rank_bytes);

  if (status == PIKA_PDB_SUCCESS)
    {
      PikaProcedure   *procedure;
      PikaValueArray  *save_retvals;
      PikaObjectArray *drawables_array;
      gchar           *paramstring;

      paramstring = pika_pixpipe_params_build (&gihparams);

      drawables_array = pika_object_array_new (PIKA_TYPE_DRAWABLE, (GObject **) drawables,
                                               n_drawables, FALSE);
      procedure    = pika_pdb_lookup_procedure (pika_get_pdb (),
                                                "file-gih-save-internal");
      save_retvals = pika_procedure_run (procedure,
                                         "image",         image,
                                         "num-drawables", n_drawables,
                                         "drawables",     drawables_array,
                                         "file",          file,
                                         "spacing",       spacing,
                                         "name",          description,
                                         "params",        paramstring,
                                         NULL);
      pika_object_array_free (drawables_array);

      if (PIKA_VALUES_GET_ENUM (save_retvals, 0) == PIKA_PDB_SUCCESS)
        {
          parasite = pika_parasite_new ("pika-brush-pipe-name",
                                        PIKA_PARASITE_PERSISTENT,
                                        strlen (description) + 1,
                                        description);
          pika_image_attach_parasite (orig_image, parasite);
          pika_parasite_free (parasite);
        }
      else
        {
          g_set_error (&error, 0, 0,
                       "Running procedure 'file-gih-save-internal' "
                       "failed: %s",
                       pika_pdb_get_last_error (pika_get_pdb ()));

          status = PIKA_PDB_EXECUTION_ERROR;
        }

      g_free (paramstring);
    }

  pika_pixpipe_params_free (&gihparams);
  g_free (description);

 out:
  if (export == PIKA_EXPORT_EXPORT)
    {
      pika_image_delete (image);
      g_free (drawables);
    }

  return pika_procedure_new_return_values (procedure, status, error);
}


/*  save routines */

static void
gih_remove_guides (PikaProcedureConfig *config,
                        PikaImage           *image)
{
  PikaArray *array;

  g_object_get (config, "guides", &array, NULL);

  if (array != NULL)
    {
      gint32 *guides   = (gint32 *) array->data;
      gint    n_guides = array->length / 4;

      for (gint i = 0; i < n_guides; i++)
        pika_image_delete_guide (image, guides[i]);
    }
  g_clear_pointer (&array, pika_array_free);

  g_object_set (config, "guides", NULL, NULL);
}

static void
gih_cell_width_notify (PikaProcedureConfig *config,
                       const GParamSpec    *pspec,
                       PikaProcedureDialog *dialog)
{
  PikaImage      *image;
  GParamSpec     *spec;
  gchar          *info_text;
  gchar          *grid_text      = NULL;
  const gchar    *width_warning  = _("Width Mismatch!");
  gchar          *height_warning = _("Height Mismatch!");
  GtkWidget      *widget;
  GtkAdjustment  *rank0;
  gint            dimension;

  gint            cell_width;
  gint            image_width;
  gint            n_horiz_cells;
  gint            n_vert_cells;

  gint            cell_height;
  gint            image_height;
  gint            n_vert_guides;
  gint            n_horiz_guides;

  gint            old_max_cells;
  gint            max_cells;
  gint            num_cells;

  image = g_object_get_data (G_OBJECT (dialog), "image");
  rank0 = g_object_get_data (G_OBJECT (dialog), "rank0");

  g_object_get (config,
                "cell-width",  &cell_width,
                "cell-height", &cell_height,
                "num-cells",   &num_cells,
                "dimension",   &dimension,
                NULL);

  spec = g_object_class_find_property (G_OBJECT_GET_CLASS (config), "cell-width");
  image_width    = G_PARAM_SPEC_INT (spec)->maximum;
  n_horiz_cells  = image_width / cell_width;
  n_vert_guides  = n_horiz_cells - 1;

  if (cell_width * n_horiz_cells == image_width)
    width_warning = NULL;

  spec = g_object_class_find_property (G_OBJECT_GET_CLASS (config), "cell-height");
  image_height   = G_PARAM_SPEC_INT (spec)->maximum;
  n_vert_cells   = image_height / cell_height;
  n_horiz_guides = n_vert_cells - 1;

  if (cell_height * n_vert_cells == image_height)
    height_warning = NULL;

  /* TRANSLATORS: \xc3\x97 is the UTF-8 encoding for the multiplication sign. */
  if (n_horiz_guides + n_vert_guides > 0)
    grid_text = g_strdup_printf (_("Displays as a %d \xc3\x97 %d grid on each layer"),
                                 n_horiz_cells, n_vert_cells);
  info_text = g_strdup_printf ("%s%s%s%s%s",
                               grid_text != NULL ? grid_text : "",
                               grid_text != NULL && width_warning != NULL ? " / " : "",
                               width_warning != NULL ? width_warning : "",
                               (grid_text != NULL || width_warning != NULL) && height_warning != NULL ? " / " : "",
                               height_warning != NULL ? height_warning : "");

  spec = g_object_class_find_property (G_OBJECT_GET_CLASS (config), "num-cells");
  old_max_cells = G_PARAM_SPEC_INT (spec)->maximum;
  max_cells     = n_horiz_cells * n_vert_cells;

  if (num_cells == old_max_cells)
    num_cells = max_cells;
  else
    num_cells = MIN (max_cells, num_cells);

  G_PARAM_SPEC_INT (spec)->maximum = max_cells;
  widget = pika_procedure_dialog_get_widget (dialog, "num-cells", G_TYPE_NONE);
  g_object_set (widget, "upper", (gdouble) max_cells, NULL);
  pika_label_spin_set_increments (PIKA_LABEL_SPIN (widget), 1.0, 1.0);

  gih_remove_guides (config, image);

  widget = pika_procedure_dialog_get_widget (dialog, "info-text", G_TYPE_NONE);
  gtk_widget_set_visible (widget, (strlen (info_text) > 0));
  if (n_horiz_guides + n_vert_guides > 0)
    {
      gint32 *guides = NULL;
      GValue  value  = G_VALUE_INIT;

      guides = g_new0 (gint32, n_horiz_guides + n_vert_guides);

      for (gint i = 0; i < n_vert_guides; i++)
        guides[i] = pika_image_add_vguide (image, cell_width * (i + 1));

      for (gint i = 0; i < n_horiz_guides; i++)
        guides[n_vert_guides + i] = pika_image_add_hguide (image, cell_height * (i + 1));

      g_value_init (&value, PIKA_TYPE_INT32_ARRAY);
      pika_value_set_int32_array (&value, guides, n_horiz_guides + n_vert_guides);
      g_object_set_property (G_OBJECT (config), "guides", &value);
      g_free (guides);
    }

  g_object_set (config,
                "info-text", info_text,
                "num-cells", num_cells,
                NULL);
  if (rank0 != NULL && dimension == 1)
    gtk_adjustment_set_value (rank0, num_cells);

  g_free (info_text);
  g_free (grid_text);
}

static void
gih_selection_mode_changed (GtkWidget *widget,
                            gpointer   data)
{
  gint index;

  index = gtk_combo_box_get_active (GTK_COMBO_BOX (widget));

  g_free (*((gchar **) data));
  *((gchar **) data) = g_strdup (selection_modes [index]);
}

static void
gih_dimension_notify (PikaProcedureConfig *config,
                      const GParamSpec    *pspec,
                      SizeAdjustmentData  *data)
{
  gint dimension;
  gint i;

  g_object_get (config, "dimension", &dimension, NULL);

  for (i = 0; i < PIKA_PIXPIPE_MAXDIM; i++)
    {
      gtk_widget_set_sensitive (data->rank_entry[i], i < dimension);
      gtk_widget_set_sensitive (data->mode_entry[i], i < dimension);
    }
}

static gboolean
gih_save_dialog (PikaImage           *image,
                 PikaProcedure       *procedure,
                 PikaProcedureConfig *config)
{
  GtkWidget           *dialog;
  GtkWidget           *dimgrid;
  GtkWidget           *label;
  GtkAdjustment       *adjustment;
  GtkWidget           *spinbutton;
  GtkWidget           *cb;
  gint                 i;
  SizeAdjustmentData   cellw_adjust;
  SizeAdjustmentData   cellh_adjust;
  gchar              **selection;
  GBytes              *rank_bytes = NULL;
  guint8               rank[PIKA_PIXPIPE_MAXDIM];
  gint                 num_cells;
  gint                 dimension;
  gboolean             run;

  dialog = pika_save_procedure_dialog_new (PIKA_SAVE_PROCEDURE (procedure),
                                           PIKA_PROCEDURE_CONFIG (config),
                                           image);

  gtk_widget_set_halign (pika_procedure_dialog_get_label (PIKA_PROCEDURE_DIALOG (dialog),
                                                          "info-text", NULL, TRUE, FALSE),
                         GTK_ALIGN_START);
  pika_procedure_dialog_fill (PIKA_PROCEDURE_DIALOG (dialog),
                              "description", "spacing",
                              "cell-width", "cell-height", "num-cells",
                              "info-text", "dimension", NULL);

  g_object_set_data (G_OBJECT (dialog), "image", image);
  g_signal_connect (config, "notify::cell-width",
                    G_CALLBACK (gih_cell_width_notify),
                    dialog);
  g_signal_connect (config, "notify::cell-height",
                    G_CALLBACK (gih_cell_width_notify),
                    dialog);
  gih_cell_width_notify (config, NULL, PIKA_PROCEDURE_DIALOG (dialog));

  g_signal_connect (config, "notify::dimension",
                    G_CALLBACK (gih_dimension_notify),
                    &cellw_adjust);

  /*
   * Ranks / Selection: ______ ______ ______ ______ ______
   */

  g_object_get (config,
                "selection-modes", &selection,
                "ranks",           &rank_bytes,
                "num-cells",       &num_cells,
                NULL);
  if (selection == NULL || g_strv_length (selection) != PIKA_PIXPIPE_MAXDIM)
    {
      GStrvBuilder *builder = g_strv_builder_new ();
      gint          old_len;

      old_len = (selection == NULL ? 0 : g_strv_length (selection));

      for (i = 0; i < MIN (old_len, PIKA_PIXPIPE_MAXDIM); i++)
        g_strv_builder_add (builder, selection[i]);

      for (i = old_len; i < PIKA_PIXPIPE_MAXDIM; i++)
        g_strv_builder_add (builder, "random");

      g_strfreev (selection);
      selection = g_strv_builder_end (builder);
      g_strv_builder_unref (builder);
    }

  if (rank_bytes == NULL)
    {
      dimension = 1;

      rank[0] = 1;
      for (i = 1; i < PIKA_PIXPIPE_MAXDIM; i++)
        rank[i] = 0;
    }
  else
    {
      const guint8 *stored;

      stored = g_bytes_get_data (rank_bytes, NULL);
      dimension = g_bytes_get_size (rank_bytes);

      for (i = 0; i < dimension; i++)
        rank[i] = stored[i];

      for (i = dimension; i < PIKA_PIXPIPE_MAXDIM; i++)
        rank[i] = 1;
    }
  g_bytes_unref (rank_bytes);
  dimension = MAX (dimension, 1);

  dimgrid = gtk_grid_new ();
  gtk_grid_set_column_spacing (GTK_GRID (dimgrid), 4);

  label = gtk_label_new (_("Ranks:"));
  gtk_grid_attach (GTK_GRID (dimgrid), label, 0, 0, 1, 1);
  gtk_widget_show (label);

  for (i = 0; i < PIKA_PIXPIPE_MAXDIM; i++)
    {
      gsize j;

      adjustment = gtk_adjustment_new (rank[i], 1, 100, 1, 1, 0);
      if (i == 0)
        g_object_set_data (G_OBJECT (dialog), "rank0", adjustment);

      spinbutton = pika_spin_button_new (adjustment, 1.0, 0);
      gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE);
      gtk_grid_attach (GTK_GRID (dimgrid), spinbutton, 1, i, 1, 1);

      gtk_widget_show (spinbutton);

      if (i >= dimension)
        gtk_widget_set_sensitive (spinbutton, FALSE);

      g_signal_connect (adjustment, "value-changed",
                        G_CALLBACK (pika_int_adjustment_update),
                        &rank[i]);

      cellw_adjust.rank_entry[i] = cellh_adjust.rank_entry[i] = spinbutton;

      cb = gtk_combo_box_text_new ();

      for (j = 0; j < G_N_ELEMENTS (selection_modes); j++)
        gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (cb),
                                        selection_modes[j]);
      gtk_combo_box_set_active (GTK_COMBO_BOX (cb), 2);  /* random */

      for (j = 0; j < G_N_ELEMENTS (selection_modes); j++)
        if (!strcmp (selection[i], selection_modes[j]))
          {
            gtk_combo_box_set_active (GTK_COMBO_BOX (cb), j);
            break;
          }

      gtk_grid_attach (GTK_GRID (dimgrid), cb, 2, i, 1, 1);

      gtk_widget_show (cb);

      if (i >= dimension)
        gtk_widget_set_sensitive (cb, FALSE);

      g_signal_connect (GTK_COMBO_BOX (cb), "changed",
                        G_CALLBACK (gih_selection_mode_changed),
                        &selection[i]);

      cellw_adjust.mode_entry[i] = cellh_adjust.mode_entry[i] = cb;
    }

  gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
                      dimgrid, TRUE, TRUE, 0);
  gtk_widget_show (dimgrid);

  run = pika_procedure_dialog_run (PIKA_PROCEDURE_DIALOG (dialog));

  g_object_get (config, "dimension", &dimension, NULL);
  rank_bytes = g_bytes_new (rank, sizeof (guint8) * dimension);

  g_object_set (config,
                "selection-modes", selection,
                "ranks",           rank_bytes,
                NULL);

  gih_remove_guides (config, image);
  gtk_widget_destroy (dialog);

  g_strfreev (selection);
  g_bytes_unref (rank_bytes);

  return run;
}
