/* GIMP - The GNU Image Manipulation Program * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * script-fu-dialog.c * Copyright (C) 2022 Lloyd Konneker * * 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 "script-fu-types.h" /* SFScript */ #include "script-fu-script.h" /* get_title */ #include "script-fu-command.h" #include "script-fu-dialog.h" /* An informal class that shows a dialog for a script then runs the script. * It is internal to libscriptfu. * * The dialog is modal for the script: * OK button hides the dialog then runs the script once. * * The dialog is non-modal with respect to the GIMP app GUI, which remains responsive. * * When called from plugin extension-script-fu, the dialog is modal on the extension: * although GIMP app continues responsive, a user choosing a menu item * that is also implemented by a script and extension-script-fu * will not show a dialog until the first called script finishes. */ /* FUTURE: delete this after v3 is stable. */ #define DEBUG_CONFIG_PROPERTIES TRUE #if DEBUG_CONFIG_PROPERTIES static void dump_properties (GimpProcedureConfig *config) { GParamSpec **pspecs; guint n_pspecs; pspecs = g_object_class_list_properties (G_OBJECT_GET_CLASS (config), &n_pspecs); for (guint i = 1; i < n_pspecs; i++) g_printerr ("%s %s\n", pspecs[i]->name, G_PARAM_SPEC_TYPE_NAME (pspecs[i])); g_free (pspecs); } static gint get_length (GimpProcedureConfig *config) { GParamSpec **pspecs; guint n_pspecs; pspecs = g_object_class_list_properties (G_OBJECT_GET_CLASS (config), &n_pspecs); g_debug ("length config: %d", n_pspecs); return n_pspecs; } /* Fill a new (length zero) gva with new gvalues (empty but holding the correct type) from the config. */ static void fill_gva_from (GimpProcedureConfig *config, GimpValueArray * gva) { GParamSpec **pspecs; guint n_pspecs; pspecs = g_object_class_list_properties (G_OBJECT_GET_CLASS (config), &n_pspecs); /* !!! Start at property 1 */ for (guint i = 1; i < n_pspecs; i++) { g_debug ("%s %s\n", pspecs[i]->name, G_PARAM_SPEC_TYPE_NAME (pspecs[i])); /* append empty gvalue */ gimp_value_array_append (gva, NULL); } g_free (pspecs); } static void dump_objects (GimpProcedureConfig *config) { /* Check it will return non-null objects. */ GimpValueArray *args; gint length; /* Need one less gvalue !!! */ args = gimp_value_array_new (get_length (config) - 1); /* The array still has length zero. */ g_debug ("GVA length: %d", gimp_value_array_length (args)); fill_gva_from (config, args); gimp_procedure_config_get_values (config, args); if (args == NULL) { g_debug ("config holds no values"); return; } length = gimp_value_array_length (args); for (guint i = 1; i < length; i++) { GValue *gvalue = gimp_value_array_index (args, i); if (G_VALUE_HOLDS_OBJECT (gvalue)) if (g_value_get_object (gvalue) == NULL) g_debug ("gvalue %d holds NULL object", i); } } #endif /* Run a dialog for a procedure, then interpret the script. * * Run dialog: create config, create dialog for config, show dialog, and return a config. * Interpret: marshal config into Scheme text for function call, then interpret script. * * One widget per param of the procedure. * Require the procedure registered with params of GTypes * corresponding to SFType the author declared in script-fu-register call. * * Require initial_args is not NULL or empty. * A caller must ensure a dialog is needed because args is not empty. */ GimpValueArray* script_fu_dialog_run (GimpProcedure *procedure, SFScript *script, GimpImage *image, guint n_drawables, GimpDrawable **drawables, const GimpValueArray *initial_args) { GimpValueArray *result = NULL; GimpProcedureDialog *dialog = NULL; GimpProcedureConfig *config = NULL; gboolean not_canceled; if ( (! G_IS_OBJECT (procedure)) || script == NULL) return gimp_procedure_new_return_values (procedure, GIMP_PDB_EXECUTION_ERROR, NULL); if ( gimp_value_array_length (initial_args) < 1) return gimp_procedure_new_return_values (procedure, GIMP_PDB_EXECUTION_ERROR, NULL); /* We don't prevent concurrent dialogs as in script-fu-interface.c. * For extension-script-fu, Gimp is already preventing concurrent dialogs. * For gimp-script-fu-interpreter, each plugin is a separate process * so concurrent dialogs CAN occur. */ /* There is no progress widget in GimpProcedureDialog. * Also, we don't need to update the progress in Gimp UI, * because Gimp shows progress: the name of all called PDB procedures. */ /* Script's menu label */ gimp_ui_init (script_fu_script_get_title (script)); config = gimp_procedure_create_config (procedure); #if DEBUG_CONFIG_PROPERTIES dump_properties (config); g_debug ("Len of initial_args %i", gimp_value_array_length (initial_args) ); #endif /* Get saved settings (last values) into the config. * Since run mode is INTERACTIVE, initial_args is moot. * Instead, last used values or default values populate the config. */ gimp_procedure_config_begin_run (config, NULL, GIMP_RUN_INTERACTIVE, initial_args); #if DEBUG_CONFIG_PROPERTIES dump_objects (config); #endif /* Create a dialog having properties (describing arguments of the procedure) * taken from the config. * * Title dialog with the menu item, not the procedure name. * Assert menu item is localized. */ dialog = (GimpProcedureDialog*) gimp_procedure_dialog_new ( procedure, config, script_fu_script_get_title (script)); /* dialog has no widgets except standard buttons. */ /* It is possible to create custom widget where the provided widget is not adequate. * Then gimp_procedure_dialog_fill_list will create the rest. * For now, the provided widgets should be adequate. */ /* NULL means create widgets for all properties of the procedure * that we have not already created widgets for. */ gimp_procedure_dialog_fill_list (dialog, NULL); not_canceled = gimp_procedure_dialog_run (dialog); /* Assert config holds validated arg values from a user interaction. */ #if DEBUG_CONFIG_PROPERTIES dump_objects (config); #endif if (not_canceled) { /* initial_args is declared const. * To avoid compiler warning "discarding const" * copy initial_args to a writeable copy. */ GimpValueArray *final_args = (GimpValueArray*) g_value_array_copy ((GValueArray*) initial_args); /* FIXME the above is deprecated. * Non-deprecated, but doesn't work: * GimpValueArray *final_args = (GimpValueArray*) g_array_copy ((GArray*) initial_args); * Maybe we need a gimp_value_array_copy method? */ /* Store config's values into final_args. */ gimp_procedure_config_get_values (config, final_args); result = script_fu_interpret_image_proc (procedure, script, image, n_drawables, drawables, final_args); } else { result = gimp_procedure_new_return_values (procedure, GIMP_PDB_CANCEL, NULL); } gtk_widget_destroy ((GtkWidget*) dialog); /* Persist config aka settings for the next run of the plugin. * Passing the GimpPDBStatus from result[0]. * We must have a matching end_run for the begin_run, regardless of status. */ gimp_procedure_config_end_run (config, g_value_get_enum (gimp_value_array_index (result, 0))); g_object_unref (config); return result; }