diff --git a/app/actions/filters-actions.c b/app/actions/filters-actions.c index e533f5dc89..fe34f908e6 100644 --- a/app/actions/filters-actions.c +++ b/app/actions/filters-actions.c @@ -21,6 +21,7 @@ #include #include +#include "glib.h" #include "libgimpwidgets/gimpwidgets.h" #include "actions-types.h" @@ -777,7 +778,7 @@ filters_actions_setup (GimpActionGroup *group) G_N_ELEMENTS (filters_interactive_actions)); gegl_actions = g_strv_builder_new (); - op_classes = gimp_gegl_get_op_classes (); + op_classes = gimp_gegl_get_op_classes (TRUE); for (iter = op_classes; iter; iter = iter->next) { @@ -789,6 +790,9 @@ filters_actions_setup (GimpActionGroup *group) const gchar *op_name; gchar *label; + if (filters_actions_gegl_op_blocklisted (op_class->name)) + continue; + formatted_op_name = g_strdup (op_class->name); gimp_make_valid_action_name (formatted_op_name); action_name = g_strdup_printf ("filters-%s", formatted_op_name); @@ -1120,6 +1124,36 @@ filters_actions_update (GimpActionGroup *group, } } +gboolean +filters_actions_gegl_op_blocklisted (const gchar *operation_name) +{ + for (gint i = 0; i < G_N_ELEMENTS (filters_actions); i++) + { + const GimpStringActionEntry *action_entry = &filters_actions[i]; + + if (g_strcmp0 (operation_name, action_entry->value) == 0) + return TRUE; + } + + for (gint i = 0; i < G_N_ELEMENTS (filters_settings_actions); i++) + { + const GimpStringActionEntry *action_entry = &filters_settings_actions[i]; + + if (g_strcmp0 (operation_name, action_entry->value) == 0) + return TRUE; + } + + for (gint i = 0; i < G_N_ELEMENTS (filters_interactive_actions); i++) + { + const GimpStringActionEntry *action_entry = &filters_interactive_actions[i]; + + if (g_strcmp0 (operation_name, action_entry->value) == 0) + return TRUE; + } + + return FALSE; +} + static void filters_actions_set_tooltips (GimpActionGroup *group, const GimpStringActionEntry *entries, diff --git a/app/actions/filters-actions.h b/app/actions/filters-actions.h index f197b861c7..3bf3731852 100644 --- a/app/actions/filters-actions.h +++ b/app/actions/filters-actions.h @@ -15,13 +15,18 @@ * along with this program. If not, see . */ +#include + +#include "widgets/widgets-types.h" + #ifndef __FILTERS_ACTIONS_H__ #define __FILTERS_ACTIONS_H__ -void filters_actions_setup (GimpActionGroup *group); -void filters_actions_update (GimpActionGroup *group, - gpointer data); +void filters_actions_setup (GimpActionGroup *group); +void filters_actions_update (GimpActionGroup *group, + gpointer data); +gboolean filters_actions_gegl_op_blocklisted (const gchar *operation_name); #endif /* __FILTERS_ACTIONS_H__ */ diff --git a/app/core/core-enums.h b/app/core/core-enums.h index 5db3dfd541..f0411a67f4 100644 --- a/app/core/core-enums.h +++ b/app/core/core-enums.h @@ -482,7 +482,7 @@ typedef enum /*< pdb-skip >*/ GType gimp_trc_type_get_type (void) G_GNUC_CONST; -typedef enum /*< pdb-skip >*/ +typedef enum { GIMP_TRC_LINEAR, /*< desc="Linear" >*/ GIMP_TRC_NON_LINEAR, /*< desc="Non-Linear" >*/ diff --git a/app/gegl/gimp-gegl-utils.c b/app/gegl/gimp-gegl-utils.c index bf38c3ba5b..10c398b99d 100644 --- a/app/gegl/gimp-gegl-utils.c +++ b/app/gegl/gimp-gegl-utils.c @@ -42,9 +42,11 @@ /* local function prototypes */ static gboolean gimp_gegl_op_blacklisted (const gchar *name, - const gchar *categories); + const gchar *categories, + gboolean block_gimp_ops); static GList * gimp_gegl_get_op_subclasses (GType type, - GList *classes); + GList *classes, + gboolean block_gimp_ops); static gint gimp_gegl_compare_op_names (GeglOperationClass *a, GeglOperationClass *b); @@ -52,11 +54,11 @@ static gint gimp_gegl_compare_op_names (GeglOperationClass *a, /* public functions */ GList * -gimp_gegl_get_op_classes (void) +gimp_gegl_get_op_classes (gboolean block_gimp_ops) { GList *operations; - operations = gimp_gegl_get_op_subclasses (GEGL_TYPE_OPERATION, NULL); + operations = gimp_gegl_get_op_subclasses (GEGL_TYPE_OPERATION, NULL, block_gimp_ops); operations = g_list_sort (operations, (GCompareFunc) @@ -473,7 +475,8 @@ gimp_gegl_buffer_set_extent (GeglBuffer *buffer, static gboolean gimp_gegl_op_blacklisted (const gchar *name, - const gchar *categories_str) + const gchar *categories_str, + gboolean block_gimp_ops) { static const gchar * const category_blacklist[] = { @@ -490,127 +493,8 @@ gimp_gegl_op_blacklisted (const gchar *name, }; static const gchar * const name_blacklist[] = { - /* these ops are already added to the menus via filters-actions */ - "gegl:alien-map", - "gegl:antialias", - "gegl:apply-lens", - "gegl:bayer-matrix", - "gegl:bloom", - "gegl:bump-map", - "gegl:c2g", - "gegl:cartoon", - "gegl:cell-noise", - "gegl:channel-mixer", - "gegl:checkerboard", - "gegl:color", - "gegl:color-enhance", - "gegl:color-exchange", - "gegl:color-rotate", - "gegl:color-temperature", - "gegl:color-to-alpha", - "gegl:component-extract", - "gegl:convolution-matrix", - "gegl:cubism", - "gegl:deinterlace", - "gegl:difference-of-gaussians", - "gegl:diffraction-patterns", - "gegl:displace", - "gegl:distance-transform", - "gegl:dither", - "gegl:dropshadow", - "gegl:edge", - "gegl:edge-laplace", - "gegl:edge-neon", - "gegl:edge-sobel", - "gegl:emboss", - "gegl:engrave", - "gegl:exposure", - "gegl:fattal02", - "gegl:focus-blur", - "gegl:fractal-trace", - "gegl:gaussian-blur", - "gegl:gaussian-blur-selective", - "gegl:gegl", - "gegl:grid", - "gegl:high-pass", - "gegl:hue-chroma", - "gegl:illusion", - "gegl:json:dropshadow2", - "gegl:json:grey2", - "gegl:image-gradient", - "gegl:invert-linear", - "gegl:invert-gamma", - "gegl:lens-blur", - "gegl:lens-distortion", - "gegl:lens-flare", - "gegl:linear-sinusoid", - "gegl:long-shadow", - "gegl:mantiuk06", - "gegl:maze", - "gegl:mean-curvature-blur", - "gegl:median-blur", - "gegl:mirrors", - "gegl:mono-mixer", - "gegl:mosaic", - "gegl:motion-blur-circular", - "gegl:motion-blur-linear", - "gegl:motion-blur-zoom", - "gegl:newsprint", - "gegl:noise-cie-lch", - "gegl:noise-hsv", - "gegl:noise-hurl", - "gegl:noise-pick", - "gegl:noise-reduction", - "gegl:noise-rgb", - "gegl:noise-slur", - "gegl:noise-solid", - "gegl:noise-spread", - "gegl:normal-map", - "gegl:oilify", - "gegl:panorama-projection", - "gegl:perlin-noise", - "gegl:photocopy", - "gegl:pixelize", - "gegl:plasma", - "gegl:polar-coordinates", - "gegl:recursive-transform", - "gegl:red-eye-removal", - "gegl:reinhard05", - "gegl:rgb-clip", - "gegl:ripple", - "gegl:saturation", - "gegl:sepia", - "gegl:shadows-highlights", - "gegl:shift", - "gegl:simplex-noise", - "gegl:sinus", - "gegl:slic", - "gegl:snn-mean", - "gegl:softglow", - "gegl:spherize", - "gegl:spiral", - "gegl:stereographic-projection", - "gegl:stretch-contrast", - "gegl:stretch-contrast-hsv", - "gegl:stress", - "gegl:supernova", - "gegl:texturize-canvas", - "gegl:tile-glass", - "gegl:tile-paper", - "gegl:tile-seamless", - "gegl:unsharp-mask", - "gegl:value-invert", - "gegl:value-propagate", - "gegl:variable-blur", - "gegl:video-degradation", - "gegl:vignette", - "gegl:waterpixels", - "gegl:wavelet-blur", - "gegl:waves", - "gegl:whirl-pinch", - "gegl:wind", - /* these ops are blacklisted for other reasons */ + "gegl:color", /* pointless */ "gegl:contrast-curve", "gegl:convert-format", /* pointless */ "gegl:ditto", /* pointless */ @@ -618,6 +502,8 @@ gimp_gegl_op_blacklisted (const gchar *name, "gegl:gray", /* we use gimp's op */ "gegl:hstack", /* deleted from GEGL and replaced by gegl:pack */ "gegl:introspect", /* pointless */ + "gegl:json:dropshadow2", /* has shortcomings, and duplicates gegl:dropshadow */ + "gegl:json:grey2", /* has shortcomings, and duplicates gegl:gray */ "gegl:layer", /* we use gimp's ops */ "gegl:lcms-from-profile", /* not usable here */ "gegl:linear-gradient", /* we use the blend tool */ @@ -637,6 +523,7 @@ gimp_gegl_op_blacklisted (const gchar *name, "gegl:tile", /* pointless */ "gegl:unpremul", /* pointless */ "gegl:vector-stroke", + "gegl:wavelet-blur", /* we use gimp's op wavelet-decompose */ }; gchar **categories; @@ -650,7 +537,7 @@ gimp_gegl_op_blacklisted (const gchar *name, if (g_getenv ("GIMP_TESTING_NO_GEGL_BLACKLIST")) return FALSE; - if (g_str_has_prefix (name, "gimp")) + if (block_gimp_ops && g_str_has_prefix (name, "gimp")) return TRUE; for (i = 0; i < G_N_ELEMENTS (name_blacklist); i++) @@ -684,8 +571,9 @@ gimp_gegl_op_blacklisted (const gchar *name, /* Builds a GList of the class structures of all subtypes of type. */ static GList * -gimp_gegl_get_op_subclasses (GType type, - GList *classes) +gimp_gegl_get_op_subclasses (GType type, + GList *classes, + gboolean block_gimp_ops) { GeglOperationClass *klass; GType *ops; @@ -701,11 +589,11 @@ gimp_gegl_get_op_subclasses (GType type, categories = gegl_operation_class_get_key (klass, "categories"); - if (! gimp_gegl_op_blacklisted (klass->name, categories)) + if (! gimp_gegl_op_blacklisted (klass->name, categories, block_gimp_ops)) classes = g_list_prepend (classes, klass); for (i = 0; i < n_ops; i++) - classes = gimp_gegl_get_op_subclasses (ops[i], classes); + classes = gimp_gegl_get_op_subclasses (ops[i], classes, block_gimp_ops); if (ops) g_free (ops); diff --git a/app/gegl/gimp-gegl-utils.h b/app/gegl/gimp-gegl-utils.h index d760781b92..9b47177173 100644 --- a/app/gegl/gimp-gegl-utils.h +++ b/app/gegl/gimp-gegl-utils.h @@ -22,7 +22,7 @@ #define __GIMP_GEGL_UTILS_H__ -GList * gimp_gegl_get_op_classes (void); +GList * gimp_gegl_get_op_classes (gboolean block_gimp_ops); GType gimp_gegl_get_op_enum_type (const gchar *operation, const gchar *property); diff --git a/app/operations/gimpcurvesconfig.c b/app/operations/gimpcurvesconfig.c index 5e6c3972c9..24fe2ee09c 100644 --- a/app/operations/gimpcurvesconfig.c +++ b/app/operations/gimpcurvesconfig.c @@ -102,8 +102,8 @@ gimp_curves_config_class_init (GimpCurvesConfigClass *klass) GIMP_CONFIG_PROP_ENUM (object_class, PROP_TRC, "trc", - _("Linear/Perceptual"), - _("Work on linear or perceptual RGB"), + _("Tone Reproduction Curve"), + _("Work on linear or perceptual RGB, or following the image's TRC"), GIMP_TYPE_TRC_TYPE, GIMP_TRC_NON_LINEAR, 0); diff --git a/app/operations/gimplevelsconfig.c b/app/operations/gimplevelsconfig.c index 8b9a3071f4..b135517ac6 100644 --- a/app/operations/gimplevelsconfig.c +++ b/app/operations/gimplevelsconfig.c @@ -108,8 +108,8 @@ gimp_levels_config_class_init (GimpLevelsConfigClass *klass) GIMP_CONFIG_PROP_ENUM (object_class, PROP_TRC, "trc", - _("Linear/Perceptual"), - _("Work on linear or perceptual RGB"), + _("Tone Reproduction Curve"), + _("Work on linear or perceptual RGB, or following the image's TRC"), GIMP_TYPE_TRC_TYPE, GIMP_TRC_NON_LINEAR, 0); diff --git a/app/pdb/drawable-filter-cmds.c b/app/pdb/drawable-filter-cmds.c index 435e53ad87..b4ad832472 100644 --- a/app/pdb/drawable-filter-cmds.c +++ b/app/pdb/drawable-filter-cmds.c @@ -28,10 +28,15 @@ #include +#include "libgimpbase/gimpbase.h" +#include "libgimpbase/gimpprotocol.h" +#include "libgimpbase/gimpwire.h" + #include "libgimpbase/gimpbase.h" #include "pdb-types.h" +#include "libgimp/gimpgpparams.h" #include "core/gimpcontainer.h" #include "core/gimpdrawable-filters.h" #include "core/gimpdrawable.h" @@ -39,6 +44,7 @@ #include "core/gimpimage-undo-push.h" #include "core/gimpitem.h" #include "core/gimpparamspecs.h" +#include "gegl/gimp-gegl-utils.h" #include "operations/gimp-operation-config.h" #include "operations/gimpoperationsettings.h" @@ -50,6 +56,58 @@ #include "gimp-intl.h" +static gboolean +validate_operation_name (const gchar *operation_name, + GError **error) +{ + GType op_type; + + /* Comes from gegl/operation/gegl-operations.h which is not public. */ + GType gegl_operation_gtype_from_name (const gchar *name); + + op_type = gegl_operation_gtype_from_name (operation_name); + + /* Using the same rules as in xcf_load_effect() for plug-in created + * effects. + */ + if (g_type_is_a (op_type, GEGL_TYPE_OPERATION_SINK)) + { + g_set_error (error, GIMP_PDB_ERROR, GIMP_PDB_ERROR_INVALID_ARGUMENT, + "%s: the filter \"%s\" is unsafe.", + G_STRFUNC, operation_name); + + return FALSE; + } + else if (g_strcmp0 (operation_name, "gegl:gegl") == 0 && + g_getenv ("GIMP_ALLOW_GEGL_GRAPH_LAYER_EFFECT") == NULL) + { + g_set_error (error, GIMP_PDB_ERROR, GIMP_PDB_ERROR_INVALID_ARGUMENT, + "%s: the filter \"gegl:gegl\" is unsafe.\n" + "For development purpose, set environment variable GIMP_ALLOW_GEGL_GRAPH_LAYER_EFFECT.", + G_STRFUNC); + + return FALSE; + } + + if (g_strcmp0 (operation_name, "gegl:nop") == 0) + { + g_set_error_literal (error, GIMP_PDB_ERROR, GIMP_PDB_ERROR_INVALID_ARGUMENT, + "The filter \"gegl:nop\" is useless and not allowed."); + return FALSE; + } + + if (! gegl_has_operation (operation_name)) + { + g_set_error (error, GIMP_PDB_ERROR, GIMP_PDB_ERROR_INVALID_ARGUMENT, + "%s: the filter \"%s\" is not installed.", + G_STRFUNC, operation_name); + + return FALSE; + } + + return TRUE; +} + static GimpValueArray * drawable_filter_id_is_valid_invoker (GimpProcedure *procedure, Gimp *gimp, @@ -100,48 +158,7 @@ drawable_filter_new_invoker (GimpProcedure *procedure, if (success) { - GType op_type; - - /* Comes from gegl/operation/gegl-operations.h which is not public. */ - GType gegl_operation_gtype_from_name (const gchar *name); - - op_type = gegl_operation_gtype_from_name (operation_name); - /* Using the same rules as in xcf_load_effect() for plug-in created - * effects. - */ - if (g_type_is_a (op_type, GEGL_TYPE_OPERATION_SINK)) - { - g_set_error (error, GIMP_PDB_ERROR, GIMP_PDB_ERROR_INVALID_ARGUMENT, - "%s: the filter \"%s\" is unsafe.", - G_STRFUNC, operation_name); - - success = FALSE; - } - else if (g_strcmp0 (operation_name, "gegl:gegl") == 0 && - g_getenv ("GIMP_ALLOW_GEGL_GRAPH_LAYER_EFFECT") == NULL) - { - g_set_error (error, GIMP_PDB_ERROR, GIMP_PDB_ERROR_INVALID_ARGUMENT, - "%s: the filter \"gegl:gegl\" is unsafe.\n" - "For development purpose, set environment variable GIMP_ALLOW_GEGL_GRAPH_LAYER_EFFECT.", - G_STRFUNC); - - success = FALSE; - } - - if (! gegl_has_operation (operation_name) || ! g_strcmp0 (operation_name, "gegl:nop")) - { - if (! g_strcmp0 (operation_name, "gegl:nop")) - g_set_error_literal (error, GIMP_PDB_ERROR, GIMP_PDB_ERROR_INVALID_ARGUMENT, - "The filter \"gegl:nop\" is useless and not allowed."); - else - g_set_error (error, GIMP_PDB_ERROR, GIMP_PDB_ERROR_INVALID_ARGUMENT, - "%s: the filter \"%s\" is not installed.", - G_STRFUNC, operation_name); - - success = FALSE; - } - - if (success) + if (validate_operation_name (operation_name, error)) { GeglNode *operation = gegl_node_new (); @@ -158,6 +175,10 @@ drawable_filter_new_invoker (GimpProcedure *procedure, gimp_drawable_filter_set_clip (filter, FALSE); g_clear_object (&operation); } + else + { + success = FALSE; + } } return_vals = gimp_procedure_get_return_values (procedure, success, @@ -692,6 +713,205 @@ drawable_filter_delete_invoker (GimpProcedure *procedure, error ? *error : NULL); } +static GimpValueArray * +drawable_filter_operation_get_available_invoker (GimpProcedure *procedure, + Gimp *gimp, + GimpContext *context, + GimpProgress *progress, + const GimpValueArray *args, + GError **error) +{ + GimpValueArray *return_vals; + gchar **names = NULL; + + GList *ops = NULL; + GStrvBuilder *builder = NULL; + + ops = gimp_gegl_get_op_classes (FALSE); + builder = g_strv_builder_new (); + + for (GList *op = ops; op != NULL; op = op->next) + { + GeglOperationClass *op_klass = op->data; + + if (!validate_operation_name(op_klass->name, NULL)) + continue; + + g_strv_builder_add (builder, op_klass->name); + } + + names = g_strv_builder_end (builder); + + g_list_free (ops); + g_strv_builder_unref (builder); + + return_vals = gimp_procedure_get_return_values (procedure, TRUE, NULL); + g_value_take_boxed (gimp_value_array_index (return_vals, 1), names); + + return return_vals; +} + +static GimpValueArray * +drawable_filter_operation_get_details_invoker (GimpProcedure *procedure, + Gimp *gimp, + GimpContext *context, + GimpProgress *progress, + const GimpValueArray *args, + GError **error) +{ + gboolean success = TRUE; + GimpValueArray *return_vals; + const gchar *operation_name; + gchar *title = NULL; + gchar *description = NULL; + gchar *categories = NULL; + gchar *license = NULL; + + operation_name = g_value_get_string (gimp_value_array_index (args, 0)); + + if (success) + { + if (validate_operation_name (operation_name, error)) + { + GeglOperationClass *klass = NULL; + GType op_type; + + /* Comes from gegl/operation/gegl-operations.h which is not public. */ + GType gegl_operation_gtype_from_name (const gchar *name); + + op_type = gegl_operation_gtype_from_name (operation_name); + klass = g_type_class_ref (op_type); + + title = g_strdup (gegl_operation_class_get_key (klass, "title")); + description = g_strdup (gegl_operation_class_get_key (klass, "description")); + categories = g_strdup (gegl_operation_class_get_key (klass, "categories")); + license = g_strdup (gegl_operation_class_get_key (klass, "license")); + if (license == NULL) + license = g_strdup ("unknown"); + } + else + { + success = FALSE; + } + } + + return_vals = gimp_procedure_get_return_values (procedure, success, + error ? *error : NULL); + + if (success) + { + g_value_take_string (gimp_value_array_index (return_vals, 1), title); + g_value_take_string (gimp_value_array_index (return_vals, 2), description); + g_value_take_string (gimp_value_array_index (return_vals, 3), categories); + g_value_take_string (gimp_value_array_index (return_vals, 4), license); + } + + return return_vals; +} + +static GimpValueArray * +drawable_filter_operation_get_pspecs_invoker (GimpProcedure *procedure, + Gimp *gimp, + GimpContext *context, + GimpProgress *progress, + const GimpValueArray *args, + GError **error) +{ + gboolean success = TRUE; + GimpValueArray *return_vals; + const gchar *operation_name; + GimpValueArray *pspecs = NULL; + + operation_name = g_value_get_string (gimp_value_array_index (args, 0)); + + if (success) + { + if (validate_operation_name (operation_name, error)) + { + GParamSpec **specs = NULL; + guint n_specs = 0; + guint n_parent_specs = 0; + + if (gimp_operation_config_is_custom (gimp, operation_name)) + { + GObjectClass *op_config_klass = NULL; + GObjectClass *op_parent_klass = NULL; + GType op_config_type; + GType op_parent_type; + + op_config_type = gimp_operation_config_get_type (gimp, operation_name, NULL, GIMP_TYPE_OPERATION_SETTINGS); + op_config_klass = g_type_class_ref (op_config_type); + specs = g_object_class_list_properties (op_config_klass, &n_specs); + g_type_class_unref (op_config_klass); + + op_parent_type = g_type_parent (op_config_type); + op_parent_klass = g_type_class_ref (op_parent_type); + g_free (g_object_class_list_properties (op_parent_klass, &n_parent_specs)); + g_type_class_unref (op_parent_klass); + + n_specs -= n_parent_specs; + } + else + { + specs = gegl_operation_list_properties (operation_name, &n_specs); + } + + pspecs = gimp_value_array_new (n_specs); + + for (gint i = 0; i < n_specs; i++) + { + GParamSpec *pspec = specs[n_parent_specs + i]; + GPParamDef param_def = { 0, }; + + /* Make sure we do not try to send param specs over the wire + * if we don't support sending their type. + */ + if (_gimp_param_spec_to_gp_param_def (pspec, ¶m_def, TRUE)) + { + GValue value = G_VALUE_INIT; + + g_value_init (&value, G_TYPE_PARAM); + + g_value_set_param (&value, g_param_spec_ref (pspec)); + gimp_value_array_append (pspecs, &value); + + g_value_unset (&value); + } + else + { + /* This is not technically a bug if an operation has + * unsupported argument types, because we cannot possibly + * support everything (third-party operations could have any + * kind of arguments; and even official ops have types + * such as audio fragment which we may never support). + * So this should not generate any WARNING. Yet we still + * want to softly notify developers, in case we can + * actually do something about some types. + */ + g_printerr ("%s: ignoring argument '%s' of procedure '%s'. " + "Unsupported argument type '%s'.\n", + G_STRFUNC, pspec->name, operation_name, + g_type_name (G_PARAM_SPEC_VALUE_TYPE (pspec))); + } + } + + g_free (specs); + } + else + { + success = FALSE; + } + } + + return_vals = gimp_procedure_get_return_values (procedure, success, + error ? *error : NULL); + + if (success) + g_value_take_boxed (gimp_value_array_index (return_vals, 1), pspecs); + + return return_vals; +} + void register_drawable_filter_procs (GimpPDB *pdb) { @@ -1156,4 +1376,112 @@ register_drawable_filter_procs (GimpPDB *pdb) GIMP_PARAM_READWRITE)); gimp_pdb_register_procedure (pdb, procedure); g_object_unref (procedure); + + /* + * gimp-drawable-filter-operation-get-available + */ + procedure = gimp_procedure_new (drawable_filter_operation_get_available_invoker, FALSE); + gimp_object_set_static_name (GIMP_OBJECT (procedure), + "gimp-drawable-filter-operation-get-available"); + gimp_procedure_set_static_help (procedure, + "Get a list of all available GEGL operation names for drawable filters.", + "This procedure returns a list of all GEGL operation names available for use with drawable filters.", + NULL); + gimp_procedure_set_static_attribution (procedure, + "Ondřej Míchal ", + "Ondřej Míchal", + "2025"); + gimp_procedure_add_return_value (procedure, + g_param_spec_boxed ("names", + "names", + "The list of GEGL operation names", + G_TYPE_STRV, + GIMP_PARAM_READWRITE)); + gimp_pdb_register_procedure (pdb, procedure); + g_object_unref (procedure); + + /* + * gimp-drawable-filter-operation-get-details + */ + procedure = gimp_procedure_new (drawable_filter_operation_get_details_invoker, FALSE); + gimp_object_set_static_name (GIMP_OBJECT (procedure), + "gimp-drawable-filter-operation-get-details"); + gimp_procedure_set_static_help (procedure, + "Get information about a GEGL operation.", + "This procedure returns information about a GEGL operation. This information includes its human-readable title, description, categories and license.\n" + "An operation can belong to no categories or to multiple categories. Multiple categories are separated by the ':' character.\n" + "Some operation's license is not specifically set. In such cases, the returned license is 'unknown'.", + NULL); + gimp_procedure_set_static_attribution (procedure, + "Ondřej Míchal ", + "Ondřej Míchal", + "2025"); + gimp_procedure_add_argument (procedure, + gimp_param_spec_string ("operation-name", + "operation name", + "The GEGL operation's name", + FALSE, FALSE, FALSE, + NULL, + GIMP_PARAM_READWRITE)); + gimp_procedure_add_return_value (procedure, + gimp_param_spec_string ("title", + "title", + "Human-readable title of the operation", + FALSE, FALSE, FALSE, + NULL, + GIMP_PARAM_READWRITE)); + gimp_procedure_add_return_value (procedure, + gimp_param_spec_string ("description", + "description", + "Description of the operation's behaviour", + FALSE, FALSE, FALSE, + NULL, + GIMP_PARAM_READWRITE)); + gimp_procedure_add_return_value (procedure, + gimp_param_spec_string ("categories", + "categories", + "Categories the operation belongs to", + FALSE, FALSE, FALSE, + NULL, + GIMP_PARAM_READWRITE)); + gimp_procedure_add_return_value (procedure, + gimp_param_spec_string ("license", + "license", + "License of the operation", + FALSE, FALSE, FALSE, + NULL, + GIMP_PARAM_READWRITE)); + gimp_pdb_register_procedure (pdb, procedure); + g_object_unref (procedure); + + /* + * gimp-drawable-filter-operation-get-pspecs + */ + procedure = gimp_procedure_new (drawable_filter_operation_get_pspecs_invoker, FALSE); + gimp_object_set_static_name (GIMP_OBJECT (procedure), + "gimp-drawable-filter-operation-get-pspecs"); + gimp_procedure_set_static_help (procedure, + "Get information for all parameters of a GEGL operation.", + "This procedure returns a list of all parameters used to configure a GEGL operation.\n" + "Each parameter is represented by GParamSpec.", + NULL); + gimp_procedure_set_static_attribution (procedure, + "Ondřej Míchal ", + "Ondřej Míchal", + "2025"); + gimp_procedure_add_argument (procedure, + gimp_param_spec_string ("operation-name", + "operation name", + "The GEGL operation's name", + FALSE, FALSE, FALSE, + NULL, + GIMP_PARAM_READWRITE)); + gimp_procedure_add_return_value (procedure, + gimp_param_spec_value_array ("pspecs", + "pspecs", + "List of all parameters of the GEGL operation", + NULL, + GIMP_PARAM_READWRITE)); + gimp_pdb_register_procedure (pdb, procedure); + g_object_unref (procedure); } diff --git a/app/pdb/internal-procs.c b/app/pdb/internal-procs.c index 704577eca0..124df49769 100644 --- a/app/pdb/internal-procs.c +++ b/app/pdb/internal-procs.c @@ -30,7 +30,7 @@ #include "internal-procs.h" -/* 718 procedures registered total */ +/* 721 procedures registered total */ void internal_procs_init (GimpPDB *pdb) diff --git a/app/plug-in/plug-in-rc.c b/app/plug-in/plug-in-rc.c index ddf632fc28..314b7e3252 100644 --- a/app/plug-in/plug-in-rc.c +++ b/app/plug-in/plug-in-rc.c @@ -1152,7 +1152,7 @@ plug_in_rc_write_proc_arg (GimpConfigWriter *writer, { GPParamDef param_def = { 0, }; - _gimp_param_spec_to_gp_param_def (pspec, ¶m_def); + _gimp_param_spec_to_gp_param_def (pspec, ¶m_def, FALSE); gimp_config_writer_open (writer, "proc-arg"); gimp_config_writer_printf (writer, "%d", param_def.param_def_type); diff --git a/app/tools/gimpgegltool.c b/app/tools/gimpgegltool.c index 9f2cc358d1..0b66435a13 100644 --- a/app/tools/gimpgegltool.c +++ b/app/tools/gimpgegltool.c @@ -34,6 +34,8 @@ #include "core/gimp.h" #include "core/gimptoolinfo.h" +#include "actions/filters-actions.h" + #include "widgets/gimphelp-ids.h" #include "widgets/gimppropwidgets.h" @@ -164,7 +166,7 @@ gimp_gegl_tool_dialog (GimpFilterTool *filter_tool) store = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING); - opclasses = gimp_gegl_get_op_classes (); + opclasses = gimp_gegl_get_op_classes (TRUE); for (iter = opclasses; iter; iter = iter->next) { @@ -174,6 +176,9 @@ gimp_gegl_tool_dialog (GimpFilterTool *filter_tool) const gchar *title; gchar *label; + if (filters_actions_gegl_op_blocklisted (opclass->name)) + continue; + if (g_str_has_prefix (opclass->name, "gegl:")) icon_name = GIMP_ICON_GEGL; diff --git a/libgimp/gimp.def b/libgimp/gimp.def index 7ca810869a..836bfaa87e 100644 --- a/libgimp/gimp.def +++ b/libgimp/gimp.def @@ -227,6 +227,9 @@ EXPORTS gimp_drawable_filter_id_is_valid gimp_drawable_filter_is_valid gimp_drawable_filter_new + gimp_drawable_filter_operation_get_available + gimp_drawable_filter_operation_get_details + gimp_drawable_filter_operation_get_pspecs gimp_drawable_filter_set_aux_input gimp_drawable_filter_set_blend_mode gimp_drawable_filter_set_opacity @@ -1056,6 +1059,7 @@ EXPORTS gimp_thumbnail_procedure_new gimp_tile_height gimp_tile_width + gimp_trc_type_get_type gimp_unit_new gimp_user_time gimp_vector_load_procedure_extract_dimensions diff --git a/libgimp/gimpdrawablefilter_pdb.c b/libgimp/gimpdrawablefilter_pdb.c index 8cc4b7367f..71f60ecef8 100644 --- a/libgimp/gimpdrawablefilter_pdb.c +++ b/libgimp/gimpdrawablefilter_pdb.c @@ -573,3 +573,142 @@ gimp_drawable_filter_delete (GimpDrawableFilter *filter) return success; } + +/** + * gimp_drawable_filter_operation_get_available: + * + * Get a list of all available GEGL operation names for drawable + * filters. + * + * This procedure returns a list of all GEGL operation names available + * for use with drawable filters. + * + * Returns: (array zero-terminated=1) (transfer full): + * The list of GEGL operation names. + * The returned value must be freed with g_strfreev(). + * + * Since: 3.2 + **/ +gchar ** +gimp_drawable_filter_operation_get_available (void) +{ + GimpValueArray *args; + GimpValueArray *return_vals; + gchar **names = NULL; + + args = gimp_value_array_new_from_types (NULL, + G_TYPE_NONE); + + return_vals = _gimp_pdb_run_procedure_array (gimp_get_pdb (), + "gimp-drawable-filter-operation-get-available", + args); + gimp_value_array_unref (args); + + if (GIMP_VALUES_GET_ENUM (return_vals, 0) == GIMP_PDB_SUCCESS) + names = GIMP_VALUES_DUP_STRV (return_vals, 1); + + gimp_value_array_unref (return_vals); + + return names; +} + +/** + * gimp_drawable_filter_operation_get_details: + * @operation_name: The GEGL operation's name. + * @title: (out) (transfer full): Human-readable title of the operation. + * @description: (out) (transfer full): Description of the operation's behaviour. + * @categories: (out) (transfer full): Categories the operation belongs to. + * @license: (out) (transfer full): License of the operation. + * + * Get information about a GEGL operation. + * + * This procedure returns information about a GEGL operation. This + * information includes its human-readable title, description, + * categories and license. + * An operation can belong to no categories or to multiple categories. + * Multiple categories are separated by the ':' character. + * Some operation's license is not specifically set. In such cases, the + * returned license is 'unknown'. + * + * Returns: TRUE on success. + * + * Since: 3.2 + **/ +gboolean +gimp_drawable_filter_operation_get_details (const gchar *operation_name, + gchar **title, + gchar **description, + gchar **categories, + gchar **license) +{ + GimpValueArray *args; + GimpValueArray *return_vals; + gboolean success = TRUE; + + args = gimp_value_array_new_from_types (NULL, + G_TYPE_STRING, operation_name, + G_TYPE_NONE); + + return_vals = _gimp_pdb_run_procedure_array (gimp_get_pdb (), + "gimp-drawable-filter-operation-get-details", + args); + gimp_value_array_unref (args); + + *title = NULL; + *description = NULL; + *categories = NULL; + *license = NULL; + + success = GIMP_VALUES_GET_ENUM (return_vals, 0) == GIMP_PDB_SUCCESS; + + if (success) + { + *title = GIMP_VALUES_DUP_STRING (return_vals, 1); + *description = GIMP_VALUES_DUP_STRING (return_vals, 2); + *categories = GIMP_VALUES_DUP_STRING (return_vals, 3); + *license = GIMP_VALUES_DUP_STRING (return_vals, 4); + } + + gimp_value_array_unref (return_vals); + + return success; +} + +/** + * gimp_drawable_filter_operation_get_pspecs: + * @operation_name: The GEGL operation's name. + * + * Get information for all parameters of a GEGL operation. + * + * This procedure returns a list of all parameters used to configure a + * GEGL operation. + * Each parameter is represented by GParamSpec. + * + * Returns: List of all parameters of the GEGL operation. + * The returned value must be freed with g_free(). + * + * Since: 3.2 + **/ +GimpValueArray * +gimp_drawable_filter_operation_get_pspecs (const gchar *operation_name) +{ + GimpValueArray *args; + GimpValueArray *return_vals; + GimpValueArray *pspecs = NULL; + + args = gimp_value_array_new_from_types (NULL, + G_TYPE_STRING, operation_name, + G_TYPE_NONE); + + return_vals = _gimp_pdb_run_procedure_array (gimp_get_pdb (), + "gimp-drawable-filter-operation-get-pspecs", + args); + gimp_value_array_unref (args); + + if (GIMP_VALUES_GET_ENUM (return_vals, 0) == GIMP_PDB_SUCCESS) + pspecs = g_value_dup_boxed (gimp_value_array_index (return_vals, 1)); + + gimp_value_array_unref (return_vals); + + return pspecs; +} diff --git a/libgimp/gimpdrawablefilter_pdb.h b/libgimp/gimpdrawablefilter_pdb.h index 844c256927..886a0d5bf5 100644 --- a/libgimp/gimpdrawablefilter_pdb.h +++ b/libgimp/gimpdrawablefilter_pdb.h @@ -32,33 +32,40 @@ G_BEGIN_DECLS /* For information look into the C source or the html documentation */ -gboolean gimp_drawable_filter_id_is_valid (gint filter_id); -GimpDrawableFilter* gimp_drawable_filter_new (GimpDrawable *drawable, - const gchar *operation_name, - const gchar *name); -gchar* gimp_drawable_filter_get_name (GimpDrawableFilter *filter); -gchar* gimp_drawable_filter_get_operation_name (GimpDrawableFilter *filter); -gboolean gimp_drawable_filter_get_visible (GimpDrawableFilter *filter); -gboolean gimp_drawable_filter_set_visible (GimpDrawableFilter *filter, - gboolean visible); -gdouble gimp_drawable_filter_get_opacity (GimpDrawableFilter *filter); -GimpLayerMode gimp_drawable_filter_get_blend_mode (GimpDrawableFilter *filter); -G_GNUC_INTERNAL gboolean _gimp_drawable_filter_update (GimpDrawableFilter *filter, - const gchar **propnames, - const GimpValueArray *propvalues, - gdouble opacity, - GimpLayerMode blend_mode, - GimpLayerColorSpace blend_space, - GimpLayerCompositeMode composite_mode, - GimpLayerColorSpace composite_space, - const gchar **auxinputnames, - const GimpDrawable **auxinputs); -G_GNUC_INTERNAL gint _gimp_drawable_filter_get_number_arguments (GimpDrawableFilter *filter); -G_GNUC_INTERNAL GParamSpec* _gimp_drawable_filter_get_pspec (GimpDrawableFilter *filter, - gint arg_num); -G_GNUC_INTERNAL gchar** _gimp_drawable_filter_get_arguments (GimpDrawableFilter *filter, - GimpValueArray **values); -gboolean gimp_drawable_filter_delete (GimpDrawableFilter *filter); +gboolean gimp_drawable_filter_id_is_valid (gint filter_id); +GimpDrawableFilter* gimp_drawable_filter_new (GimpDrawable *drawable, + const gchar *operation_name, + const gchar *name); +gchar* gimp_drawable_filter_get_name (GimpDrawableFilter *filter); +gchar* gimp_drawable_filter_get_operation_name (GimpDrawableFilter *filter); +gboolean gimp_drawable_filter_get_visible (GimpDrawableFilter *filter); +gboolean gimp_drawable_filter_set_visible (GimpDrawableFilter *filter, + gboolean visible); +gdouble gimp_drawable_filter_get_opacity (GimpDrawableFilter *filter); +GimpLayerMode gimp_drawable_filter_get_blend_mode (GimpDrawableFilter *filter); +G_GNUC_INTERNAL gboolean _gimp_drawable_filter_update (GimpDrawableFilter *filter, + const gchar **propnames, + const GimpValueArray *propvalues, + gdouble opacity, + GimpLayerMode blend_mode, + GimpLayerColorSpace blend_space, + GimpLayerCompositeMode composite_mode, + GimpLayerColorSpace composite_space, + const gchar **auxinputnames, + const GimpDrawable **auxinputs); +G_GNUC_INTERNAL gint _gimp_drawable_filter_get_number_arguments (GimpDrawableFilter *filter); +G_GNUC_INTERNAL GParamSpec* _gimp_drawable_filter_get_pspec (GimpDrawableFilter *filter, + gint arg_num); +G_GNUC_INTERNAL gchar** _gimp_drawable_filter_get_arguments (GimpDrawableFilter *filter, + GimpValueArray **values); +gboolean gimp_drawable_filter_delete (GimpDrawableFilter *filter); +gchar** gimp_drawable_filter_operation_get_available (void); +gboolean gimp_drawable_filter_operation_get_details (const gchar *operation_name, + gchar **title, + gchar **description, + gchar **categories, + gchar **license); +GimpValueArray* gimp_drawable_filter_operation_get_pspecs (const gchar *operation_name); G_END_DECLS diff --git a/libgimp/gimpenums.c.tail b/libgimp/gimpenums.c.tail index fee13c8dbf..b1c0aa0e0a 100644 --- a/libgimp/gimpenums.c.tail +++ b/libgimp/gimpenums.c.tail @@ -45,11 +45,11 @@ static const GimpGetTypeFunc get_type_funcs[] = gimp_message_handler_type_get_type, gimp_offset_type_get_type, gimp_orientation_type_get_type, + gimp_paint_application_mode_get_type, + gimp_path_stroke_type_get_type, gimp_pdb_error_handler_get_type, gimp_pdb_proc_type_get_type, gimp_pdb_status_type_get_type, - gimp_paint_application_mode_get_type, - gimp_path_stroke_type_get_type, gimp_precision_get_type, gimp_progress_command_get_type, gimp_repeat_mode_get_type, @@ -64,7 +64,8 @@ static const GimpGetTypeFunc get_type_funcs[] = gimp_text_justification_get_type, gimp_transfer_mode_get_type, gimp_transform_direction_get_type, - gimp_transform_resize_get_type + gimp_transform_resize_get_type, + gimp_trc_type_get_type }; static const gchar * const type_names[] = @@ -111,11 +112,11 @@ static const gchar * const type_names[] = "GimpMessageHandlerType", "GimpOffsetType", "GimpOrientationType", + "GimpPaintApplicationMode", + "GimpPathStrokeType", "GimpPDBErrorHandler", "GimpPDBProcType", "GimpPDBStatusType", - "GimpPaintApplicationMode", - "GimpPathStrokeType", "GimpPrecision", "GimpProgressCommand", "GimpRepeatMode", @@ -130,7 +131,8 @@ static const gchar * const type_names[] = "GimpTextJustification", "GimpTransferMode", "GimpTransformDirection", - "GimpTransformResize" + "GimpTransformResize", + "GimpTRCType" }; static gboolean enums_initialized = FALSE; diff --git a/libgimp/gimpenums.h b/libgimp/gimpenums.h index 00dc96d1b2..1ffb415476 100644 --- a/libgimp/gimpenums.h +++ b/libgimp/gimpenums.h @@ -280,6 +280,26 @@ typedef enum } GimpLayerMode; +#define GIMP_TYPE_TRC_TYPE (gimp_trc_type_get_type ()) + +GType gimp_trc_type_get_type (void) G_GNUC_CONST; + +/** + * GimpTRCType: + * @GIMP_TRC_LINEAR: GIMP_TRC_LINEAR + * @GIMP_TRC_NON_LINEAR: GIMP_TRC_NON_LINEAR + * @GIMP_TRC_PERCEPTUAL: GIMP_TRC_PERCEPTUAL + * + * Extracted from app/core/core-enums.h + **/ +typedef enum +{ + GIMP_TRC_LINEAR, + GIMP_TRC_NON_LINEAR, + GIMP_TRC_PERCEPTUAL +} GimpTRCType; + + void gimp_enums_init (void); const gchar ** gimp_enums_get_type_names (gint *n_type_names); diff --git a/libgimp/gimpgpparams-body.c b/libgimp/gimpgpparams-body.c index 0ce6fd08cb..afed740c41 100644 --- a/libgimp/gimpgpparams-body.c +++ b/libgimp/gimpgpparams-body.c @@ -367,12 +367,14 @@ _gimp_gp_param_def_to_param_spec (const GPParamDef *param_def) return NULL; } -void +gboolean _gimp_param_spec_to_gp_param_def (GParamSpec *pspec, - GPParamDef *param_def) + GPParamDef *param_def, + gboolean check_only) { GType pspec_type = G_PARAM_SPEC_TYPE (pspec); GType value_type = G_PARAM_SPEC_VALUE_TYPE (pspec); + gboolean success = TRUE; param_def->param_def_type = GP_PARAM_DEF_TYPE_DEFAULT; param_def->type_name = (gchar *) g_type_name (pspec_type); @@ -555,7 +557,8 @@ _gimp_param_spec_to_gp_param_def (GParamSpec *pspec, GParamSpecString *gsspec = G_PARAM_SPEC_STRING (pspec); if (! strcmp (param_def->type_name, "GimpParamString") || - ! strcmp (param_def->type_name, "GeglParamString")) + ! strcmp (param_def->type_name, "GeglParamString") || + ! strcmp (param_def->type_name, "GeglParamFilePath")) param_def->type_name = "GParamString"; param_def->param_def_type = GP_PARAM_DEF_TYPE_STRING; @@ -766,11 +769,19 @@ _gimp_param_spec_to_gp_param_def (GParamSpec *pspec, } else { - g_warning ("%s: GParamSpecObject for unsupported type '%s:%s'", - G_STRFUNC, - param_def->type_name, param_def->value_type_name); + success = FALSE; + if (! check_only) + g_warning ("%s: GParamSpecObject for unsupported type '%s:%s'", + G_STRFUNC, + param_def->type_name, param_def->value_type_name); } } + else + { + success = FALSE; + } + + return success; } static GimpImage * @@ -1708,7 +1719,7 @@ gimp_value_to_gp_param (const GValue *value, param->param_type = GP_PARAM_TYPE_PARAM_DEF; _gimp_param_spec_to_gp_param_def (g_value_get_param (value), - ¶m->data.d_param_def); + ¶m->data.d_param_def, FALSE); } else if (GIMP_VALUE_HOLDS_VALUE_ARRAY (value)) { diff --git a/libgimp/gimpgpparams.h b/libgimp/gimpgpparams.h index 8f88080068..1edd91d131 100644 --- a/libgimp/gimpgpparams.h +++ b/libgimp/gimpgpparams.h @@ -26,8 +26,9 @@ G_BEGIN_DECLS GParamSpec * _gimp_gp_param_def_to_param_spec (const GPParamDef *param_def); -void _gimp_param_spec_to_gp_param_def (GParamSpec *pspec, - GPParamDef *param_def); +gboolean _gimp_param_spec_to_gp_param_def (GParamSpec *pspec, + GPParamDef *param_def, + gboolean check_only); GimpValueArray * _gimp_gp_params_to_value_array (gpointer gimp, GParamSpec **pspecs, diff --git a/libgimp/gimpprocedure.c b/libgimp/gimpprocedure.c index db7bb88432..60dbe2430a 100644 --- a/libgimp/gimpprocedure.c +++ b/libgimp/gimpprocedure.c @@ -411,13 +411,13 @@ gimp_procedure_real_install (GimpProcedure *procedure) for (i = 0; i < n_args; i++) { _gimp_param_spec_to_gp_param_def (args[i], - &proc_install.params[i]); + &proc_install.params[i], FALSE); } for (i = 0; i < n_return_vals; i++) { _gimp_param_spec_to_gp_param_def (return_vals[i], - &proc_install.return_vals[i]); + &proc_install.return_vals[i], FALSE); } plug_in = gimp_procedure_get_plug_in (procedure); diff --git a/pdb/app.pl b/pdb/app.pl index 3294a06ba3..34f7ad1bd2 100644 --- a/pdb/app.pl +++ b/pdb/app.pl @@ -1111,6 +1111,9 @@ GPL elsif (!/libgimp/) { s/^/~/; } + elsif (/libgimp[a-z]/) { + s/^/"/; + } } $x cmp $y; } keys %{$out->{headers}}; @@ -1137,7 +1140,7 @@ GPL $seen = 0 if !/^{external}) { my $gtype = $func = $_; - for ($gtype) { s/Gimp//; s/([A-Z][^A-Z]+)/\U$1\E_/g; s/_$// } - for ($func) { s/Gimp//; s/([A-Z][^A-Z]+)/\L$1\E_/g; s/_$// } + for ($gtype) { s/Gimp//; s/([A-Z][^A-Z]+|[A-Z]+(?=[A-Z]))/\U$1\E_/g; s/_$// } + for ($func) { s/Gimp//; s/([A-Z][^A-Z]+|[A-Z]+(?=[A-Z]))/\L$1\E_/g; s/_$// } print ENUMFILE "\n#define GIMP_TYPE_$gtype (gimp_$func\_get_type ())\n\n"; print ENUMFILE "GType gimp_$func\_get_type (void) G_GNUC_CONST;\n\n"; @@ -136,14 +136,14 @@ static const GimpGetTypeFunc get_type_funcs[] = CODE my $first = 1; -foreach (sort keys %enums) { +foreach (sort {uc($a) cmp uc($b)} keys %enums) { if (! ($_ =~ /GimpUnit/)) { my $enum = $enums{$_}; my $func = $_; my $gegl_enum = ($func =~ /Gegl/); for ($func) { s/Gimp//; s/Gegl//; s/PDB/Pdb/; - s/([A-Z][^A-Z]+)/\L$1\E_/g; s/_$// } + s/([A-Z][^A-Z]+|[A-Z]+(?=[A-Z]))/\L$1\E_/g; s/_$// } print ENUMFILE ",\n" unless $first; @@ -166,7 +166,7 @@ static const gchar * const type_names[] = CODE $first = 1; -foreach (sort keys %enums) { +foreach (sort {uc($a) cmp uc($b)} keys %enums) { if (! ($_ =~ /GimpUnit/)) { my $enum = $enums{$_}; my $gtype = $_; diff --git a/pdb/enums.pl b/pdb/enums.pl index e34077c828..fc0c5ceefb 100644 --- a/pdb/enums.pl +++ b/pdb/enums.pl @@ -862,6 +862,15 @@ package Gimp::CodeGen::enums; GIMP_HISTOGRAM_ALPHA => '4', GIMP_HISTOGRAM_LUMINANCE => '5' } }, + GimpTRCType => + { contig => 1, + header => 'core/core-enums.h', + symbols => [ qw(GIMP_TRC_LINEAR GIMP_TRC_NON_LINEAR + GIMP_TRC_PERCEPTUAL) ], + mapping => { GIMP_TRC_LINEAR => '0', + GIMP_TRC_NON_LINEAR => '1', + GIMP_TRC_PERCEPTUAL => '2' } + }, GimpBrushApplicationMode => { contig => 1, header => 'paint/paint-enums.h', diff --git a/pdb/groups/drawable_filter.pdb b/pdb/groups/drawable_filter.pdb index 8af4fe8587..fbe3fe90ee 100644 --- a/pdb/groups/drawable_filter.pdb +++ b/pdb/groups/drawable_filter.pdb @@ -75,48 +75,7 @@ HELP %invoke = ( code => <<'CODE' { - GType op_type; - - /* Comes from gegl/operation/gegl-operations.h which is not public. */ - GType gegl_operation_gtype_from_name (const gchar *name); - - op_type = gegl_operation_gtype_from_name (operation_name); - /* Using the same rules as in xcf_load_effect() for plug-in created - * effects. - */ - if (g_type_is_a (op_type, GEGL_TYPE_OPERATION_SINK)) - { - g_set_error (error, GIMP_PDB_ERROR, GIMP_PDB_ERROR_INVALID_ARGUMENT, - "%s: the filter \"%s\" is unsafe.", - G_STRFUNC, operation_name); - - success = FALSE; - } - else if (g_strcmp0 (operation_name, "gegl:gegl") == 0 && - g_getenv ("GIMP_ALLOW_GEGL_GRAPH_LAYER_EFFECT") == NULL) - { - g_set_error (error, GIMP_PDB_ERROR, GIMP_PDB_ERROR_INVALID_ARGUMENT, - "%s: the filter \"gegl:gegl\" is unsafe.\n" - "For development purpose, set environment variable GIMP_ALLOW_GEGL_GRAPH_LAYER_EFFECT.", - G_STRFUNC); - - success = FALSE; - } - - if (! gegl_has_operation (operation_name) || ! g_strcmp0 (operation_name, "gegl:nop")) - { - if (! g_strcmp0 (operation_name, "gegl:nop")) - g_set_error_literal (error, GIMP_PDB_ERROR, GIMP_PDB_ERROR_INVALID_ARGUMENT, - "The filter \"gegl:nop\" is useless and not allowed."); - else - g_set_error (error, GIMP_PDB_ERROR, GIMP_PDB_ERROR_INVALID_ARGUMENT, - "%s: the filter \"%s\" is not installed.", - G_STRFUNC, operation_name); - - success = FALSE; - } - - if (success) + if (validate_operation_name (operation_name, error)) { GeglNode *operation = gegl_node_new (); @@ -133,6 +92,10 @@ HELP gimp_drawable_filter_set_clip (filter, FALSE); g_clear_object (&operation); } + else + { + success = FALSE; + } } CODE ); @@ -682,13 +645,301 @@ CODE ); } +sub drawable_filter_operation_get_available { + $blurb = 'Get a list of all available GEGL operation names for drawable filters.'; + + $help = <<'HELP'; +This procedure returns a list of all GEGL operation names available for use +with drawable filters. +HELP + + &martymichal_pdb_misc('2025', '3.2'); + + @outargs = ( + { + name => 'names', type => 'strv', + desc => 'The list of GEGL operation names', + } + ); + + %invoke = ( + code => <<'CODE' +{ + GList *ops = NULL; + GStrvBuilder *builder = NULL; + + ops = gimp_gegl_get_op_classes (FALSE); + builder = g_strv_builder_new (); + + for (GList *op = ops; op != NULL; op = op->next) + { + GeglOperationClass *op_klass = op->data; + + if (!validate_operation_name(op_klass->name, NULL)) + continue; + + g_strv_builder_add (builder, op_klass->name); + } + + names = g_strv_builder_end (builder); + + g_list_free (ops); + g_strv_builder_unref (builder); +} +CODE + ); +} + +sub drawable_filter_operation_get_details { + $blurb = 'Get information about a GEGL operation.'; + + $help = <<'HELP'; +This procedure returns information about a GEGL operation. This information +includes its human-readable title, description, categories and license. + +An operation can belong to no categories or to multiple categories. Multiple +categories are separated by the ':' character. + +Some operation's license is not specifically set. In such cases, the returned +license is 'unknown'. +HELP + + &martymichal_pdb_misc('2025', '3.2'); + + @inargs = ( + { + name => 'operation_name', type => 'string', + desc => 'The GEGL operation\'s name', + } + ); + + @outargs = ( + { + name => 'title', type => 'string', void_ret => 1, + desc => 'Human-readable title of the operation', + }, + { + name => 'description', type => 'string', + desc => 'Description of the operation\'s behaviour', + }, + { + name => 'categories', type => 'string', + desc => 'Categories the operation belongs to', + }, + { + name => 'license', type => 'string', + desc => 'License of the operation', + }, + ); + + %invoke = ( + code => <<'CODE' +{ + if (validate_operation_name (operation_name, error)) + { + GeglOperationClass *klass = NULL; + GType op_type; + + /* Comes from gegl/operation/gegl-operations.h which is not public. */ + GType gegl_operation_gtype_from_name (const gchar *name); + + op_type = gegl_operation_gtype_from_name (operation_name); + klass = g_type_class_ref (op_type); + + title = g_strdup (gegl_operation_class_get_key (klass, "title")); + description = g_strdup (gegl_operation_class_get_key (klass, "description")); + categories = g_strdup (gegl_operation_class_get_key (klass, "categories")); + license = g_strdup (gegl_operation_class_get_key (klass, "license")); + if (license == NULL) + license = g_strdup ("unknown"); + } + else + { + success = FALSE; + } +} +CODE + ); +} + +sub drawable_filter_operation_get_pspecs { + $blurb = 'Get information for all parameters of a GEGL operation.'; + + $help = <<'HELP'; +This procedure returns a list of all parameters used to configure a GEGL +operation. + +Each parameter is represented by GParamSpec. +HELP + + &martymichal_pdb_misc('2025', '3.2'); + + @inargs = ( + { + name => 'operation_name', type => 'string', + desc => 'The GEGL operation\'s name', + } + ); + + @outargs = ( + { + name => 'pspecs', type => 'valuearray', + desc => "List of all parameters of the GEGL operation", + } + ); + + %invoke = ( + code => <<'CODE' +{ + if (validate_operation_name (operation_name, error)) + { + GParamSpec **specs = NULL; + guint n_specs = 0; + guint n_parent_specs = 0; + + if (gimp_operation_config_is_custom (gimp, operation_name)) + { + GObjectClass *op_config_klass = NULL; + GObjectClass *op_parent_klass = NULL; + GType op_config_type; + GType op_parent_type; + + op_config_type = gimp_operation_config_get_type (gimp, operation_name, NULL, GIMP_TYPE_OPERATION_SETTINGS); + op_config_klass = g_type_class_ref (op_config_type); + specs = g_object_class_list_properties (op_config_klass, &n_specs); + g_type_class_unref (op_config_klass); + + op_parent_type = g_type_parent (op_config_type); + op_parent_klass = g_type_class_ref (op_parent_type); + g_free (g_object_class_list_properties (op_parent_klass, &n_parent_specs)); + g_type_class_unref (op_parent_klass); + + n_specs -= n_parent_specs; + } + else + { + specs = gegl_operation_list_properties (operation_name, &n_specs); + } + + pspecs = gimp_value_array_new (n_specs); + + for (gint i = 0; i < n_specs; i++) + { + GParamSpec *pspec = specs[n_parent_specs + i]; + GPParamDef param_def = { 0, }; + + /* Make sure we do not try to send param specs over the wire + * if we don't support sending their type. + */ + if (_gimp_param_spec_to_gp_param_def (pspec, ¶m_def, TRUE)) + { + GValue value = G_VALUE_INIT; + + g_value_init (&value, G_TYPE_PARAM); + + g_value_set_param (&value, g_param_spec_ref (pspec)); + gimp_value_array_append (pspecs, &value); + + g_value_unset (&value); + } + else + { + /* This is not technically a bug if an operation has + * unsupported argument types, because we cannot possibly + * support everything (third-party operations could have any + * kind of arguments; and even official ops have types + * such as audio fragment which we may never support). + * So this should not generate any WARNING. Yet we still + * want to softly notify developers, in case we can + * actually do something about some types. + */ + g_printerr ("%s: ignoring argument '%s' of procedure '%s'. " + "Unsupported argument type '%s'.\n", + G_STRFUNC, pspec->name, operation_name, + g_type_name (G_PARAM_SPEC_VALUE_TYPE (pspec))); + } + } + + g_free (specs); + } + else + { + success = FALSE; + } +} +CODE + ); +} + +$extra{app}->{code} = <<'CODE'; +static gboolean +validate_operation_name (const gchar *operation_name, + GError **error) +{ + GType op_type; + + /* Comes from gegl/operation/gegl-operations.h which is not public. */ + GType gegl_operation_gtype_from_name (const gchar *name); + + op_type = gegl_operation_gtype_from_name (operation_name); + + /* Using the same rules as in xcf_load_effect() for plug-in created + * effects. + */ + if (g_type_is_a (op_type, GEGL_TYPE_OPERATION_SINK)) + { + g_set_error (error, GIMP_PDB_ERROR, GIMP_PDB_ERROR_INVALID_ARGUMENT, + "%s: the filter \"%s\" is unsafe.", + G_STRFUNC, operation_name); + + return FALSE; + } + else if (g_strcmp0 (operation_name, "gegl:gegl") == 0 && + g_getenv ("GIMP_ALLOW_GEGL_GRAPH_LAYER_EFFECT") == NULL) + { + g_set_error (error, GIMP_PDB_ERROR, GIMP_PDB_ERROR_INVALID_ARGUMENT, + "%s: the filter \"gegl:gegl\" is unsafe.\n" + "For development purpose, set environment variable GIMP_ALLOW_GEGL_GRAPH_LAYER_EFFECT.", + G_STRFUNC); + + return FALSE; + } + + if (g_strcmp0 (operation_name, "gegl:nop") == 0) + { + g_set_error_literal (error, GIMP_PDB_ERROR, GIMP_PDB_ERROR_INVALID_ARGUMENT, + "The filter \"gegl:nop\" is useless and not allowed."); + return FALSE; + } + + if (! gegl_has_operation (operation_name)) + { + g_set_error (error, GIMP_PDB_ERROR, GIMP_PDB_ERROR_INVALID_ARGUMENT, + "%s: the filter \"%s\" is not installed.", + G_STRFUNC, operation_name); + + return FALSE; + } + + return TRUE; +} +CODE + @headers = qw( + + "libgimpbase/gimpbase.h" + "libgimpbase/gimpprotocol.h" + "libgimpbase/gimpwire.h" + + "libgimp/gimpgpparams.h" + "core/gimpcontainer.h" "core/gimpdrawablefilter.h" "core/gimpdrawable-filters.h" "core/gimpimage-undo-push.h" "core/gimpitem.h" + "gegl/gimp-gegl-utils.h" "operations/gimp-operation-config.h" "operations/gimpoperationsettings.h" "gimppdberror.h" @@ -706,7 +957,10 @@ CODE drawable_filter_get_number_arguments drawable_filter_get_pspec drawable_filter_get_arguments - drawable_filter_delete); + drawable_filter_delete + drawable_filter_operation_get_available + drawable_filter_operation_get_details + drawable_filter_operation_get_pspecs); %exports = (app => [@procs], lib => [@procs]); diff --git a/pdb/stddefs.pdb b/pdb/stddefs.pdb index 73f8b241bd..5213fa1d01 100644 --- a/pdb/stddefs.pdb +++ b/pdb/stddefs.pdb @@ -125,6 +125,10 @@ sub martin_pdb_misc { contrib_pdb_misc('Martin Nordholts', '', @_); } +sub martymichal_pdb_misc { + contrib_pdb_misc('Ondřej Míchal', 'harrymichal@seznam.cz', @_); +} + sub mitch_pdb_misc { contrib_pdb_misc('Michael Natterer', 'mitch@gimp.org', @_); } diff --git a/plug-ins/filter-browser/filter-browser.c b/plug-ins/filter-browser/filter-browser.c new file mode 100644 index 0000000000..32bdb6e3e1 --- /dev/null +++ b/plug-ins/filter-browser/filter-browser.c @@ -0,0 +1,626 @@ +/* + * GIMP plug-in for browsing available GEGL filters. + * + * Copyright (C) 2025 Ondřej Míchal + * Copyright (C) 2017 Øyvind Kolås + * + * 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 +#include +#include +#include +#include +#include +#include + +#include "libgimp/stdplugins-intl.h" +#include +#include + +#include "gegl-filter-info.h" + +#define PLUG_IN_GEGL_FILTER_BROWSER "plug-in-gegl-filter-browser" +#define PLUG_IN_BINARY "gegl-filter-browser" +#define PLUG_IN_ROLE "gegl-filter-browser" + +#define FILTER_BROWSER_WIDTH 800 +#define FILTER_BROWSER_HEIGHT 500 + +typedef enum +{ + SEARCH_NAME, + SEARCH_TITLE, + SEARCH_DESCRIPTION, + SEARCH_CATEGORY, +} FilterBrowserSearchType; + +typedef struct +{ + GtkWidget *dialog; + + GtkWidget *browser; + GtkListBox *filter_list; + + GList *filters; + + FilterBrowserSearchType search_type; +} FilterBrowserPrivate; + +typedef struct _FilterBrowser FilterBrowser; +typedef struct _FilterBrowserClass FilterBrowserClass; + +struct _FilterBrowser +{ + GimpPlugIn parent_instance; +}; + +struct _FilterBrowserClass +{ + GimpPlugInClass parent_class; +}; + +#define FILTER_BROWSER_TYPE (filter_browser_get_type ()) +#define FILTER_BROWSER(obj) \ + (g_type_check_instance_cast ((obj), browser_type, FilterBrowser)) + +GType filter_browser_get_type (void) G_GNUC_CONST; + +G_DEFINE_TYPE (FilterBrowser, filter_browser, GIMP_TYPE_PLUG_IN) + +GIMP_MAIN (FILTER_BROWSER_TYPE) +DEFINE_STD_SET_I18N + +static GtkWidget * +create_filter_info_view (GimpGeglFilterInfo *filter_info) +{ + GtkWidget *frame = NULL; + GtkWidget *label = NULL; + GtkWidget *vbox = NULL; + GtkWidget *view = NULL; + gchar *buf = NULL; + const gchar *name = NULL; + const gchar *title = NULL; + const gchar *description = NULL; + const gchar *categories = NULL; + const gchar *license = NULL; + const GimpValueArray *pspecs = NULL; + + g_object_get (G_OBJECT (filter_info), + "name", &name, + "title", &title, + "description", &description, + "categories", &categories, + "license", &license, + "pspecs", &pspecs, + NULL); + + /* + * TODO: Information to include + * - what is the are of effect? (Area, Point,..) + * - Special requirements and attributes (needs alpha, is position dependent?,..) + */ + + if (description && strlen (description) < 2) + description = NULL; + if (categories && strlen (categories) < 2) + categories = NULL; + if (license && strlen (license) < 2) + license = NULL; + + view = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + + /* + * Main information + */ + + if (title != NULL) + frame = gimp_frame_new (title); + else + frame = gimp_frame_new (name); + gtk_label_set_selectable (GTK_LABEL (gtk_frame_get_label_widget (GTK_FRAME (frame))), TRUE); + gtk_box_pack_start (GTK_BOX (view), frame, FALSE, FALSE, 0); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 8); + gtk_container_add (GTK_CONTAINER (frame), vbox); + + buf = g_strdup_printf ("operation name: %s", name); + label = gtk_label_new (buf); + g_free (buf); + gtk_label_set_selectable (GTK_LABEL (label), TRUE); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); + gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0); + + if (categories != NULL) + { + gchar *categories_buf = NULL; + + categories_buf = g_strdelimit (g_strdup (categories), ":", ' '); + buf = g_strdup_printf ("categories: %s", categories_buf); + g_free (categories_buf); + label = gtk_label_new (buf); + g_free (buf); + + gtk_label_set_selectable (GTK_LABEL (label), TRUE); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); + gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0); + } + + if (description != NULL) + { + label = gtk_label_new (description); + gtk_label_set_selectable (GTK_LABEL (label), TRUE); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); + gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0); + } + + /* + * Parameters + */ + + if (pspecs != NULL && gimp_value_array_length (pspecs) != 0) + { + GtkWidget *grid = NULL; + + frame = gimp_frame_new (_ ("Parameters")); + gtk_box_pack_start (GTK_BOX (view), frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 8); + gtk_container_add (GTK_CONTAINER (frame), vbox); + gtk_widget_show (vbox); + + grid = gtk_grid_new (); + gtk_grid_set_column_spacing (GTK_GRID (grid), 6); + gtk_grid_set_row_spacing (GTK_GRID (grid), 4); + gtk_box_pack_start (GTK_BOX (vbox), grid, FALSE, FALSE, 0); + gtk_widget_show (grid); + + for (gint i = 0; i < gimp_value_array_length (pspecs); i++) + { + GParamSpec *pspec = g_value_get_param (gimp_value_array_index (pspecs, i)); + GtkWidget *label = NULL; + + /* Some param specs are not supported by the wire. Thankfully, the + * wire is well-behaved and returns NULL in such cases for each + * param spec. */ + if (pspec == NULL) { + label = gtk_label_new ("unknown"); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_label_set_yalign (GTK_LABEL (label), 0.0); + gtk_grid_attach (GTK_GRID (grid), label, 0, i, 1, 1); + gtk_widget_show (label); + continue; + } + + label = gtk_label_new (g_param_spec_get_name (pspec)); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_label_set_yalign (GTK_LABEL (label), 0.0); + gtk_grid_attach (GTK_GRID (grid), label, 0, i, 1, 1); + gtk_widget_show (label); + + label = gtk_label_new (g_type_name (G_PARAM_SPEC_VALUE_TYPE (pspec))); + gimp_label_set_attributes (GTK_LABEL (label), PANGO_ATTR_FAMILY, "monospace", + PANGO_ATTR_STYLE, PANGO_STYLE_ITALIC, -1); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_label_set_yalign (GTK_LABEL (label), 0.0); + gtk_grid_attach (GTK_GRID (grid), label, 1, i, 1, 1); + gtk_widget_show (label); + + label = gtk_label_new (g_param_spec_get_blurb (pspec)); + gtk_label_set_use_markup (GTK_LABEL (label), TRUE); + gtk_label_set_selectable (GTK_LABEL (label), TRUE); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_label_set_yalign (GTK_LABEL (label), 0.0); + gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); + gtk_grid_attach (GTK_GRID (grid), label, 2, i, 1, 1); + gtk_widget_show (label); + } + } + + /* + * Additional information + */ + + if (license != NULL) + { + frame = gimp_frame_new (_("Additional Information")); + gtk_box_pack_start (GTK_BOX (view), frame, FALSE, FALSE, 0); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 8); + gtk_container_add (GTK_CONTAINER (frame), vbox); + + buf = g_strdup_printf ("license: %s", license); + label = gtk_label_new (buf); + g_free (buf); + + gtk_label_set_selectable (GTK_LABEL (label), TRUE); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); + gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0); + } + + gtk_widget_show_all (view); + + return view; +} + +static void +filter_list_row_selected (GtkListBox *filter_list, + GtkListBoxRow *row, + gpointer data) +{ + FilterBrowserPrivate *browser = NULL; + GimpGeglFilterInfo *filter_info = NULL; + + browser = (FilterBrowserPrivate *) data; + + g_return_if_fail (GTK_IS_LIST_BOX (filter_list)); + if (row == NULL) + return; + + filter_info = g_object_get_data (G_OBJECT (row), "filter-info"); + + gimp_browser_set_widget (GIMP_BROWSER (browser->browser), + create_filter_info_view (filter_info)); +} + +static gint +gegl_filter_info_sort_func (gconstpointer a, + gconstpointer b) +{ + GimpGeglFilterInfo *info_a = (GimpGeglFilterInfo *) a; + GimpGeglFilterInfo *info_b = (GimpGeglFilterInfo *) b; + gchar *name_a = NULL; + gchar *name_b = NULL; + + g_assert_true (GIMP_IS_GEGL_FILTER_INFO (info_a)); + g_assert_true (GIMP_IS_GEGL_FILTER_INFO (info_b)); + + g_object_get (G_OBJECT (info_a), "name", &name_a, NULL); + g_object_get (G_OBJECT (info_b), "name", &name_b, NULL); + + return g_strcmp0 (name_a, name_b); +} + +static GtkWidget * +create_filter_list_row (gpointer item, + gpointer user_data) +{ + GimpGeglFilterInfo *filter_info = item; + GtkWidget *row = NULL; + GtkWidget *box = NULL; + GtkWidget *name_label = NULL; + const gchar *name = NULL; + const gchar *categories = NULL; + + row = gtk_list_box_row_new (); + + g_object_set_data (G_OBJECT (row), "filter-info", filter_info); + g_object_get (G_OBJECT (filter_info), "name", &name, "categories", &categories, NULL); + + box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_widget_set_focus_on_click(box, FALSE); + gtk_container_add (GTK_CONTAINER (row), box); + + name_label = gtk_label_new (name); + gtk_box_pack_start (GTK_BOX (box), name_label, FALSE, FALSE, 0); + + gtk_widget_show_all (GTK_WIDGET (row)); + + return row; +} + +static void +insert_gegl_filter_info_into_list_store (gpointer data, + gpointer user_data) +{ + GimpGeglFilterInfo *filter_info = (GimpGeglFilterInfo *) data; + GListStore *model = (GListStore *) (user_data); + + g_assert_true (GIMP_IS_GEGL_FILTER_INFO (filter_info)); + g_assert_true (G_IS_LIST_STORE (model)); + + g_list_store_append (model, filter_info); +} + +static void +filter_browser_search (GimpBrowser *gimp_browser, + const gchar *search_text, + FilterBrowserSearchType search_type, + FilterBrowserPrivate *browser) +{ + GListStore *new_model = NULL; + gchar *search_msg = NULL; + gchar *search_summary = NULL; + guint matches; + + switch (search_type) + { + case SEARCH_NAME: + search_msg = _("Searching by name"); + break; + case SEARCH_TITLE: + search_msg = _("Searching by title"); + break; + case SEARCH_DESCRIPTION: + search_msg = _("Searching by description"); + break; + case SEARCH_CATEGORY: + search_msg = _("Searching by category"); + default: + return; + } + gimp_browser_show_message (GIMP_BROWSER (browser->browser), search_msg); + + new_model = g_list_store_new (GIMP_GEGL_FILTER_TYPE_INFO); + + if (strlen (search_text) != 0) + { + for (GList *iter = browser->filters; iter != NULL; iter = iter->next) + { + GimpGeglFilterInfo *filter_info = NULL; + const gchar *value = NULL; + + filter_info = (GimpGeglFilterInfo *) iter->data; + + switch (search_type) + { + case SEARCH_NAME: + g_object_get (G_OBJECT (filter_info), "name", &value, NULL); + break; + case SEARCH_TITLE: + g_object_get (G_OBJECT (filter_info), "title", &value, NULL); + break; + case SEARCH_DESCRIPTION: + g_object_get (G_OBJECT (filter_info), "description", &value, NULL); + break; + case SEARCH_CATEGORY: + g_object_get (G_OBJECT (filter_info), "categories", &value, NULL); + default: + return; + } + + if (value == NULL) + continue; + + if (! g_str_match_string (search_text, value, TRUE)) + continue; + + insert_gegl_filter_info_into_list_store (filter_info, new_model); + } + } + else + { + g_list_foreach (browser->filters, insert_gegl_filter_info_into_list_store, new_model); + } + + gtk_list_box_bind_model (browser->filter_list, G_LIST_MODEL (new_model), + create_filter_list_row, NULL, NULL); + gtk_list_box_select_row (browser->filter_list, + gtk_list_box_get_row_at_index (browser->filter_list, 0)); + + matches = g_list_model_get_n_items (G_LIST_MODEL (new_model)); + if (search_text != NULL && strlen (search_text) > 0) + { + if (matches == 0) + { + search_summary = g_strdup (_("No matches for your query")); + gimp_browser_show_message (GIMP_BROWSER (browser->browser), + _("No matches")); + } + else + { + search_summary = g_strdup_printf ( + ngettext ("%d filter matches your query", "%d filters match your query", matches), + matches); + } + } + else + { + search_summary = g_strdup_printf (ngettext ("%d filter", "%d filters", matches), matches); + } + gimp_browser_set_search_summary (gimp_browser, search_summary); + g_free (search_summary); +} + +static void +browser_dialog_response (GtkWidget *widget, + gint response_id, + FilterBrowserPrivate *browser) +{ + gtk_widget_destroy (browser->dialog); + g_list_free (browser->filters); + g_free (browser); + g_main_loop_quit (g_main_loop_new (NULL, TRUE)); +} + +static GimpValueArray * +filter_browser_run (GimpProcedure *procedure, + GimpProcedureConfig *config, + gpointer run_data) +{ + FilterBrowserPrivate *browser = NULL; + GtkWidget *scrolled_window = NULL; + GtkStyleContext *style_context = NULL; + GListStore *model = NULL; + gchar **filter_names = NULL; + + gimp_ui_init (PLUG_IN_BINARY); + + browser = g_new0 (FilterBrowserPrivate, 1); + browser->search_type = SEARCH_NAME; + + browser->dialog = gimp_dialog_new (_("GEGL Filter Browser"), + PLUG_IN_ROLE, + NULL, + 0, + gimp_standard_help_func, + PLUG_IN_GEGL_FILTER_BROWSER, + _("_Close"), + GTK_RESPONSE_CLOSE, + NULL); + gtk_window_set_default_size (GTK_WINDOW (browser->dialog), + FILTER_BROWSER_WIDTH, FILTER_BROWSER_HEIGHT); + + g_signal_connect (browser->dialog, "response", + G_CALLBACK (browser_dialog_response), browser); + + browser->browser = gimp_browser_new (); + gimp_browser_add_search_types (GIMP_BROWSER (browser->browser), + _("by name"), SEARCH_NAME, + _("by title"), SEARCH_TITLE, + _("by description"), SEARCH_DESCRIPTION, + NULL); + gtk_container_set_border_width (GTK_CONTAINER (browser->browser), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (browser->dialog))), + browser->browser, TRUE, TRUE, 0); + gtk_widget_show (browser->browser); + + g_signal_connect (browser->browser, "search", G_CALLBACK (filter_browser_search), browser); + + scrolled_window = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window), GTK_SHADOW_IN); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); + gtk_box_pack_start (GTK_BOX (gimp_browser_get_left_vbox (GIMP_BROWSER (browser->browser))), + scrolled_window, TRUE, TRUE, 0); + gtk_widget_show (scrolled_window); + + /* + * TODO: Extract the data preparation, so that it can run on idle and not + * block the GUI. + */ + filter_names = gimp_drawable_filter_operation_get_available (); + for (gint i = 0; filter_names[i] != NULL; i++) + { + GimpGeglFilterInfo *info = NULL; + gchar *title = NULL; + gchar *description = NULL; + gchar *categories = NULL; + gchar *license = NULL; + GimpValueArray *pspecs = NULL; + + gimp_drawable_filter_operation_get_details (filter_names[i], + &title, + &description, + &categories, + &license); + pspecs = gimp_drawable_filter_operation_get_pspecs (filter_names[i]); + + info = g_object_new (GIMP_GEGL_FILTER_TYPE_INFO, + "name", filter_names[i], + "title", title, + "description", description, + "categories", categories, + "license", license, + "pspecs", pspecs, + NULL); + browser->filters = g_list_append (browser->filters, info); + + g_free (title); + g_free (description); + g_free (categories); + g_free (license); + gimp_value_array_unref (pspecs); + } + g_strfreev (filter_names); + + browser->filters = g_list_sort (browser->filters, gegl_filter_info_sort_func); + + model = g_list_store_new (GIMP_GEGL_FILTER_TYPE_INFO); + g_list_foreach (browser->filters, insert_gegl_filter_info_into_list_store, model); + + browser->filter_list = GTK_LIST_BOX (gtk_list_box_new ()); + style_context = gtk_widget_get_style_context(GTK_WIDGET (browser->filter_list)); + gtk_style_context_add_class (style_context, "view"); + gtk_list_box_set_selection_mode (GTK_LIST_BOX (browser->filter_list), GTK_SELECTION_BROWSE); + gtk_list_box_bind_model (GTK_LIST_BOX (browser->filter_list), + G_LIST_MODEL (model), create_filter_list_row, NULL, NULL); + gtk_container_add (GTK_CONTAINER (scrolled_window), GTK_WIDGET (browser->filter_list)); + gtk_widget_show (GTK_WIDGET (browser->filter_list)); + + g_signal_connect (browser->filter_list, "row-selected", + G_CALLBACK (filter_list_row_selected), browser); + + gtk_list_box_select_row (browser->filter_list, + gtk_list_box_get_row_at_index (browser->filter_list, 0)); + + gtk_widget_show (GTK_WIDGET (browser->dialog)); + g_main_loop_run (g_main_loop_new (NULL, TRUE)); + + return gimp_procedure_new_return_values (procedure, GIMP_PDB_SUCCESS, NULL); +} + +static GimpProcedure * +filter_browser_create_procedure (GimpPlugIn *plug_in, + const gchar *procedure_name) +{ + GimpProcedure *procedure = NULL; + + if (strcmp (procedure_name, PLUG_IN_GEGL_FILTER_BROWSER)) + return procedure; + + procedure = gimp_procedure_new (plug_in, procedure_name, GIMP_PDB_PROC_TYPE_PLUGIN, + filter_browser_run, NULL, NULL); + + gimp_procedure_set_menu_label (procedure, _ ("_GEGL Filter Browser")); + gimp_procedure_set_icon_name (procedure, GIMP_ICON_PLUGIN); + gimp_procedure_add_menu_path (procedure, "/Help/[Programming]"); + + gimp_procedure_set_documentation (procedure, + _("Display information about available" + " GEGL operations (i.e., filters)."), + _("Shows a list of all GEGL operations," + " their details and what parameters they" + " are configured with. The list contains" + " operations provided by GEGL itself," + " GIMP and plug-ins loaded by GEGL."), + PLUG_IN_GEGL_FILTER_BROWSER); + gimp_procedure_set_attribution (procedure, "Ondřej Míchal", "Ondřej Míchal", "2025"); + + gimp_procedure_add_enum_argument (procedure, "run-mode", "Run mode", + "The run mode", GIMP_TYPE_RUN_MODE, + GIMP_RUN_INTERACTIVE, G_PARAM_READWRITE); + + return procedure; +} + +static GList * +filter_browser_query_procedures (GimpPlugIn *plug_in) +{ + return g_list_append (NULL, g_strdup (PLUG_IN_GEGL_FILTER_BROWSER)); +} + +static void +filter_browser_init (FilterBrowser *browser) +{ +} + +static void +filter_browser_class_init (FilterBrowserClass *klass) +{ + GimpPlugInClass *plug_in_class = GIMP_PLUG_IN_CLASS (klass); + + plug_in_class->query_procedures = filter_browser_query_procedures; + plug_in_class->create_procedure = filter_browser_create_procedure; + plug_in_class->set_i18n = STD_SET_I18N; +} diff --git a/plug-ins/filter-browser/filter-browser.h b/plug-ins/filter-browser/filter-browser.h new file mode 100644 index 0000000000..f5899684f6 --- /dev/null +++ b/plug-ins/filter-browser/filter-browser.h @@ -0,0 +1,28 @@ +/* + * GIMP plug-in for browsing available GEGL filters. + * + * Copyright (C) 2025 Ondřej Míchal + * + * 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 + * . + */ + +G_BEGIN_DECLS + +#define GEGL_FILTER_TYPE_INFO gegl_filter_info_get_type () +G_DECLARE_FINAL_TYPE (GeglFilterInfo, gegl_filter_info, GEGL, FILTER_INFO, GObject) + +G_END_DECLS + +#endif diff --git a/plug-ins/filter-browser/gegl-filter-info.c b/plug-ins/filter-browser/gegl-filter-info.c new file mode 100644 index 0000000000..91d377b5d4 --- /dev/null +++ b/plug-ins/filter-browser/gegl-filter-info.c @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2025 Ondřej Míchal + * + * 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 "gegl-filter-info.h" + +typedef struct _GimpGeglFilterInfo +{ + GObject parent_instance; + + gchar *name; + gchar *title; + gchar *description; + gchar *categories; + gchar *license; + GimpValueArray *pspecs; +} GimpGeglFilterInfo; + +enum +{ + PROP_0, + PROP_NAME, + PROP_TITLE, + PROP_DESCRIPTION, + PROP_CATEGORIES, + PROP_LICENSE, + PROP_PSPECS, + N_PROPERTIES, +}; + +static GParamSpec *properties[N_PROPERTIES] = { 0 }; + +G_DEFINE_FINAL_TYPE (GimpGeglFilterInfo, gimp_gegl_filter_info, G_TYPE_OBJECT) + +static void +gimp_gegl_filter_info_get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + GimpGeglFilterInfo *self = GIMP_GEGL_FILTER_INFO (object); + + switch (prop_id) + { + case PROP_NAME: + g_value_set_string (value, self->name); + break; + case PROP_TITLE: + g_value_set_string (value, self->title); + break; + case PROP_DESCRIPTION: + g_value_set_string (value, self->description); + break; + case PROP_CATEGORIES: + g_value_set_string (value, self->categories); + break; + case PROP_LICENSE: + g_value_set_string (value, self->license); + break; + case PROP_PSPECS: + g_value_set_boxed (value, self->pspecs); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gimp_gegl_filter_info_set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + GimpGeglFilterInfo *self = GIMP_GEGL_FILTER_INFO (object); + + switch (prop_id) + { + case PROP_NAME: + self->name = g_value_dup_string (value); + break; + case PROP_TITLE: + self->title = g_value_dup_string (value); + break; + case PROP_DESCRIPTION: + self->description = g_value_dup_string (value); + break; + case PROP_CATEGORIES: + self->categories = g_value_dup_string (value); + break; + case PROP_LICENSE: + self->license = g_value_dup_string (value); + break; + case PROP_PSPECS: + self->pspecs = g_value_dup_boxed (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gimp_gegl_filter_info_class_init (GimpGeglFilterInfoClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = gimp_gegl_filter_info_get_property; + object_class->set_property = gimp_gegl_filter_info_set_property; + + properties[PROP_NAME] = g_param_spec_string ("name", "Name", "", NULL, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + properties[PROP_TITLE] = g_param_spec_string ("title", "Title", "", NULL, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + properties[PROP_DESCRIPTION] = g_param_spec_string ( + "description", "Description", "", NULL, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + properties[PROP_CATEGORIES] = g_param_spec_string ( + "categories", "Categories", "", NULL, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + properties[PROP_LICENSE] = g_param_spec_string ("license", "License", "", NULL, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + properties[PROP_PSPECS] = g_param_spec_boxed ( + "pspecs", "Parameter Specifications", "", GIMP_TYPE_VALUE_ARRAY, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, N_PROPERTIES, properties); +} + +static void +gimp_gegl_filter_info_init (GimpGeglFilterInfo *self) +{ +} diff --git a/plug-ins/filter-browser/gegl-filter-info.h b/plug-ins/filter-browser/gegl-filter-info.h new file mode 100644 index 0000000000..ed86ea993c --- /dev/null +++ b/plug-ins/filter-browser/gegl-filter-info.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2025 Ondřej Míchal + * + * 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 . + */ + +#ifndef __GIMP_GEGL_FILTER_INFO_H__ +#define __GIMP_GEGL_FILTER_INFO_H__ + +#include + +G_BEGIN_DECLS + +#define GIMP_GEGL_FILTER_TYPE_INFO gimp_gegl_filter_info_get_type () +G_DECLARE_FINAL_TYPE (GimpGeglFilterInfo, gimp_gegl_filter_info, GIMP, GEGL_FILTER_INFO, GObject) +G_END_DECLS + +#endif diff --git a/plug-ins/filter-browser/meson.build b/plug-ins/filter-browser/meson.build new file mode 100644 index 0000000000..36f4e8a852 --- /dev/null +++ b/plug-ins/filter-browser/meson.build @@ -0,0 +1,30 @@ +plugin_name = 'filter-browser' + +plugin_sources = [ + 'filter-browser.c', + 'gegl-filter-info.c', +] + +if platform_windows + plugin_sources += windows.compile_resources( + gimp_plugins_rc, + args: [ + '--define', 'ORIGINALFILENAME_STR="@0@"'.format(plugin_name+'.exe'), + '--define', 'INTERNALNAME_STR="@0@"' .format(plugin_name), + '--define', 'TOP_SRCDIR="@0@"' .format(meson.project_source_root()), + ], + include_directories: [ + rootInclude, appInclude, + ], + ) +endif + +plugin_exe = executable(plugin_name, + plugin_sources, + dependencies: [ + libgimpui_dep, + ], + win_subsystem: 'windows', + install: true, + install_dir: gimpplugindir / 'plug-ins' / plugin_name) +plugin_executables += [plugin_exe.full_path()] diff --git a/plug-ins/meson.build b/plug-ins/meson.build index 98f402f546..19a2b9355a 100644 --- a/plug-ins/meson.build +++ b/plug-ins/meson.build @@ -20,6 +20,7 @@ complex_plugins_list = [ { 'name': 'file-raw', }, { 'name': 'file-sgi', }, { 'name': 'file-tiff', }, + { 'name': 'filter-browser', }, { 'name': 'flame', }, { 'name': 'fractal-explorer', }, { 'name': 'gfig', }, diff --git a/themes/Default/common.css b/themes/Default/common.css index 20a41cb2a2..b5c1fe770d 100644 --- a/themes/Default/common.css +++ b/themes/Default/common.css @@ -60,6 +60,20 @@ border: 1px solid @stronger-border-color; } +/* ListBox when used as a view. Selected items. */ +.view row { + background-color: @extreme-bg-color; + color: @fg-color; +} + +.view row:hover { + background-color: unset; +} + +.view row:selected { + background-color: @extreme-selected-color; +} + /* Define color for drag and drop borders * in Layer/Channel/Path dockables */ GimpDock .view:drop(active) {