Beyond The Minimal Module

Beyond The Minimal Module — Some common and useful module idioms.

Multiple Modules, Multiple Function, Multifunctions

Often one wants to implement a group of closely related functions that could share quite a bit of code. There are several posibilities how to do it.

The naive approach would be to put the code of all the modules to a one file and share what is shareable. But alas! there can be only one GWY_MODULE_QUERY per file, thus a one file can register only a one module and this approach would not work.

The prefered solution is to register more than function for the module. This is as simple as it sounds. One just has to define more GwyProcessFuncInfos (or other func infos) and register the functions one by one in module_register(). It is even possible to register functions of different kind in a one module, but usually you don't want to do this.

static gboolean
module_register(const gchar *name)
{
    static GwyProcessFuncInfo foo_func_info = {
        "foo",
        "/_Basic Operations/Foo",
        (GwyProcessFunc)&foo,
        FOO_RUN_MODES,
    };
    static GwyProcessFuncInfo bar_func_info = {
        "bar",
        "/_Basic Operations/Bar",
        (GwyProcessFunc)&bar,
        BAR_RUN_MODES,
    };

    gwy_process_func_register(name, &foo_func_info);
    gwy_process_func_register(name, &bar_func_info);

    return TRUE;
}

The other posibility, generally not recommended but sometimes useful, is to define more GwyProcessFuncInfos like before, but make them all points to the same C function. It can then determine from its last argument (name) what function the caller thinks it is calling. Incidentally, this is exactly how the plugin-proxy module works.

Settings

The nice thing about Gwyddion module dialog boxes is that they show the same parameter values as when you last opened them, and they remember the settings even across sessions. And you of course want this feature in your modules too.

Saving and restoring settings usually has sense only for modules with a GUI, simple noninteractive modules like value invert don't have any settings to remember. We will get to GUI later.

There is a one GwyContainer in Gwyddion containing settings for all modules (and other creatures). The function gwy_app_settings_get() will bring it to you. It is loaded on startup and saved on exit and you don't need to take care about this. So the only thing you have to care about is to read the settings from it when your module starts and store them there on exit. OK, there are in fact two things you have to care about. There are no limitations on who can access what in the settings, so to avoid surprises you should use only keys startings with "/module/my-module-name/".

Loading settings could look (remember they may not always exist, for instance when the function is run the first time):

GwyContainer *settings;

settings = gwy_app_settings_get();
ratio = 1.61803;
gwy_container_gis_double_by_name(settings,
                                 "/module/my_module/ratio",
                                 &ratio);

The gwy_container_gis_double_by_name() function updates its last argument only when there is a corresponding value in the container.

And saving settings could look:

GwyContainer *settings;

settings = gwy_app_settings_get();
gwy_container_set_double_by_name(settings,
                                 "/module/my_module/ratio",
                                 ratio);

Creating New Windows

Not always one wants to modify the original data but prefers to create a new data window for the result. This primarily means one has to clonine the data container. Fortunately containers are GwySerializables so they can be duplicated using gwy_serializable_duplicate(). We have to get our data field from the newly created container of course, otherwise all the work would be vain. A new gwyddion data window can be created with gwy_app_data_window_create() then. Putting this all together we have:

data = GWY_CONTAINER(gwy_serializable_duplicate(G_OBJECT(data)));
gwy_app_clean_up_data(data);
dfield = GWY_DATA_FIELD(gwy_container_get_object_by_name(data,
                                                         "/0/data"));
do_something_with_data_filed(dfield);
data_window = gwy_app_data_window_create(data);
gwy_app_data_window_set_untitled(GWY_DATA_WINDOW(data_window), NULL);

There are still two things not mentioned before however. First of them is the mysterious gwy_app_clean_up_data() call. It currently removes some stuff that should not be propagated from the old data to the new one, it can also become no-op in the future, but it does not any harm to use it. The second one, gwy_app_data_window_set_untitled() simply sets the title of the newly window to an unique “Untitled”. If you pass something else than NULL as the second argument, you can create untitled windows of different titles (an oxymoron?), please see gwy_app_data_window_set_untitled() for details.

Masks And Presentations

To highlight some areas in the data (like the grain modules do) you may want to use a mask. Masks are data fields too, differing only by a few things from the ordinary ones. They live at "/0/mask" instead of "/0/data" in the container, they may not be present there, and their units are irrelevant – the value range is always from 0 to 1, 0 being fully transparent and 1 the mask color set by user (may not be fully opaque). To create a mask, just create a new data field and put it to the container with key "/0/mask". When a mask is already present there, it is more efficient to reuse it.

You may also want to conceal the true nature of the data and make gwyddion to display them in a funny way, but without actually modifying them (like the shader module does). This is possible by putting a "/0/show" data field to the container. When present, it is displayed instead of the data. It is really only useful for some final presentations, because it is not obviously updated when the data changes.

File Type Modules

File type modules implement loading and saving of files. They are quite similar to data processing modules. The function info (GwyFileFuncInfo) contains pointers to three functions instead of one: loading, saving and file type detection. You don't have to implement all, just set the unimplemented members to NULL. If detection is not implemented, files of this type are loaded or saved only when user explicitely requests it from the Import or Export menu.

The loading function gets a file name and returns a container with data from the file; conversely, the saving function gets a container and a file name and writes the data from the container to the file. The exact signatures of the functions are described in the reference part.

Graphical User Interface (GUI)

You are encouraged to use the Gtk+ graphical toolkit for your module GUI. It has following advantages:

  • It is available for a variety of platforms, allowing to easily create portable modules.
  • It is licensed under GNU LGPL (for all the platforms), this meaning namely it is free, with complete source code available and imposing few restrictions on programs using it.
  • It is used by Gwyddion itself. Any specialized Gwyddion widgets you could make use of are Gtk+. Using one graphical toolkit exclusively also means more unique look and feel and less linking problems.

There is an extensive Gtk+ Tutorial and API Reference available on the Gtk+ Web site. You can use existing modules as templates for your module.

A very simple GUI dialog asking for a one value (unimaginatively called Amount) could be implemented in Gtk+ as follows:

static gboolean
slope_dialog(gdouble *amount)
{
    GtkWidget *dialog, *table, *spin;
    GtkObject *adjust;
    gint response;

    dialog = gtk_dialog_new_with_buttons(_("Frobnicate"), NULL,
                                         GTK_DIALOG_DESTROY_WITH_PARENT,
                                         GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
                                         GTK_STOCK_OK, GTK_RESPONSE_OK,
                                         NULL);

    table = gtk_table_new(1, 3, FALSE);
    gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), table,
                       FALSE, FALSE, 4);

    adjust = gtk_adjustment_new(*amount, 0, 100, 1, 10, 0);
    spin = gwy_table_attach_spinbutton(table, 0, _("Amount:"), "percents",
                                       adjust);
    gtk_spin_button_set_digits(GTK_SPIN_BUTTON(spin), 1);

    gtk_widget_show_all(dialog);
    response = gtk_dialog_run(GTK_DIALOG(dialog));
    switch (response) {
        case GTK_RESPONSE_CANCEL:
        case GTK_RESPONSE_DELETE_EVENT:
        gtk_widget_destroy(dialog);
        case GTK_RESPONSE_NONE:
        break;

        case GTK_RESPONSE_OK:
        *amount = gtk_adjustment_get_value(GTK_ADJUSTMENT(adjust));
        gtk_widget_destroy(dialog);
        break;
    }

    return response == GTK_RESPONSE_OK;
}

There is no rocket science in here: the dialog is created with some standard buttons, a value entry with a description is placed there, range of allowed values is specified; then the dialog is run. Depending on user's action the value of amount is eventually updated, and TRUE or FALSE is returned to the caller to indicate user's action (we take everything as cancellation, except pressing OK).

Note such a module would report GWY_RUN_MODAL as its run mode, not GWY_RUN_INTERACTIVE since the gtk_dialog_run() runs the dialog in a modal (exclusive) mode, blocking the rest of application until the dialog is closed. This is the simpliest possibility, suficient for most modules (as no race conditions can arise).

TODO: other modules types.