## Coding the application

### Choice of objects

In this example, the GnomeDbGrid widget will be used to display the data, which is stored as a GdaDataModel (a 2 dimensions array holding data where all the values in the same row have the same data type).

The data will be fetched from the database using SELECT SQL queries. It would have been possible at this point to use a GdaCommand for each SQL statement, but it would have been a lot of code to keep track of the dependencies (and re-run the statements when necessary), and it would have been difficult to make modifications possible. So instead, the example make use of the GdaDataModelQuery object for which one specifies a SELECT SQL statement, and optionnally SQL statements for the UPDATE, DELETE and INSERT operations, the object being responsible to run those statement when necessary.

Two GdaDataModelQuery objects are used here: one for the orders of a specific customers, and one for the details of a specific order.

### Complete code

Here is the complete example, which is self-documented.

    #include <gtk/gtk.h>
    #include <libgnomedb/libgnomedb.h>
    #include <glade/glade.h>
    #include <unistd.h>

    #define DB_NAME    "SampleDb"
    #define GLADE_FILE "gladeui.glade"

    /*
     * Application-Wide data
     */
    typedef struct {
    	GtkWidget *main_window;

    	GdaDataModel *model_cust;
    	GdaDataModel *model_orders;
    	GdaDataModel *model_contents;

    	GtkWidget *grid_orders;
    	GtkWidget *grid_contents;
    	GtkWidget *form_cust;
    } MainData;

    static GtkWidget *create_main_window  (void);
    static void       prepare_main_window (MainData *data);

    int
    main (int argc, char *argv[])
    {
    	GError *error = NULL;
    	GdaClient *client;
    	GdaConnection *cnc;
    	gchar *str;
    	MainData *data;

    	/* Libgnomedb & GTK+ inittalization */
    	gnome_db_init ();
    	gtk_init (&argc, &argv);

    	/* open a connection */
    	client = gda_client_new ();
    	str = g_strdup_printf ("DB_DIR=.;DB_NAME=" DB_NAME);
    	cnc = gda_client_open_connection_from_string (client, "SQLite", str,
    						      NULL, NULL,
                                                          0, &error);
    	g_free (str);
    	if (! cnc)
    		g_error ("Can't open connection:\n%s\n", error->message);
    	gda_dict_set_connection (default_dict, cnc);

    	/* create application specific data, and setup */
    	data = g_new0 (MainData, 1);
    	data->main_window = create_main_window ();
    	prepare_main_window (data);
    	gtk_widget_show (data->main_window);

    	gtk_main ();
    	return 0;
    }

    /*
     * Called when a parameter changes, to display the change, just
     * as an illustration
     */
    static void
    param_changed_cb (GdaSet *param_list, GdaHolder *param, gchar *context)
    {
    	gchar *str;
    	str = gda_value_stringify ((GValue *) gda_holder_get_value (param));
    	g_print ("%s: param '%s' changed to %s", context, gda_object_get_name (GDA_OBJECT (param)), str);
    	g_free (str);
    	if (gda_holder_is_valid (param))
    		g_print ("\n");
    	else
    		g_print (" -- INVALID\n");
    }

    static void notice_error (GError *error);
    static GtkWidget *lookup_widget (GtkWidget *root, const gchar *name);
    static void setup_dependencies (MainData *data);
    static void model_orders_reset_cb (GdaDataModel *model, MainData *data);

    /*
     * After having loaded the main window with Libglade,
     * create some data models for the list of customers, for the list of orders for one customer,
     * and for the details of an order
     */
    static void
    prepare_main_window (MainData *data)
    {
    	GError *error = NULL;

    	GdaQuery *query;
    	GtkWidget *table = lookup_widget (data->main_window, "table1");

    	GdaSet *params;
    	GdaHolder *param;
    	GnomeDbDataWidget *raw;
    	GdaDataModelIter *iter_orders;
    	gboolean ok;

    #define CUSTOMERS_SQL "SELECT id, name FROM customers"

    #define ORDERS_SQL "SELECT o.id, o.creation_date AS \"Creation Date\", o.delivery_before AS \"Deliver before\", o.delivery_date AS \"Delivery Date\" FROM customers c INNER JOIN orders o ON (c.id=o.customer) WHERE o.customer= ##Customer::gint"
    #define ORDERS_INS_SQL "INSERT INTO orders (customer, creation_date, delivery_before, delivery_date) VALUES (##Customer::gint, date('now'), ##+2::gchararray::null, NULL)"
    #define ORDERS_DEL_SQL "DELETE FROM orders WHERE id = ##-0::gint"
    #define ORDERS_UPD_SQL "UPDATE orders set id=##+0::gint, delivery_before=##+2::gchararray::null, delivery_date=##+3::gchararray::null WHERE id=##-0::gint"

    #define CONTENTS_SQL "SELECT p.ref AS \"Item ref#\", p.name AS \"Item name\", o.quantity AS \"Quantity\", o.discount AS \"Discount\" FROM products p INNER JOIN order_contents o ON (p.ref=o.product_ref) WHERE o.order_id= ##oid::gint"
    #define CONTENTS_INS_SQL "INSERT INTO order_contents (order_id, product_ref, quantity, discount) VALUES (##oid::gint, ##+0::gchararray, ##+2::gint, ##+3::gdouble)"
    #define CONTENTS_UPD_SQL "UPDATE order_contents SET product_ref = ##+0::gchararray, quantity = ##+2::gint, discount = ##+3::gdouble WHERE order_id = ##oid::gint AND product_ref = ##-0::gchararray AND quantity = ##-2::gint AND discount = ##-3::gdouble"
    #define CONTENTS_DEL_SQL "DELETE FROM order_contents WHERE order_id = ##oid::gint AND product_ref = ##-0::gchararray AND quantity = ##-2::gint AND discount = ##-3::gdouble"

    	/* Data model for customers */
    	query = gda_query_new_from_sql (NULL, CUSTOMERS_SQL, NULL);
    	data->model_cust = gda_data_model_query_new (query);
            g_object_unref (query);

    	/* Data model for the orders, set the modification queries (as SQL) to make the
    	 * data model writable.
    	 *
    	 * The data model is not initially filled because the SELECT SQL query requires a variable to be
    	 * set (the "Customer" variable)
    	 */
    	query = gda_query_new_from_sql (NULL, ORDERS_SQL, NULL);
    	data->model_orders = gda_data_model_query_new (query);
            g_object_unref (query);

    	ok = gda_data_model_query_set_modification_query (GDA_DATA_MODEL_QUERY (data->model_orders),
    							  ORDERS_INS_SQL, &error);
    	if (!ok || error) notice_error (error);
    	ok = gda_data_model_query_set_modification_query (GDA_DATA_MODEL_QUERY (data->model_orders),
    							  ORDERS_DEL_SQL, &error);
    	if (!ok || error) notice_error (error);
    	ok = gda_data_model_query_set_modification_query (GDA_DATA_MODEL_QUERY (data->model_orders),
    							  ORDERS_UPD_SQL, &error);
    	if (!ok || error) notice_error (error);

    	/* Data model for the orders' contents, set the modification queries (as SQL) to make the
    	 * data model writable.
    	 *
    	 * As for the data model for the orders, it is not initially filled because the SELECT SQL query
    	 * requires the order ID to be set (the "oid" variable)
    	 */
    	query = gda_query_new_from_sql (NULL, CONTENTS_SQL, NULL);
    	data->model_contents = gda_data_model_query_new (query);
            g_object_unref (query);
    	ok = gda_data_model_query_set_modification_query (GDA_DATA_MODEL_QUERY (data->model_contents),
    							  CONTENTS_INS_SQL, &error);
    	if (!ok || error) notice_error (error);
    	ok = gda_data_model_query_set_modification_query (GDA_DATA_MODEL_QUERY (data->model_contents),
    							  CONTENTS_UPD_SQL, &error);
    	if (!ok || error) notice_error (error);
    	ok = gda_data_model_query_set_modification_query (GDA_DATA_MODEL_QUERY (data->model_contents),
    							  CONTENTS_DEL_SQL, &error);
    	if (!ok || error) notice_error (error);

    	/*
    	 * Combo box to choose the customer among the list of customers
    	 * The steps are:
    	 *   - get the list of parameters required to fill the orders data model (the "Customer" variable)
    	 *   - restrict the possible values of that variable (parameter) to the contents of the customers data model
    	 *   - create a GnomeDbBasicForm from the list of parameters.
    	 */
    	params = gda_data_model_query_get_parameter_list (GDA_DATA_MODEL_QUERY (data->model_orders));
    	param = GDA_HOLDER (params->holders->data);
    	if (!gda_holder_restrict_values (param, data->model_cust, 0, &error))
    		g_error ("%s() line %d: %s", __FUNCTION__, __LINE__,
    			 error && error->message ? error->message : "No detail");
    	data->form_cust = gnome_db_basic_form_new (params);
    	gnome_db_basic_form_show_entry_actions (GNOME_DB_BASIC_FORM (data->form_cust), FALSE);
    	gtk_table_attach (GTK_TABLE (table), data->form_cust, 0, 1, 1, 2, GTK_FILL | GTK_EXPAND | GTK_SHRINK , 0, 0, 0);
    	gtk_widget_show (data->form_cust);

    	/* display changes when they occur */
    	g_signal_connect (params, "param_changed",
    			  G_CALLBACK (param_changed_cb), "Customer Id");

    	/*
    	 * Grid for the orders
    	 */
    	data->grid_orders = gnome_db_grid_new (data->model_orders);
    	gtk_table_attach_defaults (GTK_TABLE (table), data->grid_orders, 0, 1, 3, 4);
    	gtk_widget_show (data->grid_orders);

    	/* display changes when they occur */
    	g_object_get (G_OBJECT (data->grid_orders), "raw_grid", &raw, NULL);
    	iter_orders = gnome_db_data_widget_get_current_data (GNOME_DB_DATA_WIDGET (raw));
    	g_signal_connect (iter_orders, "param_changed",
    			  G_CALLBACK (param_changed_cb), "Order Id");

    	/*
    	 * Grid for the order's contents
    	 */
    	data->grid_contents = gnome_db_grid_new (data->model_contents);
    	gtk_table_attach_defaults (GTK_TABLE (table), data->grid_contents, 0, 1, 5, 6);
    	gtk_widget_show (data->grid_contents);

    	/*
    	 * Final setup: specify that when the selected order changes, the data model
    	 * containing the order's details must also be updated
    	 */
    	setup_dependencies (data);

    	/*
    	 * Safe procedure: if the list of orders is reset (the list of columns changes) which
    	 * can happen since it may not be fully defined untill it is first filled (that is
    	 * when the SELECT query is run), which is the case here, then reset the dependencies
    	 * in a callback.
    	 */
    	g_signal_connect (G_OBJECT (data->model_orders), "reset",
    			  G_CALLBACK (model_orders_reset_cb), data);
    }

    /*
     * Set up the dependencies between the selected row in the list of orders and
     * the details of the selected order
     */
    static void
    setup_dependencies (MainData *data)
    {
    	GnomeDbDataWidget *raw;
    	GdaDataModelIter *iter_orders;
    	GdaSet *params;
    	GdaHolder *param;

    	g_object_get (G_OBJECT (data->grid_orders), "raw_grid", &raw, NULL);
    	iter_orders = gnome_db_data_widget_get_current_data (GNOME_DB_DATA_WIDGET (raw));

    	params = gda_data_model_query_get_parameter_list (GDA_DATA_MODEL_QUERY (data->model_contents));
    	param = GDA_HOLDER (g_slist_nth_data (GDA_SET (iter_orders)->holders, 0));
    	gda_holder_bind_to_param (GDA_HOLDER (params->holders->data), param);

    	/* display changes when they occur */
    	g_signal_connect (iter_orders, "param_changed",
    			  G_CALLBACK (param_changed_cb), "Order Id");
    }

    static void
    model_orders_reset_cb (GdaDataModel *model, MainData *data)
    {
    	setup_dependencies (data);
    }

    static gboolean
    delete_event (GtkWidget *widget, GdkEvent  *event, gpointer data)
    {
    	return FALSE;
    }

    static void
    destroy (GtkWidget *widget, gpointer data)
    {
    	gtk_main_quit ();
    }

    /*
     * Load the Glade file with Libglade
     */
    static GtkWidget *
    create_main_window ()
    {
    	GtkWidget *win, *wid;
    	GladeXML *glade;

    	/* load the glade file */
    	glade = glade_xml_new (GLADE_FILE, NULL, NULL);
    	if (!glade)
    		g_error ("Can't load Glade UI file " GLADE_FILE);
    	win = glade_xml_get_widget (glade, "window1");
    	if (!win)
    		g_error ("Glade UI file " GLADE_FILE " does not have widget named 'window1'");

    	/* standard signals connecting to exit the app. properly */
    	g_signal_connect (G_OBJECT (win), "delete_event",
    			  G_CALLBACK (delete_event), NULL);
    	g_signal_connect (G_OBJECT (win), "destroy",
    			  G_CALLBACK (destroy), NULL);

    	wid = lookup_widget (win, "quit1");
    	g_signal_connect (wid, "activate",
    			  G_CALLBACK (destroy), NULL);

    	wid = lookup_widget (win, "toolbutton2");
    	g_signal_connect (wid, "clicked",
    			  G_CALLBACK (destroy), NULL);

    	/* trick to "attach" the GladeXML object to the main window */
    	g_object_set_data_full (G_OBJECT (win), "__for_ref", glade, g_object_unref);

    	return win;
    }

    static GtkWidget *
    lookup_widget (GtkWidget *root, const gchar *name)
    {
    	GladeXML *glade;

    	glade = glade_get_widget_tree (root);
    	g_assert (glade);
    	return glade_xml_get_widget (glade, name);
    }

    static void
    notice_error (GError *error)
    {
    	g_error ("Error: %s\n", error && error->message ? error->message : "No detail");
    }

### Compiling

To compile an application using libgnomedb, proceed as with other GTK+ or Gnome libraries, using the pkg-config program and get the CFLAGS and LDFLAGS for the 'libgnomedb' library.