Replace PF_OPTION for PDB procedure arguments
Background
This is about PF_OPTION and SF_OPTION in Gimp v2. Those let a plugin declare an enum-like thing that appeared as a pop-up menu or combobox in a plugin's dialog. PyGimp and ScriptFu implemented those.
Since v3, plugins usually don't implement their own GUI, PyGimp is obsolete, and the GUI implemented by ScriptFu is planned to be obsolete. Instead, libgimp GimpProcedureDialog implements a dialog for many plugins.
So this is about replacing a feature of v2. It migrates the feature from PyGimp and ScriptFu to libgimp, so that plugins written in any bound language can use the feature.
The feature is used infrequently, but more frequently than say PF_LAYER for choosing an auxiliary item.
In this context, the word "options" is related to "enumeration type", "list store", and "dictionary."
PF_ENUM is distinct, and declares a procedure's argument has type of an existing GIMP enumeration type.
See also
This is item 1 of the roadmap in #8495.
#8493 has some discussion
MR !722 and !709 are rejected designs.
This proposal attempts to follow the design discussed by Jehan in the comments of !709.
Brief Summary
A plugin may define a named options into the PDB. An options is like an enumeration.
Then a plugin can declare a plugin's argument to be of that named options type. Then the generated GUI for the plugin displays a popup menu of the translated, symbolic names of the option's members. The plugin receives integer values for a user's choice of option.
Such PDB options are not enums of the language of the plugin. A plugin author can define a corresponding enum in the language of the plugin, and use it to define the integer values of the PDB options, on the right hand side.
Such PDB options are owned by a plugin procedure and used almost exclusively by the procedure itself. Other plugin procedures may refer symbolically to another procedure's options but this happens at runtime and requires calls to the PDB. Such use also makes a dependency between the procedures.
In other words, a PDB option is an enumeration type in the PDB's type system, so to speak. A PDB option is not a type in the programming languages of Gimp or plugins.
When a plugin file is uninstalled, all its contained PluginOptions are no longer defined in the PDB.
Example in Python
def do_create_procedure(self, name):
...
procedure.add_options ("Hardness",
("Soft", _("Soft"), 1),
("Firm", _("Firm"), 5),
("Hard", _("Hard"), 7))
procedure.add_option_arg ("arg1", _("Edge hardness"), "Hardness")
procedure.add_option_arg ("arg2", _("Node hardness"), "Hardness")
...
How Options appear in reference documents to a plugin author
The PDB Browser will show PluginOptions.
A procedure's owned options will display after, in parallel to, the procedure's arguments.
Example:
python-fu-test-options
GIMP Plugin
...
Parameters
run-mode GimpRunMode The run mode { RUN_INTERACTIVE (0) ...
edge-hardness Options "Hardness" The hardness of edges
node-hardness Options "Hardness" The hardness of nodes
Options
"Hardness" { Soft=1, Firm=5, Hard=7 }
"Craziness" { Super=0, Bat=1 }
...
A procedure may define more than one PluginOptions.
A procedure may declare many arguments using the same PluginOptions.
Any other procedures defined in the same plugin file can not use by name a PluginOptions defined by another procedure.
Options are designated by name and the name e.g. "Hardness" is not syntactically in the programming language of the plugin procedure.
A called plugin receives an integer. (See section "Alternative")
The name of a member of a PluginOptions e.g. "Soft" is not syntactically in the language of the procedure.
The PDB Browser does not show any translated strings for arguments or options.
Alternative
This design makes an option integer valued, but they could just as well be string valued. In v2, they are integer valued, for unknown reasons. In hindsight, you might consider it premature optimization, since relatively few values are stored.
The choice of integer versus string valued does not much affect this design, except in the details.
The choice of integer versus string valued does affect third party plugin authors porting plugins from v2 to v3. Most existing plugins probably assume enums of the language are integer valued. Since 3.11 Python supports string valued enums.
Internationalization Translations
When defining a PluginOptions, a procedure will provide for each member:
programming name
translatable GUI name
integer value
The programming name is in English. It is the key, and should be unique within the set of members. The programming name is not displayed to a user.
The translatable name is in English but should be marked for translation. The translatable name is user facing, its translation is displayed to a user.
When a plugin author modifies a member of a PluginOptions, only the member's translatable GUI name may need to be retranslated.
The name of the PluginOptions is not translatable or displayed to a user, and it is not the default label for any widget displaying a procedure argument having the type of the PluginOption.
Order
The order in which members of a PluginOption are defined is preserved in the GUI. For example, most frequently used choices are usually first.
The order of the integer values is not significant. They don't need to be consecutive. They must be unsigned ints. They must be unique (for reverse lookup.)
When a plugin author later adds a member to the tail of a PluginOptions, using a previously unused integer value, this might not affect existing plugin code, or other plugins that "hard code" integer values of the PluginOptions.
When a plugin author later inserts a member into a PluginOptions, using a previously unused integer value, this should only affect the GUI.
In other words, changes to a PluginOptions can be backwards compatible.
Classes
Classes new to libgimp or libgimpbase.
GimpPluginOptions
Defines a named, run-time enumeration, a set of choices for an argument of a PDB procedure. Note plural tense, this is a container. A dictionary having unique values.
Each member as described aboved.
Responsible for:
Represent self as GtkListStore, to display in a combobox. (the list maps from translated string to integer value.)
Serialize/deserialize self (to plugin.rc)
Lookup int value by member name.
Lookup translated name by int value.
Enforcing uniqueness of its name, within a procedure.
Collaborates
GimpPluginProcedure owns an array of these, unique by name.
GimpProcedureConfig asks this to serialize self
GimpPropWidgetOption asks this for a representational GtkListStore
Asks GimpPDB whether it's name is already used by the same owning procedure.
GimpParamSpecPluginOption
Inherits GParamSpec. Metadata for an argument or property that holds a member of a PluginOptions
Note the name is singular tense, about a member of a container.
Responsible for:
Knows the name of the GimpPluginOptions
the described parameter is a member of
Get and set GValues declared to hold type GimpParamPluginOption
Will actually get/set an integer value into the GValue,
optionally validating that the int value
is a member of self's known GimpPluginOptions.
Collaborates
GimpProcedure creates a formal argument using this,
when a plugin declares an argument is an options argument.
GimpProcedure (?) creates an actual argument using this,
when marshalling arguments for a call to the procedure.
The wire protocol uses this to serialize GValues when crossing the wire.
(??? Does the name of the containing PluginOptions cross the wire?)
GimpParamPluginOption
Exists solely to be the declared type of GValues holding a member of PluginOptions, described by a GimpParamSpecPluginOption.
Not instantiated. Most traffic is in integers.
GimpPropWidgetPluginOption
A combobox widget that displays the choices in a PluginOptions.
Inherits GimpPropWidget and its responsibilities and collaborations. Only differs in look-and-feel, having no new responsibilities or collaborations. The main responsibility is to put a user's choice into a property of a GimpProcedureConfig.
Pragmatic order of development
This is a rough idea of how development might proceed, subject to changes in the design.
Add test plugin calling procedure.add_option_arg
Modify GimpProcedure, add method: add_option_arg
Add class GimpParamSpecPluginOption and class GimpParamPluginOption in a
separate file from libgimp/gimpparamspecoption.c
cloned from gimpparamspecs.[c,h]
At this point, the test plugin will create, but it's dialog will fail.
Modify GimpProcedureConfig to handle the case GimpParamSpecPluginOption.
Add class GimpPropWidgetOptions
At this point, the test plugin will have a dialog showing a combobox of a dummy PluginOptions.
Modify GimpProcedure, add method: add_options
Add a call to that procedure in the test plugin.
Implement class GimpPluginOptions
At this point, the dialog for the plugin will show the defined choices, on the first session of GIMP as the plugin is installed, But fail the second session because the plugin's owned PluginOptions doesn't serialize and the plugin is not requeried. (??? This might not be correct, it might work.)
Add serialization interface to GimpPluginOptions.
At this point, a second session of GIMP and the plugin will show the defined choices in the combobox widget.
Modify GimpConfig in libgimpconfig to handle GimpParamSpecPluginOption
At this point, choices will be persisted as settings of a plugin; a reopened plugin will show the prior choice. At this point, only Python plugins work.
Modify ScriptFu to create procedure arguments using GParamSpecPluginOption
when SF_OPTION is seen.
At this point, plugins will work, whether written in languages using GIR to bind to GIMP, or written in ScriptFu which doesn't use GIR.
But ScriptFu still rolls its own GUI. When other pieces are implemented:
Eliminate ScriptFu's GUI code.
More testing of other rarely used API for options.
More testing of options in plugins in other bound languages.
How a plugin procedure access a member of PluginOptions
Accesses are run-time function calls, since PluginOptions are not types in the language of a plugin.
Rarely, a procedure may want to lookup the translated name of an options member. IOW a reverse dictionary lookup. For example, the procedure may be called with an integer value for a PluginOption and want to display a message.
gchar * repr = gimp_procedure_get_option_representation ("Hardness", 1)
Rarely a procedure may want to lookup the value of a member of options owned by another procedure, so that it can pass the value. The procedure must known the name of the options and the name of the member of the options, and the procedure which owns the options.
guint hardness = gimp_procedure_get_option_value (procedure, "Hardness", "Soft")
guint GimpProcedure.get_option_value (gchar * options_name, gchar * member_name)
If options are string valued, this method is not needed.
Namespacing
A PluginOptions is owned by a plugin procedure. The name of a PluginOptions is only unique within the namespace of the owning plugin procedure.
Another procedure can refer to a PluginOptions only by knowing both the name of the owning procedure and the name of the PluginOptions. (Like GIMP enums are in the Gimp namespace.)