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

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

#include "libpikaconfig/pikaconfig.h"
#include "libpikawidgets/pikawidgets.h"

#include "widgets-types.h"

#include "config/pikaguiconfig.h"

#include "core/pika.h"
#include "core/pikacontext.h"

#include "menus/menus.h"

#include "pikacursor.h"
#include "pikadialogfactory.h"
#include "pikadock.h"
#include "pikadockbook.h"
#include "pikadockable.h"
#include "pikadockcontainer.h"
#include "pikadockwindow.h"
#include "pikamenufactory.h"
#include "pikasessioninfo.h"
#include "pikawidgets-utils.h"

#include "pika-log.h"


enum
{
  DOCK_WINDOW_ADDED,
  DOCK_WINDOW_REMOVED,
  LAST_SIGNAL
};


struct _PikaDialogFactoryPrivate
{
  PikaContext      *context;

  GList            *open_dialogs;
  GList            *session_infos;

  GList            *registered_dialogs;

  PikaDialogsState  dialog_state;
};


static void        pika_dialog_factory_dispose              (GObject                *object);
static void        pika_dialog_factory_finalize             (GObject                *object);
static GtkWidget * pika_dialog_factory_constructor          (PikaDialogFactory      *factory,
                                                             PikaDialogFactoryEntry *entry,
                                                             PikaContext            *context,
                                                             PikaUIManager          *ui_manager,
                                                             gint                    view_size);
static void        pika_dialog_factory_config_notify        (PikaDialogFactory      *factory,
                                                             GParamSpec             *pspec,
                                                             PikaGuiConfig          *config);
static void        pika_dialog_factory_set_widget_data      (GtkWidget              *dialog,
                                                             PikaDialogFactory      *factory,
                                                             PikaDialogFactoryEntry *entry);
static void        pika_dialog_factory_unset_widget_data    (GtkWidget              *dialog);
static gboolean    pika_dialog_factory_set_user_pos         (GtkWidget              *dialog,
                                                             GdkEventConfigure      *cevent,
                                                             gpointer                data);
static gboolean    pika_dialog_factory_dialog_configure     (GtkWidget              *dialog,
                                                             GdkEventConfigure      *cevent,
                                                             PikaDialogFactory      *factory);
static void        pika_dialog_factory_hide                 (PikaDialogFactory      *factory);
static void        pika_dialog_factory_show                 (PikaDialogFactory      *factory);


G_DEFINE_TYPE_WITH_PRIVATE (PikaDialogFactory, pika_dialog_factory,
                            PIKA_TYPE_OBJECT)

#define parent_class pika_dialog_factory_parent_class

static guint factory_signals[LAST_SIGNAL] = { 0 };


/* Is set by dialogs.c to a dialog factory initialized there.
 *
 * FIXME: The layer above should not do this kind of initialization of
 * layers below.
 */
static PikaDialogFactory *pika_toplevel_factory = NULL;


static void
pika_dialog_factory_class_init (PikaDialogFactoryClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  object_class->dispose  = pika_dialog_factory_dispose;
  object_class->finalize = pika_dialog_factory_finalize;

  factory_signals[DOCK_WINDOW_ADDED] =
    g_signal_new ("dock-window-added",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (PikaDialogFactoryClass, dock_window_added),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 1,
                  PIKA_TYPE_DOCK_WINDOW);

  factory_signals[DOCK_WINDOW_REMOVED] =
    g_signal_new ("dock-window-removed",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (PikaDialogFactoryClass, dock_window_removed),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 1,
                  PIKA_TYPE_DOCK_WINDOW);
}

static void
pika_dialog_factory_init (PikaDialogFactory *factory)
{
  factory->p = pika_dialog_factory_get_instance_private (factory);
  factory->p->dialog_state = PIKA_DIALOGS_SHOWN;
}

static void
pika_dialog_factory_dispose (GObject *object)
{
  PikaDialogFactory *factory = PIKA_DIALOG_FACTORY (object);
  GList             *list;

  /*  start iterating from the beginning each time we destroyed a
   *  toplevel because destroying a dock may cause lots of items
   *  to be removed from factory->p->open_dialogs
   */
  while (factory->p->open_dialogs)
    {
      for (list = factory->p->open_dialogs; list; list = g_list_next (list))
        {
          if (gtk_widget_is_toplevel (list->data))
            {
              gtk_widget_destroy (GTK_WIDGET (list->data));
              break;
            }
        }

      /*  the list being non-empty without any toplevel is an error,
       *  so eek and chain up
       */
      if (! list)
        {
          g_warning ("%s: %d stale non-toplevel entries in factory->p->open_dialogs",
                     G_STRFUNC, g_list_length (factory->p->open_dialogs));
          break;
        }
    }

  if (factory->p->open_dialogs)
    {
      g_list_free (factory->p->open_dialogs);
      factory->p->open_dialogs = NULL;
    }

  if (factory->p->session_infos)
    {
      g_list_free_full (factory->p->session_infos,
                        (GDestroyNotify) g_object_unref);
      factory->p->session_infos = NULL;
    }

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

static void
pika_dialog_factory_finalize (GObject *object)
{
  PikaDialogFactory *factory = PIKA_DIALOG_FACTORY (object);
  GList             *list;

  for (list = factory->p->registered_dialogs; list; list = g_list_next (list))
    {
      PikaDialogFactoryEntry *entry = list->data;

      g_free (entry->identifier);
      g_free (entry->name);
      g_free (entry->blurb);
      g_free (entry->icon_name);
      g_free (entry->help_id);

      g_slice_free (PikaDialogFactoryEntry, entry);
    }

  if (factory->p->registered_dialogs)
    {
      g_list_free (factory->p->registered_dialogs);
      factory->p->registered_dialogs = NULL;
    }

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

PikaDialogFactory *
pika_dialog_factory_new (const gchar *name,
                         PikaContext *context)
{
  PikaDialogFactory *factory;
  PikaGuiConfig     *config;

  g_return_val_if_fail (name != NULL, NULL);
  g_return_val_if_fail (PIKA_IS_CONTEXT (context), NULL);

  factory = g_object_new (PIKA_TYPE_DIALOG_FACTORY, NULL);

  pika_object_set_name (PIKA_OBJECT (factory), name);

  config = PIKA_GUI_CONFIG (context->pika->config);

  factory->p->context      = context;
  factory->p->dialog_state = (config->hide_docks ?
                              PIKA_DIALOGS_HIDDEN_EXPLICITLY :
                              PIKA_DIALOGS_SHOWN);

  g_signal_connect_object (config, "notify::hide-docks",
                           G_CALLBACK (pika_dialog_factory_config_notify),
                           factory, G_CONNECT_SWAPPED);

  return factory;
}

void
pika_dialog_factory_register_entry (PikaDialogFactory    *factory,
                                    const gchar          *identifier,
                                    const gchar          *name,
                                    const gchar          *blurb,
                                    const gchar          *icon_name,
                                    const gchar          *help_id,
                                    PikaDialogNewFunc     new_func,
                                    PikaDialogRestoreFunc restore_func,
                                    gint                  view_size,
                                    gboolean              singleton,
                                    gboolean              session_managed,
                                    gboolean              remember_size,
                                    gboolean              remember_if_open,
                                    gboolean              hideable,
                                    gboolean              image_window,
                                    gboolean              dockable)
{
  PikaDialogFactoryEntry *entry;

  g_return_if_fail (PIKA_IS_DIALOG_FACTORY (factory));
  g_return_if_fail (identifier != NULL);

  entry = g_slice_new0 (PikaDialogFactoryEntry);

  entry->identifier       = g_strdup (identifier);
  entry->name             = g_strdup (name);
  entry->blurb            = g_strdup (blurb);
  entry->icon_name        = g_strdup (icon_name);
  entry->help_id          = g_strdup (help_id);
  entry->new_func         = new_func;
  entry->restore_func     = restore_func;
  entry->view_size        = view_size;
  entry->singleton        = singleton ? TRUE : FALSE;
  entry->session_managed  = session_managed ? TRUE : FALSE;
  entry->remember_size    = remember_size ? TRUE : FALSE;
  entry->remember_if_open = remember_if_open ? TRUE : FALSE;
  entry->hideable         = hideable ? TRUE : FALSE;
  entry->image_window     = image_window ? TRUE : FALSE;
  entry->dockable         = dockable ? TRUE : FALSE;

  factory->p->registered_dialogs = g_list_prepend (factory->p->registered_dialogs,
                                                   entry);
}

PikaDialogFactoryEntry *
pika_dialog_factory_find_entry (PikaDialogFactory *factory,
                                const gchar       *identifier)
{
  GList *list;

  g_return_val_if_fail (PIKA_IS_DIALOG_FACTORY (factory), NULL);
  g_return_val_if_fail (identifier != NULL, NULL);

  for (list = factory->p->registered_dialogs; list; list = g_list_next (list))
    {
      PikaDialogFactoryEntry *entry = list->data;

      if (! strcmp (identifier, entry->identifier))
        return entry;
    }

  return NULL;
}

PikaSessionInfo *
pika_dialog_factory_find_session_info (PikaDialogFactory *factory,
                                       const gchar       *identifier)
{
  GList *list;

  g_return_val_if_fail (PIKA_IS_DIALOG_FACTORY (factory), NULL);
  g_return_val_if_fail (identifier != NULL, NULL);

  for (list = factory->p->session_infos; list; list = g_list_next (list))
    {
      PikaSessionInfo *info = list->data;

      if (pika_session_info_get_factory_entry (info) &&
          g_str_equal (identifier,
                       pika_session_info_get_factory_entry (info)->identifier))
        {
          return info;
        }
    }

  return NULL;
}

GtkWidget *
pika_dialog_factory_find_widget (PikaDialogFactory *factory,
                                 const gchar       *identifiers)
{
  GtkWidget  *widget = NULL;
  gchar     **ids;
  gint        i;

  g_return_val_if_fail (PIKA_IS_DIALOG_FACTORY (factory), NULL);
  g_return_val_if_fail (identifiers != NULL, NULL);

  ids = g_strsplit (identifiers, "|", 0);

  for (i = 0; ids[i]; i++)
    {
      PikaSessionInfo *info;

      info = pika_dialog_factory_find_session_info (factory, ids[i]);

      if (info)
        {
          widget =  pika_session_info_get_widget (info);

          if (widget)
            break;
        }
    }

  g_strfreev (ids);

  return widget;
}

/**
 * pika_dialog_factory_dialog_sane:
 * @factory:
 * @widget_factory:
 * @widget_entry:
 * @widget:
 *
 * Makes sure that the @widget with the given @widget_entry that was
 * created by the given @widget_factory belongs to @efactory.
 *
 * Returns: %TRUE if that is the case, %FALSE otherwise.
 **/
static gboolean
pika_dialog_factory_dialog_sane (PikaDialogFactory      *factory,
                                 PikaDialogFactory      *widget_factory,
                                 PikaDialogFactoryEntry *widget_entry,
                                 GtkWidget              *widget)
{
  if (! widget_factory || ! widget_entry)
    {
      g_warning ("%s: dialog was not created by a PikaDialogFactory",
                 G_STRFUNC);
      return FALSE;
    }

  if (widget_factory != factory)
    {
      g_warning ("%s: dialog was created by a different PikaDialogFactory",
                 G_STRFUNC);
      return FALSE;
    }

  return TRUE;
}

/**
 * pika_dialog_factory_dialog_new_internal:
 * @factory:
 * @monitor:
 * @context:
 * @ui_manager:
 * @parent:
 * @identifier:
 * @view_size:
 * @return_existing:   If %TRUE, (or if the dialog is a singleton),
 *                     don't create a new dialog if it exists, instead
 *                     return the existing one
 * @present:           If %TRUE, the toplevel that contains the dialog (if any)
 *                     will be gtk_window_present():ed
 * @create_containers: If %TRUE, then containers for the
 *                     dialog/dockable will be created as well. If you
 *                     want to manage your own containers, pass %FALSE
 *
 * This is the lowest level dialog factory creation function.
 *
 * Returns: A created or existing #GtkWidget.
 **/
static GtkWidget *
pika_dialog_factory_dialog_new_internal (PikaDialogFactory *factory,
                                         GdkMonitor        *monitor,
                                         PikaContext       *context,
                                         PikaUIManager     *ui_manager,
                                         GtkWidget         *parent,
                                         const gchar       *identifier,
                                         gint               view_size,
                                         gboolean           return_existing,
                                         gboolean           present,
                                         gboolean           create_containers)
{
  PikaDialogFactoryEntry *entry    = NULL;
  GtkWidget              *dialog   = NULL;
  GtkWidget              *toplevel = NULL;

  g_return_val_if_fail (PIKA_IS_DIALOG_FACTORY (factory), NULL);
  g_return_val_if_fail (identifier != NULL, NULL);

  entry = pika_dialog_factory_find_entry (factory, identifier);

  if (! entry)
    {
      g_warning ("%s: no entry registered for \"%s\"",
                 G_STRFUNC, identifier);
      return NULL;
    }

  if (! entry->new_func)
    {
      g_warning ("%s: entry for \"%s\" has no constructor",
                 G_STRFUNC, identifier);
      return NULL;
    }

  /*  a singleton dialog is always returned if it already exists  */
  if (return_existing || entry->singleton)
    {
      dialog = pika_dialog_factory_find_widget (factory, identifier);
    }

  /*  create the dialog if it was not found  */
  if (! dialog)
    {
      GtkWidget *dock        = NULL;
      GtkWidget *dockbook    = NULL;
      GtkWidget *dock_window = NULL;

      /* What follows is special-case code for some entries. At some
       * point we might want to abstract this block of code away.
       */
      if (create_containers)
        {
          if (entry->dockable)
            {
              /*  It doesn't make sense to have a dockable without a dock
               *  so create one. Create a new dock _before_ creating the
               *  dialog. We do this because the new dockable needs to be
               *  created in its dock's context.
               */
              dock = pika_dock_with_window_new (factory, monitor, FALSE);
              dockbook = pika_dockbook_new (menus_get_global_menu_factory (factory->p->context->pika));

              pika_dock_add_book (PIKA_DOCK (dock),
                                  PIKA_DOCKBOOK (dockbook),
                                  0);
            }
          else if (strcmp ("pika-toolbox", entry->identifier) == 0)
            {
              PikaDockContainer *dock_container;

              dock_window = pika_dialog_factory_dialog_new (factory,
                                                            monitor,
                                                            NULL /*ui_manager*/,
                                                            parent,
                                                            "pika-toolbox-window",
                                                            -1 /*view_size*/,
                                                            FALSE /*present*/);

              /* When we get a dock window, we also get a UI
               * manager
               */
              dock_container = PIKA_DOCK_CONTAINER (dock_window);
              ui_manager     = pika_dock_container_get_ui_manager (dock_container);
            }
        }

      /*  Create the new dialog in the appropriate context which is
       *  - the passed context if not NULL
       *  - the newly created dock's context if we just created it
       *  - the factory's context, which happens when raising a toplevel
       *    dialog was the original request.
       */
      if (view_size < PIKA_VIEW_SIZE_TINY)
        view_size = entry->view_size;

      if (context)
        dialog = pika_dialog_factory_constructor (factory, entry,
                                                  context,
                                                  ui_manager,
                                                  view_size);
      else if (dock)
        dialog = pika_dialog_factory_constructor (factory, entry,
                                                  pika_dock_get_context (PIKA_DOCK (dock)),
                                                  pika_dock_get_ui_manager (PIKA_DOCK (dock)),
                                                  view_size);
      else
        dialog = pika_dialog_factory_constructor (factory, entry,
                                                  factory->p->context,
                                                  ui_manager,
                                                  view_size);

      if (dialog)
        {
          pika_dialog_factory_set_widget_data (dialog, factory, entry);

          /*  If we created a dock before, the newly created dialog is
           *  supposed to be a PikaDockable.
           */
          if (dock)
            {
              if (PIKA_IS_DOCKABLE (dialog))
                {
                  gtk_notebook_append_page (GTK_NOTEBOOK (dockbook),
                                            dialog, NULL);
                  gtk_widget_show (dock);
                }
              else
                {
                  g_warning ("%s: PikaDialogFactory is a dockable factory "
                             "but constructor for \"%s\" did not return a "
                             "PikaDockable",
                             G_STRFUNC, identifier);

                  gtk_widget_destroy (dialog);
                  gtk_widget_destroy (dock);

                  dialog = NULL;
                  dock   = NULL;
                }
            }
          else if (dock_window)
            {
              if (PIKA_IS_DOCK (dialog))
                {
                  pika_dock_window_add_dock (PIKA_DOCK_WINDOW (dock_window),
                                             PIKA_DOCK (dialog),
                                             -1 /*index*/);

                  gtk_widget_set_visible (dialog, present);
                  gtk_widget_set_visible (dock_window, present);
                }
              else
                {
                  g_warning ("%s: PikaDialogFactory is a dock factory entry "
                             "but constructor for \"%s\" did not return a "
                             "PikaDock",
                             G_STRFUNC, identifier);

                  gtk_widget_destroy (dialog);
                  gtk_widget_destroy (dock_window);

                  dialog      = NULL;
                  dock_window = NULL;
                }
            }
        }
      else if (dock)
        {
          g_warning ("%s: constructor for \"%s\" returned NULL",
                     G_STRFUNC, identifier);

          gtk_widget_destroy (dock);

          dock = NULL;
        }

      if (dialog)
        pika_dialog_factory_add_dialog (factory, dialog, monitor);
    }

  /*  Finally, if we found an existing dialog or created a new one, raise it.
   */
  if (! dialog)
    return NULL;

  if (gtk_widget_is_toplevel (dialog))
    {
      gtk_window_set_screen (GTK_WINDOW (dialog),
                             gdk_display_get_default_screen (gdk_monitor_get_display (monitor)));

      if (parent)
        {
          GtkWidget *parent_toplevel = gtk_widget_get_toplevel (parent);

          if (GTK_IS_WINDOW (parent_toplevel))
            {
              gtk_window_set_transient_for (GTK_WINDOW (dialog),
                                            GTK_WINDOW (parent_toplevel));
            }
        }

      toplevel = dialog;
    }
  else if (PIKA_IS_DOCK (dialog))
    {
      toplevel = gtk_widget_get_toplevel (dialog);
    }
  else if (PIKA_IS_DOCKABLE (dialog))
    {
      PikaDockable *dockable = PIKA_DOCKABLE (dialog);

      if (pika_dockable_get_dockbook (dockable) &&
          pika_dockbook_get_dock (pika_dockable_get_dockbook (dockable)))
        {
          GtkNotebook *notebook = GTK_NOTEBOOK (pika_dockable_get_dockbook (dockable));
          gint         num      = gtk_notebook_page_num (notebook, dialog);

          if (num != -1)
            {
              gtk_notebook_set_current_page (notebook, num);

              pika_widget_blink (dialog);
            }
        }

      toplevel = gtk_widget_get_toplevel (dialog);
    }

  if (present && GTK_IS_WINDOW (toplevel))
    {
      /*  Work around focus-stealing protection, or whatever makes the
       *  dock appear below the one where we clicked a button to open
       *  it. See bug #630173.
       */
      gtk_widget_show_now (toplevel);
      gdk_window_raise (gtk_widget_get_window (toplevel));
    }

  return dialog;
}

/**
 * pika_dialog_factory_dialog_new:
 * @factory:      a #PikaDialogFactory
 * @monitor       the #GdkMonitor the dialog should appear on
 * @ui_manager:   A #PikaUIManager, if applicable.
 * @parent:       The parent widget, from which the call originated, if
 *                applicable, NULL otherwise.
 * @identifier:   the identifier of the dialog as registered with
 *                pika_dialog_factory_register_entry()
 * @view_size:    the initial preview size
 * @present:      whether gtk_window_present() should be called
 *
 * Creates a new toplevel dialog or a #PikaDockable, depending on whether
 * %factory is a toplevel of dockable factory.
 *
 * Returns: the newly created dialog or an already existing singleton
 *               dialog.
 **/
GtkWidget *
pika_dialog_factory_dialog_new (PikaDialogFactory *factory,
                                GdkMonitor        *monitor,
                                PikaUIManager     *ui_manager,
                                GtkWidget         *parent,
                                const gchar       *identifier,
                                gint               view_size,
                                gboolean           present)
{
  g_return_val_if_fail (PIKA_IS_DIALOG_FACTORY (factory), NULL);
  g_return_val_if_fail (GDK_IS_MONITOR (monitor), NULL);
  g_return_val_if_fail (identifier != NULL, NULL);

  return pika_dialog_factory_dialog_new_internal (factory,
                                                  monitor,
                                                  factory->p->context,
                                                  ui_manager,
                                                  parent,
                                                  identifier,
                                                  view_size,
                                                  FALSE /*return_existing*/,
                                                  present,
                                                  FALSE /*create_containers*/);
}

PikaContext *
pika_dialog_factory_get_context (PikaDialogFactory *factory)
{
  g_return_val_if_fail (PIKA_IS_DIALOG_FACTORY (factory), NULL);

  return factory->p->context;
}

GList *
pika_dialog_factory_get_open_dialogs (PikaDialogFactory *factory)
{
  g_return_val_if_fail (PIKA_IS_DIALOG_FACTORY (factory), NULL);

  return factory->p->open_dialogs;
}

GList *
pika_dialog_factory_get_session_infos (PikaDialogFactory *factory)
{
  g_return_val_if_fail (PIKA_IS_DIALOG_FACTORY (factory), NULL);

  return factory->p->session_infos;
}

void
pika_dialog_factory_add_session_info (PikaDialogFactory *factory,
                                      PikaSessionInfo   *info)
{
  g_return_if_fail (PIKA_IS_DIALOG_FACTORY (factory));
  g_return_if_fail (PIKA_IS_SESSION_INFO (info));

  /* We want to append rather than prepend so that the serialized
   * order in sessionrc remains the same
   */
  factory->p->session_infos = g_list_append (factory->p->session_infos,
                                             g_object_ref (info));
}

/**
 * pika_dialog_factory_dialog_raise:
 * @factory:      a #PikaDialogFactory
 * @monitor:      the #GdkMonitor the dialog should appear on
 * @parent:       the #GtkWidget from which the raised dialog
 *                originated, if applicable, %NULL otherwise.
 * @identifiers:  a '|' separated list of identifiers of dialogs as
 *                registered with pika_dialog_factory_register_entry()
 * @view_size:    the initial preview size if a dialog needs to be created
 *
 * Raises any of a list of already existing toplevel dialog or
 * #PikaDockable if it was already created by this %facory.
 *
 * Implicitly creates the first dialog in the list if none of the dialogs
 * were found.
 *
 * Returns: the raised or newly created dialog.
 **/
GtkWidget *
pika_dialog_factory_dialog_raise (PikaDialogFactory *factory,
                                  GdkMonitor        *monitor,
                                  GtkWidget         *parent,
                                  const gchar       *identifiers,
                                  gint               view_size)
{
  GtkWidget *dialog;
  gchar    **ids;
  gint       i;

  g_return_val_if_fail (PIKA_IS_DIALOG_FACTORY (factory), NULL);
  g_return_val_if_fail (GDK_IS_MONITOR (monitor), NULL);
  g_return_val_if_fail (identifiers != NULL, NULL);

  /*  If the identifier is a list, try to find a matching dialog and
   *  raise it. If there's no match, use the first list item.
   *
   *  (we split the identifier list manually here because we must pass
   *  a single identifier, not a list, to new_internal() below)
   */
  ids = g_strsplit (identifiers, "|", 0);
  for (i = 0; ids[i]; i++)
    {
      if (pika_dialog_factory_find_widget (factory, ids[i]))
        break;
    }

  dialog = pika_dialog_factory_dialog_new_internal (factory,
                                                    monitor,
                                                    NULL,
                                                    NULL,
                                                    parent,
                                                    ids[i] ? ids[i] : ids[0],
                                                    view_size,
                                                    TRUE /*return_existing*/,
                                                    TRUE /*present*/,
                                                    TRUE /*create_containers*/);
  g_strfreev (ids);

  return dialog;
}

/**
 * pika_dialog_factory_dockable_new:
 * @factory:      a #PikaDialogFactory
 * @dock:         a #PikaDock created by this %factory.
 * @identifier:   the identifier of the dialog as registered with
 *                pika_dialog_factory_register_entry()
 * @view_size:
 *
 * Creates a new #PikaDockable in the context of the #PikaDock it will be
 * added to.
 *
 * Implicitly raises & returns an already existing singleton dockable,
 * so callers should check that pika_dockable_get_dockbook (dockable)
 * is NULL before trying to add it to it's #PikaDockbook.
 *
 * Returns: the newly created #PikaDockable or an already existing
 *               singleton dockable.
 **/
GtkWidget *
pika_dialog_factory_dockable_new (PikaDialogFactory *factory,
                                  PikaDock          *dock,
                                  const gchar       *identifier,
                                  gint               view_size)
{
  g_return_val_if_fail (PIKA_IS_DIALOG_FACTORY (factory), NULL);
  g_return_val_if_fail (PIKA_IS_DOCK (dock), NULL);
  g_return_val_if_fail (identifier != NULL, NULL);

  return pika_dialog_factory_dialog_new_internal (factory,
                                                  pika_widget_get_monitor (GTK_WIDGET (dock)),
                                                  pika_dock_get_context (dock),
                                                  pika_dock_get_ui_manager (dock),
                                                  NULL,
                                                  identifier,
                                                  view_size,
                                                  FALSE /*return_existing*/,
                                                  FALSE /*present*/,
                                                  FALSE /*create_containers*/);
}

void
pika_dialog_factory_add_dialog (PikaDialogFactory *factory,
                                GtkWidget         *dialog,
                                GdkMonitor        *monitor)
{
  PikaDialogFactory      *dialog_factory = NULL;
  PikaDialogFactoryEntry *entry          = NULL;
  PikaSessionInfo        *info           = NULL;
  GList                  *list           = NULL;
  gboolean                toplevel       = FALSE;

  g_return_if_fail (PIKA_IS_DIALOG_FACTORY (factory));
  g_return_if_fail (GTK_IS_WIDGET (dialog));
  g_return_if_fail (GDK_IS_MONITOR (monitor));

  if (g_list_find (factory->p->open_dialogs, dialog))
    {
      g_warning ("%s: dialog already registered", G_STRFUNC);
      return;
    }

  dialog_factory = pika_dialog_factory_from_widget (dialog, &entry);

  if (! pika_dialog_factory_dialog_sane (factory,
                                         dialog_factory,
                                         entry,
                                         dialog))
    return;

  toplevel = gtk_widget_is_toplevel (dialog);

  if (entry)
    {
      /* dialog is a toplevel (but not a PikaDockWindow) or a PikaDockable */

      PIKA_LOG (DIALOG_FACTORY, "adding %s \"%s\"",
                toplevel ? "toplevel" : "dockable",
                entry->identifier);

      for (list = factory->p->session_infos; list; list = g_list_next (list))
        {
          PikaSessionInfo *current_info = list->data;

          if (pika_session_info_get_factory_entry (current_info) == entry)
            {
              if (pika_session_info_get_widget (current_info))
                {
                  if (pika_session_info_is_singleton (current_info))
                    {
                      g_warning ("%s: singleton dialog \"%s\" created twice",
                                 G_STRFUNC, entry->identifier);

                      PIKA_LOG (DIALOG_FACTORY,
                                "corrupt session info: %p (widget %p)",
                                current_info,
                                pika_session_info_get_widget (current_info));

                      return;
                    }

                  continue;
                }

              pika_session_info_set_widget (current_info, dialog);

              PIKA_LOG (DIALOG_FACTORY,
                        "updating session info %p (widget %p) for %s \"%s\"",
                        current_info,
                        pika_session_info_get_widget (current_info),
                        toplevel ? "toplevel" : "dockable",
                        entry->identifier);

              if (toplevel &&
                  pika_session_info_is_session_managed (current_info) &&
                  ! gtk_widget_get_visible (dialog))
                {
                  PikaGuiConfig *gui_config;

                  gui_config = PIKA_GUI_CONFIG (factory->p->context->pika->config);

                  pika_session_info_apply_geometry (current_info,
                                                    monitor,
                                                    gui_config->restore_monitor);
                }

              info = current_info;
              break;
            }
        }

      if (! info)
        {
          info = pika_session_info_new ();

          pika_session_info_set_widget (info, dialog);

          PIKA_LOG (DIALOG_FACTORY,
                    "creating session info %p (widget %p) for %s \"%s\"",
                    info,
                    pika_session_info_get_widget (info),
                    toplevel ? "toplevel" : "dockable",
                    entry->identifier);

          pika_session_info_set_factory_entry (info, entry);

          if (pika_session_info_is_session_managed (info))
            {
              /* Make the dialog show up at the user position the
               * first time it is shown. After it has been shown the
               * first time we don't want it to show at the mouse the
               * next time. Think of the use cases "hide and show with
               * tab" and "change virtual desktops"
               */
              PIKA_LOG (WM, "setting GTK_WIN_POS_MOUSE for %p (\"%s\")\n",
                        dialog, entry->identifier);

              gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
              g_signal_connect (dialog, "configure-event",
                                G_CALLBACK (pika_dialog_factory_set_user_pos),
                                NULL);
            }

          pika_dialog_factory_add_session_info (factory, info);
          g_object_unref (info);
        }
    }

  /* Some special logic for dock windows */
  if (PIKA_IS_DOCK_WINDOW (dialog))
    {
      g_signal_emit (factory, factory_signals[DOCK_WINDOW_ADDED], 0, dialog);
    }

  factory->p->open_dialogs = g_list_prepend (factory->p->open_dialogs, dialog);

  g_signal_connect_object (dialog, "destroy",
                           G_CALLBACK (pika_dialog_factory_remove_dialog),
                           factory,
                           G_CONNECT_SWAPPED);

  if (pika_session_info_is_session_managed (info))
    g_signal_connect_object (dialog, "configure-event",
                             G_CALLBACK (pika_dialog_factory_dialog_configure),
                             factory,
                             0);
}

void
pika_dialog_factory_add_foreign (PikaDialogFactory *factory,
                                 const gchar       *identifier,
                                 GtkWidget         *dialog,
                                 GdkMonitor        *monitor)
{
  PikaDialogFactory      *dialog_factory;
  PikaDialogFactoryEntry *entry;

  g_return_if_fail (PIKA_IS_DIALOG_FACTORY (factory));
  g_return_if_fail (identifier != NULL);
  g_return_if_fail (GTK_IS_WIDGET (dialog));
  g_return_if_fail (gtk_widget_is_toplevel (dialog));
  g_return_if_fail (GDK_IS_MONITOR (monitor));

  dialog_factory = pika_dialog_factory_from_widget (dialog, &entry);

  if (dialog_factory || entry)
    {
      g_warning ("%s: dialog was created by a PikaDialogFactory",
                 G_STRFUNC);
      return;
    }

  entry = pika_dialog_factory_find_entry (factory, identifier);

  if (! entry)
    {
      g_warning ("%s: no entry registered for \"%s\"",
                 G_STRFUNC, identifier);
      return;
    }

  if (entry->new_func)
    {
      g_warning ("%s: entry for \"%s\" has a constructor (is not foreign)",
                 G_STRFUNC, identifier);
      return;
    }

  pika_dialog_factory_set_widget_data (dialog, factory, entry);

  pika_dialog_factory_add_dialog (factory, dialog, monitor);
}

/**
 * pika_dialog_factory_position_dialog:
 * @factory:
 * @identifier:
 * @dialog:
 * @monitor:
 *
 * We correctly position all newly created dialog via
 * pika_dialog_factory_add_dialog(), but some dialogs (like various
 * color dialogs) are never destroyed but created only once per
 * session. On re-showing, whatever window managing magic kicks in and
 * the dialog sometimes goes where it shouldn't.
 *
 * This function correctly positions a dialog on re-showing so it
 * appears where it was before it was hidden.
 *
 * See https://gitlab.gnome.org/GNOME/pika/issues/1093
 **/
void
pika_dialog_factory_position_dialog (PikaDialogFactory *factory,
                                     const gchar       *identifier,
                                     GtkWidget         *dialog,
                                     GdkMonitor        *monitor)
{
  PikaSessionInfo *info;
  PikaGuiConfig   *gui_config;

  g_return_if_fail (PIKA_IS_DIALOG_FACTORY (factory));
  g_return_if_fail (identifier != NULL);
  g_return_if_fail (GTK_IS_WIDGET (dialog));
  g_return_if_fail (gtk_widget_is_toplevel (dialog));
  g_return_if_fail (GDK_IS_MONITOR (monitor));

  info = pika_dialog_factory_find_session_info (factory, identifier);

  if (! info)
    {
      g_warning ("%s: no session info found for \"%s\"",
                 G_STRFUNC, identifier);
      return;
    }

  if (pika_session_info_get_widget (info) != dialog)
    {
      g_warning ("%s: session info for \"%s\" is for a different widget",
                 G_STRFUNC, identifier);
      return;
    }

  gui_config = PIKA_GUI_CONFIG (factory->p->context->pika->config);

  pika_session_info_apply_geometry (info,
                                    monitor,
                                    gui_config->restore_monitor);
}

void
pika_dialog_factory_remove_dialog (PikaDialogFactory *factory,
                                   GtkWidget         *dialog)
{
  PikaDialogFactory      *dialog_factory;
  PikaDialogFactoryEntry *entry;
  GList                  *list;

  g_return_if_fail (PIKA_IS_DIALOG_FACTORY (factory));
  g_return_if_fail (GTK_IS_WIDGET (dialog));

  if (! g_list_find (factory->p->open_dialogs, dialog))
    {
      g_warning ("%s: dialog not registered", G_STRFUNC);
      return;
    }

  factory->p->open_dialogs = g_list_remove (factory->p->open_dialogs, dialog);

  dialog_factory = pika_dialog_factory_from_widget (dialog, &entry);

  if (! pika_dialog_factory_dialog_sane (factory,
                                         dialog_factory,
                                         entry,
                                         dialog))
    return;

  PIKA_LOG (DIALOG_FACTORY, "removing \"%s\" (dialog = %p)",
            entry->identifier,
            dialog);

  for (list = factory->p->session_infos; list; list = g_list_next (list))
    {
      PikaSessionInfo *session_info = list->data;

      if (pika_session_info_get_widget (session_info) == dialog)
        {
          PIKA_LOG (DIALOG_FACTORY,
                    "clearing session info %p (widget %p) for \"%s\"",
                    session_info, pika_session_info_get_widget (session_info),
                    entry->identifier);

          pika_session_info_set_widget (session_info, NULL);

          pika_dialog_factory_unset_widget_data (dialog);

          g_signal_handlers_disconnect_by_func (dialog,
                                                pika_dialog_factory_set_user_pos,
                                                NULL);
          g_signal_handlers_disconnect_by_func (dialog,
                                                pika_dialog_factory_remove_dialog,
                                                factory);

          if (pika_session_info_is_session_managed (session_info))
            g_signal_handlers_disconnect_by_func (dialog,
                                                  pika_dialog_factory_dialog_configure,
                                                  factory);

          if (PIKA_IS_DOCK_WINDOW (dialog))
            {
              /*  don't save session info for empty docks  */
              factory->p->session_infos = g_list_remove (factory->p->session_infos,
                                                         session_info);
              g_object_unref (session_info);

              g_signal_emit (factory, factory_signals[DOCK_WINDOW_REMOVED], 0,
                             dialog);
            }

          break;
        }
    }
}

void
pika_dialog_factory_hide_dialog (GtkWidget *dialog)
{
  PikaDialogFactory *factory = NULL;

  g_return_if_fail (GTK_IS_WIDGET (dialog));
  g_return_if_fail (gtk_widget_is_toplevel (dialog));

  if (! (factory = pika_dialog_factory_from_widget (dialog, NULL)))
    {
      g_warning ("%s: dialog was not created by a PikaDialogFactory",
                 G_STRFUNC);
      return;
    }

  gtk_widget_hide (dialog);

  if (factory->p->dialog_state != PIKA_DIALOGS_SHOWN)
    g_object_set_data (G_OBJECT (dialog), PIKA_DIALOG_VISIBILITY_KEY,
                       GINT_TO_POINTER (PIKA_DIALOG_VISIBILITY_INVISIBLE));
}

void
pika_dialog_factory_set_state (PikaDialogFactory *factory,
                               PikaDialogsState   state)
{
  g_return_if_fail (PIKA_IS_DIALOG_FACTORY (factory));

  factory->p->dialog_state = state;

  if (state == PIKA_DIALOGS_SHOWN)
    {
      pika_dialog_factory_show (factory);
    }
  else
    {
      pika_dialog_factory_hide (factory);
    }
}

PikaDialogsState
pika_dialog_factory_get_state (PikaDialogFactory *factory)
{
  g_return_val_if_fail (PIKA_IS_DIALOG_FACTORY (factory), 0);

  return factory->p->dialog_state;
}

void
pika_dialog_factory_show_with_display (PikaDialogFactory *factory)
{
  g_return_if_fail (PIKA_IS_DIALOG_FACTORY (factory));

  if (factory->p->dialog_state == PIKA_DIALOGS_HIDDEN_WITH_DISPLAY)
    {
      pika_dialog_factory_set_state (factory, PIKA_DIALOGS_SHOWN);
    }
}

void
pika_dialog_factory_hide_with_display (PikaDialogFactory *factory)
{
  g_return_if_fail (PIKA_IS_DIALOG_FACTORY (factory));

  if (factory->p->dialog_state == PIKA_DIALOGS_SHOWN)
    {
      pika_dialog_factory_set_state (factory, PIKA_DIALOGS_HIDDEN_WITH_DISPLAY);
    }
}

static GQuark pika_dialog_factory_key       = 0;
static GQuark pika_dialog_factory_entry_key = 0;

PikaDialogFactory *
pika_dialog_factory_from_widget (GtkWidget               *dialog,
                                 PikaDialogFactoryEntry **entry)
{
  g_return_val_if_fail (GTK_IS_WIDGET (dialog), NULL);

  if (! pika_dialog_factory_key)
    {
      pika_dialog_factory_key =
        g_quark_from_static_string ("pika-dialog-factory");

      pika_dialog_factory_entry_key =
        g_quark_from_static_string ("pika-dialog-factory-entry");
    }

  if (entry)
    *entry = g_object_get_qdata (G_OBJECT (dialog),
                                 pika_dialog_factory_entry_key);

  return g_object_get_qdata (G_OBJECT (dialog), pika_dialog_factory_key);
}

#define PIKA_DIALOG_FACTORY_MIN_SIZE_KEY "pika-dialog-factory-min-size"

void
pika_dialog_factory_set_has_min_size (GtkWindow *window,
                                      gboolean   has_min_size)
{
  g_return_if_fail (GTK_IS_WINDOW (window));

  g_object_set_data (G_OBJECT (window), PIKA_DIALOG_FACTORY_MIN_SIZE_KEY,
                     GINT_TO_POINTER (has_min_size ? TRUE : FALSE));
}

gboolean
pika_dialog_factory_get_has_min_size (GtkWindow *window)
{
  g_return_val_if_fail (GTK_IS_WINDOW (window), FALSE);

  return GPOINTER_TO_INT (g_object_get_data (G_OBJECT (window),
                                             PIKA_DIALOG_FACTORY_MIN_SIZE_KEY));
}


/*  private functions  */

static GtkWidget *
pika_dialog_factory_constructor (PikaDialogFactory      *factory,
                                 PikaDialogFactoryEntry *entry,
                                 PikaContext            *context,
                                 PikaUIManager          *ui_manager,
                                 gint                    view_size)
{
  GtkWidget *widget;

  widget = entry->new_func (factory, context, ui_manager, view_size);

  /* The entry is for a dockable, so we simply need to put the created
   * widget in a dockable
   */
  if (widget && entry->dockable)
    {
      GtkWidget *dockable = NULL;

      dockable = pika_dockable_new (entry->name, entry->blurb,
                                    entry->icon_name, entry->help_id);
      gtk_container_add (GTK_CONTAINER (dockable), widget);
      gtk_widget_show (widget);

      /* EEK */
      g_object_set_data (G_OBJECT (dockable), "pika-dialog-identifier",
                         entry->identifier);

      /* Return the dockable instead */
      widget = dockable;
    }

  return widget;
}

static void
pika_dialog_factory_config_notify (PikaDialogFactory *factory,
                                   GParamSpec        *pspec,
                                   PikaGuiConfig     *config)
{
  PikaDialogsState state     = pika_dialog_factory_get_state (factory);
  PikaDialogsState new_state = state;

  /* Make sure the state and config are in sync */
  if (config->hide_docks && state == PIKA_DIALOGS_SHOWN)
    new_state = PIKA_DIALOGS_HIDDEN_EXPLICITLY;
  else if (! config->hide_docks)
    new_state = PIKA_DIALOGS_SHOWN;

  if (state != new_state)
    pika_dialog_factory_set_state (factory, new_state);
}

static void
pika_dialog_factory_set_widget_data (GtkWidget              *dialog,
                                     PikaDialogFactory      *factory,
                                     PikaDialogFactoryEntry *entry)
{
  g_return_if_fail (GTK_IS_WIDGET (dialog));
  g_return_if_fail (PIKA_IS_DIALOG_FACTORY (factory));

  if (! pika_dialog_factory_key)
    {
      pika_dialog_factory_key =
        g_quark_from_static_string ("pika-dialog-factory");

      pika_dialog_factory_entry_key =
        g_quark_from_static_string ("pika-dialog-factory-entry");
    }

  g_object_set_qdata (G_OBJECT (dialog), pika_dialog_factory_key, factory);

  if (entry)
    g_object_set_qdata (G_OBJECT (dialog), pika_dialog_factory_entry_key,
                        entry);
}

static void
pika_dialog_factory_unset_widget_data (GtkWidget *dialog)
{
  g_return_if_fail (GTK_IS_WIDGET (dialog));

  if (! pika_dialog_factory_key)
    return;

  g_object_set_qdata (G_OBJECT (dialog), pika_dialog_factory_key,       NULL);
  g_object_set_qdata (G_OBJECT (dialog), pika_dialog_factory_entry_key, NULL);
}

static gboolean
pika_dialog_factory_set_user_pos (GtkWidget         *dialog,
                                  GdkEventConfigure *cevent,
                                  gpointer           data)
{
  GdkWindowHints geometry_mask;

  /* Not only set geometry hints, also reset window position */
  gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_NONE);
  g_signal_handlers_disconnect_by_func (dialog,
                                        pika_dialog_factory_set_user_pos,
                                        data);

  PIKA_LOG (WM, "setting GDK_HINT_USER_POS for %p\n", dialog);
  geometry_mask = GDK_HINT_USER_POS;

  if (pika_dialog_factory_get_has_min_size (GTK_WINDOW (dialog)))
    geometry_mask |= GDK_HINT_MIN_SIZE;

  gtk_window_set_geometry_hints (GTK_WINDOW (dialog), NULL, NULL,
                                 geometry_mask);

  return FALSE;
}

static gboolean
pika_dialog_factory_dialog_configure (GtkWidget         *dialog,
                                      GdkEventConfigure *cevent,
                                      PikaDialogFactory *factory)
{
  PikaDialogFactory      *dialog_factory;
  PikaDialogFactoryEntry *entry;
  GList                  *list;

  if (! g_list_find (factory->p->open_dialogs, dialog))
    {
      g_warning ("%s: dialog not registered", G_STRFUNC);
      return FALSE;
    }

  dialog_factory = pika_dialog_factory_from_widget (dialog, &entry);

  if (! pika_dialog_factory_dialog_sane (factory,
                                         dialog_factory,
                                         entry,
                                         dialog))
    return FALSE;

  for (list = factory->p->session_infos; list; list = g_list_next (list))
    {
      PikaSessionInfo *session_info = list->data;

      if (pika_session_info_get_widget (session_info) == dialog)
        {
          pika_session_info_read_geometry (session_info, cevent);

          PIKA_LOG (DIALOG_FACTORY,
                    "updated session info for \"%s\" from window geometry "
                    "(x=%d y=%d  %dx%d)",
                    entry->identifier,
                    pika_session_info_get_x (session_info),
                    pika_session_info_get_y (session_info),
                    pika_session_info_get_width (session_info),
                    pika_session_info_get_height (session_info));

          break;
        }
    }

  return FALSE;
}

void
pika_dialog_factory_save (PikaDialogFactory *factory,
                          PikaConfigWriter  *writer)
{
  GList *infos;

  for (infos = factory->p->session_infos; infos; infos = g_list_next (infos))
    {
      PikaSessionInfo *info = infos->data;

      /*  we keep session info entries for all toplevel dialogs created
       *  by the factory but don't save them if they don't want to be
       *  managed
       */
      if (! pika_session_info_is_session_managed (info) ||
          pika_session_info_get_factory_entry (info) == NULL)
        continue;

      if (pika_session_info_get_widget (info))
        pika_session_info_get_info (info);

      pika_config_writer_open (writer, "session-info");
      pika_config_writer_string (writer,
                                 pika_object_get_name (factory));

      PIKA_CONFIG_GET_IFACE (info)->serialize (PIKA_CONFIG (info), writer, NULL);

      pika_config_writer_close (writer);

      if (pika_session_info_get_widget (info))
        pika_session_info_clear_info (info);
    }
}

void
pika_dialog_factory_restore (PikaDialogFactory *factory,
                             GdkMonitor        *monitor)
{
  GList *infos;

  for (infos = factory->p->session_infos; infos; infos = g_list_next (infos))
    {
      PikaSessionInfo *info = infos->data;

      if (pika_session_info_get_open (info))
        {
          pika_session_info_restore (info, factory, monitor);
        }
      else
        {
          PIKA_LOG (DIALOG_FACTORY,
                    "skipping to restore session info %p, not open",
                    info);
        }
    }
}

static void
pika_dialog_factory_hide (PikaDialogFactory *factory)
{
  GList *list;

  for (list = factory->p->open_dialogs; list; list = g_list_next (list))
    {
      GtkWidget *widget = list->data;

      if (GTK_IS_WIDGET (widget) && gtk_widget_is_toplevel (widget))
        {
          PikaDialogFactoryEntry    *entry      = NULL;
          PikaDialogVisibilityState  visibility = PIKA_DIALOG_VISIBILITY_UNKNOWN;

          pika_dialog_factory_from_widget (widget, &entry);
          if (! entry->hideable)
            continue;

          if (gtk_widget_get_visible (widget))
            {
              gtk_widget_hide (widget);
              visibility = PIKA_DIALOG_VISIBILITY_HIDDEN;

              PIKA_LOG (WM, "Hiding '%s' [%p]",
                        gtk_window_get_title (GTK_WINDOW (widget)),
                        widget);
            }
          else
            {
              visibility = PIKA_DIALOG_VISIBILITY_INVISIBLE;
            }

          g_object_set_data (G_OBJECT (widget),
                             PIKA_DIALOG_VISIBILITY_KEY,
                             GINT_TO_POINTER (visibility));
        }
    }
}

static void
pika_dialog_factory_show (PikaDialogFactory *factory)
{
  GList *list;

  for (list = factory->p->open_dialogs; list; list = g_list_next (list))
    {
      GtkWidget *widget = list->data;

      if (GTK_IS_WIDGET (widget) && gtk_widget_is_toplevel (widget))
        {
          PikaDialogVisibilityState visibility;

          visibility =
            GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget),
                                                PIKA_DIALOG_VISIBILITY_KEY));

          if (! gtk_widget_get_visible (widget) &&
              visibility == PIKA_DIALOG_VISIBILITY_HIDDEN)
            {
              PIKA_LOG (WM, "Showing '%s' [%p]",
                        gtk_window_get_title (GTK_WINDOW (widget)),
                        widget);

              /* Don't use gtk_window_present() here, we don't want the
               * keyboard focus to move.
               */
              gtk_widget_show (widget);
              g_object_set_data (G_OBJECT (widget),
                                 PIKA_DIALOG_VISIBILITY_KEY,
                                 GINT_TO_POINTER (PIKA_DIALOG_VISIBILITY_VISIBLE));

              if (gtk_widget_get_visible (widget))
                gdk_window_raise (gtk_widget_get_window (widget));
            }
        }
    }
}

void
pika_dialog_factory_set_busy (PikaDialogFactory *factory)
{
  GList *list;

  if (! factory)
    return;

  for (list = factory->p->open_dialogs; list; list = g_list_next (list))
    {
      GtkWidget *widget = list->data;

      if (GTK_IS_WIDGET (widget) && gtk_widget_is_toplevel (widget))
        {
          GdkWindow *window = gtk_widget_get_window (widget);

          if (window)
            {
              GdkCursor *cursor = pika_cursor_new (window,
                                                   PIKA_HANDEDNESS_RIGHT,
                                                   (PikaCursorType) GDK_WATCH,
                                                   PIKA_TOOL_CURSOR_NONE,
                                                   PIKA_CURSOR_MODIFIER_NONE);
              gdk_window_set_cursor (window, cursor);
              g_object_unref (cursor);
            }
        }
    }
}

void
pika_dialog_factory_unset_busy (PikaDialogFactory *factory)
{
  GList *list;

  if (! factory)
    return;

  for (list = factory->p->open_dialogs; list; list = g_list_next (list))
    {
      GtkWidget *widget = list->data;

      if (GTK_IS_WIDGET (widget) && gtk_widget_is_toplevel (widget))
        {
          GdkWindow *window = gtk_widget_get_window (widget);

          if (window)
            gdk_window_set_cursor (window, NULL);
        }
    }
}

/**
 * pika_dialog_factory_get_singleton:
 *
 * Returns: The toplevel PikaDialogFactory instance.
 **/
PikaDialogFactory *
pika_dialog_factory_get_singleton (void)
{
  g_return_val_if_fail (pika_toplevel_factory != NULL, NULL);

  return pika_toplevel_factory;
}

/**
 * pika_dialog_factory_set_singleton:
 * @:
 *
 * Set the toplevel PikaDialogFactory instance. Must only be called by
 * dialogs_init()!.
 **/
void
pika_dialog_factory_set_singleton (PikaDialogFactory *factory)
{
  g_return_if_fail (pika_toplevel_factory == NULL ||
                    factory               == NULL);

  pika_toplevel_factory = factory;
}
