/* PIKA - Photo and Image Kooker Application
 * a rebranding of The GNU Image Manipulation Program (created with heckimp)
 * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
 *
 * Original copyright, applying to most contents (license remains unchanged): 
 * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

#include "config.h"

#include <string.h>

#include <cairo-gobject.h>
#include <gegl.h>
#include <gtk/gtk.h>

#include "libpikamath/pikamath.h"
#include "libpikabase/pikabase.h"
#include "libpikaconfig/pikaconfig.h"
#include "libpikawidgets/pikawidgets.h"

#include "dialogs-types.h"

#include "pika-version.h"

#include "config/pikarc.h"

#include "core/pika.h"
#include "core/pikatemplate.h"
#include "core/pika-utils.h"

#include "display/pikamodifiersmanager.h"

#include "plug-in/pikapluginmanager.h"

#include "widgets/pikaaction-history.h"
#include "widgets/pikacolorpanel.h"
#include "widgets/pikacontainercombobox.h"
#include "widgets/pikacontainerview.h"
#include "widgets/pikacontrollerlist.h"
#include "widgets/pikadevices.h"
#include "widgets/pikadialogfactory.h"
#include "widgets/pikagrideditor.h"
#include "widgets/pikahelp.h"
#include "widgets/pikahelp-ids.h"
#include "widgets/pikalanguagecombobox.h"
#include "widgets/pikamessagebox.h"
#include "widgets/pikamessagedialog.h"
#include "widgets/pikapluginview.h"
#include "widgets/pikaprefsbox.h"
#include "widgets/pikapropwidgets.h"
#include "widgets/pikamodifierseditor.h"
#include "widgets/pikastrokeeditor.h"
#include "widgets/pikatemplateeditor.h"
#include "widgets/pikatooleditor.h"
#include "widgets/pikawidgets-utils.h"

#include "menus/menus.h"

#include "tools/pika-tools.h"

#include "gui/icon-themes.h"
#include "gui/session.h"
#include "gui/modifiers.h"
#include "gui/themes.h"

#include "preferences-dialog.h"
#include "preferences-dialog-utils.h"
#include "resolution-calibrate-dialog.h"

#include "pika-intl.h"


#define RESPONSE_RESET 1


/*  preferences local functions  */

static GtkWidget * prefs_dialog_new                (Pika       *pika,
                                                    PikaConfig *config);
static void        prefs_config_notify             (GObject    *config,
                                                    GParamSpec *param_spec,
                                                    GObject    *config_copy);
static void        prefs_config_copy_notify        (GObject    *config_copy,
                                                    GParamSpec *param_spec,
                                                    GObject    *config);
static void        prefs_response                  (GtkWidget  *widget,
                                                    gint        response_id,
                                                    GtkWidget  *dialog);

static void        prefs_message                   (GtkMessageType  type,
                                                    gboolean        destroy,
                                                    const gchar    *message);

static void   prefs_color_management_reset         (GtkWidget    *widget,
                                                    GObject      *config);
static void   prefs_dialog_defaults_reset          (GtkWidget    *widget,
                                                    GObject      *config);
static void   prefs_folders_reset                  (GtkWidget    *widget,
                                                    GObject      *config);
static void   prefs_path_reset                     (GtkWidget    *widget,
                                                    GObject      *config);

static void   prefs_import_raw_procedure_callback  (GtkWidget    *widget,
                                                    GObject      *config);
static void   prefs_resolution_source_callback     (GtkWidget    *widget,
                                                    GObject      *config);
static void   prefs_resolution_calibrate_callback  (GtkWidget    *widget,
                                                    GtkWidget    *entry);
static void   prefs_input_devices_dialog           (GtkWidget    *widget,
                                                    Pika         *pika);
static void   prefs_keyboard_shortcuts_dialog      (GtkWidget    *widget,
                                                    Pika         *pika);
static void   prefs_menus_save_callback            (GtkWidget    *widget,
                                                    Pika         *pika);
static void   prefs_menus_clear_callback           (GtkWidget    *widget,
                                                    Pika         *pika);
static void   prefs_menus_remove_callback          (GtkWidget    *widget,
                                                    Pika         *pika);
static void   prefs_session_save_callback          (GtkWidget    *widget,
                                                    Pika         *pika);
static void   prefs_session_clear_callback         (GtkWidget    *widget,
                                                    Pika         *pika);
static void   prefs_devices_save_callback          (GtkWidget    *widget,
                                                    Pika         *pika);
static void   prefs_devices_clear_callback         (GtkWidget    *widget,
                                                    Pika         *pika);
static void   prefs_modifiers_clear_callback       (GtkWidget    *widget,
                                                    PikaModifiersEditor *editor);
static void   prefs_search_clear_callback          (GtkWidget    *widget,
                                                    Pika         *pika);
static void   prefs_tool_options_save_callback     (GtkWidget    *widget,
                                                    Pika         *pika);
static void   prefs_tool_options_clear_callback    (GtkWidget    *widget,
                                                    Pika         *pika);
static void   prefs_help_language_change_callback  (GtkComboBox  *combo,
                                                    Pika         *pika);
static void   prefs_help_language_change_callback2 (GtkComboBox  *combo,
                                                    GtkContainer *box);
static void   prefs_check_style_callback           (GObject      *config,
                                                    GParamSpec   *pspec,
                                                    GtkWidget    *widget);

static void   prefs_gui_config_notify_icon_size    (GObject       *config,
                                                    GParamSpec    *pspec,
                                                    GtkRange      *range);
static void   prefs_icon_size_value_changed        (GtkRange      *range,
                                                    PikaGuiConfig *config);
static void   prefs_gui_config_notify_font_size    (GObject       *config,
                                                    GParamSpec    *pspec,
                                                    GtkRange      *range);
static void   prefs_font_size_value_changed        (GtkRange      *range,
                                                    PikaGuiConfig *config);


/*  private variables  */

static GtkWidget *prefs_dialog = NULL;
static GtkWidget *tool_editor  = NULL;


/*  public function  */

GtkWidget *
preferences_dialog_create (Pika *pika)
{
  PikaConfig *config;
  PikaConfig *config_copy;
  PikaConfig *config_orig;

  g_return_val_if_fail (PIKA_IS_PIKA (pika), NULL);

  if (prefs_dialog)
    return prefs_dialog;

  /*  turn off autosaving while the prefs dialog is open  */
  pika_rc_set_autosave (PIKA_RC (pika->edit_config), FALSE);

  config       = PIKA_CONFIG (pika->edit_config);
  config_copy  = pika_config_duplicate (config);
  config_orig  = pika_config_duplicate (config);

  g_signal_connect_object (config, "notify",
                           G_CALLBACK (prefs_config_notify),
                           config_copy, 0);
  g_signal_connect_object (config_copy, "notify",
                           G_CALLBACK (prefs_config_copy_notify),
                           config, 0);

  g_set_weak_pointer (&prefs_dialog, prefs_dialog_new (pika, config_copy));

  g_object_set_data (G_OBJECT (prefs_dialog), "pika", pika);

  g_object_set_data_full (G_OBJECT (prefs_dialog), "config-copy", config_copy,
                          (GDestroyNotify) g_object_unref);
  g_object_set_data_full (G_OBJECT (prefs_dialog), "config-orig", config_orig,
                          (GDestroyNotify) g_object_unref);

  return prefs_dialog;
}


/*  private functions  */

static void
prefs_config_notify (GObject    *config,
                     GParamSpec *param_spec,
                     GObject    *config_copy)
{
  GValue global_value = G_VALUE_INIT;
  GValue copy_value   = G_VALUE_INIT;

  g_value_init (&global_value, param_spec->value_type);
  g_value_init (&copy_value,   param_spec->value_type);

  g_object_get_property (config,      param_spec->name, &global_value);
  g_object_get_property (config_copy, param_spec->name, &copy_value);

  if (g_param_values_cmp (param_spec, &global_value, &copy_value))
    {
      g_signal_handlers_block_by_func (config_copy,
                                       prefs_config_copy_notify,
                                       config);

      g_object_set_property (config_copy, param_spec->name, &global_value);

      g_signal_handlers_unblock_by_func (config_copy,
                                         prefs_config_copy_notify,
                                         config);
    }

  g_value_unset (&global_value);
  g_value_unset (&copy_value);
}

static void
prefs_config_copy_notify (GObject    *config_copy,
                          GParamSpec *param_spec,
                          GObject    *config)
{
  GValue copy_value   = G_VALUE_INIT;
  GValue global_value = G_VALUE_INIT;

  g_value_init (&copy_value,   param_spec->value_type);
  g_value_init (&global_value, param_spec->value_type);

  g_object_get_property (config_copy, param_spec->name, &copy_value);
  g_object_get_property (config,      param_spec->name, &global_value);

  if (g_param_values_cmp (param_spec, &copy_value, &global_value))
    {
      if (param_spec->flags & PIKA_CONFIG_PARAM_CONFIRM)
        {
#ifdef PIKA_CONFIG_DEBUG
          g_print ("NOT Applying prefs change of '%s' to edit_config "
                   "because it needs confirmation\n",
                   param_spec->name);
#endif
        }
      else
        {
#ifdef PIKA_CONFIG_DEBUG
          g_print ("Applying prefs change of '%s' to edit_config\n",
                   param_spec->name);
#endif
          g_signal_handlers_block_by_func (config,
                                           prefs_config_notify,
                                           config_copy);

          g_object_set_property (config, param_spec->name, &copy_value);

          g_signal_handlers_unblock_by_func (config,
                                             prefs_config_notify,
                                             config_copy);
        }
    }

  g_value_unset (&copy_value);
  g_value_unset (&global_value);
}

static void
prefs_response (GtkWidget *widget,
                gint       response_id,
                GtkWidget *dialog)
{
  Pika *pika = g_object_get_data (G_OBJECT (dialog), "pika");

  switch (response_id)
    {
    case RESPONSE_RESET:
      {
        GtkWidget *confirm;

        confirm = pika_message_dialog_new (_("Reset All Preferences"),
                                           PIKA_ICON_DIALOG_QUESTION,
                                           dialog,
                                           GTK_DIALOG_MODAL |
                                           GTK_DIALOG_DESTROY_WITH_PARENT,
                                           pika_standard_help_func, NULL,

                                           _("_Cancel"), GTK_RESPONSE_CANCEL,
                                           _("_Reset"),  GTK_RESPONSE_OK,

                                           NULL);

        pika_dialog_set_alternative_button_order (GTK_DIALOG (confirm),
                                                 GTK_RESPONSE_OK,
                                                 GTK_RESPONSE_CANCEL,
                                                 -1);

        pika_message_box_set_primary_text (PIKA_MESSAGE_DIALOG (confirm)->box,
                                           _("Do you really want to reset all "
                                             "preferences to default values?"));

        if (pika_dialog_run (PIKA_DIALOG (confirm)) == GTK_RESPONSE_OK)
          {
            PikaConfig *config_copy;

            config_copy = g_object_get_data (G_OBJECT (dialog), "config-copy");

            pika_config_reset (config_copy);
            pika_rc_load_system (PIKA_RC (config_copy));

            /* don't use the default value if there is no help browser */
            if (! pika_help_browser_is_installed (pika))
              {
                g_object_set (config_copy,
                              "help-browser", PIKA_HELP_BROWSER_WEB_BROWSER,
                              NULL);
              }
          }

        gtk_widget_destroy (confirm);

        return;
      }
      break;

    case GTK_RESPONSE_OK:
      {
        GObject *config_copy;
        GList   *restart_diff;
        GList   *confirm_diff;
        GList   *list;

        config_copy = g_object_get_data (G_OBJECT (dialog), "config-copy");

        /*  destroy config_orig  */
        g_object_set_data (G_OBJECT (dialog), "config-orig", NULL);

        gtk_widget_set_sensitive (GTK_WIDGET (dialog), FALSE);

        confirm_diff = pika_config_diff (G_OBJECT (pika->edit_config),
                                         config_copy,
                                         PIKA_CONFIG_PARAM_CONFIRM);

        g_object_freeze_notify (G_OBJECT (pika->edit_config));

        for (list = confirm_diff; list; list = g_list_next (list))
          {
            GParamSpec *param_spec = list->data;
            GValue      value      = G_VALUE_INIT;

            g_value_init (&value, param_spec->value_type);

            g_object_get_property (config_copy,
                                   param_spec->name, &value);
            g_object_set_property (G_OBJECT (pika->edit_config),
                                   param_spec->name, &value);

            g_value_unset (&value);
          }

        g_object_thaw_notify (G_OBJECT (pika->edit_config));

        g_list_free (confirm_diff);

        pika_rc_save (PIKA_RC (pika->edit_config));

        /*  spit out a solely informational warning about changed values
         *  which need restart
         */
        restart_diff = pika_config_diff (G_OBJECT (pika->edit_config),
                                         G_OBJECT (pika->config),
                                         PIKA_CONFIG_PARAM_RESTART);

        if (restart_diff)
          {
            GString *string;

            string = g_string_new (_("You will have to restart PIKA for "
                                     "the following changes to take effect:"));
            g_string_append (string, "\n\n");

            for (list = restart_diff; list; list = g_list_next (list))
              {
                GParamSpec *param_spec = list->data;

                /* The first 3 bytes are the bullet unicode character
                 * for doing a list (U+2022).
                 */
                g_string_append_printf (string, "\xe2\x80\xa2 %s\n", g_param_spec_get_nick (param_spec));
              }

            prefs_message (GTK_MESSAGE_INFO, FALSE, string->str);

            g_string_free (string, TRUE);
          }

        g_list_free (restart_diff);
      }
      break;

    default:
      {
        GObject *config_orig;
        GList   *diff;
        GList   *list;

        config_orig = g_object_get_data (G_OBJECT (dialog), "config-orig");

        /*  destroy config_copy  */
        g_object_set_data (G_OBJECT (dialog), "config-copy", NULL);

        gtk_widget_set_sensitive (GTK_WIDGET (dialog), FALSE);

        diff = pika_config_diff (G_OBJECT (pika->edit_config),
                                 config_orig,
                                 PIKA_CONFIG_PARAM_SERIALIZE);

        g_object_freeze_notify (G_OBJECT (pika->edit_config));

        for (list = diff; list; list = g_list_next (list))
          {
            GParamSpec *param_spec = list->data;
            GValue      value      = G_VALUE_INIT;

            g_value_init (&value, param_spec->value_type);

            g_object_get_property (config_orig,
                                   param_spec->name, &value);
            g_object_set_property (G_OBJECT (pika->edit_config),
                                   param_spec->name, &value);

            g_value_unset (&value);
          }

        pika_tool_editor_revert_changes (PIKA_TOOL_EDITOR (tool_editor));

        g_object_thaw_notify (G_OBJECT (pika->edit_config));

        g_list_free (diff);
      }

      tool_editor = NULL;
    }

  /*  enable autosaving again  */
  pika_rc_set_autosave (PIKA_RC (pika->edit_config), TRUE);

  gtk_widget_destroy (dialog);
}

static void
prefs_color_management_reset (GtkWidget *widget,
                              GObject   *config)
{
  PikaCoreConfig *core_config = PIKA_CORE_CONFIG (config);

  pika_config_reset (PIKA_CONFIG (core_config->color_management));
  pika_config_reset_property (config, "color-profile-policy");
  pika_config_reset_property (config, "filter-tool-show-color-options");
}

static void
prefs_dialog_defaults_reset (GtkWidget *widget,
                             GObject   *config)
{
  GParamSpec **pspecs;
  guint        n_pspecs;
  guint        i;

  pspecs = g_object_class_list_properties (G_OBJECT_GET_CLASS (config),
                                           &n_pspecs);

  g_object_freeze_notify (config);

  for (i = 0; i < n_pspecs; i++)
    {
      GParamSpec *pspec = pspecs[i];

      if (pspec->owner_type == PIKA_TYPE_DIALOG_CONFIG)
        pika_config_reset_property (config, pspec->name);
    }

  pika_config_reset_property (config, "filter-tool-max-recent");
  pika_config_reset_property (config, "filter-tool-use-last-settings");
  pika_config_reset_property (config, "filter-tool-show-color-options");

  g_object_thaw_notify (config);

  g_free (pspecs);
}

static void
prefs_folders_reset (GtkWidget *widget,
                     GObject   *config)
{
  pika_config_reset_property (config, "temp-path");
  pika_config_reset_property (config, "swap-path");
}

static void
prefs_path_reset (GtkWidget *widget,
                  GObject   *config)
{
  const gchar *path_property;
  const gchar *writable_property;

  path_property     = g_object_get_data (G_OBJECT (widget), "path");
  writable_property = g_object_get_data (G_OBJECT (widget), "path-writable");

  pika_config_reset_property (config, path_property);

  if (writable_property)
    pika_config_reset_property (config, writable_property);
}

static gboolean
prefs_template_select_callback (PikaContainerView *view,
                                GList             *templates,
                                GList             *paths,
                                PikaTemplate      *edit_template)
{
  g_return_val_if_fail (g_list_length (templates) < 2, FALSE);

  if (templates)
    {
      /*  make sure the resolution values are copied first (see bug #546924)  */
      pika_config_sync (G_OBJECT (templates->data), G_OBJECT (edit_template),
                        PIKA_TEMPLATE_PARAM_COPY_FIRST);
      pika_config_sync (G_OBJECT (templates->data), G_OBJECT (edit_template),
                        0);
    }

  return TRUE;
}

static void
prefs_import_raw_procedure_callback (GtkWidget *widget,
                                     GObject   *config)
{
  gchar *raw_plug_in;

  raw_plug_in = pika_plug_in_view_get_plug_in (PIKA_PLUG_IN_VIEW (widget));

  g_object_set (config,
                "import-raw-plug-in", raw_plug_in,
                NULL);

  g_free (raw_plug_in);
}

static void
prefs_resolution_source_callback (GtkWidget *widget,
                                  GObject   *config)
{
  gdouble  xres;
  gdouble  yres;
  gboolean from_gdk;

  from_gdk = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget));

  if (from_gdk)
    {
      pika_get_monitor_resolution (pika_widget_get_monitor (widget),
                                   &xres, &yres);
    }
  else
    {
      PikaSizeEntry *entry = g_object_get_data (G_OBJECT (widget),
                                                "monitor_resolution_sizeentry");

      g_return_if_fail (PIKA_IS_SIZE_ENTRY (entry));

      xres = pika_size_entry_get_refval (entry, 0);
      yres = pika_size_entry_get_refval (entry, 1);
    }

  g_object_set (config,
                "monitor-xresolution",                      xres,
                "monitor-yresolution",                      yres,
                "monitor-resolution-from-windowing-system", from_gdk,
                NULL);
}

static void
prefs_resolution_calibrate_callback (GtkWidget *widget,
                                     GtkWidget *entry)
{
  GtkWidget   *dialog;
  GtkWidget   *prefs_box;
  const gchar *icon_name;

  dialog = gtk_widget_get_toplevel (entry);

  prefs_box = g_object_get_data (G_OBJECT (dialog), "prefs-box");
  icon_name = pika_prefs_box_get_current_icon_name (PIKA_PREFS_BOX (prefs_box));

  resolution_calibrate_dialog (entry, icon_name);
}

static void
prefs_input_devices_dialog (GtkWidget *widget,
                            Pika      *pika)
{
  pika_dialog_factory_dialog_raise (pika_dialog_factory_get_singleton (),
                                    pika_widget_get_monitor (widget),
                                    widget,
                                    "pika-input-devices-dialog", 0);
}

static void
prefs_keyboard_shortcuts_dialog (GtkWidget *widget,
                                 Pika      *pika)
{
  pika_dialog_factory_dialog_raise (pika_dialog_factory_get_singleton (),
                                    pika_widget_get_monitor (widget),
                                    widget,
                                    "pika-keyboard-shortcuts-dialog", 0);
}

static void
prefs_menus_save_callback (GtkWidget *widget,
                           Pika      *pika)
{
  GtkWidget *clear_button;

  menus_save (pika, TRUE);

  clear_button = g_object_get_data (G_OBJECT (widget), "clear-button");

  if (clear_button)
    gtk_widget_set_sensitive (clear_button, TRUE);
}

static void
prefs_menus_clear_callback (GtkWidget *widget,
                            Pika      *pika)
{
  GError *error = NULL;

  if (! menus_clear (pika, &error))
    {
      prefs_message (GTK_MESSAGE_ERROR, TRUE, error->message);
      g_clear_error (&error);
    }
  else
    {
      gtk_widget_set_sensitive (widget, FALSE);

      prefs_message (GTK_MESSAGE_INFO, TRUE,
                     _("Your keyboard shortcuts will be reset to "
                       "default values the next time you start PIKA."));
    }
}

static void
prefs_menus_remove_callback (GtkWidget *widget,
                             Pika      *pika)
{
  GtkWidget *dialog;

  dialog = pika_message_dialog_new (_("Remove all Keyboard Shortcuts"),
                                    PIKA_ICON_DIALOG_QUESTION,
                                    gtk_widget_get_toplevel (widget),
                                    GTK_DIALOG_MODAL |
                                    GTK_DIALOG_DESTROY_WITH_PARENT,
                                    pika_standard_help_func, NULL,

                                    _("_Cancel"), GTK_RESPONSE_CANCEL,
                                    _("Cl_ear"),  GTK_RESPONSE_OK,

                                    NULL);

  pika_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
                                           GTK_RESPONSE_OK,
                                           GTK_RESPONSE_CANCEL,
                                           -1);

  g_signal_connect_object (gtk_widget_get_toplevel (widget), "unmap",
                           G_CALLBACK (gtk_widget_destroy),
                           dialog, G_CONNECT_SWAPPED);

  pika_message_box_set_primary_text (PIKA_MESSAGE_DIALOG (dialog)->box,
                                     _("Do you really want to remove all "
                                       "keyboard shortcuts from all menus?"));

  if (pika_dialog_run (PIKA_DIALOG (dialog)) == GTK_RESPONSE_OK)
    {
      menus_remove (pika);
    }

  gtk_widget_destroy (dialog);
}

static void
prefs_session_save_callback (GtkWidget *widget,
                             Pika      *pika)
{
  GtkWidget *clear_button;

  session_save (pika, TRUE);

  clear_button = g_object_get_data (G_OBJECT (widget), "clear-button");

  if (clear_button)
    gtk_widget_set_sensitive (clear_button, TRUE);
}

static void
prefs_session_clear_callback (GtkWidget *widget,
                              Pika      *pika)
{
  GError *error = NULL;

  if (! session_clear (pika, &error))
    {
      prefs_message (GTK_MESSAGE_ERROR, TRUE, error->message);
      g_clear_error (&error);
    }
  else
    {
      gtk_widget_set_sensitive (widget, FALSE);

      prefs_message (GTK_MESSAGE_INFO, TRUE,
                     _("Your window setup will be reset to "
                       "default values the next time you start PIKA."));
    }
}

static void
prefs_devices_save_callback (GtkWidget *widget,
                             Pika      *pika)
{
  GtkWidget *clear_button;

  pika_devices_save (pika, TRUE);

  clear_button = g_object_get_data (G_OBJECT (widget), "clear-button");

  if (clear_button)
    gtk_widget_set_sensitive (clear_button, TRUE);
}

static void
prefs_devices_clear_callback (GtkWidget *widget,
                              Pika      *pika)
{
  GError *error = NULL;

  if (! pika_devices_clear (pika, &error))
    {
      prefs_message (GTK_MESSAGE_ERROR, TRUE, error->message);
      g_clear_error (&error);
    }
  else
    {
      gtk_widget_set_sensitive (widget, FALSE);

      prefs_message (GTK_MESSAGE_INFO, TRUE,
                     _("Your input device settings will be reset to "
                       "default values the next time you start PIKA."));
    }
}

static void
prefs_modifiers_clear_callback (GtkWidget           *widget,
                                PikaModifiersEditor *editor)
{
  pika_modifiers_editor_clear (editor);
}

#ifdef G_OS_WIN32

static gboolean
prefs_devices_api_sensitivity_func (gint      value,
                                    gpointer  data)
{
  static gboolean have_wintab      = TRUE;
  static gboolean have_windows_ink = TRUE;
  static gboolean inited           = FALSE;

  if (!inited)
    {
      have_wintab      = pika_win32_have_wintab ();
      have_windows_ink = pika_win32_have_windows_ink ();

      inited = TRUE;
    }

  switch (value)
    {
    case PIKA_WIN32_POINTER_INPUT_API_WINTAB:
      return have_wintab;
    case PIKA_WIN32_POINTER_INPUT_API_WINDOWS_INK:
      return have_windows_ink;
    default:
      return TRUE;
    }
}

#endif

static void
prefs_search_clear_callback (GtkWidget *widget,
                             Pika      *pika)
{
  pika_action_history_clear (pika);
}

static void
prefs_tool_options_save_callback (GtkWidget *widget,
                                  Pika      *pika)
{
  GtkWidget *clear_button;

  pika_tools_save (pika, TRUE, TRUE);

  clear_button = g_object_get_data (G_OBJECT (widget), "clear-button");

  if (clear_button)
    gtk_widget_set_sensitive (clear_button, TRUE);
}

static void
prefs_tool_options_clear_callback (GtkWidget *widget,
                                   Pika      *pika)
{
  GError *error = NULL;

  if (! pika_tools_clear (pika, &error))
    {
      prefs_message (GTK_MESSAGE_ERROR, TRUE, error->message);
      g_clear_error (&error);
    }
  else
    {
      gtk_widget_set_sensitive (widget, FALSE);

      prefs_message (GTK_MESSAGE_INFO, TRUE,
                     _("Your tool options will be reset to "
                       "default values the next time you start PIKA."));
    }
}

static void
prefs_help_language_change_callback (GtkComboBox *combo,
                                     Pika        *pika)
{
  gchar *help_locales = NULL;
  gchar *code;

  code = pika_language_combo_box_get_code (PIKA_LANGUAGE_COMBO_BOX (combo));
  if (code && g_strcmp0 ("", code) != 0)
    {
      help_locales = g_strdup_printf ("%s:", code);
    }
  g_object_set (pika->config,
                "help-locales", help_locales? help_locales : "",
                NULL);
  g_free (code);
  if (help_locales)
    g_free (help_locales);
}

static void
prefs_help_language_change_callback2 (GtkComboBox  *combo,
                                      GtkContainer *box)
{
  Pika        *pika;
  GtkLabel    *label = NULL;
  GtkImage    *icon  = NULL;
  GList       *children;
  GList       *iter;
  const gchar *text;
  const gchar *icon_name;

  pika = g_object_get_data (G_OBJECT (box), "pika");
  children = gtk_container_get_children (box);
  for (iter = children; iter; iter = iter->next)
    {
      if (GTK_IS_LABEL (iter->data))
        {
          label = iter->data;
        }
      else if (GTK_IS_IMAGE (iter->data))
        {
          icon = iter->data;
        }
    }
  if (pika_help_user_manual_is_installed (pika))
    {
      text = _("There's a local installation of the user manual.");
      icon_name = PIKA_ICON_DIALOG_INFORMATION;
    }
  else
    {
      text = _("The user manual is not installed locally.");
      icon_name = PIKA_ICON_DIALOG_WARNING;
    }
  if (label)
    {
      gtk_label_set_text (label, text);
    }
  if (icon)
    {
      gtk_image_set_from_icon_name (icon, icon_name,
                                    GTK_ICON_SIZE_BUTTON);
    }

  g_list_free (children);
}

static void
prefs_check_style_callback (GObject    *config,
                            GParamSpec *pspec,
                            GtkWidget  *widget)
{
  PikaDisplayConfig *display_config = PIKA_DISPLAY_CONFIG (config);

  gtk_widget_set_sensitive (widget,
                            display_config->transparency_type == PIKA_CHECK_TYPE_CUSTOM_CHECKS);
}

static void
prefs_icon_size_value_changed (GtkRange      *range,
                               PikaGuiConfig *config)
{
  gint value = (gint) gtk_range_get_value (range);

  g_signal_handlers_block_by_func (config,
                                   G_CALLBACK (prefs_gui_config_notify_icon_size),
                                   range);
  g_object_set (G_OBJECT (config),
                "custom-icon-size", (PikaIconSize) value,
                NULL);
  g_signal_handlers_unblock_by_func (config,
                                     G_CALLBACK (prefs_gui_config_notify_icon_size),
                                     range);
}

static void
prefs_gui_config_notify_icon_size (GObject    *config,
                                   GParamSpec *pspec,
                                   GtkRange   *range)
{
  PikaIconSize size = PIKA_GUI_CONFIG (config)->custom_icon_size;

  g_signal_handlers_block_by_func (range,
                                   G_CALLBACK (prefs_icon_size_value_changed),
                                   config);
  gtk_range_set_value (range, (gdouble) size);
  g_signal_handlers_unblock_by_func (range,
                                     G_CALLBACK (prefs_icon_size_value_changed),
                                     config);
}

static void
prefs_font_size_value_changed (GtkRange      *range,
                               PikaGuiConfig *config)
{
  gdouble value = gtk_range_get_value (range);

  g_signal_handlers_block_by_func (config,
                                   G_CALLBACK (prefs_gui_config_notify_font_size),
                                   range);
  g_object_set (G_OBJECT (config),
                "font-relative-size", value / 100.0,
                NULL);
  g_signal_handlers_unblock_by_func (config,
                                     G_CALLBACK (prefs_gui_config_notify_font_size),
                                     range);
}

static void
prefs_gui_config_notify_font_size (GObject    *config,
                                   GParamSpec *pspec,
                                   GtkRange   *range)
{
  g_signal_handlers_block_by_func (range,
                                   G_CALLBACK (prefs_font_size_value_changed),
                                   config);
  gtk_range_set_value (range,
                       PIKA_GUI_CONFIG (config)->font_relative_size * 100.0);
  g_signal_handlers_unblock_by_func (range,
                                     G_CALLBACK (prefs_font_size_value_changed),
                                     config);
}

static void
prefs_format_string_select_callback (GtkListBox    *listbox,
                                     GtkListBoxRow *row,
                                     gpointer       user_data)
{
  GtkEntry *entry = GTK_ENTRY (user_data);

  gtk_entry_set_text (entry, g_object_get_data (G_OBJECT (row), "format"));
}

static void
prefs_theme_select_callback (GtkListBox    *listbox,
                             GtkListBoxRow *row,
                             Pika          *pika)
{
  const char *theme;

  g_return_if_fail (row != NULL);

  theme = g_object_get_data (G_OBJECT (row), "theme");
  g_object_set (pika->config, "theme", theme, NULL);
}

static void
prefs_theme_reload_callback (GtkWidget *button,
                             Pika      *pika)
{
  g_object_notify (G_OBJECT (pika->config), "theme");
}

static void
prefs_icon_theme_select_callback (GtkListBox    *listbox,
                                  GtkListBoxRow *row,
                                  Pika          *pika)
{
  const char *icon_theme;

  icon_theme = g_object_get_data (G_OBJECT (row), "icon-theme");
  g_object_set (pika->config, "icon-theme", icon_theme, NULL);
}

static void
prefs_canvas_padding_color_changed (GtkWidget *button,
                                    GtkWidget *combo)
{
  pika_int_combo_box_set_active (PIKA_INT_COMBO_BOX (combo),
                                 PIKA_CANVAS_PADDING_MODE_CUSTOM);
}

static void
prefs_display_options_frame_add (Pika         *pika,
                                 GObject      *object,
                                 const gchar  *label,
                                 GtkContainer *parent)
{
  GtkWidget *vbox;
  GtkWidget *hbox;
  GtkWidget *checks_vbox;
  GtkWidget *grid;
  GtkWidget *combo;
  GtkWidget *button;

  vbox = prefs_frame_new (label, parent, FALSE);

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

  checks_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
  gtk_box_pack_start (GTK_BOX (hbox), checks_vbox, TRUE, TRUE, 0);
  gtk_widget_show (checks_vbox);

  prefs_check_button_add (object, "show-selection",
                          _("Show s_election"),
                          GTK_BOX (checks_vbox));
  prefs_check_button_add (object, "show-layer-boundary",
                          _("Show _layer boundary"),
                          GTK_BOX (checks_vbox));
  prefs_check_button_add (object, "show-canvas-boundary",
                          _("Show can_vas boundary"),
                          GTK_BOX (checks_vbox));
  prefs_check_button_add (object, "show-guides",
                          _("Show _guides"),
                          GTK_BOX (checks_vbox));
  prefs_check_button_add (object, "show-grid",
                          _("Show gri_d"),
                          GTK_BOX (checks_vbox));
  prefs_check_button_add (object, "show-sample-points",
                          _("Show _sample points"),
                          GTK_BOX (checks_vbox));

  checks_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
  gtk_box_pack_start (GTK_BOX (hbox), checks_vbox, TRUE, TRUE, 0);
  gtk_widget_show (checks_vbox);

#ifndef GDK_WINDOWING_QUARTZ
  prefs_check_button_add (object, "show-menubar",
                          _("Show _menubar"),
                          GTK_BOX (checks_vbox));
#endif /* !GDK_WINDOWING_QUARTZ */
  prefs_check_button_add (object, "show-rulers",
                          _("Show _rulers"),
                          GTK_BOX (checks_vbox));
  prefs_check_button_add (object, "show-scrollbars",
                          _("Show scroll_bars"),
                          GTK_BOX (checks_vbox));
  prefs_check_button_add (object, "show-statusbar",
                          _("Show s_tatusbar"),
                          GTK_BOX (checks_vbox));

  grid = prefs_grid_new (GTK_CONTAINER (vbox));

  combo = prefs_enum_combo_box_add (object, "padding-mode", 0, 0,
                                    _("Canvas _padding mode:"),
                                    GTK_GRID (grid), 0,
                                    NULL);

  button = prefs_color_button_add (object, "padding-color",
                                   _("Custom p_adding color:"),
                                   _("Select Custom Canvas Padding Color"),
                                   GTK_GRID (grid), 1, NULL,
                                   pika_get_user_context (pika));

  g_signal_connect (button, "color-changed",
                    G_CALLBACK (prefs_canvas_padding_color_changed),
                    combo);

  prefs_check_button_add (object, "padding-in-show-all",
                          _("_Keep canvas padding in \"Show All\" mode"),
                          GTK_BOX (vbox));
}

static void
prefs_behavior_options_frame_add (Pika         *pika,
                                  GObject      *object,
                                  const gchar  *label,
                                  GtkContainer *parent)
{
  GtkWidget *vbox;
  GtkWidget *hbox;
  GtkWidget *checks_vbox;

  vbox = prefs_frame_new (label, parent, FALSE);

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

  checks_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
  gtk_box_pack_start (GTK_BOX (hbox), checks_vbox, TRUE, TRUE, 0);
  gtk_widget_show (checks_vbox);

  prefs_check_button_add (object, "snap-to-guides",
                          _("Snap to _Guides"),
                          GTK_BOX (checks_vbox));
  prefs_check_button_add (object, "snap-to-grid",
                          _("S_nap to Grid"),
                          GTK_BOX (checks_vbox));
  prefs_check_button_add (object, "snap-to-canvas",
                          _("Snap to Canvas _Edges"),
                          GTK_BOX (checks_vbox));

  checks_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
  gtk_box_pack_start (GTK_BOX (hbox), checks_vbox, TRUE, TRUE, 0);
  gtk_widget_show (checks_vbox);

  prefs_check_button_add (object, "snap-to-path",
                          _("Snap to _Active Path"),
                          GTK_BOX (checks_vbox));
  prefs_check_button_add (object, "snap-to-bbox",
                          _("Snap to _Bounding Box"),
                          GTK_BOX (checks_vbox));
  prefs_check_button_add (object, "snap-to-equidistance",
                          _("Snap to _Equidistance"),
                          GTK_BOX (checks_vbox));
}

static void
prefs_help_func (const gchar *help_id,
                 gpointer     help_data)
{
  GtkWidget *prefs_box;

  prefs_box = g_object_get_data (G_OBJECT (help_data), "prefs-box");

  help_id = pika_prefs_box_get_current_help_id (PIKA_PREFS_BOX (prefs_box));

  pika_standard_help_func (help_id, NULL);
}

static void
prefs_message (GtkMessageType  type,
               gboolean        destroy_with_parent,
               const gchar    *message)
{
  GtkWidget *dialog;

  dialog = gtk_message_dialog_new (GTK_WINDOW (prefs_dialog),
                                   destroy_with_parent ?
                                   GTK_DIALOG_DESTROY_WITH_PARENT : 0,
                                   type, GTK_BUTTONS_OK,
                                   "%s", message);

  gtk_dialog_run (GTK_DIALOG (dialog));

  gtk_widget_destroy (dialog);
}

static GtkWidget *
prefs_dialog_new (Pika       *pika,
                  PikaConfig *config)
{
  GtkWidget         *dialog;
  GtkTreeIter        top_iter;
  GtkTreeIter        child_iter;

  GtkWidget         *prefs_box;
  GtkSizeGroup      *size_group = NULL;
  GtkWidget         *vbox;
  GtkWidget         *hbox;
  GtkWidget         *vbox2;
  GtkWidget         *vbox3;
  GtkWidget         *button;
  GtkWidget         *button2;
  GtkWidget         *grid;
  GtkWidget         *label;
  GtkWidget         *entry;
  GtkWidget         *calibrate_button;
  GSList            *group;
  GtkWidget         *separator;
  GtkWidget         *editor;
  gint               i;

  GObject           *object;
  PikaCoreConfig    *core_config;
  PikaDisplayConfig *display_config;
  GList             *manuals;

  g_return_val_if_fail (PIKA_IS_PIKA (pika), NULL);
  g_return_val_if_fail (PIKA_IS_CONFIG (config), NULL);

  object         = G_OBJECT (config);
  core_config    = PIKA_CORE_CONFIG (config);
  display_config = PIKA_DISPLAY_CONFIG (config);

  dialog = pika_dialog_new (_("Preferences"), "pika-preferences",
                            NULL, 0,
                            prefs_help_func,
                            PIKA_HELP_PREFS_DIALOG,

                            _("_Reset"),  RESPONSE_RESET,
                            _("_Cancel"), GTK_RESPONSE_CANCEL,
                            _("_OK"),     GTK_RESPONSE_OK,

                            NULL);

  pika_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
                                           RESPONSE_RESET,
                                           GTK_RESPONSE_OK,
                                           GTK_RESPONSE_CANCEL,
                                           -1);

  g_signal_connect (dialog, "response",
                    G_CALLBACK (prefs_response),
                    dialog);

  /* The prefs box */
  prefs_box = pika_prefs_box_new ();
  gtk_container_set_border_width (GTK_CONTAINER (prefs_box), 12);
  gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
                      prefs_box, TRUE, TRUE, 0);
  gtk_widget_show (prefs_box);

  g_object_set_data (G_OBJECT (dialog), "prefs-box", prefs_box);


  /**********************/
  /*  System Resources  */
  /**********************/
  vbox = pika_prefs_box_add_page (PIKA_PREFS_BOX (prefs_box),
                                  "pika-prefs-system-resources",
                                  _("System Resources"),
                                  _("System Resources"),
                                  PIKA_HELP_PREFS_SYSTEM_RESOURCES,
                                  NULL,
                                  &top_iter);
  pika_prefs_box_set_page_scrollable (PIKA_PREFS_BOX (prefs_box), vbox, TRUE);

  size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);

  vbox2 = prefs_frame_new (_("Resource Consumption"),
                           GTK_CONTAINER (vbox), FALSE);

  grid = prefs_grid_new (GTK_CONTAINER (vbox2));

  prefs_spin_button_add (object, "undo-levels", 1.0, 5.0, 0,
                         _("Minimal number of _undo levels:"),
                         GTK_GRID (grid), 0, size_group);
  prefs_memsize_entry_add (object, "undo-size",
                           _("Maximum undo _memory:"),
                           GTK_GRID (grid), 1, size_group);
  prefs_memsize_entry_add (object, "tile-cache-size",
                           _("Tile cache _size:"),
                           GTK_GRID (grid), 2, size_group);
  prefs_memsize_entry_add (object, "max-new-image-size",
                           _("Maximum _new image size:"),
                           GTK_GRID (grid), 3, size_group);

  prefs_compression_combo_box_add (object, "swap-compression",
                                   _("S_wap compression:"),
                                   GTK_GRID (grid), 4, size_group);

#ifdef ENABLE_MP
  prefs_spin_button_add (object, "num-processors", 1.0, 4.0, 0,
                         _("Number of _threads to use:"),
                         GTK_GRID (grid), 5, size_group);
#endif /* ENABLE_MP */

  /*  Internet access  */
#ifdef CHECK_UPDATE
  if (pika_version_check_update ())
    {
      vbox2 = prefs_frame_new (_("Network access"), GTK_CONTAINER (vbox),
                               FALSE);

      prefs_switch_add (object, "check-updates",
                        _("Check for updates (requires internet)"),
                        GTK_BOX (vbox2),
                        size_group);
    }
#endif

  /*  Image Thumbnails  */
  vbox2 = prefs_frame_new (_("Image Thumbnails"), GTK_CONTAINER (vbox), FALSE);

  grid = prefs_grid_new (GTK_CONTAINER (vbox2));

  prefs_enum_combo_box_add (object, "thumbnail-size", 0, 0,
                            _("Size of _thumbnails:"),
                            GTK_GRID (grid), 0, size_group);

  prefs_memsize_entry_add (object, "thumbnail-filesize-limit",
                           _("Maximum _filesize for thumbnailing:"),
                           GTK_GRID (grid), 1, size_group);

  /*  Document History  */
  vbox2 = prefs_frame_new (_("Document History"), GTK_CONTAINER (vbox), FALSE);

  prefs_switch_add (object, "save-document-history",
                    _("_Keep record of used files in the Recent Documents list"),
                    GTK_BOX (vbox2),
                    size_group);

  g_clear_object (&size_group);


  /***************/
  /*  Debugging  */
  /***************/
  /* No debugging preferences are needed on win32. Either PIKA has been
   * built with DrMinGW support (HAVE_EXCHNDL) or not. If it has, then
   * the backtracing is enabled and can't be disabled. It assume it will
   * work only upon a crash.
   */
#ifndef G_OS_WIN32
  vbox = pika_prefs_box_add_page (PIKA_PREFS_BOX (prefs_box),
                                  "pika-mascot-eek", /* TODO: icon needed. */
                                  _("Debugging"),
                                  _("Debugging"),
                                  PIKA_HELP_PREFS_DEBUGGING,
                                  NULL,
                                  &top_iter);

  hbox = g_object_new (PIKA_TYPE_HINT_BOX,
                       "icon-name", PIKA_ICON_DIALOG_WARNING,
                       "hint",      _("We hope you will never need these "
                                      "settings, but as all software, PIKA "
                                      "has bugs, and crashes can occur. If it "
                                      "happens, you can help us by reporting "
                                      "bugs."),
                        NULL);
  gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
  gtk_widget_show (hbox);

  vbox2 = prefs_frame_new (_("Bug Reporting"),
                           GTK_CONTAINER (vbox), FALSE);

  grid = prefs_grid_new (GTK_CONTAINER (vbox2));

  button = prefs_enum_combo_box_add (object, "debug-policy", 0, 0,
                                     _("Debug _policy:"),
                                     GTK_GRID (grid), 0, NULL);

  /* Check existence of gdb or lldb to activate the preference, as a
   * good hint of its prerequisite, unless backtrace() API exists, in
   * which case the feature is always available.
   */
  hbox = NULL;
  if (! pika_stack_trace_available (TRUE))
    {
#ifndef HAVE_EXECINFO_H
      hbox = prefs_hint_box_new (PIKA_ICON_DIALOG_WARNING,
                                 _("This feature requires \"gdb\" or \"lldb\" installed on your system."));
      gtk_widget_set_sensitive (button, FALSE);
#else
      hbox = prefs_hint_box_new (PIKA_ICON_DIALOG_WARNING,
                                 _("This feature is more efficient with \"gdb\" or \"lldb\" installed on your system."));
#endif /* ! HAVE_EXECINFO_H */
    }
  if (hbox)
    gtk_box_pack_start (GTK_BOX (vbox2), hbox, FALSE, FALSE, 0);

#endif /* ! G_OS_WIN32 */

  /**********************/
  /*  Color Management  */
  /**********************/
  vbox = pika_prefs_box_add_page (PIKA_PREFS_BOX (prefs_box),
                                  "pika-prefs-color-management",
                                  _("Color Management"),
                                  _("Color Management"),
                                  PIKA_HELP_PREFS_COLOR_MANAGEMENT,
                                  NULL,
                                  &top_iter);

  pika_prefs_box_set_page_scrollable (PIKA_PREFS_BOX (prefs_box), vbox, TRUE);

  button = pika_prefs_box_set_page_resettable (PIKA_PREFS_BOX (prefs_box),
                                               vbox,
                                               _("R_eset Color Management"));
  g_signal_connect (button, "clicked",
                    G_CALLBACK (prefs_color_management_reset),
                    config);

  {
    GObject      *color_config = G_OBJECT (core_config->color_management);
    GtkListStore *store;
    GFile        *file;
    gint          row = 0;

    file = pika_directory_file ("profilerc", NULL);
    store = pika_color_profile_store_new (file);
    g_object_unref (file);

    pika_color_profile_store_add_file (PIKA_COLOR_PROFILE_STORE (store),
                                       NULL, NULL);

    size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);

    grid = prefs_grid_new (GTK_CONTAINER (vbox));

    prefs_enum_combo_box_add (color_config, "mode", 0, 0,
                              _("Image display _mode:"),
                              GTK_GRID (grid), row++, NULL);

    /*  Color Managed Display  */
    vbox2 = prefs_frame_new (_("Color Managed Display"), GTK_CONTAINER (vbox),
                             FALSE);

    grid = prefs_grid_new (GTK_CONTAINER (vbox2));
    row = 0;

    prefs_profile_combo_box_add (color_config,
                                 "display-profile",
                                 store,
                                 _("Select Monitor Color Profile"),
                                 _("_Monitor profile:"),
                                 GTK_GRID (grid), row++, size_group,
                                 object, "color-profile-path");

    button = pika_prop_check_button_new (color_config,
                                         "display-profile-from-gdk",
                                         _("_Try to use the system monitor "
                                           "profile"));
    gtk_grid_attach (GTK_GRID (grid), button, 1, row, 1, 1);
    row++;

    prefs_enum_combo_box_add (color_config,
                              "display-rendering-intent", 0, 0,
                              _("_Rendering intent:"),
                              GTK_GRID (grid), row++, size_group);

    button = pika_prop_check_button_new (color_config,
                                         "display-use-black-point-compensation",
                                         _("Use _black point compensation"));
    gtk_grid_attach (GTK_GRID (grid), button, 1, row, 1, 1);
    row++;

    prefs_boolean_combo_box_add (color_config,
                                 "display-optimize",
                                 _("Speed"),
                                 _("Precision / Color Fidelity"),
                                 _("_Optimize image display for:"),
                                 GTK_GRID (grid), row++, size_group);

    /*  Print Simulation (Soft-proofing)  */
    vbox2 = prefs_frame_new (_("Soft-Proofing"),
                             GTK_CONTAINER (vbox),
                             FALSE);

    grid = prefs_grid_new (GTK_CONTAINER (vbox2));
    row = 0;

    prefs_boolean_combo_box_add (color_config,
                                 "simulation-optimize",
                                 _("Speed"),
                                 _("Precision / Color Fidelity"),
                                 _("O_ptimize soft-proofing for:"),
                                 GTK_GRID (grid), row++, size_group);

    hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
    gtk_grid_attach (GTK_GRID (grid), hbox, 1, row, 1, 1);
    gtk_widget_show (hbox);
    row++;

    button = pika_prop_check_button_new (color_config, "simulation-gamut-check",
                                         _("Mar_k out of gamut colors"));
    gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0);

    button = pika_prop_color_button_new (color_config, "out-of-gamut-color",
                                         _("Select Warning Color"),
                                         PREFS_COLOR_BUTTON_WIDTH,
                                         PREFS_COLOR_BUTTON_HEIGHT,
                                         PIKA_COLOR_AREA_FLAT);
    gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);

    pika_color_panel_set_context (PIKA_COLOR_PANEL (button),
                                  pika_get_user_context (pika));

    /*  Preferred profiles  */
    vbox2 = prefs_frame_new (_("Preferred Profiles"), GTK_CONTAINER (vbox),
                             FALSE);

    grid = prefs_grid_new (GTK_CONTAINER (vbox2));
    row = 0;

    prefs_profile_combo_box_add (color_config,
                                 "rgb-profile",
                                 store,
                                 _("Select Preferred RGB Color Profile"),
                                 _("_RGB profile:"),
                                 GTK_GRID (grid), row++, size_group,
                                 object, "color-profile-path");

    prefs_profile_combo_box_add (color_config,
                                 "gray-profile",
                                 store,
                                 _("Select Preferred Grayscale Color Profile"),
                                 _("_Grayscale profile:"),
                                 GTK_GRID (grid), row++, size_group,
                                 object, "color-profile-path");

    prefs_profile_combo_box_add (color_config,
                                 "cmyk-profile",
                                 store,
                                 _("Select CMYK Color Profile"),
                                 _("_CMYK profile:"),
                                 GTK_GRID (grid), row++, size_group,
                                 object, "color-profile-path");

    /*  Policies  */
    vbox2 = prefs_frame_new (_("Policies"), GTK_CONTAINER (vbox),
                             FALSE);
    grid = prefs_grid_new (GTK_CONTAINER (vbox2));

    button = prefs_enum_combo_box_add (object, "color-profile-policy", 0, 0,
                                       _("_File Open behavior:"),
                                       GTK_GRID (grid), 0, size_group);

    /*  Filter Dialogs  */
    vbox2 = prefs_frame_new (_("Filter Dialogs"), GTK_CONTAINER (vbox),
                             FALSE);

    button = prefs_check_button_add (object, "filter-tool-show-color-options",
                                     _("Show _advanced color options"),
                                     GTK_BOX (vbox2));

    g_clear_object (&size_group);

    g_object_unref (store);
  }


  /***************************/
  /*  Image Import / Export  */
  /***************************/
  vbox = pika_prefs_box_add_page (PIKA_PREFS_BOX (prefs_box),
                                  "pika-prefs-import-export",
                                  _("Image Import & Export"),
                                  _("Image Import & Export"),
                                  PIKA_HELP_PREFS_IMPORT_EXPORT,
                                  NULL,
                                  &top_iter);

  pika_prefs_box_set_page_scrollable (PIKA_PREFS_BOX (prefs_box), vbox, TRUE);

  size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);

  /*  Import Policies  */
  vbox2 = prefs_frame_new (_("Import Policies"),
                           GTK_CONTAINER (vbox), FALSE);

  button = prefs_check_button_add (object, "import-promote-float",
                                   _("Promote imported images to "
                                     "_floating point precision"),
                                   GTK_BOX (vbox2));

  vbox3 = prefs_frame_new (NULL, GTK_CONTAINER (vbox2), FALSE);
  g_object_bind_property (button, "active",
                          vbox3,  "sensitive",
                          G_BINDING_SYNC_CREATE);
  button = prefs_check_button_add (object, "import-promote-dither",
                                   _("_Dither images when promoting to "
                                     "floating point"),
                                   GTK_BOX (vbox3));

  button = prefs_check_button_add (object, "import-add-alpha",
                                   _("_Add an alpha channel to imported images"),
                                   GTK_BOX (vbox2));

  grid = prefs_grid_new (GTK_CONTAINER (vbox2));
  button = prefs_enum_combo_box_add (object, "color-profile-policy", 0, 0,
                                     _("Color _profile policy:"),
                                     GTK_GRID (grid), 0, size_group);
  button = prefs_enum_combo_box_add (object, "metadata-rotation-policy", 0, 0,
                                     _("Metadata _rotation policy:"),
                                     GTK_GRID (grid), 1, size_group);

  /*  Export Policies  */
  vbox2 = prefs_frame_new (_("Export Policies"),
                           GTK_CONTAINER (vbox), FALSE);

  button = prefs_check_button_add (object, "export-color-profile",
                                   _("Export the i_mage's color profile by default"),
                                   GTK_BOX (vbox2));
  button = prefs_check_button_add (object, "export-comment",
                                   _("Export the image's comment by default"),
                                   GTK_BOX (vbox2));
  button = prefs_check_button_add (object, "export-thumbnail",
                                   _("Export the image's thumbnail by default"),
                                   GTK_BOX (vbox2));
  button = prefs_check_button_add (object, "export-metadata-exif",
                                   /* Translators: label for
                                    * configuration option (checkbox).
                                    * It determines how file export
                                    * plug-ins handle Exif by default.
                                    */
                                   _("Export _Exif metadata by default when available"),
                                   GTK_BOX (vbox2));
  button = prefs_check_button_add (object, "export-metadata-xmp",
                                   /* Translators: label for
                                    * configuration option (checkbox).
                                    * It determines how file export
                                    * plug-ins handle XMP by default.
                                    */
                                   _("Export _XMP metadata by default when available"),
                                   GTK_BOX (vbox2));
  button = prefs_check_button_add (object, "export-metadata-iptc",
                                   /* Translators: label for
                                    * configuration option (checkbox).
                                    * It determines how file export
                                    * plug-ins handle IPTC by default.
                                    */
                                   _("Export _IPTC metadata by default when available"),
                                   GTK_BOX (vbox2));
  hbox = prefs_hint_box_new (PIKA_ICON_DIALOG_WARNING,
                             _("Metadata can contain sensitive information."));
  gtk_box_pack_start (GTK_BOX (vbox2), hbox, FALSE, FALSE, 0);

  /*  Export File Type  */
  vbox2 = prefs_frame_new (_("Export File Type"), GTK_CONTAINER (vbox), FALSE);
  grid = prefs_grid_new (GTK_CONTAINER (vbox2));

  prefs_enum_combo_box_add (object, "export-file-type", 0, 0,
                            _("Default export file t_ype:"),
                            GTK_GRID (grid), 0, size_group);

  /*  Raw Image Importer  */
  vbox2 = prefs_frame_new (_("Raw Image Importer"),
                           GTK_CONTAINER (vbox), TRUE);

  {
    GtkWidget *scrolled_window;
    GtkWidget *view;

    scrolled_window = gtk_scrolled_window_new (NULL, NULL);
    gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
                                    GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
    gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window),
                                         GTK_SHADOW_IN);
    gtk_box_pack_start (GTK_BOX (vbox2), scrolled_window, TRUE, TRUE, 0);
    gtk_widget_show (scrolled_window);

    view = pika_plug_in_view_new (pika->plug_in_manager->display_raw_load_procs);
    pika_plug_in_view_set_plug_in (PIKA_PLUG_IN_VIEW (view),
                                   core_config->import_raw_plug_in);
    gtk_container_add (GTK_CONTAINER (scrolled_window), view);
    gtk_widget_show (view);

    g_signal_connect (view, "changed",
                      G_CALLBACK (prefs_import_raw_procedure_callback),
                      config);
  }

  g_clear_object (&size_group);


  /****************/
  /*  Playground  */
  /****************/
  if (pika->show_playground)
    {
      vbox = pika_prefs_box_add_page (PIKA_PREFS_BOX (prefs_box),
                                      "pika-prefs-playground",
                                      _("Experimental Playground"),
                                      _("Playground"),
                                      PIKA_HELP_PREFS_PLAYGROUND,
                                      NULL,
                                      &top_iter);

      hbox = g_object_new (PIKA_TYPE_HINT_BOX,
                           "icon-name", PIKA_ICON_DIALOG_WARNING,
                           "hint",      _("These features are unfinished, buggy "
                                          "and may crash PIKA. It is unadvised to "
                                          "use them unless you really know what "
                                          "you are doing or you intend to contribute "
                                          "patches."),
                           NULL);
      gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
      gtk_widget_show (hbox);

      /*  Hardware Acceleration  */
      vbox2 = prefs_frame_new (_("Hardware Acceleration"), GTK_CONTAINER (vbox),
                               FALSE);

      hbox = prefs_hint_box_new (PIKA_ICON_DIALOG_WARNING,
                                 _("OpenCL drivers and support are experimental, "
                                   "expect slowdowns and possible crashes "
                                   "(please report)."));
      gtk_box_pack_start (GTK_BOX (vbox2), hbox, FALSE, FALSE, 0);

      prefs_switch_add (object, "use-opencl",
                        _("Use O_penCL"),
                        GTK_BOX (vbox2),
                        NULL);

      /*  Very unstable tools  */
      vbox2 = prefs_frame_new (_("Insane Options"),
                               GTK_CONTAINER (vbox), FALSE);

      button = prefs_check_button_add (object, "playground-npd-tool",
                                       _("_N-Point Deformation tool"),
                                       GTK_BOX (vbox2));
      button = prefs_check_button_add (object, "playground-seamless-clone-tool",
                                       _("_Seamless Clone tool"),
                                       GTK_BOX (vbox2));
      button = prefs_check_button_add (object, "playground-paint-select-tool",
                                       _("_Paint Select tool"),
                                       GTK_BOX (vbox2));
      if (! gegl_has_operation ("gegl:paint-select"))
        {
          gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), FALSE);
          gtk_widget_set_sensitive (button, FALSE);
          /* The tooltip is not translated on purpose. By the time it
           * hits stable release, I sure hope this won't be considered
           * an optional operation anymore. The info is still useful for
           * dev-release testers, but no need to bother translators with
           * a temporary string otherwise.
           */
          pika_help_set_help_data (button, "Missing GEGL operation 'gegl:paint-select'.", NULL);
        }
    }


  /******************/
  /*  Tool Options  */
  /******************/
  vbox = pika_prefs_box_add_page (PIKA_PREFS_BOX (prefs_box),
                                  "pika-prefs-tool-options",
                                  C_("preferences", "Tool Options"),
                                  C_("preferences", "Tool Options"),
                                  PIKA_HELP_PREFS_TOOL_OPTIONS,
                                  NULL,
                                  &top_iter);
  pika_prefs_box_set_page_scrollable (PIKA_PREFS_BOX (prefs_box), vbox, TRUE);

  size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);

  /*  General  */
  vbox2 = prefs_frame_new (_("General"), GTK_CONTAINER (vbox), FALSE);

  prefs_check_button_add (object, "edit-non-visible",
                          _("Allow _editing on non-visible layers"),
                          GTK_BOX (vbox2));

  prefs_check_button_add (object, "save-tool-options",
                          _("_Save tool options on exit"),
                          GTK_BOX (vbox2));

  button = prefs_button_add (PIKA_ICON_DOCUMENT_SAVE,
                             _("Save Tool Options _Now"),
                             GTK_BOX (vbox2));
  g_signal_connect (button, "clicked",
                    G_CALLBACK (prefs_tool_options_save_callback),
                    pika);

  button2 = prefs_button_add (PIKA_ICON_RESET,
                              _("_Reset Saved Tool Options to "
                                "Default Values"),
                              GTK_BOX (vbox2));
  g_signal_connect (button2, "clicked",
                    G_CALLBACK (prefs_tool_options_clear_callback),
                    pika);

  g_object_set_data (G_OBJECT (button), "clear-button", button2);

  /*  Scaling  */
  vbox2 = prefs_frame_new (_("Scaling"), GTK_CONTAINER (vbox), FALSE);
  grid = prefs_grid_new (GTK_CONTAINER (vbox2));

  prefs_enum_combo_box_add (object, "interpolation-type", 0, 0,
                            _("Default _interpolation:"),
                            GTK_GRID (grid), 0, size_group);

  g_object_unref (size_group);

  size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);

  /*  Global Brush, Pattern, ...  */
  vbox2 = prefs_frame_new (_("Paint Options Shared Between Tools"),
                           GTK_CONTAINER (vbox), FALSE);

  prefs_check_button_add_with_icon (object, "global-brush",
                                    _("_Brush"),    PIKA_ICON_BRUSH,
                                    GTK_BOX (vbox2), size_group);
  prefs_check_button_add_with_icon (object, "global-dynamics",
                                    _("_Dynamics"), PIKA_ICON_DYNAMICS,
                                    GTK_BOX (vbox2), size_group);
  prefs_check_button_add_with_icon (object, "global-pattern",
                                    _("_Pattern"),  PIKA_ICON_PATTERN,
                                    GTK_BOX (vbox2), size_group);
  prefs_check_button_add_with_icon (object, "global-gradient",
                                    _("_Gradient"), PIKA_ICON_GRADIENT,
                                    GTK_BOX (vbox2), size_group);

  /*  Move Tool */
  vbox2 = prefs_frame_new (_("Move Tool"),
                           GTK_CONTAINER (vbox), FALSE);

  prefs_check_button_add_with_icon (object, "move-tool-changes-active",
                                    _("Set _layer or path as active"),
                                    PIKA_ICON_TOOL_MOVE,
                                    GTK_BOX (vbox2), size_group);

  g_clear_object (&size_group);


  /*******************/
  /*  Default Image  */
  /*******************/
  vbox = pika_prefs_box_add_page (PIKA_PREFS_BOX (prefs_box),
                                  "pika-prefs-new-image",
                                  _("Default New Image"),
                                  _("Default Image"),
                                  PIKA_HELP_PREFS_NEW_IMAGE,
                                  NULL,
                                  &top_iter);

  pika_prefs_box_set_page_scrollable (PIKA_PREFS_BOX (prefs_box), vbox, TRUE);

  grid = prefs_grid_new (GTK_CONTAINER (vbox));

  {
    GtkWidget *combo;

    combo = pika_container_combo_box_new (pika->templates,
                                          pika_get_user_context (pika),
                                          16, 0);
    pika_grid_attach_aligned (GTK_GRID (grid), 0, 0,
                               _("_Template:"),  0.0, 0.5,
                               combo, 1);

    pika_container_view_select_items (PIKA_CONTAINER_VIEW (combo), NULL);

    g_signal_connect (combo, "select-items",
                      G_CALLBACK (prefs_template_select_callback),
                      core_config->default_image);
  }

  editor = pika_template_editor_new (core_config->default_image, pika, FALSE);
  gtk_widget_set_vexpand (editor, FALSE);
  pika_template_editor_show_advanced (PIKA_TEMPLATE_EDITOR (editor), TRUE);
  gtk_box_pack_start (GTK_BOX (vbox), editor, FALSE, FALSE, 0);
  gtk_widget_show (editor);

  /*  Quick Mask Color */
  vbox2 = prefs_frame_new (_("Quick Mask"), GTK_CONTAINER (vbox), FALSE);
  grid = prefs_grid_new (GTK_CONTAINER (vbox2));

  prefs_color_button_add (object, "quick-mask-color",
                          _("Quick Mask color:"),
                          _("Set the default Quick Mask color"),
                          GTK_GRID (grid), 0, NULL,
                          pika_get_user_context (pika));


  /**********************************/
  /*  Default Image / Default Grid  */
  /**********************************/
  vbox = pika_prefs_box_add_page (PIKA_PREFS_BOX (prefs_box),
                                  "pika-prefs-default-grid",
                                  _("Default Image Grid"),
                                  _("Default Grid"),
                                  PIKA_HELP_PREFS_DEFAULT_GRID,
                                  &top_iter,
                                  &child_iter);
  pika_prefs_box_set_page_scrollable (PIKA_PREFS_BOX (prefs_box), vbox, TRUE);

  /*  Grid  */
  editor = pika_grid_editor_new (core_config->default_grid,
                                 pika_get_user_context (pika),
                                 pika_template_get_resolution_x (core_config->default_image),
                                 pika_template_get_resolution_y (core_config->default_image));
  gtk_box_pack_start (GTK_BOX (vbox), editor, TRUE, TRUE, 0);
  gtk_widget_show (editor);


  /***************/
  /*  Interface  */
  /***************/
  vbox = pika_prefs_box_add_page (PIKA_PREFS_BOX (prefs_box),
                                  "pika-prefs-interface",
                                  _("User Interface"),
                                  _("Interface"),
                                  PIKA_HELP_PREFS_INTERFACE,
                                  NULL,
                                  &top_iter);
  pika_prefs_box_set_page_scrollable (PIKA_PREFS_BOX (prefs_box), vbox, TRUE);

  /*  Language  */

  /*  Only add the language entry if the iso-codes package is available.  */
#ifdef HAVE_ISO_CODES
  vbox2 = prefs_frame_new (_("Language"), GTK_CONTAINER (vbox), FALSE);

  prefs_language_combo_box_add (object, "language", GTK_BOX (vbox2));
#endif

  /*  Previews  */
  vbox2 = prefs_frame_new (_("Previews"), GTK_CONTAINER (vbox), FALSE);

  button = prefs_check_button_add (object, "layer-previews",
                                   _("_Enable layer & channel previews"),
                                   GTK_BOX (vbox2));

  vbox3 = prefs_frame_new (NULL, GTK_CONTAINER (vbox2), FALSE);
  g_object_bind_property (button, "active",
                          vbox3,  "sensitive",
                          G_BINDING_SYNC_CREATE);
  button = prefs_check_button_add (object, "group-layer-previews",
                                   _("Enable layer _group previews"),
                                   GTK_BOX (vbox3));

  grid = prefs_grid_new (GTK_CONTAINER (vbox2));

  prefs_enum_combo_box_add (object, "layer-preview-size", 0, 0,
                            _("_Default layer & channel preview size:"),
                            GTK_GRID (grid), 0, NULL);
  prefs_enum_combo_box_add (object, "undo-preview-size", 0, 0,
                            _("_Undo preview size:"),
                            GTK_GRID (grid), 1, NULL);
  prefs_enum_combo_box_add (object, "navigation-preview-size", 0, 0,
                            _("Na_vigation preview size:"),
                            GTK_GRID (grid), 2, NULL);

  /*  Item   */
  vbox2 = prefs_frame_new (_("Item search"), GTK_CONTAINER (vbox), FALSE);
  grid = prefs_grid_new (GTK_CONTAINER (vbox2));
  prefs_enum_combo_box_add (object, "items-select-method", 0, 0,
                            _("Pattern syntax for searching and selecting items:"),
                            GTK_GRID (grid), 0, NULL);

  /* Keyboard Shortcuts */
  vbox2 = prefs_frame_new (_("Keyboard Shortcuts"),
                           GTK_CONTAINER (vbox), FALSE);

  button = prefs_button_add (PIKA_ICON_PREFERENCES_SYSTEM,
                             _("Configure _Keyboard Shortcuts..."),
                             GTK_BOX (vbox2));
  g_signal_connect (button, "clicked",
                    G_CALLBACK (prefs_keyboard_shortcuts_dialog),
                    pika);

  prefs_check_button_add (object, "save-accels",
                          _("_Save keyboard shortcuts on exit"),
                          GTK_BOX (vbox2));

  button = prefs_button_add (PIKA_ICON_DOCUMENT_SAVE,
                             _("Save Keyboard Shortcuts _Now"),
                             GTK_BOX (vbox2));
  g_signal_connect (button, "clicked",
                    G_CALLBACK (prefs_menus_save_callback),
                    pika);

  button2 = prefs_button_add (PIKA_ICON_RESET,
                              _("_Reset Keyboard Shortcuts to Default Values"),
                              GTK_BOX (vbox2));
  g_signal_connect (button2, "clicked",
                    G_CALLBACK (prefs_menus_clear_callback),
                    pika);

  g_object_set_data (G_OBJECT (button), "clear-button", button2);

  button = prefs_button_add (PIKA_ICON_EDIT_CLEAR,
                             _("Remove _All Keyboard Shortcuts"),
                             GTK_BOX (vbox2));
  g_signal_connect (button, "clicked",
                    G_CALLBACK (prefs_menus_remove_callback),
                    pika);


  /***********************/
  /*  Interface / Theme  */
  /***********************/
  vbox = pika_prefs_box_add_page (PIKA_PREFS_BOX (prefs_box),
                                  "pika-prefs-theme",
                                  _("Theme"),
                                  _("Theme"),
                                  PIKA_HELP_PREFS_THEME,
                                  &top_iter,
                                  &child_iter);

  vbox2 = prefs_frame_new (_("Select Theme"), GTK_CONTAINER (vbox), TRUE);

  {
    GtkWidget         *scrolled_win;
    GtkWidget         *listbox;
    GtkWidget         *scale;
    gchar            **themes;
    gint               n_themes;
    gint               i;

    scrolled_win = gtk_scrolled_window_new (NULL, NULL);
    gtk_widget_set_size_request (scrolled_win, -1, 80);
    gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_win),
                                         GTK_SHADOW_IN);
    gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_win),
                                    GTK_POLICY_AUTOMATIC,
                                    GTK_POLICY_AUTOMATIC);
    gtk_box_pack_start (GTK_BOX (vbox2), scrolled_win, TRUE, TRUE, 0);
    gtk_widget_show (scrolled_win);

    listbox = gtk_list_box_new ();
    gtk_list_box_set_selection_mode (GTK_LIST_BOX (listbox),
                                     GTK_SELECTION_BROWSE);
    gtk_container_add (GTK_CONTAINER (scrolled_win), listbox);
    gtk_widget_show (listbox);

    themes = themes_list_themes (pika, &n_themes);

    for (i = 0; i < n_themes; i++)
      {
        GtkWidget *row;
        GtkWidget *grid;
        GtkWidget *name_label, *folder_label;
        GFile     *theme_dir = themes_get_theme_dir (pika, themes[i]);

        row = gtk_list_box_row_new ();
        g_object_set_data_full (G_OBJECT (row),
                                "theme",
                                g_strdup (themes[i]),
                                g_free);

        grid = gtk_grid_new ();
        gtk_grid_set_column_spacing (GTK_GRID (grid), 12);
        gtk_container_add (GTK_CONTAINER (row), grid);

        name_label = gtk_label_new (themes[i]);
        g_object_set (name_label, "xalign", 0.0, NULL);
        gtk_grid_attach (GTK_GRID (grid), name_label, 1, 0, 1, 1);

        folder_label = gtk_label_new (pika_file_get_utf8_name (theme_dir));
        g_object_set (folder_label, "xalign", 0.0, NULL);
        gtk_style_context_add_class (gtk_widget_get_style_context (folder_label),
                                     "dim-label");
        gtk_grid_attach (GTK_GRID (grid), folder_label, 1, 1, 1, 1);

        gtk_widget_show_all (row);
        gtk_list_box_insert (GTK_LIST_BOX (listbox), row, -1);

        if (PIKA_GUI_CONFIG (object)->theme &&
            ! strcmp (PIKA_GUI_CONFIG (object)->theme, themes[i]))
          {
            gtk_list_box_select_row (GTK_LIST_BOX (listbox),
                                     GTK_LIST_BOX_ROW (row));
          }
      }

    g_strfreev (themes);

    g_signal_connect (listbox, "row-selected",
                      G_CALLBACK (prefs_theme_select_callback),
                      pika);

    prefs_check_button_add (object, "prefer-dark-theme",
                            _("Use dark theme variant if available"),
                            GTK_BOX (vbox2));

    /* Override icon sizes. */
    button = prefs_check_button_add (object, "override-theme-icon-size",
                                     _("_Override icon sizes set by the theme"),
                                     GTK_BOX (vbox2));

    vbox3 = prefs_frame_new (NULL, GTK_CONTAINER (vbox2), FALSE);
    g_object_bind_property (button, "active",
                            vbox3,  "sensitive",
                            G_BINDING_SYNC_CREATE);
    scale = gtk_scale_new_with_range (GTK_ORIENTATION_HORIZONTAL,
                                      0.0, 3.0, 1.0);
    /* 'draw_value' updates round_digits. So set it first. */
    gtk_scale_set_draw_value (GTK_SCALE (scale), FALSE);
    gtk_range_set_round_digits (GTK_RANGE (scale), 0.0);
    gtk_scale_add_mark (GTK_SCALE (scale), 0.0, GTK_POS_BOTTOM,
                        _("Small"));
    gtk_scale_add_mark (GTK_SCALE (scale), 1.0, GTK_POS_BOTTOM,
                        _("Medium"));
    gtk_scale_add_mark (GTK_SCALE (scale), 2.0, GTK_POS_BOTTOM,
                        _("Large"));
    gtk_scale_add_mark (GTK_SCALE (scale), 3.0, GTK_POS_BOTTOM,
                        _("Huge"));
    gtk_range_set_value (GTK_RANGE (scale),
                         (gdouble) PIKA_GUI_CONFIG (object)->custom_icon_size);
    g_signal_connect (G_OBJECT (scale), "value-changed",
                      G_CALLBACK (prefs_icon_size_value_changed),
                      PIKA_GUI_CONFIG (object));
    g_signal_connect (G_OBJECT (object), "notify::custom-icon-size",
                      G_CALLBACK (prefs_gui_config_notify_icon_size),
                      scale);
    gtk_box_pack_start (GTK_BOX (vbox3), scale, FALSE, FALSE, 0);
    gtk_widget_show (scale);

    /* Font sizes. */
    vbox3 = prefs_frame_new (_("Font Scaling"), GTK_CONTAINER (vbox2), FALSE);
    pika_help_set_help_data (vbox3,
                             _("Font scaling will not work with themes using absolute sizes."),
                             NULL);
    scale = gtk_scale_new_with_range (GTK_ORIENTATION_HORIZONTAL,
                                      50, 200, 10);
    gtk_scale_set_value_pos (GTK_SCALE (scale), GTK_POS_BOTTOM);
    gtk_scale_add_mark (GTK_SCALE (scale), 50.0, GTK_POS_BOTTOM,
                        _("50%"));
    gtk_scale_add_mark (GTK_SCALE (scale), 100.0, GTK_POS_BOTTOM,
                        _("100%"));
    gtk_scale_add_mark (GTK_SCALE (scale), 200.0, GTK_POS_BOTTOM,
                        _("200%"));
    gtk_range_set_value (GTK_RANGE (scale),
                         (gdouble) PIKA_GUI_CONFIG (object)->font_relative_size * 100.0);
    g_signal_connect (G_OBJECT (scale), "value-changed",
                      G_CALLBACK (prefs_font_size_value_changed),
                      PIKA_GUI_CONFIG (object));
    g_signal_connect (G_OBJECT (object), "notify::font-relative-size",
                      G_CALLBACK (prefs_gui_config_notify_font_size),
                      scale);
    gtk_box_pack_start (GTK_BOX (vbox3), scale, FALSE, FALSE, 0);
    gtk_widget_show (scale);

    /* Reload Current Theme button */
    hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
    gtk_box_pack_start (GTK_BOX (vbox2), hbox, FALSE, FALSE, 0);
    gtk_widget_show (hbox);

    button = prefs_button_add (PIKA_ICON_VIEW_REFRESH,
                               _("Reload C_urrent Theme"),
                               GTK_BOX (hbox));
    g_signal_connect (button, "clicked",
                      G_CALLBACK (prefs_theme_reload_callback),
                      pika);
  }

  /****************************/
  /*  Interface / Icon Theme  */
  /****************************/
  vbox = pika_prefs_box_add_page (PIKA_PREFS_BOX (prefs_box),
                                  "pika-prefs-icon-theme",
                                  _("Icon Theme"),
                                  _("Icon Theme"),
                                  PIKA_HELP_PREFS_ICON_THEME,
                                  &top_iter,
                                  &child_iter);

  vbox2 = prefs_frame_new (_("Select an Icon Theme"), GTK_CONTAINER (vbox), TRUE);

  {
    GtkWidget         *scrolled_win;
    GtkWidget         *listbox;
    gchar            **icon_themes;
    gint               scale_factor;
    gint               n_icon_themes;
    gint               i;

    scrolled_win = gtk_scrolled_window_new (NULL, NULL);
    gtk_widget_set_size_request (scrolled_win, -1, 80);
    gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_win),
                                         GTK_SHADOW_IN);
    gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_win),
                                    GTK_POLICY_AUTOMATIC,
                                    GTK_POLICY_AUTOMATIC);
    gtk_box_pack_start (GTK_BOX (vbox2), scrolled_win, TRUE, TRUE, 0);
    gtk_widget_show (scrolled_win);

    listbox = gtk_list_box_new ();
    gtk_list_box_set_selection_mode (GTK_LIST_BOX (listbox),
                                     GTK_SELECTION_BROWSE);
    gtk_container_add (GTK_CONTAINER (scrolled_win), listbox);
    gtk_widget_show (listbox);

     /* _("Icon Theme"), */
     /* _("Folder"), */

    scale_factor = gtk_widget_get_scale_factor (scrolled_win);
    icon_themes = icon_themes_list_themes (pika, &n_icon_themes);

    for (i = 0; i < n_icon_themes; i++)
      {
        GtkWidget       *row;
        GtkWidget       *grid;
        GtkWidget       *image;
        GtkWidget       *name_label, *folder_label;
        GFile           *icon_theme_dir = icon_themes_get_theme_dir (pika, icon_themes[i]);
        GFile           *icon_theme_search_path = g_file_get_parent (icon_theme_dir);
        GtkIconTheme    *theme;
        gchar           *example;
        cairo_surface_t *surface;

        theme = gtk_icon_theme_new ();
        gtk_icon_theme_prepend_search_path (theme,
                                            pika_file_get_utf8_name (icon_theme_search_path));
        g_object_unref (icon_theme_search_path);
        gtk_icon_theme_set_custom_theme (theme, icon_themes[i]);

        example = gtk_icon_theme_get_example_icon_name (theme);
        if (! example)
          {
            /* If the icon theme didn't explicitly specify an example
             * icon, try "pika-mascot".
             */
            example = g_strdup ("pika-mascot-symbolic");
          }
        surface = gtk_icon_theme_load_surface (theme, example, 30,
                                               scale_factor, NULL,
                                               GTK_ICON_LOOKUP_GENERIC_FALLBACK,
                                               NULL);

        row = gtk_list_box_row_new ();
        g_object_set_data_full (G_OBJECT (row),
                                "icon-theme",
                                g_strdup (icon_themes[i]),
                                g_free);

        grid = gtk_grid_new ();
        gtk_grid_set_column_spacing (GTK_GRID (grid), 12);
        gtk_container_add (GTK_CONTAINER (row), grid);

        image = gtk_image_new_from_surface (surface);
        gtk_grid_attach (GTK_GRID (grid), image, 0, 0, 1, 2);

        name_label = gtk_label_new (icon_themes[i]);
        g_object_set (name_label, "xalign", 0.0, NULL);
        gtk_grid_attach (GTK_GRID (grid), name_label, 1, 0, 1, 1);

        folder_label = gtk_label_new (pika_file_get_utf8_name (icon_theme_dir));
        g_object_set (folder_label, "xalign", 0.0, NULL);
        gtk_style_context_add_class (gtk_widget_get_style_context (folder_label),
                                     "dim-label");
        gtk_grid_attach (GTK_GRID (grid), folder_label, 1, 1, 1, 1);

        gtk_widget_show_all (row);
        gtk_list_box_insert (GTK_LIST_BOX (listbox), row, -1);

        g_object_unref (theme);
        cairo_surface_destroy (surface);
        g_free (example);

        if (PIKA_GUI_CONFIG (object)->icon_theme &&
            ! strcmp (PIKA_GUI_CONFIG (object)->icon_theme, icon_themes[i]))
          {
            gtk_list_box_select_row (GTK_LIST_BOX (listbox),
                                     GTK_LIST_BOX_ROW (row));

          }
      }

    g_strfreev (icon_themes);

    g_signal_connect (listbox, "row-selected",
                      G_CALLBACK (prefs_icon_theme_select_callback),
                      pika);

    prefs_check_button_add (object, "prefer-symbolic-icons",
                            _("Use symbolic icons if available"),
                            GTK_BOX (vbox2));
  }


  /*************************/
  /*  Interface / Toolbox  */
  /*************************/
  vbox = pika_prefs_box_add_page (PIKA_PREFS_BOX (prefs_box),
                                  "pika-prefs-toolbox",
                                  _("Toolbox"),
                                  _("Toolbox"),
                                  PIKA_HELP_PREFS_TOOLBOX,
                                  &top_iter,
                                  &child_iter);

  size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);

  /*  Appearance  */
  vbox2 = prefs_frame_new (_("Appearance"),
                           GTK_CONTAINER (vbox), FALSE);

  prefs_check_button_add_with_icon (object, "toolbox-mascot",
                                    _("Show PIKA _logo (drag-and-drop target)"),
                                    PIKA_ICON_MASCOT,
                                    GTK_BOX (vbox2), size_group);
  prefs_check_button_add_with_icon (object, "toolbox-color-area",
                                    _("Show _foreground & background color"),
                                    PIKA_ICON_COLORS_DEFAULT,
                                    GTK_BOX (vbox2), size_group);
  prefs_check_button_add_with_icon (object, "toolbox-foo-area",
                                    _("Show active _brush, pattern & gradient"),
                                    PIKA_ICON_BRUSH,
                                    GTK_BOX (vbox2), size_group);
  prefs_check_button_add_with_icon (object, "toolbox-image-area",
                                    _("Show active _image"),
                                    PIKA_ICON_IMAGE,
                                    GTK_BOX (vbox2), size_group);

  separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
  gtk_box_pack_start (GTK_BOX (vbox2), separator, FALSE, FALSE, 0);
  gtk_widget_show (separator);

  prefs_check_button_add_with_icon (object, "toolbox-groups",
                                    _("Use tool _groups"),
                                    NULL,
                                    GTK_BOX (vbox2), size_group);

  g_clear_object (&size_group);

  /* Tool Editor */
  vbox2 = prefs_frame_new (_("Tools Configuration"),
                           GTK_CONTAINER (vbox), TRUE);
  tool_editor = pika_tool_editor_new (pika->tool_item_list, pika->user_context,
                                      PIKA_VIEW_SIZE_SMALL, 1);

  gtk_box_pack_start (GTK_BOX (vbox2), tool_editor, TRUE, TRUE, 0);
  gtk_widget_show (tool_editor);


  /*********************************/
  /*  Interface / Dialog Defaults  */
  /*********************************/
  vbox = pika_prefs_box_add_page (PIKA_PREFS_BOX (prefs_box),
                                  /* FIXME need an icon */
                                  "pika-prefs-controllers",
                                  _("Dialog Defaults"),
                                  _("Dialog Defaults"),
                                  PIKA_HELP_PREFS_DIALOG_DEFAULTS,
                                  &top_iter,
                                  &child_iter);

  pika_prefs_box_set_page_scrollable (PIKA_PREFS_BOX (prefs_box), vbox, TRUE);

  button = pika_prefs_box_set_page_resettable (PIKA_PREFS_BOX (prefs_box),
                                               vbox,
                                               _("Reset Dialog _Defaults"));
  g_signal_connect (button, "clicked",
                    G_CALLBACK (prefs_dialog_defaults_reset),
                    config);

  size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);

  /*  Color profile import dialog  */
  vbox2 = prefs_frame_new (_("Color Profile Import Dialog"), GTK_CONTAINER (vbox),
                           FALSE);
  grid = prefs_grid_new (GTK_CONTAINER (vbox2));

  button = prefs_enum_combo_box_add (object, "color-profile-policy", 0, 0,
                                     _("Color profile policy:"),
                                     GTK_GRID (grid), 0, size_group);

  /*  All color profile chooser dialogs  */
  vbox2 = prefs_frame_new (_("Color Profile File Dialogs"), GTK_CONTAINER (vbox),
                           FALSE);
  grid = prefs_grid_new (GTK_CONTAINER (vbox2));

  prefs_file_chooser_button_add (object, "color-profile-path",
                                 _("Profile folder:"),
                                 _("Select Default Folder for Color Profiles"),
                                 GTK_GRID (grid), 0, size_group);

  /*  Convert to Color Profile Dialog  */
  vbox2 = prefs_frame_new (_("Convert to Color Profile Dialog"),
                           GTK_CONTAINER (vbox), FALSE);
  grid = prefs_grid_new (GTK_CONTAINER (vbox2));

  prefs_enum_combo_box_add (object, "image-convert-profile-intent", 0, 0,
                            _("Rendering intent:"),
                            GTK_GRID (grid), 0, size_group);

  prefs_check_button_add (object, "image-convert-profile-black-point-compensation",
                          _("Black point compensation"),
                          GTK_BOX (vbox2));

  /*  Convert Precision Dialog  */
  vbox2 = prefs_frame_new (_("Precision Conversion Dialog"),
                           GTK_CONTAINER (vbox), FALSE);
  grid = prefs_grid_new (GTK_CONTAINER (vbox2));

  prefs_enum_combo_box_add (object,
                            "image-convert-precision-layer-dither-method",
                            0, 0,
                            _("Dither layers:"),
                            GTK_GRID (grid), 0, size_group);
  prefs_enum_combo_box_add (object,
                            "image-convert-precision-text-layer-dither-method",
                            0, 0,
                            _("Dither text layers:"),
                            GTK_GRID (grid), 1, size_group);
  prefs_enum_combo_box_add (object,
                            "image-convert-precision-channel-dither-method",
                            0, 0,
                            _("Dither channels/masks:"),
                            GTK_GRID (grid), 2, size_group);

  /*  Convert Indexed Dialog  */
  vbox2 = prefs_frame_new (_("Indexed Conversion Dialog"),
                           GTK_CONTAINER (vbox), FALSE);
  grid = prefs_grid_new (GTK_CONTAINER (vbox2));

  prefs_enum_combo_box_add (object, "image-convert-indexed-palette-type", 0, 0,
                            _("Colormap:"),
                            GTK_GRID (grid), 0, size_group);
  prefs_spin_button_add (object, "image-convert-indexed-max-colors", 1.0, 8.0, 0,
                         _("Maximum number of colors:"),
                         GTK_GRID (grid), 1, size_group);

  prefs_check_button_add (object, "image-convert-indexed-remove-duplicates",
                          _("Remove unused and duplicate colors "
                            "from colormap"),
                          GTK_BOX (vbox2));

  grid = prefs_grid_new (GTK_CONTAINER (vbox2));
  prefs_enum_combo_box_add (object, "image-convert-indexed-dither-type", 0, 0,
                            _("Color dithering:"),
                            GTK_GRID (grid), 0, size_group);

  prefs_check_button_add (object, "image-convert-indexed-dither-alpha",
                          _("Enable dithering of transparency"),
                          GTK_BOX (vbox2));
  prefs_check_button_add (object, "image-convert-indexed-dither-text-layers",
                          _("Enable dithering of text layers"),
                          GTK_BOX (vbox2));

  /*  Filter Dialogs  */
  vbox2 = prefs_frame_new (_("Filter Dialogs"),
                           GTK_CONTAINER (vbox), FALSE);
  grid = prefs_grid_new (GTK_CONTAINER (vbox2));

  prefs_spin_button_add (object, "filter-tool-max-recent", 1.0, 8.0, 0,
                         _("Keep recent settings:"),
                         GTK_GRID (grid), 1, size_group);

  button = prefs_check_button_add (object, "filter-tool-use-last-settings",
                                   _("Default to the last used settings"),
                                   GTK_BOX (vbox2));
  button = prefs_check_button_add (object, "filter-tool-show-color-options",
                                   _("Show advanced color options"),
                                   GTK_BOX (vbox2));

  /*  Canvas Size Dialog  */
  vbox2 = prefs_frame_new (_("Canvas Size Dialog"),
                           GTK_CONTAINER (vbox), FALSE);
  grid = prefs_grid_new (GTK_CONTAINER (vbox2));

  prefs_enum_combo_box_add (object, "image-resize-fill-type", 0, 0,
                            _("Fill with:"),
                            GTK_GRID (grid), 0, size_group);
  prefs_enum_combo_box_add (object, "image-resize-layer-set", 0, 0,
                            _("Resize layers:"),
                            GTK_GRID (grid), 1, size_group);

  prefs_check_button_add (object, "image-resize-resize-text-layers",
                          _("Resize text layers"),
                          GTK_BOX (vbox2));

  /*  New Layer Dialog  */
  vbox2 = prefs_frame_new (_("New Layer Dialog"),
                           GTK_CONTAINER (vbox), FALSE);
  grid = prefs_grid_new (GTK_CONTAINER (vbox2));

  prefs_entry_add (object, "layer-new-name",
                   _("Layer name:"),
                   GTK_GRID (grid), 0, size_group);

  prefs_enum_combo_box_add (object, "layer-new-fill-type", 0, 0,
                            _("Fill type:"),
                            GTK_GRID (grid), 1, size_group);

  /*  Layer Boundary Size Dialog  */
  vbox2 = prefs_frame_new (_("Layer Boundary Size Dialog"),
                           GTK_CONTAINER (vbox), FALSE);
  grid = prefs_grid_new (GTK_CONTAINER (vbox2));

  prefs_enum_combo_box_add (object, "layer-resize-fill-type", 0, 0,
                            _("Fill with:"),
                            GTK_GRID (grid), 0, size_group);

  /*  Add Layer Mask Dialog  */
  vbox2 = prefs_frame_new (_("Add Layer Mask Dialog"),
                           GTK_CONTAINER (vbox), FALSE);
  grid = prefs_grid_new (GTK_CONTAINER (vbox2));

  prefs_enum_combo_box_add (object, "layer-add-mask-type", 0, 0,
                            _("Layer mask type:"),
                            GTK_GRID (grid), 0, size_group);

  prefs_check_button_add (object, "layer-add-mask-invert",
                          _("Invert mask"),
                          GTK_BOX (vbox2));

  /*  Merge Layers Dialog  */
  vbox2 = prefs_frame_new (_("Merge Layers Dialog"),
                           GTK_CONTAINER (vbox), FALSE);
  grid = prefs_grid_new (GTK_CONTAINER (vbox2));

  prefs_enum_combo_box_add (object, "layer-merge-type",
                            PIKA_EXPAND_AS_NECESSARY,
                            PIKA_CLIP_TO_BOTTOM_LAYER,
                            _("Merged layer size:"),
                            GTK_GRID (grid), 0, size_group);

  prefs_check_button_add (object, "layer-merge-active-group-only",
                          _("Merge within active groups only"),
                          GTK_BOX (vbox2));
  prefs_check_button_add (object, "layer-merge-discard-invisible",
                          _("Discard invisible layers"),
                          GTK_BOX (vbox2));

  /*  New Channel Dialog  */
  vbox2 = prefs_frame_new (_("New Channel Dialog"),
                           GTK_CONTAINER (vbox), FALSE);
  grid = prefs_grid_new (GTK_CONTAINER (vbox2));

  prefs_entry_add (object, "channel-new-name",
                   _("Channel name:"),
                   GTK_GRID (grid), 0, size_group);

  prefs_color_button_add (object, "channel-new-color",
                          _("Color and opacity:"),
                          _("Default New Channel Color and Opacity"),
                          GTK_GRID (grid), 1, size_group,
                          pika_get_user_context (pika));

  /*  New Path Dialog  */
  vbox2 = prefs_frame_new (_("New Path Dialog"),
                           GTK_CONTAINER (vbox), FALSE);
  grid = prefs_grid_new (GTK_CONTAINER (vbox2));

  prefs_entry_add (object, "path-new-name",
                   _("Path name:"),
                   GTK_GRID (grid), 0, size_group);

  /*  Export Path Dialog  */
  vbox2 = prefs_frame_new (_("Export Paths Dialog"),
                           GTK_CONTAINER (vbox), FALSE);
  grid = prefs_grid_new (GTK_CONTAINER (vbox2));

  prefs_file_chooser_button_add (object, "path-export-path",
                                 _("Export folder:"),
                                 _("Select Default Folder for Exporting Paths"),
                                 GTK_GRID (grid), 0, size_group);

  prefs_check_button_add (object, "path-export-active-only",
                          _("Export the selected paths only"),
                          GTK_BOX (vbox2));

  /*  Import Path Dialog  */
  vbox2 = prefs_frame_new (_("Import Paths Dialog"),
                           GTK_CONTAINER (vbox), FALSE);
  grid = prefs_grid_new (GTK_CONTAINER (vbox2));

  prefs_file_chooser_button_add (object, "path-import-path",
                                 _("Import folder:"),
                                 _("Select Default Folder for Importing Paths"),
                                 GTK_GRID (grid), 0, size_group);

  prefs_check_button_add (object, "path-import-merge",
                          _("Merge imported paths"),
                          GTK_BOX (vbox2));
  prefs_check_button_add (object, "path-import-scale",
                          _("Scale imported paths"),
                          GTK_BOX (vbox2));

  /*  Feather Selection Dialog  */
  vbox2 = prefs_frame_new (_("Feather Selection Dialog"),
                           GTK_CONTAINER (vbox), FALSE);
  grid = prefs_grid_new (GTK_CONTAINER (vbox2));

  prefs_spin_button_add (object, "selection-feather-radius", 1.0, 10.0, 2,
                         _("Feather radius:"),
                         GTK_GRID (grid), 0, size_group);

  prefs_check_button_add (object, "selection-feather-edge-lock",
                          _("Selected areas continue outside the image"),
                          GTK_BOX (vbox2));

  /*  Grow Selection Dialog  */
  vbox2 = prefs_frame_new (_("Grow Selection Dialog"),
                           GTK_CONTAINER (vbox), FALSE);
  grid = prefs_grid_new (GTK_CONTAINER (vbox2));

  prefs_spin_button_add (object, "selection-grow-radius", 1.0, 10.0, 0,
                         _("Grow radius:"),
                         GTK_GRID (grid), 0, size_group);

  /*  Shrink Selection Dialog  */
  vbox2 = prefs_frame_new (_("Shrink Selection Dialog"),
                           GTK_CONTAINER (vbox), FALSE);
  grid = prefs_grid_new (GTK_CONTAINER (vbox2));

  prefs_spin_button_add (object, "selection-shrink-radius", 1.0, 10.0, 0,
                         _("Shrink radius:"),
                         GTK_GRID (grid), 0, size_group);

  prefs_check_button_add (object, "selection-shrink-edge-lock",
                          _("Selected areas continue outside the image"),
                          GTK_BOX (vbox2));

  /*  Border Selection Dialog  */
  vbox2 = prefs_frame_new (_("Border Selection Dialog"),
                           GTK_CONTAINER (vbox), FALSE);
  grid = prefs_grid_new (GTK_CONTAINER (vbox2));

  prefs_spin_button_add (object, "selection-border-radius", 1.0, 10.0, 0,
                         _("Border radius:"),
                         GTK_GRID (grid), 0, size_group);

  prefs_enum_combo_box_add (object, "selection-border-style", 0, 0,
                            _("Border style:"),
                            GTK_GRID (grid), 1, size_group);

  prefs_check_button_add (object, "selection-border-edge-lock",
                          _("Selected areas continue outside the image"),
                          GTK_BOX (vbox2));

  /*  Fill Options Dialog  */
  vbox2 = prefs_frame_new (_("Fill Selection Outline & Fill Path Dialogs"),
                           GTK_CONTAINER (vbox), FALSE);

  editor = pika_fill_editor_new (PIKA_DIALOG_CONFIG (object)->fill_options,
                                 FALSE, FALSE);
  gtk_box_pack_start (GTK_BOX (vbox2), editor, FALSE, FALSE, 0);
  gtk_widget_show (editor);

  /*  Stroke Options Dialog  */
  vbox2 = prefs_frame_new (_("Stroke Selection & Stroke Path Dialogs"),
                           GTK_CONTAINER (vbox), FALSE);

  /* The stroke line width physical values could be based on either the
   * x or y resolution, some average, or whatever which makes a bit of
   * sense. There is no perfect answer. The actual stroke dialog though
   * uses the y resolution on the opened image. So using the y resolution
   * of the default image seems like the best compromise in the preferences.
   */
  editor = pika_stroke_editor_new (PIKA_DIALOG_CONFIG (object)->stroke_options,
                                   pika_template_get_resolution_y (core_config->default_image),
                                   FALSE, FALSE);
  gtk_box_pack_start (GTK_BOX (vbox2), editor, FALSE, FALSE, 0);
  gtk_widget_show (editor);

  g_clear_object (&size_group);


  /*****************************/
  /*  Interface / Help System  */
  /*****************************/
  vbox = pika_prefs_box_add_page (PIKA_PREFS_BOX (prefs_box),
                                  "pika-prefs-help-system",
                                  _("Help System"),
                                  _("Help System"),
                                  PIKA_HELP_PREFS_HELP,
                                  &top_iter,
                                  &child_iter);

  size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);

  /*  General  */
  vbox2 = prefs_frame_new (_("General"), GTK_CONTAINER (vbox), FALSE);

  prefs_check_button_add (object, "show-help-button",
                          _("Show help _buttons"),
                          GTK_BOX (vbox2));

  grid = prefs_grid_new (GTK_CONTAINER (vbox2));
  button = prefs_boolean_combo_box_add (object, "user-manual-online",
                                        _("Use the online version"),
                                        _("Use a locally installed copy"),
                                        _("U_ser manual:"),
                                        GTK_GRID (grid), 0, size_group);
  pika_help_set_help_data (button, NULL, NULL);

  manuals = pika_help_get_installed_languages ();
  entry   = NULL;
  if (manuals != NULL)
    {
      gchar *help_locales = NULL;

      entry = pika_language_combo_box_new (TRUE,
                                           _("User interface language"));

      g_object_get (config, "help-locales", &help_locales, NULL);
      if (help_locales && strlen (help_locales))
        {
          gchar *sep;

          sep = strchr (help_locales, ':');
          if (sep)
            *sep = '\0';
        }
      if (help_locales)
        {
          pika_language_combo_box_set_code (PIKA_LANGUAGE_COMBO_BOX (entry),
                                            help_locales);
          g_free (help_locales);
        }
      else
        {
          pika_language_combo_box_set_code (PIKA_LANGUAGE_COMBO_BOX (entry),
                                            "");
        }
      g_signal_connect (entry, "changed",
                        G_CALLBACK (prefs_help_language_change_callback),
                        pika);
      gtk_grid_attach (GTK_GRID (grid), entry, 1, 1, 1, 1);
      gtk_widget_show (entry);
    }

  if (pika_help_user_manual_is_installed (pika))
    {
      hbox = prefs_hint_box_new (PIKA_ICON_DIALOG_INFORMATION,
                                 _("There's a local installation "
                                   "of the user manual."));
    }
  else
    {
      hbox = prefs_hint_box_new (PIKA_ICON_DIALOG_WARNING,
                                 _("The user manual is not installed "
                                   "locally."));
    }
  if (manuals)
    {
      g_object_set_data (G_OBJECT (hbox), "pika", pika);
      g_signal_connect (entry, "changed",
                        G_CALLBACK (prefs_help_language_change_callback2),
                        hbox);
      g_list_free_full (manuals, g_free);
    }

  gtk_grid_attach (GTK_GRID (grid), hbox, 1, 2, 1, 1);
  gtk_widget_show (hbox);

  /*  Help Browser  */
#ifdef HAVE_WEBKIT
  /*  If there is no webkit available, assume we are on a platform
   *  that doesn't use the help browser, so don't bother showing
   *  the combo.
   */
  vbox2 = prefs_frame_new (_("Help Browser"), GTK_CONTAINER (vbox), FALSE);

  if (pika_help_browser_is_installed (pika))
    {
      grid = prefs_grid_new (GTK_CONTAINER (vbox2));

      button = prefs_enum_combo_box_add (object, "help-browser", 0, 0,
                                         _("H_elp browser to use:"),
                                         GTK_GRID (grid), 0, size_group);
    }
  else
    {
      hbox = prefs_hint_box_new (PIKA_ICON_DIALOG_WARNING,
                                 _("The PIKA help browser doesn't seem to "
                                   "be installed. Using the web browser "
                                   "instead."));
      gtk_box_pack_start (GTK_BOX (vbox2), hbox, FALSE, FALSE, 0);
      gtk_widget_show (hbox);

      g_object_set (config,
                    "help-browser", PIKA_HELP_BROWSER_WEB_BROWSER,
                    NULL);
    }
#else
  g_object_set (config,
                "help-browser", PIKA_HELP_BROWSER_WEB_BROWSER,
                NULL);
#endif /* HAVE_WEBKIT */

  /* Action Search */
  vbox2 = prefs_frame_new (_("Action Search"), GTK_CONTAINER (vbox), FALSE);
  grid = prefs_grid_new (GTK_CONTAINER (vbox2));

  prefs_spin_button_add (object, "action-history-size", 1.0, 10.0, 0,
                         _("_Maximum History Size:"),
                         GTK_GRID (grid), 0, size_group);

  button = prefs_button_add (PIKA_ICON_SHRED,
                             _("C_lear Action History"),
                             GTK_BOX (vbox2));
  g_signal_connect (button, "clicked",
                    G_CALLBACK (prefs_search_clear_callback),
                    pika);

  g_clear_object (&size_group);


  /*************************/
  /*  Interface / Display  */
  /*************************/
  vbox = pika_prefs_box_add_page (PIKA_PREFS_BOX (prefs_box),
                                  "pika-prefs-display",
                                  _("Display"),
                                  _("Display"),
                                  PIKA_HELP_PREFS_DISPLAY,
                                  &top_iter,
                                  &child_iter);
  pika_prefs_box_set_page_scrollable (PIKA_PREFS_BOX (prefs_box), vbox, TRUE);

  size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);

  /*  Transparency  */
  vbox2 = prefs_frame_new (_("Transparency"), GTK_CONTAINER (vbox), FALSE);
  grid = prefs_grid_new (GTK_CONTAINER (vbox2));

  prefs_enum_combo_box_add (object, "transparency-type", 0, 0,
                            _("_Check style:"),
                            GTK_GRID (grid), 0, size_group);

  button = pika_prop_label_color_new (object,
                                      "transparency-custom-color1",
                                      TRUE);
  pika_grid_attach_aligned (GTK_GRID (grid), 0, 1,
                            NULL, 0.0, 0.5,
                            button, 1);
  gtk_widget_set_hexpand (button, FALSE);
  pika_color_button_set_color_config (PIKA_COLOR_BUTTON (pika_label_color_get_color_widget (PIKA_LABEL_COLOR (button))),
                                      pika->config->color_management);
  gtk_widget_set_sensitive (button,
                            display_config->transparency_type == PIKA_CHECK_TYPE_CUSTOM_CHECKS);
  g_signal_connect (object, "notify::transparency-type",
                    G_CALLBACK (prefs_check_style_callback),
                    button);

  button = pika_prop_label_color_new (object,
                                      "transparency-custom-color2",
                                      TRUE);
  pika_grid_attach_aligned (GTK_GRID (grid), 0, 2,
                            NULL, 0.0, 0.5,
                            button, 1);
  gtk_widget_set_hexpand (button, FALSE);
  pika_color_button_set_color_config (PIKA_COLOR_BUTTON (pika_label_color_get_color_widget (PIKA_LABEL_COLOR (button))),
                                      pika->config->color_management);
  gtk_widget_set_sensitive (button,
                            display_config->transparency_type == PIKA_CHECK_TYPE_CUSTOM_CHECKS);
  g_signal_connect (object, "notify::transparency-type",
                    G_CALLBACK (prefs_check_style_callback),
                    button);

  prefs_enum_combo_box_add (object, "transparency-size", 0, 0,
                            _("Check _size:"),
                            GTK_GRID (grid), 3, size_group);

  /*  Zoom Quality  */
  vbox2 = prefs_frame_new (_("Zoom Quality"), GTK_CONTAINER (vbox), FALSE);
  grid = prefs_grid_new (GTK_CONTAINER (vbox2));

  prefs_enum_combo_box_add (object, "zoom-quality", 0, 0,
                            _("_Zoom quality:"),
                            GTK_GRID (grid), 0, size_group);

  /*  Monitor Resolution  */
  vbox2 = prefs_frame_new (_("Monitor Resolution"),
                           GTK_CONTAINER (vbox), FALSE);

  {
    gchar *pixels_per_unit = g_strconcat (_("Pixels"), "/%s", NULL);

    entry = pika_prop_coordinates_new (object,
                                       "monitor-xresolution",
                                       "monitor-yresolution",
                                       NULL,
                                       pixels_per_unit,
                                       PIKA_SIZE_ENTRY_UPDATE_RESOLUTION,
                                       0.0, 0.0,
                                       TRUE);

    g_free (pixels_per_unit);
  }

  gtk_grid_set_column_spacing (GTK_GRID (entry), 2);
  gtk_grid_set_row_spacing (GTK_GRID (entry), 2);

  pika_size_entry_attach_label (PIKA_SIZE_ENTRY (entry),
                                _("Horizontal"), 0, 1, 0.0);
  pika_size_entry_attach_label (PIKA_SIZE_ENTRY (entry),
                                _("Vertical"), 0, 2, 0.0);
  pika_size_entry_attach_label (PIKA_SIZE_ENTRY (entry),
                                _("ppi"), 1, 4, 0.0);

  hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
  gtk_widget_set_halign (hbox, GTK_ALIGN_START);

  gtk_box_pack_start (GTK_BOX (hbox), entry, FALSE, FALSE, 24);
  gtk_widget_set_sensitive (entry, ! display_config->monitor_res_from_gdk);

  group = NULL;

  {
    gdouble  xres;
    gdouble  yres;
    gchar   *str;

    pika_get_monitor_resolution (gdk_display_get_monitor (gdk_display_get_default (), 0),
                                 &xres, &yres);

    str = g_strdup_printf (_("_Detect automatically (currently %d × %d ppi)"),
                           ROUND (xres), ROUND (yres));

    button = gtk_radio_button_new_with_mnemonic (group, str);

    g_free (str);
  }

  group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (button));
  gtk_box_pack_start (GTK_BOX (vbox2), button, FALSE, FALSE, 0);
  gtk_widget_show (button);

  g_object_set_data (G_OBJECT (button), "monitor_resolution_sizeentry", entry);

  g_signal_connect (button, "toggled",
                    G_CALLBACK (prefs_resolution_source_callback),
                    config);

  button = gtk_radio_button_new_with_mnemonic (group, _("_Enter manually"));
  group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (button));
  gtk_box_pack_start (GTK_BOX (vbox2), button, FALSE, FALSE, 0);
  gtk_widget_show (button);

  gtk_box_pack_start (GTK_BOX (vbox2), hbox, FALSE, FALSE, 0);
  gtk_widget_show (hbox);

  if (! display_config->monitor_res_from_gdk)
    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);

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

  calibrate_button = gtk_button_new_with_mnemonic (_("C_alibrate..."));
  label = gtk_bin_get_child (GTK_BIN (calibrate_button));
  g_object_set (label,
                "margin-start", 4,
                "margin-end",   4,
                NULL);
  gtk_box_pack_start (GTK_BOX (hbox), calibrate_button, FALSE, FALSE, 0);
  gtk_widget_show (calibrate_button);
  gtk_widget_set_sensitive (calibrate_button,
                            ! display_config->monitor_res_from_gdk);

  g_object_bind_property (button, "active",
                          entry,  "sensitive",
                          G_BINDING_SYNC_CREATE);
  g_object_bind_property (button,           "active",
                          calibrate_button, "sensitive",
                          G_BINDING_SYNC_CREATE);

  g_signal_connect (calibrate_button, "clicked",
                    G_CALLBACK (prefs_resolution_calibrate_callback),
                    entry);

  g_clear_object (&size_group);


  /***********************************/
  /*  Interface / Window Management  */
  /***********************************/
  vbox = pika_prefs_box_add_page (PIKA_PREFS_BOX (prefs_box),
                                  "pika-prefs-window-management",
                                  _("Window Management"),
                                  _("Window Management"),
                                  PIKA_HELP_PREFS_WINDOW_MANAGEMENT,
                                  &top_iter,
                                  &child_iter);

  vbox2 = prefs_frame_new (_("Window Manager Hints"),
                           GTK_CONTAINER (vbox), FALSE);

  grid = prefs_grid_new (GTK_CONTAINER (vbox2));

  prefs_enum_combo_box_add (object, "dock-window-hint", 0, 0,
                            _("Hint for _docks and toolbox:"),
                            GTK_GRID (grid), 1, NULL);

  vbox2 = prefs_frame_new (_("Focus"),
                           GTK_CONTAINER (vbox), FALSE);

  prefs_check_button_add (object, "activate-on-focus",
                          _("Activate the _focused image"),
                          GTK_BOX (vbox2));

  /* Window Positions */
  vbox2 = prefs_frame_new (_("Window Positions"), GTK_CONTAINER (vbox), FALSE);

  prefs_check_button_add (object, "save-session-info",
                          _("_Save window positions on exit"),
                          GTK_BOX (vbox2));
  prefs_check_button_add (object, "restore-monitor",
                          _("Open windows on the same _monitor they were open before"),
                          GTK_BOX (vbox2));

  button = prefs_button_add (PIKA_ICON_DOCUMENT_SAVE,
                             _("Save Window Positions _Now"),
                             GTK_BOX (vbox2));
  g_signal_connect (button, "clicked",
                    G_CALLBACK (prefs_session_save_callback),
                    pika);

  button2 = prefs_button_add (PIKA_ICON_RESET,
                              _("_Reset Saved Window Positions to "
                                "Default Values"),
                              GTK_BOX (vbox2));
  g_signal_connect (button2, "clicked",
                    G_CALLBACK (prefs_session_clear_callback),
                    pika);

  g_object_set_data (G_OBJECT (button), "clear-button", button2);


  /************************/
  /*  Canvas Interaction  */
  /************************/
  vbox = pika_prefs_box_add_page (PIKA_PREFS_BOX (prefs_box),
                                  "pika-prefs-image-windows",
                                  _("Canvas Interaction"),
                                  _("Canvas Interaction"),
                                  PIKA_HELP_PREFS_CANVAS_INTERACTION,
                                  NULL,
                                  &top_iter);
  pika_prefs_box_set_page_scrollable (PIKA_PREFS_BOX (prefs_box), vbox, TRUE);

  size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);

  /*  Space Bar  */
  vbox2 = prefs_frame_new (_("Space Bar"),
                           GTK_CONTAINER (vbox), FALSE);

  grid = prefs_grid_new (GTK_CONTAINER (vbox2));

  prefs_enum_combo_box_add (object, "space-bar-action", 0, 0,
                            _("_While space bar is pressed:"),
                            GTK_GRID (grid), 0, size_group);

  /*  Zoom by drag Behavior  */
  vbox2 = prefs_frame_new (_("Zoom"),
                           GTK_CONTAINER (vbox), FALSE);

  grid = prefs_grid_new (GTK_CONTAINER (vbox2));

  prefs_enum_combo_box_add (object, "drag-zoom-mode", 0, 0,
                            _("Dra_g-to-zoom behavior:"),
                            GTK_GRID (grid), 0, size_group);

  grid = prefs_grid_new (GTK_CONTAINER (vbox2));

  prefs_spin_button_add (object, "drag-zoom-speed", 5.0, 25.0, 0,
                         _("Drag-to-zoom spe_ed:"),
                         GTK_GRID (grid), 0, size_group);


  /************************************/
  /*  Canvas Interaction / Modifiers  */
  /************************************/
  vbox = pika_prefs_box_add_page (PIKA_PREFS_BOX (prefs_box),
                                  /* TODO: custom icon. */
                                  "pika-prefs-image-windows",
                                  _("Modifiers"),
                                  _("Modifiers"),
                                  PIKA_HELP_PREFS_CANVAS_MODIFIERS,
                                  &top_iter,
                                  &child_iter);

  vbox2 = pika_modifiers_editor_new (PIKA_MODIFIERS_MANAGER (display_config->modifiers_manager),
                                     pika);
  gtk_widget_show (vbox2);
  gtk_box_pack_start (GTK_BOX (vbox), vbox2, FALSE, FALSE, 0);

  button2 = prefs_button_add (PIKA_ICON_RESET,
                              _("_Reset Saved Modifiers Settings to "
                                "Default Values"),
                              GTK_BOX (vbox));
  g_signal_connect (button2, "clicked",
                    G_CALLBACK (prefs_modifiers_clear_callback),
                    vbox2);

  g_object_set_data (G_OBJECT (button), "clear-button", button2);

  /***********************************/
  /*  Canvas Interaction / Snapping  */
  /***********************************/
  vbox = pika_prefs_box_add_page (PIKA_PREFS_BOX (prefs_box),
                                  "pika-prefs-image-windows-snapping",
                                  _("Snapping Behavior"),
                                  _("Snapping"),
                                  PIKA_HELP_PREFS_IMAGE_WINDOW_SNAPPING,
                                  &top_iter,
                                  &child_iter);

  prefs_behavior_options_frame_add (pika,
                                    G_OBJECT (display_config->default_view),
                                    _("Default Behavior in Normal Mode"),
                                    GTK_CONTAINER (vbox));
  prefs_behavior_options_frame_add (pika,
                                    G_OBJECT (display_config->default_fullscreen_view),
                                    _("Default Behavior in Fullscreen Mode"),
                                    GTK_CONTAINER (vbox));

  /*  Snapping Distance  */
  vbox2 = prefs_frame_new (_("General"),
                           GTK_CONTAINER (vbox), FALSE);
  grid = prefs_grid_new (GTK_CONTAINER (vbox2));

  prefs_spin_button_add (object, "snap-distance", 1.0, 5.0, 0,
                         _("_Snapping distance:"),
                         GTK_GRID (grid), 0, NULL);


  /*******************/
  /*  Image Windows  */
  /*******************/
  vbox = pika_prefs_box_add_page (PIKA_PREFS_BOX (prefs_box),
                                  "pika-prefs-image-windows",
                                  _("Image Windows"),
                                  _("Image Windows"),
                                  PIKA_HELP_PREFS_IMAGE_WINDOW,
                                  NULL,
                                  &top_iter);
  pika_prefs_box_set_page_scrollable (PIKA_PREFS_BOX (prefs_box), vbox, TRUE);

  size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);

  /*  General  */
  vbox2 = prefs_frame_new (_("General"), GTK_CONTAINER (vbox), FALSE);

  /* See app/display/pikaimagewindow.c: the code path where "custom-title-bar"
   * is verified never happens for macOS which always uses
   * gtk_application_set_menubar() instead.
   */
#ifndef GDK_WINDOWING_QUARTZ
  prefs_check_button_add (object, "custom-title-bar",
                          _("Merge menu and title bar"),
                          GTK_BOX (vbox2));
  hbox = prefs_hint_box_new (PIKA_ICON_DIALOG_WARNING,
                             _("PIKA will try to convince your system not to decorate image windows. "
                               "If it doesn't work properly on your system "
                               "(i.e. you get 2 title bars), please report."));
  gtk_box_pack_start (GTK_BOX (vbox2), hbox, FALSE, FALSE, 0);
  g_object_bind_property (object, "custom-title-bar",
                          hbox,   "visible",
                          G_BINDING_SYNC_CREATE);
#endif

  prefs_check_button_add (object, "default-show-all",
                          _("Use \"Show _all\" by default"),
                          GTK_BOX (vbox2));

  prefs_check_button_add (object, "default-dot-for-dot",
                          _("Use \"_Dot for dot\" by default"),
                          GTK_BOX (vbox2));

  grid = prefs_grid_new (GTK_CONTAINER (vbox2));

  prefs_spin_button_add (object, "marching-ants-speed", 1.0, 10.0, 0,
                         _("Marching ants s_peed:"),
                         GTK_GRID (grid), 0, size_group);

  /*  Zoom & Resize Behavior  */
  vbox2 = prefs_frame_new (_("Zoom & Resize Behavior"),
                           GTK_CONTAINER (vbox), FALSE);

  prefs_check_button_add (object, "resize-windows-on-zoom",
                          _("Resize window on _zoom"),
                          GTK_BOX (vbox2));
  prefs_check_button_add (object, "resize-windows-on-resize",
                          _("Resize window on image _size change"),
                          GTK_BOX (vbox2));

  grid = prefs_grid_new (GTK_CONTAINER (vbox2));

  prefs_boolean_combo_box_add (object, "initial-zoom-to-fit",
                               _("Show entire image"),
                               "1:1",
                               _("Initial zoom _ratio:"),
                               GTK_GRID (grid), 0, size_group);

  /********************************/
  /*  Image Windows / Appearance  */
  /********************************/
  vbox = pika_prefs_box_add_page (PIKA_PREFS_BOX (prefs_box),
                                  "pika-prefs-image-windows-appearance",
                                  _("Image Window Appearance"),
                                  _("Appearance"),
                                  PIKA_HELP_PREFS_IMAGE_WINDOW_APPEARANCE,
                                  &top_iter,
                                  &child_iter);

  pika_prefs_box_set_page_scrollable (PIKA_PREFS_BOX (prefs_box), vbox, TRUE);

  prefs_display_options_frame_add (pika,
                                   G_OBJECT (display_config->default_view),
                                   _("Default Appearance in Normal Mode"),
                                   GTK_CONTAINER (vbox));

  prefs_display_options_frame_add (pika,
                                   G_OBJECT (display_config->default_fullscreen_view),
                                   _("Default Appearance in Fullscreen Mode"),
                                   GTK_CONTAINER (vbox));


  /****************************************************/
  /*  Image Windows / Image Title & Statusbar Format  */
  /****************************************************/
  vbox = pika_prefs_box_add_page (PIKA_PREFS_BOX (prefs_box),
                                  "pika-prefs-image-title",
                                  _("Image Title & Statusbar Format"),
                                  _("Title & Status"),
                                  PIKA_HELP_PREFS_IMAGE_WINDOW_TITLE,
                                  &top_iter,
                                  &child_iter);

  {
    const gchar *format_strings[] =
    {
      NULL,
      NULL,
      "%f-%p.%i (%t) %z%%",
      "%f-%p.%i (%t) %d:%s",
      "%f-%p.%i (%t) %wx%h",
      "%f-%p-%i (%t) %wx%h (%xx%y)"
    };

    const gchar *format_names[] =
    {
      N_("Current format"),
      N_("Default format"),
      N_("Show zoom percentage"),
      N_("Show zoom ratio"),
      N_("Show image size"),
      N_("Show drawable size")
    };

    struct
    {
      gchar       *current_setting;
      const gchar *default_setting;
      const gchar *title;
      const gchar *property_name;
    }
    formats[] =
    {
      { NULL, PIKA_CONFIG_DEFAULT_IMAGE_TITLE_FORMAT,
        N_("Image Title Format"),     "image-title-format"  },
      { NULL, PIKA_CONFIG_DEFAULT_IMAGE_STATUS_FORMAT,
        N_("Image Statusbar Format"), "image-status-format" }
    };

    gint format;

    pika_assert (G_N_ELEMENTS (format_strings) == G_N_ELEMENTS (format_names));

    formats[0].current_setting = display_config->image_title_format;
    formats[1].current_setting = display_config->image_status_format;

    for (format = 0; format < G_N_ELEMENTS (formats); format++)
      {
        GtkWidget     *scrolled_win;
        GtkWidget     *listbox;
        GtkSizeGroup  *name_group, *format_group;
        gint           i;

        format_strings[0] = formats[format].current_setting;
        format_strings[1] = formats[format].default_setting;

        vbox2 = prefs_frame_new (gettext (formats[format].title),
                                 GTK_CONTAINER (vbox), TRUE);

        entry = pika_prop_entry_new (object, formats[format].property_name, 0);
        gtk_box_pack_start (GTK_BOX (vbox2), entry, FALSE, FALSE, 0);

        scrolled_win = gtk_scrolled_window_new (NULL, NULL);
        gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_win),
                                             GTK_SHADOW_IN);
        gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_win),
                                        GTK_POLICY_AUTOMATIC,
                                        GTK_POLICY_AUTOMATIC);
        gtk_box_pack_start (GTK_BOX (vbox2), scrolled_win, TRUE, TRUE, 0);
        gtk_widget_show (scrolled_win);

        listbox = gtk_list_box_new ();
        gtk_list_box_set_selection_mode (GTK_LIST_BOX (listbox),
                                         GTK_SELECTION_BROWSE);
        gtk_container_add (GTK_CONTAINER (scrolled_win), listbox);
        gtk_widget_show (listbox);

        name_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
        format_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);

        for (i = 0; i < G_N_ELEMENTS (format_strings); i++)
          {
            GtkWidget *row;
            GtkWidget *grid;
            GtkWidget *name_label, *format_label;

            row = gtk_list_box_row_new ();
            g_object_set_data_full (G_OBJECT (row),
                                    "format",
                                    g_strdup (format_strings[i]),
                                    g_free);

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

            name_label = gtk_label_new (gettext (format_names[i]));
            g_object_set (name_label, "xalign", 0.0, "margin", 3, NULL);
            gtk_size_group_add_widget (name_group, name_label);
            gtk_grid_attach (GTK_GRID (grid), name_label, 0, 0, 1, 1);

            format_label = gtk_label_new (format_strings[i]);
            g_object_set (format_label, "xalign", 0.0, "margin", 3, NULL);
            gtk_size_group_add_widget (format_group, format_label);
            gtk_grid_attach (GTK_GRID (grid), format_label, 1, 0, 1, 1);

            gtk_widget_show_all (row);
            gtk_list_box_insert (GTK_LIST_BOX (listbox), row, -1);

            if (i == 0)
              {
                gtk_list_box_select_row (GTK_LIST_BOX (listbox),
                                         GTK_LIST_BOX_ROW (row));
              }
          }
        g_object_unref (name_group);
        g_object_unref (format_group);

        g_signal_connect (listbox, "row-selected",
                          G_CALLBACK (prefs_format_string_select_callback),
                          entry);
      }
  }


  /*******************/
  /*  Input Devices  */
  /*******************/
  vbox = pika_prefs_box_add_page (PIKA_PREFS_BOX (prefs_box),
                                  "pika-prefs-input-devices",
                                  _("Input Devices"),
                                  _("Input Devices"),
                                  PIKA_HELP_PREFS_INPUT_DEVICES,
                                  NULL,
                                  &top_iter);

  /*  Mouse Pointers  */
  hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10);
  gtk_widget_set_halign (hbox, GTK_ALIGN_START);
  gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
  gtk_widget_show (hbox);

  vbox2 = prefs_frame_new (_("Pointers"),
                           GTK_CONTAINER (hbox), FALSE);

  grid = prefs_grid_new (GTK_CONTAINER (vbox2));

  size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
  prefs_enum_combo_box_add (object, "cursor-mode", 0, 0,
                            _("Pointer _mode:"),
                            GTK_GRID (grid), 0, size_group);
  prefs_enum_combo_box_add (object, "cursor-handedness", 0, 0,
                            _("Pointer _handedness:"),
                            GTK_GRID (grid), 1, size_group);
  g_clear_object (&size_group);

  vbox2 = prefs_frame_new (_("Paint Tools"),
                           GTK_CONTAINER (hbox), FALSE);

  button = prefs_check_button_add (object, "show-brush-outline",
                                   _("Show _brush outline"),
                                   GTK_BOX (vbox2));

  vbox3 = prefs_frame_new (NULL, GTK_CONTAINER (vbox2), FALSE);
  g_object_bind_property (button, "active",
                          vbox3,  "sensitive",
                          G_BINDING_SYNC_CREATE);
  prefs_check_button_add (object, "snap-brush-outline",
                          _("S_nap brush outline to stroke"),
                          GTK_BOX (vbox3));

  prefs_check_button_add (object, "show-paint-tool-cursor",
                          _("Show pointer for paint _tools"),
                          GTK_BOX (vbox2));

  /*  Extended Input Devices  */
  vbox2 = prefs_frame_new (_("Extended Input Devices"),
                           GTK_CONTAINER (vbox), FALSE);

#ifdef G_OS_WIN32

  if ((gtk_get_major_version () == 3 &&
       gtk_get_minor_version () > 24) ||
      (gtk_get_major_version () == 3 &&
       gtk_get_minor_version () == 24 &&
       gtk_get_micro_version () >= 30))
    {
      GtkWidget *combo;

      grid = prefs_grid_new (GTK_CONTAINER (vbox2));

      combo = prefs_enum_combo_box_add (object, "win32-pointer-input-api", 0, 0,
                                        _("Pointer Input API:"),
                                        GTK_GRID (grid), 0, NULL);

      pika_int_combo_box_set_sensitivity (PIKA_INT_COMBO_BOX (combo),
                                          prefs_devices_api_sensitivity_func,
                                          NULL, NULL);
    }

#endif

  prefs_check_button_add (object, "devices-share-tool",
                          _("S_hare tool and tool options between input devices"),
                          GTK_BOX (vbox2));

  button = prefs_button_add (PIKA_ICON_PREFERENCES_SYSTEM,
                             _("Configure E_xtended Input Devices..."),
                             GTK_BOX (vbox2));
  g_signal_connect (button, "clicked",
                    G_CALLBACK (prefs_input_devices_dialog),
                    pika);

  prefs_check_button_add (object, "save-device-status",
                          _("_Save input device settings on exit"),
                          GTK_BOX (vbox2));

  button = prefs_button_add (PIKA_ICON_DOCUMENT_SAVE,
                             _("Save Input Device Settings _Now"),
                             GTK_BOX (vbox2));
  g_signal_connect (button, "clicked",
                    G_CALLBACK (prefs_devices_save_callback),
                    pika);

  button2 = prefs_button_add (PIKA_ICON_RESET,
                              _("_Reset Saved Input Device Settings to "
                                "Default Values"),
                              GTK_BOX (vbox2));
  g_signal_connect (button2, "clicked",
                    G_CALLBACK (prefs_devices_clear_callback),
                    pika);

  g_object_set_data (G_OBJECT (button), "clear-button", button2);


  /****************************/
  /*  Additional Controllers  */
  /****************************/
  vbox = pika_prefs_box_add_page (PIKA_PREFS_BOX (prefs_box),
                                  "pika-prefs-controllers",
                                  _("Additional Input Controllers"),
                                  _("Input Controllers"),
                                  PIKA_HELP_PREFS_INPUT_CONTROLLERS,
                                  &top_iter,
                                  &child_iter);

  vbox2 = pika_controller_list_new (pika);
  gtk_box_pack_start (GTK_BOX (vbox), vbox2, TRUE, TRUE, 0);
  gtk_widget_show (vbox2);


  /*************/
  /*  Folders  */
  /*************/
  vbox = pika_prefs_box_add_page (PIKA_PREFS_BOX (prefs_box),
                                  "pika-prefs-folders",
                                  _("Folders"),
                                  _("Folders"),
                                  PIKA_HELP_PREFS_FOLDERS,
                                  NULL,
                                  &top_iter);

  button = pika_prefs_box_set_page_resettable (PIKA_PREFS_BOX (prefs_box),
                                               vbox,
                                               _("Reset _Folders"));
  g_signal_connect (button, "clicked",
                    G_CALLBACK (prefs_folders_reset),
                    config);

  {
    static const struct
    {
      const gchar *property_name;
      const gchar *label;
      const gchar *dialog_title;
    }
    dirs[] =
    {
      {
        "temp-path",
        N_("_Temporary folder:"),
        N_("Select Folder for Temporary Files")
      },
      {
        "swap-path",
        N_("_Swap folder:"),
        N_("Select Swap Folder")
      }
    };

    grid = prefs_grid_new (GTK_CONTAINER (vbox));

    for (i = 0; i < G_N_ELEMENTS (dirs); i++)
      {
        prefs_file_chooser_button_add (object, dirs[i].property_name,
                                       gettext (dirs[i].label),
                                       gettext (dirs[i].dialog_title),
                                       GTK_GRID (grid), i, NULL);
      }
  }


  /*********************/
  /* Folders / <paths> */
  /*********************/
  {
    static const struct
    {
      const gchar *tree_label;
      const gchar *label;
      const gchar *icon;
      const gchar *help_data;
      const gchar *reset_label;
      const gchar *fs_label;
      const gchar *path_property_name;
      const gchar *writable_property_name;
    }
    paths[] =
    {
      { N_("Brushes"), N_("Brush Folders"),
        "folders-brushes",
        PIKA_HELP_PREFS_FOLDERS_BRUSHES,
        N_("Reset Brush _Folders"),
        N_("Select Brush Folders"),
        "brush-path", "brush-path-writable" },
      { N_("Dynamics"), N_("Dynamics Folders"),
        "folders-dynamics",
        PIKA_HELP_PREFS_FOLDERS_DYNAMICS,
        N_("Reset Dynamics _Folders"),
        N_("Select Dynamics Folders"),
        "dynamics-path", "dynamics-path-writable" },
      { N_("Patterns"), N_("Pattern Folders"),
        "folders-patterns",
        PIKA_HELP_PREFS_FOLDERS_PATTERNS,
        N_("Reset Pattern _Folders"),
        N_("Select Pattern Folders"),
        "pattern-path", "pattern-path-writable" },
      { N_("Palettes"), N_("Palette Folders"),
        "folders-palettes",
        PIKA_HELP_PREFS_FOLDERS_PALETTES,
        N_("Reset Palette _Folders"),
        N_("Select Palette Folders"),
        "palette-path", "palette-path-writable" },
      { N_("Gradients"), N_("Gradient Folders"),
        "folders-gradients",
        PIKA_HELP_PREFS_FOLDERS_GRADIENTS,
        N_("Reset Gradient _Folders"),
        N_("Select Gradient Folders"),
        "gradient-path", "gradient-path-writable" },
      { N_("Fonts"), N_("Font Folders"),
        "folders-fonts",
        PIKA_HELP_PREFS_FOLDERS_FONTS,
        N_("Reset Font _Folders"),
        N_("Select Font Folders"),
        "font-path", NULL },
      { N_("Tool Presets"), N_("Tool Preset Folders"),
        "folders-tool-presets",
        PIKA_HELP_PREFS_FOLDERS_TOOL_PRESETS,
        N_("Reset Tool Preset _Folders"),
        N_("Select Tool Preset Folders"),
        "tool-preset-path", "tool-preset-path-writable" },
      { N_("MyPaint Brushes"), N_("MyPaint Brush Folders"),
        "folders-mypaint-brushes",
        PIKA_HELP_PREFS_FOLDERS_MYPAINT_BRUSHES,
        N_("Reset MyPaint Brush _Folders"),
        N_("Select MyPaint Brush Folders"),
        "mypaint-brush-path", "mypaint-brush-path-writable" },
      { N_("Plug-ins"), N_("Plug-in Folders"),
        "folders-plug-ins",
        PIKA_HELP_PREFS_FOLDERS_PLUG_INS,
        N_("Reset plug-in _Folders"),
        N_("Select plug-in Folders"),
        "plug-in-path", NULL },
      { N_("Scripts"), N_("Script-Fu Folders"),
        "folders-scripts",
        PIKA_HELP_PREFS_FOLDERS_SCRIPTS,
        N_("Reset Script-Fu _Folders"),
        N_("Select Script-Fu Folders"),
        "script-fu-path", NULL },
      { N_("Modules"), N_("Module Folders"),
        "folders-modules",
        PIKA_HELP_PREFS_FOLDERS_MODULES,
        N_("Reset Module _Folders"),
        N_("Select Module Folders"),
        "module-path", NULL },
      { N_("Interpreters"), N_("Interpreter Folders"),
        "folders-interp",
        PIKA_HELP_PREFS_FOLDERS_INTERPRETERS,
        N_("Reset Interpreter _Folders"),
        N_("Select Interpreter Folders"),
        "interpreter-path", NULL },
      { N_("Environment"), N_("Environment Folders"),
        "folders-environ",
        PIKA_HELP_PREFS_FOLDERS_ENVIRONMENT,
        N_("Reset Environment _Folders"),
        N_("Select Environment Folders"),
        "environ-path", NULL },
      { N_("Themes"), N_("Theme Folders"),
        "folders-themes",
        PIKA_HELP_PREFS_FOLDERS_THEMES,
        N_("Reset Theme _Folders"),
        N_("Select Theme Folders"),
        "theme-path", NULL },
      { N_("Icon Themes"), N_("Icon Theme Folders"),
        "folders-icon-themes",
        PIKA_HELP_PREFS_FOLDERS_ICON_THEMES,
        N_("Reset Icon Theme _Folders"),
        N_("Select Icon Theme Folders"),
        "icon-theme-path", NULL }
    };

    for (i = 0; i < G_N_ELEMENTS (paths); i++)
      {
        GtkWidget *editor;
        gchar     *icon_name;

        icon_name = g_strconcat ("pika-prefs-", paths[i].icon, NULL);
        vbox = pika_prefs_box_add_page (PIKA_PREFS_BOX (prefs_box),
                                        icon_name,
                                        gettext (paths[i].label),
                                        gettext (paths[i].tree_label),
                                        paths[i].help_data,
                                        &top_iter,
                                        &child_iter);
        g_free (icon_name);

        button = pika_prefs_box_set_page_resettable (PIKA_PREFS_BOX (prefs_box),
                                                     vbox,
                                                     gettext (paths[i].reset_label));
        g_object_set_data (G_OBJECT (button), "path",
                           (gpointer) paths[i].path_property_name);
        g_object_set_data (G_OBJECT (button), "path-writable",
                           (gpointer) paths[i].writable_property_name);
        g_signal_connect (button, "clicked",
                          G_CALLBACK (prefs_path_reset),
                          config);

        editor = pika_prop_path_editor_new (object,
                                            paths[i].path_property_name,
                                            paths[i].writable_property_name,
                                            gettext (paths[i].fs_label));
        gtk_box_pack_start (GTK_BOX (vbox), editor, TRUE, TRUE, 0);
      }
  }

  {
    GtkWidget    *tv;
    GtkTreeModel *model;
    GtkTreePath  *path;

    tv = pika_prefs_box_get_tree_view (PIKA_PREFS_BOX (prefs_box));
    gtk_tree_view_expand_all (GTK_TREE_VIEW (tv));

    /*  collapse the Folders subtree */
    model = gtk_tree_view_get_model (GTK_TREE_VIEW (tv));
    path = gtk_tree_model_get_path (model, &top_iter);
    gtk_tree_view_collapse_row (GTK_TREE_VIEW (tv), path);
    gtk_tree_path_free (path);
  }

  return dialog;
}
