Gtk, Glade, and gPhoto Steep Learning Curve

In my quest to build a Linux replacment for DSLRFocus, I’m hitting a steep learning curve with Glade, Gtk, and gPhoto. I’m making progress, but it is slow. I’ve also come to realize that sometimes "best practices" can get in the way. What do I mean?

Well, in the gPhoto library, a Camera object is hidden with the typical C-style opaque typedef. So you can’t see inside it. You’re not supposed to see inside it, that’s private information and you use the accessors. That’s okay for the most part, but sometimes I’d kind of like to be able to see the internals in the debugger simply because the API documentation isn’t always clear. No can do. In a way, that’s a good thing, since I really don’t want to code my application to take advantage of the implementation, but it does mean I have to ask a lot of stupid questions on the development list. And its a low-traffic list so I don’t have a lot of people to ask 🙁

Gtk has its own learning curve. Something as simple as displaying a list of items to let someone choose is not hard. In my case, it was getting gPhoto to detected cameras attached to the computer and put those in a list. You can choose any number to connect to and I keep track. So when you decide you’d like to change which ones are connected, I need to display the changed status.

That’s what a GtkTreeModel is for, something I really didn’t understand. Your model is basically a list of rows, but the row can contain information about the row, not just the data you want to display. So you can insert a column that includes the color information for a row, or whether or not the row should even be displayed. I’ve incorporated my new-found knowledge into my application, but so I don’t lose the original simple example, I’m stuffing it in here.

 /*
* Copyright © 2008, Roland Roberts
*
*/

#include <stdio.h>
#include <string.h>
#include <gtk/gtk.h>
#include <glade/glade.h>

/* Global GladeXML struct for use by anyone needing to look up a UI
element. */
GladeXML *xml;

enum _state {IS_UNKNOWN, IS_CONNECTED, IS_NOT_CONNECTED, IS_NEWLY_DETECTED };
typedef enum _state state;
char *_colormap[] = { "red", "#008000", "blue", "black" };

struct _data {
const char *name;
const char *port;
state connected;
};

struct _data data[] =
{ { "RoloCam 2000", "hyper:001,007", IS_CONNECTED },
{ "AstroCam 100", "air:27,31", IS_NOT_CONNECTED },
{ "NoCam 2020", "pipe:/smoke/0", IS_UNKNOWN },
{ "NoCam 2021", "pipe:/smoke/1", IS_UNKNOWN },
{ "RoloCam 2001", "hyper:001,007", IS_NEWLY_DETECTED },
{ "AstroCam 101", "air:27,31", IS_NOT_CONNECTED },
{ "NoCam 3020", "pipe:/smoke/2", IS_UNKNOWN },
{ "NoCam 3021", "pipe:/smoke/3", IS_NEWLY_DETECTED },
{ 0, 0, 0 },
};

enum _model_columns { NAME_COL, PORT_COL, STATE_COL, COLOR_COL, NUM_COLS };

void
main_window_destroy_cb(GtkObject *object, gpointer user_data)
{
gtk_main_quit();
}

void
main_menubar_quit_activate_cb(GtkObject *object, gpointer user_data)
{
gtk_main_quit();
}

int
main(int argc, char *argv[])
{
GtkWidget *window;
GtkTreeView *treeview;
GtkTreeSelection *selection;
GtkCellRenderer *renderer;
GtkListStore *store;
struct _data *row;
int i;

gtk_init(&argc, &argv);

xml = glade_xml_new("dummy.glade", NULL, NULL);
window = GTK_WIDGET(glade_xml_get_widget(xml, "window1"));
glade_xml_signal_autoconnect(xml);

treeview = GTK_TREE_VIEW(glade_xml_get_widget(xml, "treeview1"));
selection = gtk_tree_view_get_selection(treeview);
gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);

/* Only need one generic renderer for our list. */
renderer = gtk_cell_renderer_text_new();
gtk_tree_view_insert_column_with_attributes(treeview, -1, "Name", renderer,
"text", NAME_COL, "foreground", COLOR_COL, NULL);
gtk_tree_view_insert_column_with_attributes(treeview, -1, "Port", renderer,
"text", PORT_COL, "foreground", COLOR_COL, NULL);
gtk_tree_view_insert_column_with_attributes(treeview, -1, "State", renderer,
"text", STATE_COL, "foreground", COLOR_COL, NULL);
gtk_tree_view_insert_column_with_attributes(treeview, -1, "Color", renderer,
"text", COLOR_COL, "foreground", COLOR_COL, NULL);

store = gtk_list_store_new(4, G_TYPE_STRING, G_TYPE_STRING,
G_TYPE_INT, G_TYPE_STRING);
gtk_tree_view_set_model(treeview, GTK_TREE_MODEL(store));

for (i = 0; data[i].name; i++) {
GtkTreeIter iter;
struct _data *row = &data[i];
gtk_list_store_append(store, &iter);
gtk_list_store_set(store, &iter,
NAME_COL, row->name,
PORT_COL, row->port,
STATE_COL, row->connected,
COLOR_COL, _colormap[row->connected],
-1);
printf("inserted: %-15s %-15s %2d %s\n",
row->name, row->port, row->connected, _colormap[row->connected]);
}

gtk_widget_show(window);
gtk_main ();

return 0;
}

To make this work, you will need to create a "dummy.glade" file which has a top-level window named "window1" and someplace in it a GtkTreeView named "treeview1". Then you should be able to compile like this:

gcc -o dummy `pkg-config --cflags --libs libglade-2.0` -rdynamic dummy.c && ./dummy  

assuming you named the application dummy.c.

I owe thanks to Damien Caliste from the gtk-app-devel mailing list for pointing me to an old post of his to get me started on this.