Chapter 9. Views

Important

I changed the view plug-in API recently and you should skip this chapter waiting I rewrote it

There is a sample plugin logically called 'hello' which is a minimal implementation of a classic (it only shows one record at the same time) plugin.

9.1. Initialization

When Gaby starts it reads the description of the database to be used (which is guess from the name you used to invoke Gaby or from the --as flags). I'll not talk about this file here but somewhere it will have a line like this : viewable as form,list,xlist. from which Gaby "guess" it has to load form, list and xlist plugins. The first step is to initialize those plugins [1] which is done looking for a function like this in the plugin :

#include <gaby.h>
int init_view_plugin(ViewPluginData *vpd);

Important

If you want your plug-in to support being part of the huge binary produced when configuring with "Miguel wishes" you'll have to enclose the function in a #ifndef FOLLOW_MIGUEL.

The ViewPluginData structure is defined in gaby.h (like all structures used in Gaby) :

typedef struct _ViewPluginData ViewPluginData;

struct _ViewPluginData {
        GModule *handle;
        int     (*init_plugin)  ( ViewPluginData *vpd );

        void    (*view_create)  (gabywindow *win, gboolean first);

        void    (*view_fill)    (gabywindow *win);
        void    (*view_save)    (gabywindow *win);
#ifndef NO_GTK
        GtkWidget* (*configure) (ViewPluginData *vpd);
#endif

        gchar *name;
        gchar *i18n_name;
        enum {
                ALL_RECORDS = 1 << 0,
                ONE_RECORD = 1 << 1,
                FILTER = 1 << 2
        } type;
        enum {
                NONE = 0,
                EDITABLE = 1 << 0,
                FILTERABLE = 1 << 1,
        } capabilities;
};

I believe you already have a good picture of what will have its place in init_view_plugin. To confirm :

Example 9-1. Initializing a view plug-in

int init_view_plugin(ViewPluginData *vpd)
{
        vpd->view_create = hello_create;
        vpd->view_fill = hello_fill;
        vpd->configure = NULL;

        vpd->name = "hello";
        vpd->i18n_name = _("Hello");
        vpd->type = ONE_RECORD;
        vpd->capabilities = NONE;

        return 0;
}

FIXME: a few comments would be welcomed.

9.1. View creation

It is the vpd->view_create = hello_create; with hello_create being a function defined like this :

mstatic void* hello_create(gabywindow* window, gboolean first);

Important

hello_create is defined as a "mstatic" function, this means that it will be static excepted when compiled with Miguel wishes.

Its purpose is to create a widget which will then be put either in the main window of Gaby either in a separate window. This widget will then be recorded at window->widget. This means that the widget has not to be a window with title bar and the like. It will commonly be a Gtk[V,H]Box but any GTK container will work.

Let's have an example :

Example 9-2. Creating a view

mstatic void hello_create ( gabywindow *window, gboolean first )
{
        GtkWidget *vbox;
        GtkWidget *label;
        GtkWidget *hbb;
        GtkWidget *button;
        int *id = &(window->id);
        record *r;

        r = table_first(v->subtable->table, -1);
        *id = ( r == NULL ? 0 : r->id );

        r = table_first(window->view->subtable->table, -1);
        *id = ( r == NULL ? 0 : r->id );

        vbox = gtk_vbox_new(FALSE,0);
        window->widget = vbox;

        label = gtk_label_new("");
        gtk_widget_show(label);
        gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, TRUE, 0);

        hbb = gtk_hbutton_box_new();
        gtk_widget_show(hbb);
        gtk_box_pack_start(GTK_BOX(vbox), hbb, FALSE, TRUE, 0);

        button = gtk_button_new_with_label(_("Previous"));
        gtk_widget_show(button);
        gtk_signal_connect(GTK_OBJECT(button), "clicked", \
                                GTK_SIGNAL_FUNC(previous_clicked), window);
        gtk_container_add(GTK_CONTAINER(hbb), button);

        button = gtk_button_new_with_label(_("Next"));
        gtk_widget_show(button);
        gtk_signal_connect(GTK_OBJECT(button), "clicked", \
                                GTK_SIGNAL_FUNC(next_clicked), window);
        gtk_container_add(GTK_CONTAINER(hbb), button);

        gtk_widget_show(vbox);

        return;
}
Most lines are typical GTK as described in GTK documentation. The interesting lines are :
        int *id = &(window->id);
        record *r;
        r = table_first(v->subtable->table, -1);
        *id = ( r == NULL ? 0 : r->id );
This sets the record you're currently on to the first record of the table; or to 0 if the table is empty.

Note that unlike the previous version of the API you don't have to play with lots of gtk_object_get_data in your plug-ins, making them cleaner.

9.1. View filling

As I just said (and as the title says) this is the function which will take datas from a table to show them in the beautiful widgets you just created.

Here is the code :

Example 9-3. Filling a view

mstatic void hello_fill ( gabywindow *window )
{
        view *v = window->view;
        int *id = &(window->id);
        GtkWidget *label = gtk_object_get_data(GTK_OBJECT(win), "label");
        GString *str;
	
        if ( *id == 0 ) {
                gtk_label_set_text(GTK_LABEL(label), _("Hello, world !"));
                return;
        }

        str = get_subtable_stringed_field_id(v->subtable, *id, 0 );

        str = g_string_prepend(str, _("Hello, "));
        gtk_label_set_text(GTK_LABEL(label), str->str);
        g_string_free(str, 1);
}

The first thing is to retrieve the label widget we'll use to show the information :

        GtkWidget *label = gtk_object_get_data(GTK_OBJECT(win), "label");

Then we check if we have a record to show, since record ids start at 1, 0 means no record to show :

        if ( *id == 0 ) {
                gtk_label_set_text(GTK_LABEL(label), _("Hello, world !"));
                return;
        }

The easy case is done. Now we know that we have a real record to show. Since this is just an example, we'll just show one field and to be sure it will work with any tables we'll show the first one.

        str = get_subtable_stringed_field_id(v->subtable, *id, 0 );
It is now easy as adding "Hello" before the string we got and showing it in a widget :
        str = g_string_prepend(str, _("Hello, "));
        gtk_label_set_text(GTK_LABEL(label), str->str);
It is now finished, we free the string and leave.

9.1. Moving between records

We have two buttons in our view (namely "Previous" and "Next"), their purpose is evident, the way it is done also (hem hem).

static void previous_clicked(GtkWidget *button, gabywindow *window)
{
        record *r;
        view *v = window->view;
        int *id = &(window->id);

        r = get_record_no(v->subtable->table, *id);
        r = table_prev(v->subtable->table, r, -1);
        *id = ( r == NULL ) ? 0 : r->id;
        hello_fill(win);

        update_bound_windows(window);
}
The interesting is r = table_prev(v->subtable->table, r, -1);.

FIXME: here is a few notes, they should be moved in a section with table_{first,last,next,prev} and get_record_no.

table_prev takes 3 arguments :

  • table which is the table in which the record is,

  • r which is the record we were at,

  • sort_by which is a number explaining with which kind of sorting we want to move (-1 means no sorting otherwise it is a field number).

Then we fill again the view and tell gaby about this change so bound windows are updated (with update_bound_windows).

next_clicked is the same with table_next instead of table_prev. Note that you don't need to check if you're on the first (or latest) record, it is handled by Gaby (previous when on first and you'll stay on it, same with next on latest).

9.1. Configuring

In init_view_plugin we affected something to vpd->configure. It is a function defined as : mstatic GtkWidget* configure(ViewPluginData *vpd); It is used in the 'configure' box inside Gaby. Since you don't have anything to configure you may simply type (or affect NULL to the variable :

mstatic GtkWidget* configure(ViewPluginData *vpd)
{
        return NULL;
}

Notes

[1]

Note that this could be done only when the user asks for them but since it is not implemented this way ...