C plug-ins - GUI
[Theory] Introduction
This tutorial is a continuation of the previous basic C plug-ins tutorial. When we look at our existing demo plug-in, we notice that there are definitely a few part of the procedure which could be parameterized, i.e. for which it seems like we could let people choose. In particular:
- The text to display;
- The font to use;
- The font size.
Our next update will add “arguments” to our demo plug-in
plug-in-zemarmot-c-demo-hello-world
.
🛈 Important Note: even if you are not interested into making a graphical interface to your plug-in, you should still consider the various “arguments” you want your plug-in to have. This is useful for other plug-ins calling yours, as well as for storing last run values.
[Code] Adding Procedure Arguments
Update the create_procedure()
method like this:
static GimpProcedure *
hello_world_create_procedure (GimpPlugIn *plug_in,
const gchar *name)
{
GimpProcedure *procedure = NULL;
if (g_strcmp0 (name, PLUG_IN_PROC) == 0)
{
procedure = gimp_image_procedure_new (plug_in, name,
GIMP_PDB_PROC_TYPE_PLUGIN,
hello_world_run, NULL, NULL);
gimp_procedure_set_sensitivity_mask (procedure, GIMP_PROCEDURE_SENSITIVE_ALWAYS);
gimp_procedure_set_menu_label (procedure, "_C Hello World");
gimp_procedure_add_menu_path (procedure, "<Image>/Hell_o Worlds/");
gimp_procedure_set_documentation (procedure,
"Official Hello World Tutorial in C",
"Some longer text to explain about this procedure. "
"This is mostly for other developers calling this procedure.",
NULL);
gimp_procedure_set_attribution (procedure, "Jehan",
"Jehan, ZeMarmot project",
"2025");
gimp_procedure_add_font_argument (procedure, "font", "Font", NULL,
FALSE, NULL, TRUE,
G_PARAM_READWRITE);
gimp_procedure_add_int_argument (procedure, "font-size", "Font Size", NULL,
1, 1000, 20,
G_PARAM_READWRITE);
gimp_procedure_add_unit_argument (procedure, "font-unit", "Font Unit", NULL,
TRUE, FALSE, gimp_unit_pixel (),
G_PARAM_READWRITE);
gimp_procedure_add_string_argument (procedure, "text", "Text", NULL,
"Hello World!",
G_PARAM_READWRITE);
}
return procedure;
}
We are adding a font argument which will defaults to whatever is the current contextual font, an integer argument defaulting to 20, a unit argument defaulting to pixel unit, and a string argument with default value “Hello World!”.
I am not going to dive deeper in these functions and will let you read
the reference documentation instead. In particular, you may be
interested into the full list of
gimp_procedure_add_*_argument()
functions.
[Code] Using Procedure Arguments
Accessing the arguments from the run()
function is pretty simple. You
may have noticed a GimpProcedureConfig
parameter earlier. This object is in reality a custom subclass of
GimpProcedureConfig
which is created on the fly and has all your
procedure arguments as GObject
properties.
For anyone used to GObject
code, you know you would typically get an object
property with g_object_get()
in C code:
static GimpValueArray *
hello_world_run (GimpProcedure *procedure,
GimpRunMode run_mode,
GimpImage *image,
GimpDrawable **drawables,
GimpProcedureConfig *config,
gpointer run_data)
{
GimpTextLayer *text_layer;
GimpLayer *parent = NULL;
gint position = 0;
gint n_drawables;
gchar *text;
GimpFont *font;
gint size;
GimpUnit *unit;
n_drawables = gimp_core_object_array_get_length ((GObject **) drawables);
if (n_drawables > 1)
{
GError *error = NULL;
g_set_error (&error, GIMP_PLUG_IN_ERROR, 0,
"Procedure '%s' works with zero or one layer.",
PLUG_IN_PROC);
return gimp_procedure_new_return_values (procedure,
GIMP_PDB_CALLING_ERROR,
error);
}
else if (n_drawables == 1)
{
GimpDrawable *drawable = drawables[0];
if (! GIMP_IS_LAYER (drawable))
{
GError *error = NULL;
g_set_error (&error, GIMP_PLUG_IN_ERROR, 0,
"Procedure '%s' works with layers only.",
PLUG_IN_PROC);
return gimp_procedure_new_return_values (procedure,
GIMP_PDB_CALLING_ERROR,
error);
}
parent = GIMP_LAYER (gimp_item_get_parent (GIMP_ITEM (drawable)));
position = gimp_image_get_item_position (image, GIMP_ITEM (drawable));
}
g_object_get (config,
"text", &text,
"font", &font,
"font-size", &size,
"font-unit", &unit,
NULL);
text_layer = gimp_text_layer_new (image, text, font, size, unit);
gimp_image_insert_layer (image, GIMP_LAYER (text_layer), parent, position);
g_clear_object (&font);
g_clear_object (&unit);
g_free (text);
return gimp_procedure_new_return_values (procedure, GIMP_PDB_SUCCESS, NULL);
}
Now so far, we still don’t have any graphical interface so it may not seem that useful. Yet we already made a big step forward! While the plug-in cannot be tweaked in interactive mode (e.g. when called from the GUI), it can already be called by other plug-ins with customized arguments. For instance, another plug-in could call your procedure with a custom text, font or font size.
Now of course, this still doesn’t sound that useful because the plug-in is so basic. But imagine that you had a very incredible plug-in doing complicated computations or some awesome process (if you read this tutorial, I am guessing you already have some ideas in mind!). Any other plug-in could depend on yours so that they don’t have to reimplement everything and focus on their own awesome part.
[Code] Creating your First Dialog
To add our dialog, first include the libgimpui
library at the top of
your code:
#include <libgimp/gimpui.h>
Now update the run()
function this way:
#define PLUG_IN_BINARY "c-hello-world"
static GimpValueArray *
hello_world_run (GimpProcedure *procedure,
GimpRunMode run_mode,
GimpImage *image,
GimpDrawable **drawables,
GimpProcedureConfig *config,
gpointer run_data)
{
GimpTextLayer *text_layer;
GimpLayer *parent = NULL;
gint position = 0;
gint n_drawables;
gchar *text;
GimpFont *font;
gint size;
GimpUnit *unit;
n_drawables = gimp_core_object_array_get_length ((GObject **) drawables);
if (n_drawables > 1)
{
GError *error = NULL;
g_set_error (&error, GIMP_PLUG_IN_ERROR, 0,
"Procedure '%s' works with zero or one layer.",
PLUG_IN_PROC);
return gimp_procedure_new_return_values (procedure,
GIMP_PDB_CALLING_ERROR,
error);
}
else if (n_drawables == 1)
{
GimpDrawable *drawable = drawables[0];
if (! GIMP_IS_LAYER (drawable))
{
GError *error = NULL;
g_set_error (&error, GIMP_PLUG_IN_ERROR, 0,
"Procedure '%s' works with layers only.",
PLUG_IN_PROC);
return gimp_procedure_new_return_values (procedure,
GIMP_PDB_CALLING_ERROR,
error);
}
parent = GIMP_LAYER (gimp_item_get_parent (GIMP_ITEM (drawable)));
position = gimp_image_get_item_position (image, GIMP_ITEM (drawable));
}
if (run_mode == GIMP_RUN_INTERACTIVE)
{
GtkWidget *dialog;
gimp_ui_init (PLUG_IN_BINARY);
dialog = gimp_procedure_dialog_new (procedure,
GIMP_PROCEDURE_CONFIG (config),
"Hello World");
gimp_procedure_dialog_fill (GIMP_PROCEDURE_DIALOG (dialog), NULL);
if (! gimp_procedure_dialog_run (GIMP_PROCEDURE_DIALOG (dialog)))
return gimp_procedure_new_return_values (procedure, GIMP_PDB_CANCEL, NULL);
}
g_object_get (config,
"text", &text,
"font", &font,
"font-size", &size,
"font-unit", &unit,
NULL);
text_layer = gimp_text_layer_new (image, text, font, size, unit);
gimp_image_insert_layer (image, GIMP_LAYER (text_layer), parent, position);
g_clear_object (&font);
g_clear_object (&unit);
g_free (text);
return gimp_procedure_new_return_values (procedure, GIMP_PDB_SUCCESS, NULL);
}
Finally you will now compile your plug-in with libgimpui
too:
gcc main.c `pkg-config --cflags --libs gimp-3.0 gimpui-3.0` -o c-hello-world
Running the plug-in, you now get greeted by this nice dialog:
[Theory] Studying the GUI Code
Run Mode
You may notice how very small is this code addition, even though it actually creates a fully functional dialog which allows you to actually edit all the procedure arguments we created!
The first test we do is checking the run_mode
. There are currently 3
possible values for the GimpRunMode
parameter which is given to the run()
function:
- The run may be interactive: this mostly means that if you procedure can be customized, then a graphical interface is expected. This is the run mode set when any procedure is started from menus.
- The run mode may be non-interactive: this is when no questions should
be asked and the values in the
config
object should be used. This is usually when a plug-in calls a procedure from another plug-in. - The run mode may be called with latest values: this happens for
instance when one calls the filter from the
Filters > Repeat
menu item.
Note that in the non-interactive
and last-vals
cases, you should
just use the config
object as-is. In the former case, it is assumed
that whatever called your procedure also sets the config
properties as
they want them; in the latter case, the whole libgimp
infrastructure
will have taken care to retrieve the last values (defaulting to default
values if this plug-in is run for the first time).
GUI Initialization
The first thing you should do before running any function from
libgimpui
is calling gimp_ui_init()
.
As implied by the function name it will initialize various parts of the
system, including GTK, GDK and more. It also sets up the same theme as
core is using so that your plug-in dialog doesn’t stick out like a sore
thumb.
The argument is the “program name”, which should be ideally the name of
your executable (here c-hello-world
).
Procedure Dialog
The GimpProcedureDialog
is where the big UI-generation magic happens.
After creating your dialog, all you have to do is filling it with all the procedure arguments in the order you declared them. And that’s about it!
Now run
your dialog. If it returns FALSE
, it means that the user canceled the
interactive dialog. This is not an error, just a normal exit where you
are asked to do nothing (the user changed their mind). In such case, we
return with GIMP_PDB_CANCEL
and no GError
.
On the other hand, if the dialog run returned TRUE
, you go on with the
processing, and this is where the second part of the magic happens: the
config
object has been automatically updated! It now contains the
argument values per interactive choices in the just-run dialog.
[Theory] Tweaking the GUI
Maybe the dialog is not exactly what you want. In particular the default GUI generation will just create widgets for each argument, one under another.
The first thing to know is that gimp_procedure_dialog_fill()
actually
takes a NULL
-terminated list of argument names). Your procedure has 4
arguments: “font”, “font-size”, “font-unit” and “text”. Setting an empty
list is equivalent to set all this list in the same order. It also means
you can reorder the widgets in the interface (e.g. if you want to show
the “text” entry first).
It is also possible to add a bit of further widget organization. For instance here, you know that the font size unit argument is here to qualify the font size argument. Maybe you want these horizontally, one after the other?
The GimpProcedureDialog
provides a few utility functions for this. And
in particular several functions whose name is prefixed with
gimp_procedure_dialog_fill_
will create GTK
containers which can be filled by some arguments.
For instance gimp_procedure_dialog_fill_box()
will create a GtkBox
which
you can further customize. The container_id
parameter will be a new name
which you can mix with the real arguments.
[Code] Customized GUI
Let’s show a proper example. We will now reorder the arguments by putting the “text” argument first, then reorganize the dialog by putting the unit next to the font size in a horizontal box.
We will only edit the piece of code within the if (run_mode == GIMP_RUN_INTERACTIVE)
test block:
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 (GIMP_PROCEDURE_DIALOG (dialog),
"text", "font", "size-box", 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);
}
And here is what it looks like now that we reorganized the widgets:
Conclusion
You now have seen how ligbimpui
helps you to create dialogs for your
plug-in in barely a few lines. The widgets will be generated
according to your argument settings. For instance, the minimum and
maximum values
set to your integer arguments are reflected in the widget created by
the dialog.
Or if you add a font argument, you also tell the system if you want a specific default font, or if you prefer to just default to whatever is the currently selected font (which will especially matter with the “Reset to Factory Defaults” feature).
If the next tutorial, we will even further complexify the graphical user interface while leaving the code very simple.
- Previous tutorial: C plug-ins - basic
- Next tutorial: C plug-ins - Complex GUI