/* GIMP - The GNU Image Manipulation Program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * busy-dialog.c
 * Copyright (C) 2018 Ell
 *
 * 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 <libgimp/gimp.h>
#include <libgimp/gimpui.h>

#include "libgimp/stdplugins-intl.h"


#define PLUG_IN_PROC   "plug-in-busy-dialog"
#define PLUG_IN_BINARY "busy-dialog"
#define PLUG_IN_ROLE   "gimp-busy-dialog"


typedef struct
{
  GIOChannel *read_channel;
  GIOChannel *write_channel;
} Context;


static void                query                           (void);
static void                run                             (const gchar      *name,
                                                            gint              nparams,
                                                            const GimpParam  *param,
                                                            gint             *nreturn_vals,
                                                            GimpParam       **return_vals);

static GimpPDBStatusType   busy_dialog                     (gint              read_fd,
                                                            gint              write_fd,
                                                            const gchar      *message,
                                                            gboolean          cancelable);

static gboolean            busy_dialog_read_channel_notify (GIOChannel       *source,
                                                            GIOCondition      condition,
                                                            Context          *context);

static gboolean            busy_dialog_delete_event        (GtkDialog        *dialog,
                                                            GdkEvent         *event,
                                                            Context          *context);
static void                busy_dialog_response            (GtkDialog        *dialog,
                                                            gint              response_id,
                                                            Context          *context);


const GimpPlugInInfo PLUG_IN_INFO =
{
  NULL,  /* init_proc  */
  NULL,  /* quit_proc  */
  query, /* query_proc */
  run,   /* run_proc   */
};


MAIN ()


static void
query (void)
{
  static const GimpParamDef args [] =
  {
    { GIMP_PDB_INT32,  "run-mode",   "The run mode { RUN-INTERACTIVE (0) }"             },
    { GIMP_PDB_INT32,  "read-fd",    "The read file descriptor"                         },
    { GIMP_PDB_INT32,  "write-fd",   "The write file descriptor"                        },
    { GIMP_PDB_STRING, "message",    "The message"                                      },
    { GIMP_PDB_INT32,  "cancelable", "Whether the dialog is cancelable (TRUE or FALSE)" }
  };

  gimp_install_procedure (PLUG_IN_PROC,
                          "Show a dialog while waiting for an operation to finish",
                          "Used by GIMP to display a dialog, containing a "
                          "spinner and a custom message, while waiting for an "
                          "ongoing operation to finish. Optionally, the dialog "
                          "may provide a \"Cancel\" button, which can be used "
                          "to cancel the operation.",
                          "Ell",
                          "Ell",
                          "2018",
                          NULL,
                          "",
                          GIMP_PLUGIN,
                          G_N_ELEMENTS (args), 0,
                          args, NULL);
}

static void
run (const gchar      *name,
     gint              n_params,
     const GimpParam  *params,
     gint             *n_return_vals,
     GimpParam       **return_vals)
{
  GimpRunMode       run_mode = params[0].data.d_int32;
  GimpPDBStatusType status   = GIMP_PDB_SUCCESS;

  static GimpParam  values[1];

  values[0].type          = GIMP_PDB_STATUS;
  values[0].data.d_status = status;

  *n_return_vals = 1;
  *return_vals   = values;

  INIT_I18N ();

  switch (run_mode)
    {
    case GIMP_RUN_INTERACTIVE:
    case GIMP_RUN_NONINTERACTIVE:
    case GIMP_RUN_WITH_LAST_VALS:
      if (n_params != 5)
        {
          status = GIMP_PDB_CALLING_ERROR;
        }
      else
        {
          status = busy_dialog (params[1].data.d_int32,
                                params[2].data.d_int32,
                                params[3].data.d_string,
                                params[4].data.d_int32);
        }
      break;

    default:
      status = GIMP_PDB_CALLING_ERROR;
      break;
    }

  values[0].data.d_status = status;
}

static GimpPDBStatusType
busy_dialog (gint         read_fd,
             gint         write_fd,
             const gchar *message,
             gboolean     cancelable)
{
  Context    context;
  GtkWidget *window;
  GtkWidget *content_area;
  GtkWidget *vbox;
  GtkWidget *label;
  GtkWidget *box;

#ifdef G_OS_WIN32
  context.read_channel  = g_io_channel_win32_new_fd (read_fd);
  context.write_channel = g_io_channel_win32_new_fd (write_fd);
#else
  context.read_channel  = g_io_channel_unix_new (read_fd);
  context.write_channel = g_io_channel_unix_new (write_fd);
#endif

  g_io_channel_set_close_on_unref (context.read_channel,  TRUE);
  g_io_channel_set_close_on_unref (context.write_channel, TRUE);

  /* triggered when the operation is finished in the main app, and we should
   * quit.
   */
  g_io_add_watch (context.read_channel, G_IO_IN | G_IO_ERR | G_IO_HUP,
                  (GIOFunc) busy_dialog_read_channel_notify,
                  &context);

  /* call gtk_init() before gimp_ui_init(), to avoid DESKTOP_STARTUP_ID from
   * taking effect -- we want the dialog to be prominently displayed above
   * other plug-in windows.
   */
  gtk_init (NULL, NULL);

  gimp_ui_init (PLUG_IN_BINARY, FALSE);

  /* the main window */
  if (! cancelable)
    {
      window = g_object_new (GTK_TYPE_WINDOW,
                             "title",             _("Please Wait"),
                             "skip-taskbar-hint", TRUE,
                             "deletable",         FALSE,
                             "resizable",         FALSE,
                             "role",              "gimp-busy-dialog",
                             "type-hint",         GDK_WINDOW_TYPE_HINT_DIALOG,
                             "window-position",   GTK_WIN_POS_CENTER,
                             NULL);

      g_signal_connect (window, "delete-event", G_CALLBACK (gtk_true), NULL);

      content_area = window;
    }
  else
    {
      window = g_object_new (GTK_TYPE_DIALOG,
                             "title",             _("Please Wait"),
                             "skip-taskbar-hint", TRUE,
                             "resizable",         FALSE,
                             "role",              "gimp-busy-dialog",
                             "window-position",   GTK_WIN_POS_CENTER,
                             NULL);

      gtk_dialog_add_button (GTK_DIALOG (window),
                             _("_Cancel"), GTK_RESPONSE_CANCEL);

      g_signal_connect (window, "delete-event",
                        G_CALLBACK (busy_dialog_delete_event),
                        &context);

      g_signal_connect (window, "response",
                        G_CALLBACK (busy_dialog_response),
                        &context);

      content_area = gtk_dialog_get_content_area (GTK_DIALOG (window));
    }

  /* the main vbox */
  vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 8);
  gtk_container_set_border_width (GTK_CONTAINER (vbox), 16);
  gtk_container_add (GTK_CONTAINER (content_area), vbox);
  gtk_widget_show (vbox);

  /* the title label */
  label = gtk_label_new (_("Please wait for the operation to complete"));
  gimp_label_set_attributes (GTK_LABEL (label),
                             PANGO_ATTR_WEIGHT, PANGO_WEIGHT_BOLD,
                             -1);
  gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
  gtk_widget_show (label);

  /* the busy box */
  box = gimp_busy_box_new (message);
  gtk_container_set_border_width (GTK_CONTAINER (box), 8);
  gtk_box_pack_start (GTK_BOX (vbox), box, TRUE, TRUE, 0);
  gtk_widget_show (box);

  gtk_window_present (GTK_WINDOW (window));

  gtk_main ();

  gtk_widget_destroy (window);

  g_clear_pointer (&context.read_channel,  g_io_channel_unref);
  g_clear_pointer (&context.write_channel, g_io_channel_unref);

  return GIMP_PDB_SUCCESS;
}

static gboolean
busy_dialog_read_channel_notify (GIOChannel   *source,
                                 GIOCondition  condition,
                                 Context      *context)
{
  gtk_main_quit ();

  return FALSE;
}

static gboolean
busy_dialog_delete_event (GtkDialog *dialog,
                          GdkEvent  *event,
                          Context   *context)
{
  gtk_dialog_response (dialog, GTK_RESPONSE_CANCEL);

  return TRUE;
}

static void
busy_dialog_response (GtkDialog *dialog,
                      gint       response_id,
                      Context   *context)
{
  switch (response_id)
    {
    case GTK_RESPONSE_CANCEL:
      {
        GtkWidget *button;

        gtk_dialog_set_response_sensitive (dialog, GTK_RESPONSE_CANCEL, FALSE);

        button = gtk_dialog_get_widget_for_response (dialog,
                                                     GTK_RESPONSE_CANCEL);
        gtk_button_set_label (GTK_BUTTON (button), _("Canceling..."));

        /* signal the cancellation request to the main app */
        g_clear_pointer (&context->write_channel, g_io_channel_unref);
      }
      break;

    default:
      break;
    }
}