/* 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 * * 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 . */ #include "config.h" #include #include #include "libpika/stdplugins-intl.h" #define PLUG_IN_PROC "plug-in-busy-dialog" #define PLUG_IN_BINARY "busy-dialog" #define PLUG_IN_ROLE "pika-busy-dialog" typedef struct { GIOChannel *read_channel; GIOChannel *write_channel; } Context; typedef struct _BusyDialog BusyDialog; typedef struct _BusyDialogClass BusyDialogClass; struct _BusyDialog { PikaPlugIn parent_instance; }; struct _BusyDialogClass { PikaPlugInClass parent_class; }; #define BUSY_DIALOG_TYPE (busy_dialog_get_type ()) #define BUSY_DIALOG (obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), BUSY_DIALOG_TYPE, BusyDialog)) GType busy_dialog_get_type (void) G_GNUC_CONST; static GList * busy_dialog_query_procedures (PikaPlugIn *plug_in); static PikaProcedure * busy_dialog_create_procedure (PikaPlugIn *plug_in, const gchar *name); static PikaValueArray * busy_dialog_run (PikaProcedure *procedure, const PikaValueArray *args, gpointer run_data); static PikaPDBStatusType 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); G_DEFINE_TYPE (BusyDialog, busy_dialog, PIKA_TYPE_PLUG_IN) PIKA_MAIN (BUSY_DIALOG_TYPE) DEFINE_STD_SET_I18N static void busy_dialog_class_init (BusyDialogClass *klass) { PikaPlugInClass *plug_in_class = PIKA_PLUG_IN_CLASS (klass); plug_in_class->query_procedures = busy_dialog_query_procedures; plug_in_class->create_procedure = busy_dialog_create_procedure; plug_in_class->set_i18n = STD_SET_I18N; } static void busy_dialog_init (BusyDialog *busy_dialog) { } static GList * busy_dialog_query_procedures (PikaPlugIn *plug_in) { return g_list_append (NULL, g_strdup (PLUG_IN_PROC)); } static PikaProcedure * busy_dialog_create_procedure (PikaPlugIn *plug_in, const gchar *name) { PikaProcedure *procedure = NULL; if (! strcmp (name, PLUG_IN_PROC)) { procedure = pika_procedure_new (plug_in, name, PIKA_PDB_PROC_TYPE_PLUGIN, busy_dialog_run, NULL, NULL); pika_procedure_set_documentation (procedure, "Show a dialog while waiting for an " "operation to finish", "Used by PIKA 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.", name); pika_procedure_set_attribution (procedure, "Ell", "Ell", "2018"); PIKA_PROC_ARG_ENUM (procedure, "run-mode", "Run mode", "The run mode", PIKA_TYPE_RUN_MODE, PIKA_RUN_INTERACTIVE, G_PARAM_READWRITE); PIKA_PROC_ARG_INT (procedure, "read-fd", "The read file descriptor", "The read file descriptor", G_MININT, G_MAXINT, 0, G_PARAM_READWRITE); PIKA_PROC_ARG_INT (procedure, "write-fd", "The write file descriptor", "The write file descriptor", G_MININT, G_MAXINT, 0, G_PARAM_READWRITE); PIKA_PROC_ARG_STRING (procedure, "message", "The message", "The message", NULL, G_PARAM_READWRITE); PIKA_PROC_ARG_BOOLEAN (procedure, "cancelable", "Whether the dialog is cancelable", "Whether the dialog is cancelable", FALSE, G_PARAM_READWRITE); } return procedure; } static PikaValueArray * busy_dialog_run (PikaProcedure *procedure, const PikaValueArray *args, gpointer run_data) { PikaValueArray *return_vals = NULL; PikaPDBStatusType status = PIKA_PDB_SUCCESS; PikaRunMode run_mode; run_mode = PIKA_VALUES_GET_ENUM (args, 0); switch (run_mode) { case PIKA_RUN_INTERACTIVE: case PIKA_RUN_NONINTERACTIVE: case PIKA_RUN_WITH_LAST_VALS: if (pika_value_array_length (args) != 5) { status = PIKA_PDB_CALLING_ERROR; } else { status = busy_dialog (PIKA_VALUES_GET_INT (args, 1), PIKA_VALUES_GET_INT (args, 2), PIKA_VALUES_GET_STRING (args, 3), PIKA_VALUES_GET_BOOLEAN (args, 4)); } break; default: status = PIKA_PDB_CALLING_ERROR; break; } return_vals = pika_procedure_new_return_values (procedure, status, NULL); return return_vals; } static PikaPDBStatusType 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 pika_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); pika_ui_init (PLUG_IN_BINARY); /* the main window */ if (! cancelable) { window = g_object_new (GTK_TYPE_WINDOW, "title", _("Please Wait"), "skip-taskbar-hint", TRUE, "deletable", FALSE, "resizable", FALSE, "role", "pika-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", "pika-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")); pika_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 = pika_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 PIKA_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; } }