Well it’s been nearly a month since I first wrote about project freedom (as in financial) and I thought it might be time give a brief update on where I’m at. Given that we’ve just had Christmas and I’m currently on holidays progress hasn’t been all that dazzling. That said, I’m still pretty happy with what has been done all things considered. So what have I been doing? Well so far I’ve mainly been looking at different approaches to building the application.

The Java Way

Naturally I immediately jumped to looking at a Java based solution – after all it is what I’d call my “home” language. Anyone who’s been around a while and has had the opportunity to program in Java as well as a variety of other programming languages/paradigms would undoubtedly agree that it is a beautiful language. I for one really appreciate the consistency of the syntax, the uncompromising enforcement of the OOP paradigm, and most of all the rich standard library with its accompanying documentation.

Being that I work with this stuff quite a bit my head was filled with all sorts of ideas about how we could achieve our goals using Java as the weapon of choice. The first thing that came to mind was that I could utilize the Eclipse RCP and JFace to get some quick wins getting a basic application framework up and running. This approach has the following distinct advantages:

  1. Eclipse RCP contains an OSGi container built in (Equinox to be exact) which is a very powerful plugin framework in a manner of speaking. Potentially it means that each discrete piece of functionality can be build as a separate plugin (or bundle in OSGi-speak).
  2. SWT/JFace contain a rich component set which would do about 95% of what I require and look/behave just like native components on Mac, Windows and Linux.
  3. RCP comes with a tool that allows to generate an executable and budle a JRE with the application so you can avoid “but my system doesn’t have Java (or version required) installed” type support headaches. It just runs.
  4. It’ll run on Mac, Windows, and Linux with no issues whatsoever.

So why wouldn’t I take that approach?

  1. Personally I’ve found RCP very hard to come to grips with. Documentation is scarce and outdated from what I can determine.
  2. OSGi adds a large amount of overhead in terms of complexity. Now I’m not exactly the smartest guy going round, but I’m not a moron either. I’ve spent hours frigging around with manifests, configurations, interfaces, dodgy docos and classpath hassles getting very little done. It all feels like a tremendous waste of time at this stage.
  3. Need to learn and ORM mapping tool like Hibernate or similar to overcome the OO to Relational mapping mismatch. I’m predominantly a user interface specialist so I usually leave this kinda stuff to someone else. Alternatively I can just battle on through with JDBC (*shudder).

As far as abstractions go Donald Knuth is reputed to have once said:

“There is no complexity problem in programming that cannot be eased by adding a layer of indirection. And there is no performance problem in programming that cannot be eased by removing a layer of indirection.”

Ordinarily I’d agree with this statement. You only have to look as far as something like an ORM to see this principle at work. What could be simpler than just working with your domain objects and letting something else worry about the whole persistence issue? On the contrary though I’m inclined to say that the sheer number of abstractions present in the aforementioned solution actually add to its complexity – not reduce it as promised. As always in Java I seem to be forced to spend a lot of time thinking about the correct way to do something as opposed to just doing it and refactoring later.

So there it is, I’ve spent a whole lot of time without a great deal of success. About the best thing it’s got going for it so far is that I’ve managed to do some modelling of the domain level objects. So where do we go from here?

Back to the Future

Some might consider this a backward step from comfortable walled garden that is Java but I’ve been experimenting a bit lately with C and GTK+. Yes you read right C, not C++, just pure unadulterated structured programming. Why not C++ indeed.

I’ll probably get flamed for saying this, but I just don’t like it. With great power comes great responsibility, and it’s like a wild beast that can take on any form it likes depending on the wild mood swings of your average programmer. Every time I read C++ I get the impression that whoever’s driving the evolution of the language can’t quite decide what they want it to be. If Java is a well manicured, if somewhat anal, golfing green then C++ resembles a hoarders front yard. You know the ones, full of old car bodies and useless junk left over after you take all the useful stuff out. Don’t take my word for it though, ask Linus. That said you can always pick and choose the bits you like and leave out the bits you don’t.

Anyway, so far I’m really impressed. Previously I’d thought that leaving OO behind would just be too painful however I was pleasantly surprised. To demonstrate a few concepts to myself I briefly knocked up this little demo which consists of a simple GTK+ interface sitting atop an SQLite database. As far as I’m concerned this a great “thin line” of technology from top to bottom which could conceivably be employed to write the app.

db_test.c

#include <stdlib.h>
#include <stdio.h>
#include <gtk/gtk.h>
#include <sqlite3.h>

/* Database file name. */
const gchar *DB_FILENAME = "test.db";

/* A simple SQL query to select all rows from the person table. */
const gchar *DB_QUERY = "SELECT * FROM person;";

/* Index of the name column in the list view */
const gint LIST_COL_NAME = 0;

/* Index of the age column in the list view */
const gint LIST_COL_AGE = 1;

/* The total number of columns in the list view */
const gint LIST_NUM_COLS = 2;

/* Global reference to DB handle. */
static sqlite3 *db;

/* Callback that processes a single row returned from the database. The first
 * parameter is expected to be a pointer to a GtkTreeView. */
static int
process_db_results( gpointer  data,
                    gint      ncols,
                    gchar    *col_values[],
                    gchar    *col_names[] )
{
    GtkTreeView    *view;
    GtkListStore   *store;
    GtkTreeIter     iter;

    /* Get reference to underlying GtkListStore so we can add to it. */
    view = GTK_TREE_VIEW (data);
    store = GTK_LIST_STORE (gtk_tree_view_get_model (view));

    /* Append a row and populate it using the row data provided */
    gtk_list_store_append (store, &iter);
    gtk_list_store_set (store, &iter,
            LIST_COL_NAME, col_values[0],
            LIST_COL_AGE, col_values[1],
            -1);

    /* Auto-adjust columns to fit data. */
    gtk_tree_view_columns_autosize (view);

    /* Return a zero to let SQLite know it's okay to continue processing */
    return 0;
}

/* User clicked the "Load List" button. */
static void
button_load_clicked( gpointer data )
{
    gint rc;
    gchar *err_msg = 0;

    /* Populate the list component with data from the DB. */
    rc = sqlite3_exec (db, DB_QUERY, process_db_results, data, &err_msg);
    if (rc != SQLITE_OK)
      {
        fprintf (stderr, "SQL error: %s\n", err_msg);
        sqlite3_free (err_msg);
      }

    return;
}

/* User clicked the "Clear List" button. */
static void
button_clear_clicked( gpointer data )
{
    GtkTreeModel  *model; 

    /* Get reference to underlying GtkTreeModel so we can clear it. */
    model = gtk_tree_view_get_model (GTK_TREE_VIEW (data));

    /* Clear the list using gtk_list_store_clear. This is much faster than
     * calling gtk_list_store_remove once for each row. */
    gtk_list_store_clear( GTK_LIST_STORE (model));

    return;
}

/* This callback quits the program */
static gboolean
delete_event( GtkWidget *widget,
              GdkEvent  *event,
              gpointer   data )
{
    /* Close the database connection. */
    sqlite3_close (db);

    /* Terminate the main program loop. */
    gtk_main_quit ();

    return FALSE;
}

/* Creates a list view component consisting of two columns. The first column
 * is for a persons name, the second for their age. */
static GtkWidget *
create_list_view( void )
{
    GtkCellRenderer        *renderer;
    GtkTreeViewColumn	 *column;
    GtkListStore             *store;
    GtkTreeView             *view;

    view = GTK_TREE_VIEW (gtk_tree_view_new ());

    /* --- Column #1 --- */
    renderer = gtk_cell_renderer_text_new ();
    column = gtk_tree_view_column_new_with_attributes ("Name", renderer,
            "text", LIST_COL_NAME,
			NULL);
    gtk_tree_view_column_set_resizable (column, TRUE);
    gtk_tree_view_insert_column (view, column, -1);

    /* --- Column #2 --- */
    renderer = gtk_cell_renderer_text_new ();
    column = gtk_tree_view_column_new_with_attributes ("Age", renderer,
            "text", LIST_COL_AGE,
            NULL);
    gtk_tree_view_insert_column (view, column, -1);

    store = gtk_list_store_new (LIST_NUM_COLS, G_TYPE_STRING, G_TYPE_STRING);
    gtk_tree_view_set_model (view, GTK_TREE_MODEL (store));

    return GTK_WIDGET (view);
}

/* Initialises the application's database connection. */
static void
init_db( void )
{
    gint rc;

    /* Open the database to initialise the connection object. */
    rc = sqlite3_open (DB_FILENAME, &db);
    if (rc)
      {
        fprintf (stderr, "Can't open database: %s\n", sqlite3_errmsg (db));
        sqlite3_close (db);
        exit(1);
      }
}

/* Initialises the application's user interface. */
static void
init_ui( void )
{
    GtkWidget *window;
    GtkWidget *vbox, *hbox;
    GtkWidget *scrolled_window;
    GtkWidget *list_view;
    GtkWidget *button_load, *button_clear;

    /* Create the main application window */
    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
    gtk_widget_set_size_request (GTK_WIDGET (window), 300, 150);
    gtk_window_set_title (GTK_WINDOW (window), "GTK/SQLite DB Example");

    /* Configure app to terminate when closed */
    g_signal_connect (G_OBJECT (window), "delete_event",
			G_CALLBACK (delete_event), NULL);

    vbox = gtk_vbox_new (FALSE, 5);
    gtk_container_set_border_width (GTK_CONTAINER (vbox), 5);
    gtk_container_add (GTK_CONTAINER (window), vbox);
    gtk_widget_show (vbox);

    /* Create a scrolled window to pack the CList widget into */
    scrolled_window = gtk_scrolled_window_new (NULL, NULL);
    gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
    		GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);

    gtk_box_pack_start (GTK_BOX (vbox), scrolled_window, TRUE, TRUE, 0);
    gtk_widget_show (scrolled_window);

    /* Create the list view which will display the data. */
    list_view = create_list_view ();

    /* Add the list view widget to the vertical box and show it. */
    gtk_container_add (GTK_CONTAINER (scrolled_window), list_view);
    gtk_widget_show (list_view);

    /* Create a horizontal box to hold our buttons */
    hbox = gtk_hbox_new (FALSE, 0);
    gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, TRUE, 0);
    gtk_widget_show (hbox);

    /* Create the buttons and add them to the window. */
    button_load = gtk_button_new_with_label ("Load List");
    button_clear = gtk_button_new_with_label ("Clear List");

    gtk_box_pack_start (GTK_BOX (hbox), button_load, TRUE, TRUE, 0);
    gtk_box_pack_start (GTK_BOX (hbox), button_clear, TRUE, TRUE, 0);

    /* Connect our callbacks to the three buttons */
    g_signal_connect_swapped (G_OBJECT (button_load), "clicked",
    		G_CALLBACK (button_load_clicked), list_view);
    g_signal_connect_swapped (G_OBJECT (button_clear), "clicked",
            G_CALLBACK (button_clear_clicked), list_view);

    gtk_widget_show (button_load);
    gtk_widget_show (button_clear);

	/* The interface is completely set up so we show the window. */
    gtk_widget_show (window);

    return;
}

int
main( gint   argc,
      gchar *argv[] )
{
    /* Before we even think about doing anything we must initialise GTK */
    gtk_init (&argc, &argv);

    /* Initialise the database connection */
    init_db ();

    /* Initialise the user interface */
    init_ui ();

    /* Now that everything's ready, start the main program loop. */
    gtk_main ();

    return 0;
}

Not bad for 236 lines of code huh? Feel free to use the code in any way you like. You’ll have to create a new database using the sqlite command-line utility first before running it though:

$ sqlite3 temp.db
SQLite version 3.5.9
Enter ".help" for instructions
sqlite> create table person (name varchar(30), age smallint);
sqlite> insert into person values ("Phillip J. Fry", 33);
sqlite> insert into person values ("Hubert J. Farnsworth", 167);
sqlite> insert into person values ("Zapp Brannigan", 45);
then type CTRL-D to exit

As far as I’m concerned writing this thing in C has a few compelling advantages:

  1. It’s portable. C is arguably the most portable (not to mention widely used) language in the world. If binaries aren’t available for your processor architecture, you can always compile it especially to suit that Z80 you’ve had sitting in your basement – just because you can.
  2. No special libraries or runtimes required here. Just run it baby.
  3. Glib, GTK+ again provide 95% of what I need for the app out of the box. Not to mention that the documentation is awesome and free. They’re cross-platform too!
  4. Working with relational databases is a snack. No OO/RDBMS disparity here, it’s all smooth sailing.
  5. It’s really, really fast. There’s no VM here, just bare-metal machine code.
  6. Like Java, there’s heaps of free libraries and tools out there to take advantage of.

Of course using something as raw as C/GTK+ isn’t without its hangups:

  1. Memory management and pointers frighten the bejesus out of many programmers. I don’t mind them so much, but I do understand that some have a pathological aversion to them.
  2. Cross-compilation isn’t exactly what I’d call straight forward.
  3. If you’ve been spoilt, like I have, working in 4th gen languages with awesome support for strings  NULL terminated character arrays are a rude shock. Don’t get me wrong, it’s not difficult, it’s just one extra thing to keep in mind.
  4. Very little in the way of syntactic sugar.
  5. GTK is still yet to be ported to Mac OSX. This is potentially a dealbreaker.

One thing in particular that I found was very nice was the GTK+ “Object Oriented” style interfaces which allow for a form of inheritance within a standard C program. This is a massive benefit because UI programming can get quite repetitive if you’re unable to extend and modify components simply by subclassing them.

Well that’s about all I’ve got for now. I’d be really interested in hearing what you think on the matter of technology choice, especially if you’re looking to contribute in future.

The Wisdom of Juan

August 21, 2008

Over the past few weeks I’ve been blessed with the opportunity to work with an awesome guy by the name of Juan Ruiz of Hyro. He’s an exceedingly wise man and has taught me a lot about his trade as a user experience architect. Amongst the many gems that he’s enlightened me with lately, 3 in particular have stood out:

#1

“When presenting/unveiling new designs for review never create an atmosphere that suggests the design is final. Make a point that it is unfinished and invite suggestion. This way rather than criticise, your audience is more likely to buy in and take ownership of the design and thus aid in improving it”

#2

“When taking notes in a meeting always confine them to or collate them into a single list. This way you can succinctly wrap up the meeting with a clear summary of what was discussed and perform a final check that everyone is clear on any decisions made. It also has the added bonus of making the people in the room feel like they were truly heard.”

#3

“You can more easily convince a person of your argument and debunk theirs through the asking of questions rather than simply stating your point of view. Questions have the ability to expose the holes in ones arguments without mounting a direct assault upon the person. Carefully chosen questions will allow the opposing party to convince themselves of the soundness of your own position without causing them to lose face. Furthermore, a person convinced of themselves will thereafter champion your cause their own, as you have created the illusion that they arrived at that conclusion of their own violition.”

    Follow

    Get every new post delivered to your Inbox.