aboutsummaryrefslogblamecommitdiffstats
path: root/addressbook/gui/contact-editor/e-contact-editor-dyntable.c
blob: 68f298cdc94c7b6824faefb88b7a20211ece92a8 (plain) (tree)


































































































































































































































































                                                                                                


                                                                        









































































                                                                                           


                                                                                               












































































                                                                                 
                            
                        



                                                            






                                                                                       





                                                               


                                              



































































                                                                                    
                                                                




                                                                                                                                    

















































































































































































































































                                                                                            
/*
 * e-contact-editor-dyntable.c
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, see <http://www.gnu.org/licenses/>.
 *
 */

#include "e-contact-editor-dyntable.h"

G_DEFINE_TYPE(EContactEditorDynTable, e_contact_editor_dyntable, GTK_TYPE_GRID)

/* one position is occupied by two widgets: combo+entry */
#define ENTRY_SIZE 2
#define MAX_CAPACITY 100

enum {
    CHANGED_SIGNAL,
    ACTIVATE_SIGNAL,
    ROW_ADDED_SIGNAL,
    LAST_SIGNAL
};

static guint dyntable_signals[LAST_SIGNAL];

struct _EContactEditorDynTablePrivate {

    /* absolute max, dyntable will ignore the rest */
    guint   max_entries;

    /* current number of entries with text or requested by user*/
    guint   curr_entries;

    /* minimum to show, with or without text */
    guint   show_min_entries;

    /* no matter how much data, show only */
    guint   show_max_entries;

    /* number of entries (combo/text) per row */
    guint   columns;

    /* if true, fill line with empty slots*/
    gboolean justified;

    GtkWidget   *add_button;
    GtkListStore    *combo_store;
    GtkListStore    *data_store;

    /* array of default values for combo box */
    const gint  *combo_defaults;

    /* number of elements in the array */
    size_t      combo_defaults_n;
};

GtkWidget*
e_contact_editor_dyntable_new (void)
{
    GtkWidget* widget;
    widget = GTK_WIDGET (g_object_new (e_contact_editor_dyntable_get_type (), NULL));
    return widget;
}

static void
set_combo_box_active (EContactEditorDynTable *dyntable,
                      GtkComboBox *combo_box,
                      gint active)
{
    g_signal_handlers_block_matched (combo_box,
            G_SIGNAL_MATCH_DATA, 0, 0, NULL,
            NULL, dyntable);
    gtk_combo_box_set_active (combo_box, active);
    g_signal_handlers_unblock_matched (combo_box,
                                       G_SIGNAL_MATCH_DATA, 0, 0, NULL,
                                       NULL, dyntable);
}

/* translate position into column/row */
static void
position_to_grid (EContactEditorDynTable *dyntable,
                  guint pos,
                  guint *col,
                  guint *row)
{
    *row = pos / dyntable->priv->columns;
    *col = pos % dyntable->priv->columns * ENTRY_SIZE;
}

static void
move_widget (GtkGrid *grid, GtkWidget *w, guint col, guint row)
{
    GValue rowValue = G_VALUE_INIT, colValue = G_VALUE_INIT;

    g_value_init (&rowValue, G_TYPE_UINT);
    g_value_init (&colValue, G_TYPE_UINT);

    g_value_set_uint (&rowValue, row);
    g_value_set_uint (&colValue, col);

    gtk_container_child_set_property (GTK_CONTAINER (grid), w,
                                      "left-attach", &colValue);
    gtk_container_child_set_property (GTK_CONTAINER (grid), w,
                                      "top-attach", &rowValue);
}

static gboolean
is_button_required (EContactEditorDynTable *dyntable)
{
    if (dyntable->priv->curr_entries >= dyntable->priv->max_entries)
        return FALSE;
    if (dyntable->priv->curr_entries <= dyntable->priv->show_max_entries)
        return TRUE;
    else
        return FALSE;
}

static void
sensitize_button (EContactEditorDynTable *dyntable)
{
    guint row, col, current_entries;
    GtkWidget *w;
    GtkGrid *grid;
    EContactEditorDynTableClass *class;
    gboolean enabled;

    grid = GTK_GRID (dyntable);
    class = E_CONTACT_EDITOR_DYNTABLE_GET_CLASS (dyntable);

    current_entries = dyntable->priv->curr_entries;
    enabled = TRUE;
    if (current_entries > 0) {
        /* last entry */
        current_entries--;
        position_to_grid (dyntable, current_entries, &col, &row);
        w = gtk_grid_get_child_at (grid, col + 1, row);

        enabled = !class->widget_is_empty (dyntable, w);
    }

    gtk_widget_set_sensitive (dyntable->priv->add_button, enabled);
}

static void
show_button (EContactEditorDynTable *dyntable)
{
    guint col,row, pos;
    gboolean visible = FALSE;
    GtkGrid *grid;

    grid = GTK_GRID (dyntable);

    /* move button to end of current line */
    pos = dyntable->priv->curr_entries;
    if (pos > 0)
        pos--;
    position_to_grid(dyntable, pos, &col, &row);
    move_widget (grid, dyntable->priv->add_button,
                 dyntable->priv->columns*ENTRY_SIZE+1, row);

    /* set visibility */
    if (is_button_required (dyntable))
        visible = TRUE;

    gtk_widget_set_visible (dyntable->priv->add_button, visible);

    sensitize_button (dyntable);
}

static void
increment_counter (EContactEditorDynTable *dyntable)
{
    dyntable->priv->curr_entries++;
    show_button (dyntable);
}

static void
decrement_counter (EContactEditorDynTable *dyntable)
{
    dyntable->priv->curr_entries--;
    show_button (dyntable);
}

static gint
get_next_combo_index (EContactEditorDynTable *dyntable)
{
    size_t array_size = dyntable->priv->combo_defaults_n;
    gint idx = 0;

    if (dyntable->priv->combo_defaults != NULL) {
        idx = dyntable->priv->combo_defaults[dyntable->priv->curr_entries % array_size];
    }

    return idx;
}

static GtkWidget*
combo_box_create (EContactEditorDynTable *dyntable)
{
    GtkWidget *w;
    GtkComboBox *combo;
    GtkListStore *store;
    GtkCellRenderer *cell;

    w = gtk_combo_box_new ();
    combo = GTK_COMBO_BOX (w);
    store = dyntable->priv->combo_store;

    gtk_combo_box_set_model (combo, GTK_TREE_MODEL(store));

    gtk_cell_layout_clear (GTK_CELL_LAYOUT (combo));

    cell = gtk_cell_renderer_text_new ();
    gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), cell, TRUE);
    gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo), cell,
                                    "text", DYNTABLE_COMBO_COLUMN_TEXT,
                                    "sensitive", DYNTABLE_COMBO_COLUMN_SENSITIVE,
                                    NULL);

    gtk_combo_box_set_active (combo, get_next_combo_index (dyntable));

    return w;
}

static void
emit_changed (EContactEditorDynTable *dyntable)
{
    g_signal_emit (dyntable, dyntable_signals[CHANGED_SIGNAL], 0);
}

static void
emit_activated (EContactEditorDynTable *dyntable)
{
    g_signal_emit (dyntable, dyntable_signals[ACTIVATE_SIGNAL], 0);
}

static void
emit_row_added (EContactEditorDynTable *dyntable)
{
    g_signal_emit (dyntable, dyntable_signals[ROW_ADDED_SIGNAL], 0);
}

static void
add_empty_entry (EContactEditorDynTable *dyntable)
{
    GtkGrid *grid;
    guint row, col;
    GtkWidget *box, *entry;
    EContactEditorDynTableClass *class;

    if (dyntable->priv->curr_entries >= dyntable->priv->max_entries)
        return;

    grid = GTK_GRID (dyntable);
    position_to_grid (dyntable, dyntable->priv->curr_entries, &col, &row);

    /* create new entry at last position */
    class = E_CONTACT_EDITOR_DYNTABLE_GET_CLASS (dyntable);
    box = combo_box_create (dyntable);
    gtk_grid_attach (grid, box, col, row, 1, 1);
    gtk_widget_show (box);

    entry = class->widget_create (dyntable);
    g_object_set (entry, "margin-left", 2, NULL);
    g_object_set (entry, "margin-right", 5, NULL);
    gtk_widget_set_hexpand (entry, GTK_EXPAND);
    gtk_grid_attach (grid, entry, col + 1, row, 1, 1);
    gtk_widget_show (entry);

    g_signal_connect_swapped(box, "changed",
                             G_CALLBACK (gtk_widget_grab_focus), entry);
    g_signal_connect_swapped(box, "changed",
                             G_CALLBACK (emit_changed), dyntable);
    g_signal_connect_swapped(entry, "changed",
                             G_CALLBACK (emit_changed), dyntable);
    g_signal_connect_swapped(entry, "changed",
                         G_CALLBACK (sensitize_button), dyntable);
    g_signal_connect_swapped(entry, "activate",
                             G_CALLBACK (emit_activated), dyntable);

    increment_counter (dyntable);

    if ( (dyntable->priv->justified && col < dyntable->priv->columns-1) ||
         (dyntable->priv->curr_entries < dyntable->priv->show_min_entries) )
        add_empty_entry (dyntable);

    gtk_widget_grab_focus (entry);
}

static void
remove_empty_entries (EContactEditorDynTable *dyntable, gboolean fillup)
{
    guint row, col, pos;
    GtkGrid* grid;
    GtkWidget* w;
    EContactEditorDynTableClass *class;

    grid = GTK_GRID (dyntable);
    class = E_CONTACT_EDITOR_DYNTABLE_GET_CLASS (dyntable);

    for (pos = 0; pos < dyntable->priv->curr_entries; pos++) {
        position_to_grid (dyntable, pos, &col, &row);
        w = gtk_grid_get_child_at (grid, col + 1, row);

        if (w != NULL && class->widget_is_empty (dyntable, w)) {
            guint pos2, next_col, next_row;

            gtk_widget_destroy (w);
            w = gtk_grid_get_child_at (grid, col, row);
            gtk_widget_destroy (w);

            /* now fill gap */
            for (pos2 = pos + 1; pos2 < dyntable->priv->curr_entries; pos2++) {
                position_to_grid (dyntable, pos2, &next_col, &next_row);
                w = gtk_grid_get_child_at (grid, next_col, next_row);
                move_widget (grid, w, col, row);
                w = gtk_grid_get_child_at (grid, next_col + 1, next_row);
                move_widget (grid, w, col + 1, row);
                col = next_col;
                row = next_row;
            }
            decrement_counter (dyntable);
            pos--; /* check the new widget on our current position */
        }

    }

    if (fillup
        && (dyntable->priv->curr_entries < dyntable->priv->show_min_entries
                || (dyntable->priv->justified && col < dyntable->priv->columns-1)))
        add_empty_entry (dyntable);

}

/* clears data, not the combo box list store */
void
e_contact_editor_dyntable_clear_data (EContactEditorDynTable *dyntable)
{
    guint i, col, row;
    GtkGrid *grid;
    GtkWidget *w;
    EContactEditorDynTableClass *class;

    grid = GTK_GRID(dyntable);
    class = E_CONTACT_EDITOR_DYNTABLE_GET_CLASS(dyntable);

    for (i = 0; i < dyntable->priv->curr_entries; i++) {
        position_to_grid (dyntable, i, &col, &row);
        w = gtk_grid_get_child_at (grid, col + 1, row);
        class->widget_clear (dyntable, w);
    }
    remove_empty_entries (dyntable, TRUE);

    gtk_list_store_clear (dyntable->priv->data_store);
}

static void
adjust_visibility_of_widgets (EContactEditorDynTable *dyntable)
{
    guint pos, col, row;
    GtkGrid *grid;
    GtkWidget *w;

    grid = GTK_GRID (dyntable);
    for (pos = 0; pos < dyntable->priv->curr_entries; pos++) {
        gboolean visible = FALSE;

        if (pos < dyntable->priv->show_max_entries)
            visible = TRUE;

        position_to_grid (dyntable, pos, &col, &row);
        w = gtk_grid_get_child_at (grid, col, row);
        gtk_widget_set_visible (w, visible);
        w = gtk_grid_get_child_at (grid, col + 1, row);
        gtk_widget_set_visible (w, visible);
    }

    show_button (dyntable);
}

/* number of columns can only be set before any data is added to this dyntable */
void e_contact_editor_dyntable_set_num_columns (EContactEditorDynTable *dyntable,
                                                guint number_of_columns,
                                                gboolean justified)
{
    GtkTreeIter iter;
    GtkTreeModel *store;
    gboolean holds_data;

    g_return_if_fail (number_of_columns > 0);

    store = GTK_TREE_MODEL (dyntable->priv->data_store);
    holds_data = gtk_tree_model_get_iter_first (store, &iter);
    g_return_if_fail (!holds_data);

    remove_empty_entries(dyntable, FALSE);

    dyntable->priv->columns = number_of_columns;
    dyntable->priv->justified = justified;

    remove_empty_entries(dyntable, TRUE);
}

void
e_contact_editor_dyntable_set_max_entries (EContactEditorDynTable *dyntable,
                                           guint max)
{
    GtkTreeModel *store;
    gint n_children;

    g_return_if_fail (max > 0);

    store = GTK_TREE_MODEL (dyntable->priv->data_store);

    n_children = gtk_tree_model_iter_n_children (store, NULL);
    if (n_children > max) {
        g_warning ("Dyntable holds %i items, setting max to %i, instead of %i",
                n_children, n_children, max);
        max = n_children;
    }

    dyntable->priv->max_entries = max;
    if (dyntable->priv->show_max_entries>max)
        dyntable->priv->show_max_entries = max;
    if (dyntable->priv->show_min_entries>max)
            dyntable->priv->show_min_entries = max;

    remove_empty_entries (dyntable, TRUE);
    show_button (dyntable);
}

/* show at least number_of_entries, with or without data */
void
e_contact_editor_dyntable_set_show_min (EContactEditorDynTable *dyntable,
                                        guint number_of_entries)
{
    if (number_of_entries > dyntable->priv->show_max_entries)
        dyntable->priv->show_min_entries = dyntable->priv->show_max_entries;
    else
        dyntable->priv->show_min_entries = number_of_entries;

    if (dyntable->priv->curr_entries < dyntable->priv->show_min_entries)
        add_empty_entry (dyntable);

    adjust_visibility_of_widgets (dyntable);
}

/* show no more than number_of_entries, hide the rest */
void
e_contact_editor_dyntable_set_show_max (EContactEditorDynTable *dyntable,
                                        guint number_of_entries)
{
    if (number_of_entries > dyntable->priv->max_entries) {
        dyntable->priv->show_max_entries = dyntable->priv->max_entries;
    } else if (number_of_entries < dyntable->priv->show_min_entries) {
        dyntable->priv->show_max_entries = dyntable->priv->show_min_entries;
    } else {
        dyntable->priv->show_max_entries = number_of_entries;
    }

    adjust_visibility_of_widgets (dyntable);
}

/* use data_store to fill data into widgets */
void
e_contact_editor_dyntable_fill_in_data (EContactEditorDynTable *dyntable)
{
    guint pos = 0, col, row;
    EContactEditorDynTableClass *class;
    GtkGrid *grid;
    GtkTreeIter iter;
    GtkTreeModel *store;
    GtkWidget *w;
    gboolean valid;
    gchar *str_data;
    gint int_data;

    class = E_CONTACT_EDITOR_DYNTABLE_GET_CLASS (dyntable);
    grid = GTK_GRID (dyntable);
    store = GTK_TREE_MODEL (dyntable->priv->data_store);

    valid = gtk_tree_model_get_iter_first (store, &iter);
    while (valid) {
        gtk_tree_model_get (store, &iter,
                DYNTABLE_STORE_COLUMN_ENTRY_STRING, &str_data,
                DYNTABLE_STORE_COLUMN_SELECTED_ITEM, &int_data,
                -1);

        if (pos >= dyntable->priv->curr_entries)
            add_empty_entry (dyntable);

        position_to_grid (dyntable, pos++, &col, &row);
        w = gtk_grid_get_child_at (grid, col, row);
        set_combo_box_active (dyntable, GTK_COMBO_BOX(w), int_data);
        w = gtk_grid_get_child_at (grid, col + 1, row);
        class->widget_fill (dyntable, w, str_data);

        valid = gtk_tree_model_iter_next (store, &iter);

        if (valid && pos >= dyntable->priv->max_entries) {
            g_warning ("dyntable is configured with max_entries = %i, ignoring the rest.", dyntable->priv->max_entries);
            break;
        }
    }

    /* fix visibility of added items */
    adjust_visibility_of_widgets (dyntable);
}

/* the model returned has 3 columns
 *
 * UINT: sort order
 * UINT: active combo item
 * STRING: data extracted with widget_extract()
 */
GtkListStore*
e_contact_editor_dyntable_extract_data (EContactEditorDynTable *dyntable)
{
    EContactEditorDynTableClass *class;
    GtkGrid *grid;
    GtkListStore *data_store;
    GtkWidget *w;
    guint pos, col, row;

    grid = GTK_GRID(dyntable);
    class = E_CONTACT_EDITOR_DYNTABLE_GET_CLASS(dyntable);
    data_store = dyntable->priv->data_store;

    gtk_list_store_clear (data_store);

    for (pos = 0; pos < dyntable->priv->curr_entries; pos++) {

        position_to_grid (dyntable, pos, &col, &row);
        w = gtk_grid_get_child_at (grid, col + 1, row);

        if (!class->widget_is_empty (dyntable, w)) {
            GtkTreeIter iter;
            gchar *dup;
            gint combo_item;
            const gchar *data;

            data = class->widget_extract (dyntable, w);
            w = gtk_grid_get_child_at (grid, col, row);
            combo_item = gtk_combo_box_get_active (GTK_COMBO_BOX(w));

            dup = g_strdup (data);
            g_strstrip(dup);

            gtk_list_store_append (data_store, &iter);
            gtk_list_store_set (data_store, &iter,
                                DYNTABLE_STORE_COLUMN_SORTORDER, pos,
                                DYNTABLE_STORE_COLUMN_SELECTED_ITEM, combo_item,
                                DYNTABLE_STORE_COLUMN_ENTRY_STRING, dup,
                                -1);

            g_free (dup);
        }
    }

    return dyntable->priv->data_store;
}

/* the model returned has two columns
 *
 * STRING: bound to attribute "text"
 * BOOLEAN: bound to attribute "sensitive"
 */
GtkListStore*
e_contact_editor_dyntable_get_combo_store (EContactEditorDynTable *dyntable)
{
    return dyntable->priv->combo_store;
}

void
e_contact_editor_dyntable_set_combo_defaults (EContactEditorDynTable *dyntable,
                                              const gint *defaults,
                                              size_t defaults_n)
{
    dyntable->priv->combo_defaults = defaults;
    dyntable->priv->combo_defaults_n = defaults_n;
}

static void
dispose_impl (GObject *object)
{
    GtkListStore *store;
    EContactEditorDynTable *dyntable;

    dyntable = E_CONTACT_EDITOR_DYNTABLE(object);

    store = dyntable->priv->data_store;
    if (store) {
        gtk_list_store_clear (store);
        g_object_unref (store);
        dyntable->priv->data_store = NULL;
    }

    store = dyntable->priv->combo_store;
    if (store) {
        g_object_unref (store);
        dyntable->priv->combo_store = NULL;
    }

    G_OBJECT_CLASS(e_contact_editor_dyntable_parent_class)->dispose (object);
}

static GtkWidget*
default_impl_widget_create (EContactEditorDynTable *dyntable)
{
    return gtk_entry_new ();
}

static void
default_impl_widget_clear (EContactEditorDynTable *dyntable,
                           GtkWidget *w)
{
    GtkEntry *e;
    e = GTK_ENTRY(w);
    gtk_entry_set_text (e, "");
}

static const gchar*
default_impl_widget_extract (EContactEditorDynTable *dyntable,
                             GtkWidget *w)
{
    GtkEntry *e;

    e = GTK_ENTRY(w);
    return gtk_entry_get_text (e);
}

static void
default_impl_widget_fill (EContactEditorDynTable *dyntable,
                          GtkWidget *w,
                          const gchar *value)
{
    GtkEntry *e;

    e = GTK_ENTRY(w);
    gtk_entry_set_text (e, value);
}

static gboolean
default_impl_widget_is_empty (EContactEditorDynTable *dyntable,
                              GtkWidget *w)
{
    GtkEntry *e;
    const gchar *data;
    gchar * dup;
    size_t len = -1;

    e = GTK_ENTRY(w);
    if (0 == gtk_entry_get_text_length (e))
        return TRUE;

    data = gtk_entry_get_text (e);

    dup = g_strdup (data);
    g_strchug (dup);
    len = strlen (dup);
    g_free (dup);

    return len <= 0;
}

static void
e_contact_editor_dyntable_init (EContactEditorDynTable *dyntable)
{
    GtkGrid *grid;

    dyntable->priv = G_TYPE_INSTANCE_GET_PRIVATE(dyntable,
            E_TYPE_CONTACT_EDITOR_DYNTABLE,
            EContactEditorDynTablePrivate);

    /* fill in defaults */
    dyntable->priv->max_entries = MAX_CAPACITY;
    dyntable->priv->curr_entries = 0;
    dyntable->priv->show_min_entries = 0;
    dyntable->priv->show_max_entries = dyntable->priv->max_entries;
    dyntable->priv->columns = 2;
    dyntable->priv->justified = FALSE;
    dyntable->priv->combo_defaults = NULL;

    dyntable->priv->combo_store = gtk_list_store_new (DYNTABLE_COBMO_COLUMN_NUM_COLUMNS,
            G_TYPE_STRING, G_TYPE_BOOLEAN);
    dyntable->priv->data_store = gtk_list_store_new (DYNTABLE_STORE_COLUMN_NUM_COLUMNS,
            G_TYPE_UINT, G_TYPE_INT, G_TYPE_STRING);
    gtk_tree_sortable_set_sort_column_id (
            GTK_TREE_SORTABLE (dyntable->priv->data_store),
            DYNTABLE_STORE_COLUMN_SORTORDER,
            GTK_SORT_ASCENDING);

    dyntable->priv->add_button = gtk_button_new_with_label ("+");
    g_signal_connect_swapped (dyntable->priv->add_button, "clicked",
            G_CALLBACK (add_empty_entry), dyntable);
    g_signal_connect_swapped(dyntable->priv->add_button, "clicked",
            G_CALLBACK (emit_row_added), dyntable);

    grid = GTK_GRID (dyntable);

    gtk_grid_attach (grid, dyntable->priv->add_button, 0, 0, 1, 1);
    gtk_widget_set_valign (dyntable->priv->add_button, GTK_ALIGN_CENTER);
    gtk_widget_set_halign (dyntable->priv->add_button, GTK_ALIGN_START);
    gtk_widget_show (dyntable->priv->add_button);

    if (dyntable->priv->curr_entries < dyntable->priv->show_min_entries)
        add_empty_entry (dyntable);
}

static void
e_contact_editor_dyntable_class_init (EContactEditorDynTableClass *class)
{
    GObjectClass *object_class;

    g_type_class_add_private (class, sizeof(EContactEditorDynTablePrivate));

    dyntable_signals[CHANGED_SIGNAL] = g_signal_new ("changed",
            G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
            G_STRUCT_OFFSET(EContactEditorDynTableClass, changed),
            NULL, NULL,
            g_cclosure_marshal_VOID__VOID,
            G_TYPE_NONE, 0);
    dyntable_signals[ACTIVATE_SIGNAL] = g_signal_new ("activate",
            G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
            G_STRUCT_OFFSET(EContactEditorDynTableClass, activate),
            NULL, NULL,
            g_cclosure_marshal_VOID__VOID,
            G_TYPE_NONE, 0);
    dyntable_signals[ROW_ADDED_SIGNAL] = g_signal_new ("row-added",
            G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
            G_STRUCT_OFFSET(EContactEditorDynTableClass, row_added),
            NULL, NULL,
            g_cclosure_marshal_VOID__VOID,
            G_TYPE_NONE, 0);

    object_class = G_OBJECT_CLASS (class);
    object_class->dispose = dispose_impl;

    /* virtual functions */
    class->widget_create    = default_impl_widget_create;
    class->widget_is_empty  = default_impl_widget_is_empty;
    class->widget_clear     = default_impl_widget_clear;
    class->widget_extract   = default_impl_widget_extract;
    class->widget_fill  = default_impl_widget_fill;
}