/* LIBPIKA - The PIKA 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 "pika.h" /* Data bounced back and forth to/from core and libpika, * 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. */ guint idle_id; gint resource_id; GType resource_type; gboolean closing; /* This portion is passed to and from core, and to idle. */ gchar *temp_PDB_callback_name; PikaResourceChoosedCallback callback; gpointer owner_data; GDestroyNotify data_destroy; } PikaResourceAdaption; /* local */ static void pika_resource_data_free (PikaResourceAdaption *adaption); static PikaValueArray * pika_temp_resource_run (PikaProcedure *procedure, const PikaValueArray *args, gpointer run_data); static gboolean pika_temp_resource_idle (PikaResourceAdaption *adaption); /* public */ /* Annotation, which appears in the libpika API doc. * Not a class, only functions. * The functions appear as class methods of PikaResource class. * Formerly, API had separate functions for each resource subclass. */ /** * SECTION: pikaresourceselect * @title: PikaResourceSelect * @short_description: A resource selection dialog. * * A resource selection dialog. * * An adapter and proxy between libpika 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 PikaResource, * e.g. PikaBrush, PikaFont, etc. * * Adapter: gets a callback via PDB procedure from remote dialog * and shuffles parameters to call a owner's callback on libpika side. * * Generic on type of PikaResource subclass. * That is, the type of PikaResource subclass is passed. * * Responsibilities: * * - implement a proxy to a chooser widget in core * * Collaborations: * * - called by PikaResourceSelectButton 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 pika_fonts_popup) * - plugins implementing their own GUI **/ /* This was extracted from pikabrushselect.c, pikafontselect.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 pika_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, libpika gets the attributes of the resource * from core in a separate PDB proc call back to core. * While core uses the old design and libpika uses the new design, * libpika simply ignores the extra args (adapts the signature.) */ static void create_callback_PDB_procedure_params (PikaProcedure *procedure, GType resource_type) { PIKA_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, PIKA_TYPE_FONT)) { /* No other args. */ } else if (g_type_is_a (resource_type, PIKA_TYPE_GRADIENT)) { PIKA_PROC_ARG_INT (procedure, "gradient-width", "Gradient width", "The gradient width", 0, G_MAXINT, 0, G_PARAM_READWRITE); PIKA_PROC_ARG_FLOAT_ARRAY (procedure, "gradient-data", "Gradient data", "The gradient data", G_PARAM_READWRITE); } else if (g_type_is_a (resource_type, PIKA_TYPE_BRUSH)) { PIKA_PROC_ARG_DOUBLE (procedure, "opacity", "Opacity", NULL, 0.0, 100.0, 100.0, G_PARAM_READWRITE); PIKA_PROC_ARG_INT (procedure, "spacing", "Spacing", NULL, -1, 1000, 20, G_PARAM_READWRITE); PIKA_PROC_ARG_ENUM (procedure, "paint-mode", "Paint mode", NULL, PIKA_TYPE_LAYER_MODE, PIKA_LAYER_MODE_NORMAL, G_PARAM_READWRITE); PIKA_PROC_ARG_INT (procedure, "mask-width", "Brush width", NULL, 0, 10000, 0, G_PARAM_READWRITE); PIKA_PROC_ARG_INT (procedure, "mask-height", "Brush height", NULL, 0, 10000, 0, G_PARAM_READWRITE); PIKA_PROC_ARG_BYTES (procedure, "mask-data", "Mask data", "The brush mask data", G_PARAM_READWRITE); } else if (g_type_is_a (resource_type, PIKA_TYPE_PALETTE)) { PIKA_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, PIKA_TYPE_PATTERN)) { PIKA_PROC_ARG_INT (procedure, "mask-width", "Mask width", "Pattern width", 0, 10000, 0, G_PARAM_READWRITE); PIKA_PROC_ARG_INT (procedure, "mask-height", "Mask height", "Pattern height", 0, 10000, 0, G_PARAM_READWRITE); PIKA_PROC_ARG_INT (procedure, "mask-bpp", "Mask bpp", "Pattern bytes per pixel", 0, 10000, 0, G_PARAM_READWRITE); PIKA_PROC_ARG_BYTES (procedure, "mask-data", "Mask data", "The pattern mask data", G_PARAM_READWRITE); } else { g_warning ("%s: unhandled resource type", G_STRFUNC); } PIKA_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 PikaResource. * Call a PDB procedure that communicates with core to create remote dialog. */ static gboolean popup_remote_chooser (const gchar *title, PikaResource *resource, gchar *temp_PDB_callback_name, GType resource_type) { gboolean result = FALSE; gchar *resource_name; /* The PDB procedure still takes a name */ resource_name = pika_resource_get_name (resource); if (g_type_is_a (resource_type, PIKA_TYPE_BRUSH)) { result = pika_brushes_popup (temp_PDB_callback_name, title, resource_name); } else if (g_type_is_a (resource_type, PIKA_TYPE_FONT)) { result = pika_fonts_popup (temp_PDB_callback_name, title, resource_name); } else if (g_type_is_a (resource_type, PIKA_TYPE_GRADIENT)) { result = pika_gradients_popup (temp_PDB_callback_name, title, resource_name); } else if (g_type_is_a (resource_type, PIKA_TYPE_PALETTE)) { result = pika_palettes_popup (temp_PDB_callback_name, title, resource_name); } else if (g_type_is_a (resource_type, PIKA_TYPE_PATTERN)) { result = pika_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, PIKA_TYPE_FONT)) { pika_fonts_close_popup (temp_PDB_callback_name); } else if (g_type_is_a (resource_type, PIKA_TYPE_GRADIENT)) { pika_gradients_close_popup (temp_PDB_callback_name); } else if (g_type_is_a (resource_type, PIKA_TYPE_BRUSH)) { pika_brushes_close_popup (temp_PDB_callback_name); } else if (g_type_is_a (resource_type, PIKA_TYPE_PALETTE)) { pika_palettes_close_popup (temp_PDB_callback_name); } else if (g_type_is_a (resource_type, PIKA_TYPE_PATTERN)) { pika_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 PikaValueArray 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, PIKA_TYPE_FONT)) { return 1; } else if (g_type_is_a (resource_type, PIKA_TYPE_GRADIENT)) { return 3; } else if (g_type_is_a (resource_type, PIKA_TYPE_BRUSH)) { return 7; } else if (g_type_is_a (resource_type, PIKA_TYPE_PALETTE)) { return 2; } else if (g_type_is_a (resource_type, PIKA_TYPE_PATTERN)) { return 5; } else { g_warning ("%s: unhandled resource type", G_STRFUNC); return 0; } } /** * pika_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 #PikaResource 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 * pika_resource_select_new (const gchar *title, PikaResource *resource, GType resource_type, PikaResourceChoosedCallback callback, gpointer owner_data, GDestroyNotify data_destroy) { PikaPlugIn *plug_in = pika_get_plug_in (); PikaProcedure *procedure; gchar *temp_PDB_callback_name; PikaResourceAdaption *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 = pika_pdb_temp_procedure_name (pika_get_pdb ()); adaption = g_slice_new0 (PikaResourceAdaption); 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 = pika_procedure_new (plug_in, temp_PDB_callback_name, PIKA_PDB_PROC_TYPE_TEMPORARY, pika_temp_resource_run, adaption, (GDestroyNotify) pika_resource_data_free); create_callback_PDB_procedure_params (procedure, resource_type); pika_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 */ pika_plug_in_extension_enable (plug_in); return temp_PDB_callback_name; } else { g_warning ("Failed to open remote resource select dialog."); pika_plug_in_remove_temp_procedure (plug_in, temp_PDB_callback_name); return NULL; } } void pika_resource_select_destroy (const gchar *temp_PDB_callback_name) { PikaPlugIn *plug_in = pika_get_plug_in (); g_return_if_fail (temp_PDB_callback_name != NULL); pika_plug_in_remove_temp_procedure (plug_in, temp_PDB_callback_name); } /* 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 pika_resource_select_set (const gchar *temp_pdb_callback, PikaResource *resource, GType resource_type) { gchar *resource_name; /* The remote setter is e.g. pika_fonts_set_popup, a PDB procedure. * It still takes a name aka ID instead of a resource object. */ resource_name = pika_resource_get_name (resource); if (g_type_is_a (resource_type, PIKA_TYPE_FONT)) { pika_fonts_set_popup (temp_pdb_callback, resource_name); } else if (g_type_is_a (resource_type, PIKA_TYPE_GRADIENT)) { pika_gradients_set_popup (temp_pdb_callback, resource_name); } else if (g_type_is_a (resource_type, PIKA_TYPE_BRUSH)) { pika_brushes_set_popup (temp_pdb_callback, resource_name); } else if (g_type_is_a (resource_type, PIKA_TYPE_PALETTE)) { pika_palettes_set_popup (temp_pdb_callback, resource_name); } else if (g_type_is_a (resource_type, PIKA_TYPE_PATTERN)) { pika_patterns_set_popup (temp_pdb_callback, resource_name); } else { g_warning ("%s: unhandled resource type", G_STRFUNC); } } /* private functions */ /* Free a PikaResourceAdaption struct. * A PikaResourceAdaption and this func are passed to a PikaProcedure * 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 pika_resource_data_free (PikaResourceAdaption *adaption) { if (adaption->idle_id) g_source_remove (adaption->idle_id); if (adaption->temp_PDB_callback_name) { close_remote_chooser (adaption->temp_PDB_callback_name, adaption->resource_type); g_free (adaption->temp_PDB_callback_name); } if (adaption->data_destroy) adaption->data_destroy (adaption->owner_data); g_slice_free (PikaResourceAdaption, adaption); } /* Run func for the temporary PDB procedure. * Called when user chooses a resource in remote dialog. */ static PikaValueArray * pika_temp_resource_run (PikaProcedure *procedure, const PikaValueArray *args, gpointer run_data) { PikaResourceAdaption *adaption = run_data; const gchar *resource_name; PikaResource *resource; resource_name = PIKA_VALUES_GET_STRING (args, 0); resource = pika_resource_get_by_name (adaption->resource_type, resource_name); adaption->resource_id = pika_resource_get_id (resource); adaption->closing = PIKA_VALUES_GET_BOOLEAN (args, index_of_is_closing_arg (adaption->resource_type)); if (! adaption->idle_id) adaption->idle_id = g_idle_add ((GSourceFunc) pika_temp_resource_idle, adaption); return pika_procedure_new_return_values (procedure, PIKA_PDB_SUCCESS, NULL); } static gboolean pika_temp_resource_idle (PikaResourceAdaption *adaption) { adaption->idle_id = 0; if (adaption->callback) adaption->callback (pika_resource_get_by_id (adaption->resource_id), adaption->closing, adaption->owner_data); adaption->resource_id = 0; if (adaption->closing) { gchar *temp_PDB_callback_name = adaption->temp_PDB_callback_name; adaption->temp_PDB_callback_name = NULL; pika_resource_select_destroy (temp_PDB_callback_name); g_free (temp_PDB_callback_name); } return G_SOURCE_REMOVE; }