C plug-ins - Complex GUI
[Theory] Introduction
Of course, graphical interfaces are not always completely static widgets. In particular, sometimes you may want some options to only appear or be made sensitive depending on the state of other arguments.
Our GimpProcedureDialog
allows such use cases.
[Code] Adding a Boolean Argument
As an example, inside hello_world_create_procedure()
, we may now add a
boolean argument called “compute-size”:
gimp_procedure_add_boolean_argument (procedure, "compute-size", "Compute Ideal Size",
"This option will compute a font size "
"so that the text optimally fills the whole canvas",
FALSE, G_PARAM_READWRITE);
You may notice that I set a longer string in the fourth parameter for this argument, unlike all the previous arguments. This is because this argument may be a bit less obvious than the previous ones. While the third argument will be used as the checkbox title (which we usually want short), some longer explanation would be useful. This is what this fourth argument is for. In particular, in our generated dialogs, it will be used as tooltip text when hovering the widget.
Now you remember when I left as an exercise to compute a nice default font size in the first tutorial? Let’s do this now! Then you can compare with your solution.
In the end of hello_world_run()
, the actual processing code could
become:
gboolean compute_size;
[…]
g_object_get (config,
"text", &text,
"font", &font,
"compute-size", &compute_size,
"font-size", &size,
"font-unit", &unit,
NULL);
gimp_image_undo_group_start (image);
text_layer = gimp_text_layer_new (image, text, font, size, unit);
gimp_image_insert_layer (image, GIMP_LAYER (text_layer), parent, position);
if (compute_size)
{
gint image_width;
gint layer_width;
image_width = gimp_image_get_width (image);
layer_width = gimp_drawable_get_width (GIMP_DRAWABLE (text_layer));
size = (gint) ((gdouble) size * (image_width - 1) / layer_width);
gimp_text_layer_set_font_size (text_layer, size, gimp_unit_pixel ());
/* The font size heuristic is not perfect. Verify the text layer is
* smaller than the image.
*/
while (size > 1)
{
layer_width = gimp_drawable_get_width (GIMP_DRAWABLE (text_layer));
if (layer_width < image_width)
break;
size--;
gimp_text_layer_set_font_size (text_layer, size, gimp_unit_pixel ());
}
}
gimp_image_undo_group_end (image);
g_clear_object (&font);
g_clear_object (&unit);
g_free (text);
return gimp_procedure_new_return_values (procedure, GIMP_PDB_SUCCESS, NULL);
I am not going to comment much the code logic. What I said in the first
tutorial is still true: we are not going to explain the full extents of
possibilities given by libgimp
.
I would still just attract your attention to the
gimp_image_undo_group_start()
and gimp_image_undo_group_end()
calls. These group various individual actions into a single undo item.
If we weren’t doing this, since we may have changed the font size
several times, the plug-in would require several undo steps to
be reverted, which is usually not what we want (we are usually expecting
the plug-in actions to look like a single action as a “black box”).
[Code] Further Tweak our Dialog: Conditional Sensitivity
But let’s get back to the graphical interface! Now the main problem is that this new boolean argument is kinda countering the “Font Size” and “Font Size Unit”. When “Compute Ideal Size” is checked ✅, then whatever we set as font size and font unit will not matter. Unfortunately the interface does not make this fact very clear.
Now the GtkFrame
created
by gimp_procedure_dialog_fill_frame()
is actually able to do some magic here. This function allows to set a
boolean argument as title_id
, which will determine the sensitivity of
the frame contents.
Concretely, here is our new GUI code:
if (run_mode == GIMP_RUN_INTERACTIVE)
{
GtkWidget *dialog;
GtkWidget *box;
gimp_ui_init (PLUG_IN_BINARY);
dialog = gimp_procedure_dialog_new (procedure,
GIMP_PROCEDURE_CONFIG (config),
"Hello World");
box = gimp_procedure_dialog_fill_box (GIMP_PROCEDURE_DIALOG (dialog),
"size-box", "font-size", "font-unit", NULL);
gtk_orientable_set_orientation (GTK_ORIENTABLE (box), GTK_ORIENTATION_HORIZONTAL);
gimp_procedure_dialog_fill_frame (GIMP_PROCEDURE_DIALOG (dialog),
"size-frame", "compute-size",
TRUE, "size-box");
gimp_procedure_dialog_fill (GIMP_PROCEDURE_DIALOG (dialog),
"text", "font", "size-frame", NULL);
if (! gimp_procedure_dialog_run (GIMP_PROCEDURE_DIALOG (dialog)))
{
gtk_widget_destroy (dialog);
return gimp_procedure_new_return_values (procedure, GIMP_PDB_CANCEL, NULL);
}
gtk_widget_destroy (dialog);
}
Note that we set invert_title
to TRUE
since we want the size
arguments to be disabled when the box is checked, not the other way
around. This is how now the 2 font size (and font size unit) widgets are
inside a frame whose title is the “compute-size” boolean argument. And
these 2 size widgets are conditionally made insensitive when the
“compute-size” option is checked:
Note that when you want to handle more complicated cases, you may also
be interested by gimp_procedure_dialog_set_sensitive()
or gimp_procedure_dialog_set_sensitive_if_in()
.
Custom GTK
The various methods of GimpProcedureDialog
actually gives you pointers
to the widgets created automatically, which is already a good start if
you wish to customize your GTK
widgets.
Sometimes though, you may want to create fully custom widgets. This may
happen for very complex GUI code, or simply for some types of arguments not
supported yet by GimpProcedureDialog
. In such cases, you may insert your
custom widgets into the various generated containers and connect to
signals directly, like any custom GTK code.
Now it is also possible that you want to make your completely home-made
dialog, which is not something we encourage.
The reason is that GimpProcedureDialog
is not only about generating
dialog. Firstly it also provides a whole infrastructure for remembering
and resetting arguments across sessions. You may notice the “Reset”
button in any such dialog, with 2 options:
- Reset to Initial Values: this will set back to the values stored
in the
config
at dialog creation. - Reset to Factory Defaults: this will set the default values as set in the argument’s code.
There are also the 2 “Load Saved Settings” and “Save Settings” buttons which have quite explicit titles and allow people to save common settings they often use for a given plug-in.
Basically this dialog class creates expectation that all plug-ins will provide such features and you may not want to lose this.
The second big point to take care of is that you always want to end your
plug-in with an updated config
object whose all properties must be set
to the actual values you used. The reason for this is that GIMP will
store all values of a successful plug-in run. This will be not only used
when running a plug-in “with last values”, but also this will be the
default settings in the next interactive mode.
Furthermore, going forward, in our plans of implementing macros, the
last set properties are the way for GIMP to rerun a plug-in exactly the
way it was run. If you don’t leave a properly set config
object, then
your plug-in will not work well with our upcoming macro infrastructure.
In other words, say that you decide not to use GimpProcedureDialog
and
make completely custom GUI code (again, we don’t recommend this). Then
you want to end your plug-in with:
g_object_set (config,
"text", text,
"font", font,
"compute-size", compute_size,
"font-size", size,
"font-unit", unit,
NULL);
This way, you set all the properties to the used values and make the
core processus aware of how your plug-in was called and how to reproduce
the same result.
Doing this is unneeded when using our GimpProcedureDialog
infrastructure.
Conclusion
Now you know a bit more about creating complex graphical interfaces to your plug-ins. From now on, the rest is mostly up to you to create awesome code! 🤗
- Previous tutorial: C plug-ins - GUI
- Next tutorial: C plug-ins - Procedure DataBase