/* LIBGIMP - The GIMP Library * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball * * This library is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see * . */ #include "config.h" #include "gimp.h" /* Data bounced back and forth to/from core and libgimp, * and here from the temp PDB procedure to the idle func. * But it is opaque to core, passed and returned unaltered. * * Not all fields are meaningful in each direction or transfer. * !!! We don't pass resource to core in this struct, * only from the temp callback to the idle func. * * Lifetime is as long as the remote dialog is open. * Closing the chooser dialog frees the adaption struct. */ typedef struct { /* This portion is passed to and from idle. */ /* !!! resource is not long lived, only during a transfer to idle. */ guint idle_id; GimpResource *resource; GType resource_type; gboolean closing; /* This portion is passed to and from core, and to idle. */ gchar *temp_PDB_callback_name; GimpResourceChoosedCallback callback; gpointer owner_data; GDestroyNotify data_destroy; } GimpResourceAdaption; /* local */ static void gimp_resource_data_free (GimpResourceAdaption *adaption); static GimpValueArray * gimp_temp_resource_run (GimpProcedure *procedure, const GimpValueArray *args, gpointer run_data); static gboolean gimp_temp_resource_idle (GimpResourceAdaption *adaption); /* public */ /* Annotation, which appears in the libgimp API doc. * Not a class, only functions. * The functions appear as class methods of GimpResource class. * Formerly, API had separate functions for each resource subclass. */ /** * SECTION: gimpresourceselect * @title: GimpResourceSelect * @short_description: A resource selection dialog. * * A resource selection dialog. * * An adapter and proxy between libgimp and core. * (see Adapter and Proxy patterns in programming literature.) * * Proxy: to a remote dialog in core. * Is a dialog, but the dialog is remote (another process.) * Remote dialog is a chooser dialog of subclass of GimpResource, * e.g. GimpBrush, GimpFont, etc. * * Adapter: gets a callback via PDB procedure from remote dialog * and shuffles parameters to call a owner's callback on libgimp side. * * Generic on type of GimpResource subclass. * That is, the type of GimpResource subclass is passed. * * Responsibilities: * * - implement a proxy to a chooser widget in core * * Collaborations: * * - called by GimpResourceSelectButton to popup as a sibling widget * - PDB procedures to/from core, which implements the remote dialog * (from via PDB temp callback, to via PDB procs such as gimp_fonts_popup) * - plugins implementing their own GUI **/ /* This was extracted from gimpbrushselect.c, gimpfontselect.c, etc. * and those were deleted. */ /* Functions that dispatch on resource type. * * For now, the design is that core callbacks pass * attributes of the resource (not just the resource.) * FUTURE: core will use the same signature for all remote dialogs, * regardless of resource type. * Then all this dispatching code can be deleted. */ /* Create args for temp PDB callback. * Must match signature that core hardcodes in a subclass of gimp_pdb_dialog. * * For example, see app/widgets/paletteselect.c * * When testing, the error "Unable to run callback... temp_proc... wrong type" * means a signature mismatch. * * Note the signature from core might be from an old design where * the core sent all data needed to draw the resource. * In the new design, libgimp gets the attributes of the resource * from core in a separate PDB proc call back to core. * While core uses the old design and libgimp uses the new design, * libgimp simply ignores the extra args (adapts the signature.) */ static void create_callback_PDB_procedure_params (GimpProcedure *procedure, GType resource_type) { /* Order of args is important. */ /* Prefix of args is same across resource_type. */ /* !!! core gimp_pdb_dialog is still passing string. */ GIMP_PROC_ARG_STRING (procedure, "resource-name", "Resource name", "The resource name", NULL, G_PARAM_READWRITE); /* Create args for the extra, superfluous args that core is passing.*/ if (g_type_is_a (resource_type, GIMP_TYPE_FONT)) { /* No other args. */ } else if (g_type_is_a (resource_type, GIMP_TYPE_GRADIENT)) { GIMP_PROC_ARG_INT (procedure, "gradient-width", "Gradient width", "The gradient width", 0, G_MAXINT, 0, G_PARAM_READWRITE); GIMP_PROC_ARG_FLOAT_ARRAY (procedure, "gradient-data", "Gradient data", "The gradient data", G_PARAM_READWRITE); } else if (g_type_is_a (resource_type, GIMP_TYPE_BRUSH)) { GIMP_PROC_ARG_DOUBLE (procedure, "opacity", "Opacity", NULL, 0.0, 100.0, 100.0, G_PARAM_READWRITE); GIMP_PROC_ARG_INT (procedure, "spacing", "Spacing", NULL, -1, 1000, 20, G_PARAM_READWRITE); GIMP_PROC_ARG_ENUM (procedure, "paint-mode", "Paint mode", NULL, GIMP_TYPE_LAYER_MODE, GIMP_LAYER_MODE_NORMAL, G_PARAM_READWRITE); GIMP_PROC_ARG_INT (procedure, "mask-width", "Brush width", NULL, 0, 10000, 0, G_PARAM_READWRITE); GIMP_PROC_ARG_INT (procedure, "mask-height", "Brush height", NULL, 0, 10000, 0, G_PARAM_READWRITE); GIMP_PROC_ARG_INT (procedure, "mask-len", "Mask length", "Length of brush mask data", 0, G_MAXINT, 0, G_PARAM_READWRITE); GIMP_PROC_ARG_UINT8_ARRAY (procedure, "mask-data", "Mask data", "The brush mask data", G_PARAM_READWRITE); } else if (g_type_is_a (resource_type, GIMP_TYPE_PALETTE)) { GIMP_PROC_ARG_INT (procedure, "num-colors", "Num colors", "Number of colors", 0, G_MAXINT, 0, G_PARAM_READWRITE); } else if (g_type_is_a (resource_type, GIMP_TYPE_PATTERN)) { GIMP_PROC_ARG_INT (procedure, "mask-width", "Mask width", "Pattern width", 0, 10000, 0, G_PARAM_READWRITE); GIMP_PROC_ARG_INT (procedure, "mask-height", "Mask height", "Pattern height", 0, 10000, 0, G_PARAM_READWRITE); GIMP_PROC_ARG_INT (procedure, "mask-bpp", "Mask bpp", "Pattern bytes per pixel", 0, 10000, 0, G_PARAM_READWRITE); GIMP_PROC_ARG_INT (procedure, "mask-len", "Mask length", "Length of pattern mask data", 0, G_MAXINT, 0, G_PARAM_READWRITE); GIMP_PROC_ARG_UINT8_ARRAY (procedure, "mask-data", "Mask data", "The pattern mask data", G_PARAM_READWRITE); } else { g_warning ("%s: unhandled resource type", G_STRFUNC); } /* Suffix of args is same across resource_type. */ GIMP_PROC_ARG_BOOLEAN (procedure, "closing", "Closing", "If the dialog was closing", FALSE, G_PARAM_READWRITE); } /* Open (create) a remote chooser dialog of resource. * * Dispatch on subclass of GimpResource. * Call a PDB procedure that communicates with core to create remote dialog. */ static gboolean popup_remote_chooser (const gchar *title, GimpResource *resource, gchar *temp_PDB_callback_name, GType resource_type) { gboolean result = FALSE; gchar *resource_name; g_debug ("%s", G_STRFUNC); /* The PDB procedure still takes a name aka ID instead of a resource object. */ g_object_get (resource, "id", &resource_name, NULL); if (g_type_is_a (resource_type, GIMP_TYPE_BRUSH)) { result = gimp_brushes_popup (temp_PDB_callback_name, title, resource_name); } else if (g_type_is_a (resource_type, GIMP_TYPE_FONT)) { result = gimp_fonts_popup (temp_PDB_callback_name, title, resource_name); } else if (g_type_is_a (resource_type, GIMP_TYPE_GRADIENT)) { result = gimp_gradients_popup (temp_PDB_callback_name, title, resource_name); } else if (g_type_is_a (resource_type, GIMP_TYPE_PALETTE)) { result = gimp_palettes_popup (temp_PDB_callback_name, title, resource_name); } else if (g_type_is_a (resource_type, GIMP_TYPE_PATTERN)) { result = gimp_patterns_popup (temp_PDB_callback_name, title, resource_name); } else { g_warning ("%s: unhandled resource type", G_STRFUNC); } return result; } /*Does nothing, quietly, when the remote dialog is not open. */ static void close_remote_chooser (gchar *temp_PDB_callback_name, GType resource_type) { if (g_type_is_a (resource_type, GIMP_TYPE_FONT)) { gimp_fonts_close_popup (temp_PDB_callback_name); } else if (g_type_is_a (resource_type, GIMP_TYPE_GRADIENT)) { gimp_gradients_close_popup (temp_PDB_callback_name); } else if (g_type_is_a (resource_type, GIMP_TYPE_BRUSH)) { gimp_brushes_close_popup (temp_PDB_callback_name); } else if (g_type_is_a (resource_type, GIMP_TYPE_PALETTE)) { gimp_palettes_close_popup (temp_PDB_callback_name); } else if (g_type_is_a (resource_type, GIMP_TYPE_PATTERN)) { gimp_patterns_close_popup (temp_PDB_callback_name); } else { g_warning ("%s: unhandled resource type", G_STRFUNC); } } /* Get the index of the is_closing arg in a GimpValueArray of the tempPDBproc. * Count the extra args above, and add that to 1. */ static gint index_of_is_closing_arg (GType resource_type) { if (g_type_is_a (resource_type, GIMP_TYPE_FONT)) { return 1; } else if (g_type_is_a (resource_type, GIMP_TYPE_GRADIENT)) { return 3; } else if (g_type_is_a (resource_type, GIMP_TYPE_BRUSH)) { return 8; } else if (g_type_is_a (resource_type, GIMP_TYPE_PALETTE)) { return 2; } else if (g_type_is_a (resource_type, GIMP_TYPE_PATTERN)) { return 6; } else { g_warning ("%s: unhandled resource type", G_STRFUNC); return 0; } } /** * gimp_resource_select_new: * @title: Title of the resource selection dialog. * @resource: The resource to set as the initial choice. * @resource_type: The type of the subclass of resource. * @callback: (scope notified): The callback function to call when a user chooses a resource. * @owner_data: (closure callback): The run_data given to @callback. * @data_destroy: (destroy owner_data): The destroy function for @owner_data. * * Invoke a resource chooser dialog which may call @callback with the chosen * resource and owner's @data. * * A proxy to a remote dialog in core, which knows the model i.e. installed resources. * * Generic on type of #GimpResource subclass passed in @resource_type. * * Returns: (transfer none): the name of a temporary PDB procedure. The * string belongs to the resource selection dialog and will be * freed automatically when the dialog is closed. **/ const gchar * gimp_resource_select_new (const gchar *title, GimpResource *resource, GType resource_type, GimpResourceChoosedCallback callback, gpointer owner_data, GDestroyNotify data_destroy) { GimpPlugIn *plug_in = gimp_get_plug_in (); GimpProcedure *procedure; gchar *temp_PDB_callback_name; GimpResourceAdaption *adaption; g_debug ("%s", G_STRFUNC); g_return_val_if_fail (resource != NULL, NULL); g_return_val_if_fail (callback != NULL, NULL); g_return_val_if_fail (resource_type != 0, NULL); temp_PDB_callback_name = gimp_pdb_temp_procedure_name (gimp_get_pdb ()); adaption = g_slice_new0 (GimpResourceAdaption); adaption->temp_PDB_callback_name = temp_PDB_callback_name; adaption->callback = callback; adaption->owner_data = owner_data; adaption->data_destroy = data_destroy; adaption->resource_type = resource_type; /* !!! Only part of the adaption has been initialized. */ procedure = gimp_procedure_new (plug_in, temp_PDB_callback_name, GIMP_PDB_PROC_TYPE_TEMPORARY, gimp_temp_resource_run, adaption, (GDestroyNotify) gimp_resource_data_free); create_callback_PDB_procedure_params (procedure, resource_type); gimp_plug_in_add_temp_procedure (plug_in, procedure); g_object_unref (procedure); if (popup_remote_chooser (title, resource, temp_PDB_callback_name, resource_type)) { /* Allow callbacks to be watched */ gimp_plug_in_extension_enable (plug_in); return temp_PDB_callback_name; } else { g_warning ("Failed to open remote resource select dialog."); gimp_plug_in_remove_temp_procedure (plug_in, temp_PDB_callback_name); return NULL; } } void gimp_resource_select_destroy (const gchar *temp_PDB_callback_name) { GimpPlugIn *plug_in = gimp_get_plug_in (); g_debug ("%s", G_STRFUNC); g_return_if_fail (temp_PDB_callback_name != NULL); gimp_plug_in_remove_temp_procedure (plug_in, temp_PDB_callback_name); /* Assert that removing temp procedure will callback the destroy function * gimp_resource_data_free. * The allocated adaption must not be used again. */ } /* Set currently selected resource in remote chooser. * * Calls a PDB procedure. * * Note core is still using string name of resource, * so pdb/groups/_select.pdb, _set_popup must have type string. */ void gimp_resource_select_set (const gchar *temp_pdb_callback, GimpResource *resource, GType resource_type) { gchar * resource_name; g_debug ("%s", G_STRFUNC); /* The remote setter is e.g. gimp_fonts_set_popup, a PDB procedure. * It still takes a name aka ID instead of a resource object. */ g_object_get (resource, "id", &resource_name, NULL); if (g_type_is_a (resource_type, GIMP_TYPE_FONT)) { gimp_fonts_set_popup (temp_pdb_callback, resource_name); } else if (g_type_is_a (resource_type, GIMP_TYPE_GRADIENT)) { gimp_gradients_set_popup (temp_pdb_callback, resource_name); } else if (g_type_is_a (resource_type, GIMP_TYPE_BRUSH)) { gimp_brushes_set_popup (temp_pdb_callback, resource_name); } else if (g_type_is_a (resource_type, GIMP_TYPE_PALETTE)) { gimp_palettes_set_popup (temp_pdb_callback, resource_name); } else if (g_type_is_a (resource_type, GIMP_TYPE_PATTERN)) { gimp_patterns_set_popup (temp_pdb_callback, resource_name); } else { g_warning ("%s: unhandled resource type", G_STRFUNC); } } /* private functions */ /* Free a GimpResourceAdaption struct. * A GimpResourceAdaption and this func are passed to a GimpProcedure * and this func is called back when the procedure is removed. * * This can be called for the exception: failed to open remote dialog. * * Each allocated field must be safely freed (not assuming it is valid pointer.) */ static void gimp_resource_data_free (GimpResourceAdaption *adaption) { g_debug ("%s", G_STRFUNC); if (adaption->idle_id) g_source_remove (adaption->idle_id); /* adaption->resource should be NULL, else we failed to transfer to the owner. */ g_clear_object (&adaption->resource); if (adaption->temp_PDB_callback_name) { close_remote_chooser (adaption->temp_PDB_callback_name, adaption->resource_type); g_free (adaption->temp_PDB_callback_name); } /* Destroy any inner generic data passed by the owner. */ if (adaption->data_destroy) adaption->data_destroy (adaption->owner_data); g_slice_free (GimpResourceAdaption, adaption); } /* Run func for the temporary PDB procedure. * Called when user chooses a resource in remote dialog. */ static GimpValueArray * gimp_temp_resource_run (GimpProcedure *procedure, const GimpValueArray *args, gpointer run_data) /* is-a adaption */ { GimpResourceAdaption *adaption = run_data; const gchar *resource_name; GimpResource *resource; g_debug ("%s", G_STRFUNC); /* Convert name string to object. */ resource_name = GIMP_VALUES_GET_STRING(args, 0); resource = g_object_new (adaption->resource_type, "id", resource_name, NULL); /* Pass the new resource object to the idle func, which will transfer ownership. * and clear adaption->resource. * Thus assert that adaption->resource is NULL, and this is not a leak. */ adaption->resource = resource; /* !!! Adapting the temp PDB callback signature to simplified libgimp callback. * I.E. discarding args that are attributes of the resource. */ adaption->closing = GIMP_VALUES_GET_BOOLEAN (args, index_of_is_closing_arg (adaption->resource_type)); /* Set idle_id to remember an idle source exists, * but idle_id is not used by the idle func. */ if (! adaption->idle_id) adaption->idle_id = g_idle_add ((GSourceFunc) gimp_temp_resource_idle, adaption); return gimp_procedure_new_return_values (procedure, GIMP_PDB_SUCCESS, NULL); } static gboolean gimp_temp_resource_idle (GimpResourceAdaption *adaption) { adaption->idle_id = 0; g_debug ("%s", G_STRFUNC); /* We don't expect callback to be NULL, but check anyway. * This is asynchronous so there could be a race. */ if (adaption->callback) adaption->callback (adaption->resource, adaption->closing, adaption->owner_data); /* Ownership of resource transfers to whom we are calling back. */ adaption->resource = NULL; if (adaption->closing) { gchar *temp_PDB_callback_name = adaption->temp_PDB_callback_name; adaption->temp_PDB_callback_name = NULL; gimp_resource_select_destroy (temp_PDB_callback_name); g_free (temp_PDB_callback_name); } return G_SOURCE_REMOVE; }