Data driven application with GTK+ and APR DBD routines

Posted by – June 19, 2010

I have been trying to find a reason to study c programming for a long time, now I found a reason to do it, I wrote a very small application that uses APR and GTK+ where I can manager costumers and suppliers contact info.

Before start coding you must pay attention to the following requirements:

  • Apache APR
  • GTK+
  • pkg-config

All the requirements above can be easily installed in your system by using the linux distribution package management system, you can also visit their respective sites to download the sources, compile them and install the binaries. The Apache APR sources can be found at apr.apache.org and GTK+ sources at gtk.org

Now we can start coding our application, let’s start with main window, the main window only contains a menu with few options, one of these menu options will open the suppliers contact info management window. In the main window we also do the APR initialization and termination, this step is very important since all structures used by APR must be initialized to be properly used.

Creating the main window

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include <apr_general.h>
#include <gtk/gtk.h>
#include "suppliers/management.h"

//The callback function to be called when the main window is closed
static gboolean close_application_event(
    GtkWidget *widget, GdkEvent *event, gpointer data)
{
    //Exit application
    gtk_main_quit();

    return FALSE;
}

//The callback function that is called when the main window is destroyed
static void destroy(GtkWidget *widget, gpointer data)
{
    //Exit application
    gtk_main_quit();
}

int main( int   argc,
          char *argv[] )
{
    GtkWidget *main_window;
   
    //Initialize apr and its internal data structures
    apr_initialize();  
   
    //Initializing GTK 
    gtk_init(&argc, &argv);
   
    //Configuring the window
    main_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    g_signal_connect(main_window, "delete-event", G_CALLBACK(close_application_event), NULL);
    gtk_container_set_border_width(GTK_CONTAINER(main_window), 10);
    //Placing the window at the top on the left corner of window
    gtk_widget_set_uposition(main_window, 0, 0);    
    gtk_window_set_title(GTK_WINDOW(main_window), "Registration of Suppliers");

    //Configuring menubar
    GtkWidget *menu_bar = gtk_menu_bar_new();
    gtk_container_add (GTK_CONTAINER(main_window), menu_bar);

    //Configuring registration menu
    GtkWidget *register_menu = gtk_menu_new();

    GtkWidget* register_item = gtk_menu_item_new_with_label("Register");
    gtk_menu_item_set_submenu(GTK_MENU_ITEM(register_item), register_menu);
    gtk_menu_bar_append(GTK_MENU_BAR(menu_bar), register_item);

    //Configuring suppliers management submenu
    GtkWidget *suppliers_management_item = gtk_menu_item_new_with_label("Suppliers");
    gtk_menu_shell_append(GTK_MENU_SHELL(register_menu), suppliers_management_item);
    g_signal_connect(management_suppliers_item, "activate", G_CALLBACK(suppliers_management), (gpointer) main_window);

     //Configuring quit menu item
    GtkWidget* quit_item = gtk_menu_item_new_with_label("Quit");
    g_signal_connect_swapped(quit_item, "activate", G_CALLBACK(destroy), NULL);
    gtk_menu_bar_append(GTK_MENU_BAR(menu_bar), quit_item);

    gtk_widget_show_all(main_window);
   
    //Start the application main loop
    gtk_main ();

    //Shutting down APR
    apr_terminate();

    return 0;
}

Once the main window is done we are ready to code the suppliers contact info management window.

Creating the suppliers window

The file below contains few pieces of code related with supplier contact info window, this window has some callback functions for its controls, when the user press the ENTER key on the first entry widget in this window the persistence layer code is called to retrieve data from database.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
#include <gtk/gtk.h>
#include <gdk/gdk.h>
#include <gdk/gdkkeysyms.h>
#include <stdlib.h>
#include "management.h"
#include "persistence.h"

//The flag used to mark the record as new
gboolean    NEW;

GtkToolItem *button_new;   

GtkWidget *id;
GtkWidget *name;
GtkWidget *address;

//This function does some adjustements on window and its input fields
void edit_mode(gboolean editing) {

    //Enabling/disabling toolbar buttons and entry widgets
    gtk_widget_set_sensitive(GTK_WIDGET(button_new), ! editing);  
   
    gtk_widget_set_sensitive(GTK_WIDGET(id), ! editing);  
    gtk_widget_set_sensitive(GTK_WIDGET(name), editing);  
    gtk_widget_set_sensitive(GTK_WIDGET(address), editing);  

    //If we are working on a new record or we aren't editing data
    if(! editing || NEW) {
        //Clean all input fields
        gtk_entry_set_text(GTK_ENTRY(name), "");
        gtk_entry_set_text(GTK_ENTRY(address), "");
        if(! NEW) {
            gtk_entry_set_text(GTK_ENTRY(id), "");
            gtk_widget_grab_focus(id);
        }
        else {
            gtk_widget_grab_focus(name);
        }
    }
    else {
        gtk_widget_grab_focus(name);
    }
}

//The callback function to be called when the user clicks on "New" button of toolbar
void button_new_supplier_clicked(GtkWidget *widget, gpointer data)
{
    NOVO = TRUE;       
    edit_mode(FALSE);  
}

//The callback function to be called when the user presses any key in a input field
gboolean id_keypress_event(
    GtkWidget *widget, GdkEventKey *event, gpointer user_data)  
{
    supplier_t *supplier;
    gboolean     ok;   
    const gchar  *value;
    //If the user press the [Enter] or [Return] keys load the supplier info and show to the user
    if(event->keyval == GDK_Return || event->keyval == GDK_KP_Enter) {
        value = gtk_entry_get_text(GTK_ENTRY(id));
        ok = get_supplier(value, &supplier);
        NOVO = ! ok;
        edit_mode(TRUE);
        if(ok) {
            if(! NEW) {
                gtk_entry_set_text(GTK_ENTRY(name), supplier->name);
                gtk_entry_set_text(GTK_ENTRY(address), supplier->address);
            }      
            free(supplier);
        }
        return TRUE;   
    }
   
    return FALSE;  
}

//The callback function to be called when the supplier contact info window is closed
gboolean close_supplier_management_window_event(
    GtkWidget *widget, GdkEvent *event, gpointer data)
{
    //We must return FALSE in order to let GTK close the window and free its resources
    return FALSE;
}

//The function that setup the window used for manage suppliers contact info
void suppliers_management(GtkWidget *widget, gpointer data)
{
    GtkWidget   *window;
    GtkWidget   *label;
    GtkWidget   *entry;
    GtkWidget   *alignment;
    GtkWidget   *table;
    GtkWidget   *toolbar;
    GtkToolItem *toolbar_item;
   
    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    //Hide the taskbar button for this window in window manager
    gtk_window_set_skip_taskbar_hint(GTK_WINDOW(window), TRUE);
    g_signal_connect(window, "delete-event", G_CALLBACK(close_supplier_management_window_event), NULL);
    gtk_container_set_border_width (GTK_CONTAINER (window), 10);
    gtk_window_set_transient_for(GTK_WINDOW(window), (GtkWindow *) data);  
    //Set the window title
    gtk_window_set_title(GTK_WINDOW(window), "Registration of Suppliers");

    //Adding table layout to the window
    table = gtk_table_new(4, 2, FALSE);
   
    gtk_table_set_row_spacings(GTK_TABLE(table), 4);
    gtk_table_set_col_spacings(GTK_TABLE(table), 4);
   
    //Configuring toolbar
    toolbar = gtk_toolbar_new();
    gtk_toolbar_set_orientation(GTK_TOOLBAR(toolbar), GTK_ORIENTATION_HORIZONTAL);
    gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_BOTH);
    gtk_container_set_border_width(GTK_CONTAINER(toolbar), 5);
 
    //Adding the "New" button to toolbar
    toolbar_item = gtk_tool_button_new(NULL, "New");
    gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolbar_item, -1);
    g_signal_connect(toolbar_item, "clicked", G_CALLBACK(button_new_supplier_clicked), NULL);
    botao_novo = toolbar_item;

    //Adicionando toolbar dentro da tabela
    gtk_table_attach(GTK_TABLE(table), toolbar, 0, 2, 0, 1, GTK_FILL, GTK_SHRINK, 0, 0);   
   
    //Adding fields to the table
    label = gtk_label_new("Id");   
    alignment = gtk_alignment_new(0.9, 0.5, 0.0, 0.0);
    gtk_container_add(GTK_CONTAINER(alignment), label);
    gtk_table_attach(GTK_TABLE(table), alignment, 0, 1, 1, 2, GTK_FILL, GTK_SHRINK, 0, 0); 

    entry = gtk_entry_new();   
    gtk_entry_set_max_length(GTK_ENTRY(entry), 14);  
    gtk_entry_set_width_chars(GTK_ENTRY(entry), 14);    
    alignment = gtk_alignment_new(0.0, 0.5, 0.0, 0.0);
    gtk_container_add(GTK_CONTAINER(alignment), entry);
    gtk_table_attach_defaults(GTK_TABLE(table), alignment, 1, 2, 1, 2);
    g_signal_connect(entry, "key-press-event", G_CALLBACK(id_keypress_event), NULL);
    id = entry;
   
    label = gtk_label_new("Name");   
    alignment = gtk_alignment_new(0.9, 0.5, 0.0, 0.0);
    gtk_container_add(GTK_CONTAINER(alignment), label);
    gtk_table_attach(GTK_TABLE(table), alignment, 0, 1, 2, 3, GTK_FILL, GTK_SHRINK, 0, 0); 

    entry = gtk_entry_new();
    gtk_entry_set_max_length(GTK_ENTRY(entry), 40);  
    gtk_entry_set_width_chars(GTK_ENTRY(entry), 40);    
    alignment = gtk_alignment_new(0.0, 0.5, 0.0, 0.0);
    gtk_container_add(GTK_CONTAINER(alignment), entry);
    gtk_table_attach_defaults(GTK_TABLE(table), alignment, 1, 2, 2, 3);
    name = entry;

    label = gtk_label_new("Address");    
    alignment = gtk_alignment_new(0.9, 0.5, 0.0, 0.0);
    gtk_container_add(GTK_CONTAINER(alignment), label);
    gtk_table_attach(GTK_TABLE(table), alignment, 0, 1, 3, 4, GTK_FILL, GTK_SHRINK, 0, 0); 

    entry = gtk_entry_new();
    gtk_entry_set_max_length(GTK_ENTRY(entry), 11);  
    gtk_entry_set_width_chars(GTK_ENTRY(entry), 11);    
    alignment = gtk_alignment_new(0.0, 0.5, 0.0, 0.0);
    gtk_container_add(GTK_CONTAINER(alignment), entry);
    gtk_table_attach_defaults(GTK_TABLE(table), alignment, 1, 2, 3, 4);
    address = entry;
   
    //Adding the table to the window
    gtk_container_add (GTK_CONTAINER(window), table);
   
    //Centering the window on the screen
    gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);   
   
    //Set editing mode of window
    edit_mode(FALSE);
   
    //Showing window and all controls inside it
    gtk_widget_show_all(window);
}

This a partial the definition of contact management window, we have several functions implemented above, most of them are callback functions, the most important is suppliers_management, this function is called from the menu on main window, in line 97 we are initializing the window as top level window, the lines 99 to 104 does a basic setup of window on window manager.
This window also has a table layout added to it lines 107 to 110, this widget allow us to align some widgets in this window, the first of them is the toolbar widget, it takes space of two columns in this layout as we can see on gtk_table_attach function call on line 125, we also include some labels and entry widgets to this table to align them in the window lines 128 to 166. Now we can finish the implementation of the window by just placing it on the center of screen line 172 and set the default state of its widgets line 175.

I also wrote a small abtraction layer around APR DBD routines.

Writing functions that does some database abstraction on top of APR DBD

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
#include <stdarg.h>
#include <stdlib.h>
#include <glib/gprintf.h>
#include <gtk/gtk.h>
#include "apr_lib.h"
#include "apr_dbd.h"
#include "database.h"

//Open database connection
gboolean db_open_connection(const char* driver_name, const char* parameters, db_connection_t **connection) {
    const apr_dbd_driver_t  *driver;
    apr_pool_t              *pool;
    apr_dbd_t               *handle;
    apr_status_t            status;
   
    //Initializing a new memory pool
    status = apr_pool_create(&pool, NULL); 
    if(status == APR_SUCCESS) {
        //Initializing the memory pool
        status = apr_dbd_init(pool);
        if(status == APR_SUCCESS) {
            //Loading the database driver
            status = apr_dbd_get_driver(pool, driver_name, &driver);
            if(status == APR_SUCCESS) {
                //Open the connection to database
                status = apr_dbd_open(driver, pool, parameters, &handle);
                if(status == APR_SUCCESS) {
                    *connection = apr_pcalloc(pool, sizeof(db_connection_t));
                    (*connection)->pool   = pool;
                    (*connection)->handle = handle;
                    (*connection)->driver = driver;
                    return TRUE;
                }
            }  
        }
    }
   
    return FALSE;
}

//Count the number of parameters in a query
int db_count_sql_parameters(const char* query) {
    int args = 0;
    const char *q;
   
    for (q = query; *q; q++) {
        if (q[0] == '%') {
            if (apr_isalpha(q[1])) {
                args++;
            } else if (q[1] == '%') {
                q++;
            }
        }
    }
   
    return args;
}

//Convert a va_list to a binary list of values
const void** db_binary_args_from_var_args(apr_pool_t *pool, int nargs, va_list args) {
    const void **values;
    int i;

    values = apr_palloc(pool, sizeof(*values) * nargs);

    for (i = 0; i < nargs; i++) {
        values[i] = va_arg(args, const void*);
    }
   
    return values;
}

//Execute a query on database and store the results on db_resultset_t structure.
gboolean db_execute_query(db_connection_t *connection, db_resultset_t **resultset, const char* query, ...) {
    apr_dbd_prepared_t *prepared_statement;
    apr_dbd_results_t  *results;
    const void         **values;
    int                result;
    va_list            args;
   
    va_start(args, query);

    //Converting a va_list holding a variable number of arguments to binary arguments
    values = db_binary_args_from_var_args(connection->pool, db_count_sql_parameters(query), args);
   
    va_end(args);  
   
    results = NULL;
    prepared_statement = NULL;

    //Prepare the sql statement
    result = apr_dbd_prepare(connection->driver, connection->pool, connection->handle, query, NULL, &prepared_statement);
    if(result == 0) {
        //Pass the values to the prepared statement and execute it
        result = apr_dbd_pbselect(connection->driver, connection->pool, connection->handle, &results, prepared_statement, 0, values);
        if(result == 0) {
            //Allow the db_resultset_t structure and pass the apr resultset resultset to it
            *resultset = apr_pcalloc(connection->pool, sizeof(db_resultset_t));
            (*resultset)->data = results;
            return TRUE;
        }
    }  

    return FALSE;
}

//Move to the next row on resultset
gboolean db_next_row(db_connection_t *connection, db_resultset_t *resultset) {
    apr_dbd_row_t *row;
    int           result;

    row = NULL;
   
    //Call the apr function to move to the next row on resultset
    result = apr_dbd_get_row(connection->driver, connection->pool, resultset->data, &row, -1);   
    if(result == 0) {
        //If the row is successfully retrieved set it on our resultset
        resultset->row = row;  
        return TRUE;
    }
    //Print any error occurred while moving to the next row
    g_printf("%s", apr_dbd_error(connection->driver, connection->handle, result));
   
    return FALSE;
}

//Read data from column in resultset
const char* db_read_value(db_connection_t *connection, db_resultset_t *resultset, int column) {
    const char *value;
   
    //Read the value in a column at the row pointed by resultset structure
    value = apr_dbd_get_entry(connection->driver, resultset->row, column);
    if(value != NULL) {
       
        return value;
    }
   
    return "";
}

//Close database connection
gboolean db_close_connection(db_connection_t *connection) {
    apr_status_t status;

    //Close database connection
    status = apr_dbd_close(connection->driver, connection->handle);
    if(status == APR_SUCCESS) {
        //Do apr pool cleanup and free all pointers  
        apr_pool_destroy(connection->pool);
        return TRUE;
    }
   
    return FALSE;
}

The file above contains a small portion of our database abstraction layer, both memory management and database connection handling are done with help of APR and its DBD routines, please put special focus on db_open_connection function, in this function we do the abstraction of the database connection, first we create the memory pool line 17, if the pool is successfully created we can now init it line 20, now we are read to load the database driver at line 23, once driver is successfully loaded we are able to open the database connection and then create an internal structure containing a pointer to memory pool, connection handle and database driver structures.
The majority of APR DBD routines takes a pool as argument, this means that all memory allocated by these methods will be hold by this memory pool and will released when the database connection is closed and the pool is destroyed as we can see in db_close_connection at line 149.

Now we can finish our application code with a persistence abtraction layer to help us to load/save data on database.

Writing few functions that does persistence abstraction

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <gtk/gtk.h>
#include <stdio.h>
#include <stdlib.h>
#include "persistence.h"
#include "../util/database.h"

gboolean get_supplier(const char *id, supplier_t **supplier) {
    db_connection_t *connection = NULL;
    db_resultset_t  *resultset  = NULL;
    gboolean        ok;
   
    //Open database connection
    ok = db_open_connection("pgsql", "dbname=contacts user=dbuser password=dbpassword", &connection);
    if(ok) {
        //Execute query on database
        ok = db_execute_query(connection, &resultset, "SELECT \"ID\", \"NAME\"," \
            "\"ADDRESS\" FROM SUPPLIER WHERE \"ID\" = %s", id);
        if(ok) {
            //Move to the first row of resultset
            ok = db_next_row(connection, resultset);
            if(ok) {
                //Initialize the memory for structure
                *supplier = malloc(sizeof(supplier_t));
                (*supplier)->id = db_read_value(connection, resultset, 0); 
                (*supplier)->name = db_read_value(connection, resultset, 1);   
                (*supplier)->address = db_read_value(connection, resultset, 2);
                //Close database connection
                db_close_connection(connection);
                return TRUE;       
            }  
        }
        //Close database connection
        db_close_connection(connection);
    }
    return FALSE;  
}

The function get_supplier is one of several functions that defines our persistence layer, this function just do a SQL statement in a table and them map the data retrieved from database to an allocated struct. Note the lines 13 to 21 where we are calling the functions of our database abstraction layer, in each call we have a boolean value to be returned, we use this approach to check if the function has finished with a error, if the function call returns TRUE we can move on and do another call to the database layer.

Writing the makefile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#Using gcc as c compiler
CC=gcc

#Defining compiler flags, making use of pkg-config to retrieve compiler flags for gtk and apr also
CFLAGS=-Wall -g -O2 -I. `pkg-config gtk+-2.0 --cflags` `pkg-config apr-1 --cflags` `pkg-config apr-util-1 --cflags`

#Defining libs to be used for compile this programin, using pkg-config to discover the libs used by gtk and apr
LIBS=`pkg-config gtk+-2.0 --libs` `pkg-config apr-1 --libs` `pkg-config apr-util-1 --libs`

#The default target will call program compilation
all:registration

#Linking all libraries into main program
registration: registration.o util.la customers.la suppliers.la
    $(CC) $(LIBS) -o $@ registration.o customers/customers.la suppliers/suppliers.la util/util.la

registration.o: registration.c
    $(CC) $(CFLAGS) -c -o $@ $<

#Target to compile util code as library
util.la:
    cd util && $(MAKE)

#Target to compile customers code as library
customers.la:
    cd customers && $(MAKE)

#Target to compile suppliers code as library
suppliers.la:
    cd suppliers && $(MAKE)

#Defining the target to clean a previous build
clean:
    cd util && $(MAKE) $@
    cd customers && $(MAKE) $@
    cd suppliers && $(MAKE) $@
    rm -f cadastro cadastro.o

# This is GNU Makefile extension to notify that roughly means: 'clean' does
# not depend on any files in order to call it.
.PHONY: clean

Compiling the application

Just type:

make

Running application

Just type:

./supplier_registration

Share

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>