Hi all,
after reading many topics here, which have given me plenty of inspirations how to write software in C using GTK+ for GUI, I've started to write a demo for an excel/calc like sheet, that I need for a bigger application.
What I need is a sheet with row- and col-headers, where users can type in numerical data just by selecting a cell with mouse button or cursor keys and then typing in data without a need of some extra buttons or keys (blank, return, etc.). Just select a cell, type in a number (float), press return and selection goes automatically to the next "logical" cell (after last cell in row, cursor should jump to first "editable" cell in next row)
Please find attached the code of my demo, which isn't quite finished (cell navigation and highlight with cursor keys works, but with mouse button I've got a problem getting the right col number; data validation (float) also missing), but hopefully explains a little more, what I've in mind.
But Jesus - isn't there an easier way to achieve what I want?
Code:
#include <gtk/gtk.h>
enum {
COL_CHARS, COL_MIN, COL_NOM, COL_MAX,
NUMCOLS
};
/* define some row and column headers */
static char *ColHeader[] = {
"Characteristic", "min", "nom", "max"
};
#define NaN "******.***"
#define NUMROWHEADER 7
static char *RowHeader[] = {
"Voltage", "Current", "Power", "Resistance", "Length", "Width", "Lifetime"
};
/* treeview's cursor_changed event */
void view_cursor_changed_cb (GtkTreeView *view, GtkListStore *model)
{
static gint col = -1;
static GtkTreeIter iter;
GtkTreePath *path;
GtkTreeViewColumn *column;
GList *renderers;
if (col > -1)
gtk_list_store_set(model, &iter, col + NUMCOLS, FALSE, -1);
gtk_tree_view_get_cursor(view, &path, &column);
if (column) {
renderers = gtk_tree_view_column_get_cell_renderers(column);
col = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(renderers->data), "column"));
gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &iter, path);
gtk_list_store_set(model, &iter, col + NUMCOLS, TRUE, -1);
}
}
/* treeview's key_pressed event */
gboolean view_key_pressed_cb (GtkWidget *widget, GdkEventKey *event, gpointer user_data)
{
GtkTreePath *path;
GtkTreeViewColumn *column;
// check if keyval = numkey, comma, plus, minus, etc. from keyboard or keypad (should use GDK_KeySyms instead)
if ((event->keyval >= 0x02b && event->keyval <= 0x039) || (event->keyval >= 0xffac && event->keyval <= 0xffb9)) {
// get cell coordinates, set cursor to cell, start editing and put keyval via event queue into cell
gtk_tree_view_get_cursor(GTK_TREE_VIEW(widget), &path, &column);
gtk_tree_view_set_cursor(GTK_TREE_VIEW(widget), path, column, TRUE);
gdk_event_put((GdkEvent *) event);
}
// keyval = return or enter
else if (event->keyval == 0xff0d || event->keyval == 0xff8d) {
// TODO: skip edit instead jump to next logical cell
;
}
return FALSE;
}
/* treeview's button_pressed event */
gboolean view_button_pressed_cb (GtkWidget *widget, GdkEventButton *event, gpointer user_data)
{
GtkTreePath *path;
GtkTreeViewColumn *column;
gint x, y;
gint *cell_x = NULL, *cell_y = NULL;
path = gtk_tree_path_new();
column = gtk_tree_view_column_new();
x = event->x;
y = event->y;
gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), x, y, &path, &column, cell_x, cell_y);
gtk_tree_view_set_cursor(GTK_TREE_VIEW(widget), path, column, FALSE);
return TRUE;
}
/* get logical next cell */
void next_cell (GtkTreeModel *model, GtkTreeView *view, GtkTreeIter iter, gint colno)
{
GtkTreePath *path;
GtkTreeViewColumn *column;
if (colno < NUMCOLS - 1)
column = gtk_tree_view_get_column(view, ++colno);
else {
column = gtk_tree_view_get_column(view, COL_MIN);
if (!gtk_tree_model_iter_next(model, &iter))
gtk_tree_model_get_iter_first(model, &iter);
}
path = gtk_tree_model_get_path(model, &iter);
gtk_tree_view_set_cursor(view, path, column, FALSE);
view_cursor_changed_cb(view, GTK_LIST_STORE(model));
}
/* update cell in model and jump to next cell */
void cell_edited_cb (GtkCellRendererText *cell, gchar *pathtext, gchar *newtext, GtkTreeView *view)
{
GtkTreeModel *model;
GtkTreeIter iter;
gint column;
model = gtk_tree_view_get_model(view);
gtk_tree_model_get_iter_from_string(model, &iter, pathtext);
column = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(cell), "column"));
gtk_list_store_set(GTK_LIST_STORE(model), &iter, column, newtext, -1);
next_cell(model, view, iter, column);
}
GtkWidget *create_view(void)
{
int idx;
GtkWidget *view;
GtkTreeSelection *selection;
GtkListStore *model;
GtkTreeIter iter;
GtkCellRenderer *cell;
model = gtk_list_store_new(NUMCOLS * 2,
G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
G_TYPE_BOOLEAN, G_TYPE_BOOLEAN, G_TYPE_BOOLEAN, G_TYPE_BOOLEAN);
view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
gtk_tree_view_set_enable_search(GTK_TREE_VIEW(view), FALSE);
g_signal_connect(view, "key-press-event", G_CALLBACK(view_key_pressed_cb), NULL);
g_signal_connect(view, "button-press-event", G_CALLBACK(view_button_pressed_cb), NULL);
g_signal_connect(view, "cursor-changed", G_CALLBACK(view_cursor_changed_cb), (gpointer) model);
g_object_unref(model);
selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
gtk_tree_selection_set_mode(selection, GTK_SELECTION_NONE);
for (idx = 0; idx < NUMCOLS; idx++) {
cell = gtk_cell_renderer_text_new();
g_object_set(cell, "background", "yellow", NULL);
g_object_set_data(G_OBJECT(cell), "column", GINT_TO_POINTER(idx));
if (idx > 0) {
g_object_set(cell, "editable", TRUE, "xalign", 1.0, "align-set", TRUE, NULL);
g_object_set_data(G_OBJECT(cell), "column", GINT_TO_POINTER(idx));
g_signal_connect(cell, "edited", G_CALLBACK(cell_edited_cb), (gpointer) view);
}
gtk_tree_view_insert_column_with_attributes
(GTK_TREE_VIEW(view), -1,
ColHeader[idx], cell,
"text", idx,
"background-set", idx + NUMCOLS,
NULL);
}
/* add some data */
for (idx = 0; idx < NUMROWHEADER; idx++) {
gtk_list_store_append(model, &iter);
gtk_list_store_set(model, &iter, 0, RowHeader[idx], 1, NaN, 2, NaN, 3, NaN, -1);
}
return view;
}
gint main (gint argc, gchar **argv)
{
GtkWidget *win;
GtkWidget *view;
gtk_init(&argc, &argv);
win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
g_signal_connect(win, "delete-event", G_CALLBACK(gtk_main_quit), NULL);
view = create_view();
gtk_container_add(GTK_CONTAINER(win), view);
gtk_widget_show(view);
gtk_widget_show(win);
gtk_main();
return 0;
}
I hope, someone could guide me to "simplify" my demo a little bit ;-)
greetings
Rufus