+typedef enum {
+ SCAN_IDLE,
+ SCAN_LINES,
+ SCAN_LINES_DONE,
+ SCAN_KEYS,
+ SCAN_KEYS_DONE
+} ScanTimerState;
+
+typedef struct {
+ ScanTimerState timer_state;
+ gint scan_row;
+ gint scan_column;
+} ScanState;
+
+GdkColor *
+button_default_bg_color (GtkButton *button)
+{
+ static GdkColor bg_color;
+ static gboolean initialized = FALSE;
+ if (!initialized)
+ {
+ bg_color = gtk_widget_get_style (GTK_WIDGET (button))->bg[GTK_STATE_NORMAL];
+ initialized = TRUE;
+ }
+ return &bg_color;
+}
+
+GdkColor *
+button_default_selected_color (GtkButton *button)
+{
+ static GdkColor selected_color;
+ static gboolean initialized = FALSE;
+ if (!initialized)
+ {
+ selected_color = gtk_widget_get_style (GTK_WIDGET (button))->bg[GTK_STATE_SELECTED];
+ initialized = TRUE;
+ }
+ return &selected_color;
+}
+
+void
+select_key (gint lineno, gint keyno)
+{
+ /*
+ * Before we do this, we need to make sure we've saved the default normal bg.
+ * The button_default_bg_color() call caches this for us (as a side-effect).
+ * Probably we should do this a cleaner way...
+ */
+ button_default_bg_color (buttons [lineno][keyno]);
+ gtk_widget_modify_bg (GTK_WIDGET (buttons [lineno][keyno]),
+ GTK_STATE_NORMAL,
+ button_default_selected_color (buttons [lineno][keyno]));
+}
+
+void
+deselect_key (gint lineno, gint keyno)
+{
+ gtk_widget_modify_bg (GTK_WIDGET (buttons [lineno][keyno]),
+ GTK_STATE_NORMAL,
+ button_default_bg_color (buttons [lineno][keyno]));
+}
+
+void
+deselect_line (gint lineno)
+{
+ int i;
+ int max_columns = MAX_COLUMNS;
+ if (lineno == MAX_ROWS-1) max_columns = 2;
+ for (i=0; i<max_columns; ++i)
+ deselect_key (lineno, i);
+}
+
+void
+select_line (gint lineno)
+{
+ int i;
+ int max_columns = MAX_COLUMNS;
+ if (lineno == MAX_ROWS-1) max_columns = 2;
+ for (i=0; i<max_columns; ++i)
+ select_key (lineno, i);
+}
+
+
+static ScanState*
+scan_state ()
+{
+ static ScanState state = {SCAN_IDLE, 0, 0};
+ return &state;
+}
+
+static gboolean
+timeout_scan (gpointer data)
+{
+ ScanState *state = (ScanState *) data;
+ state->timer_state = SCAN_IDLE;
+ deselect_key (state->scan_row, state->scan_column);
+ return FALSE;
+}
+
+static gboolean
+increment_scan (gpointer data)
+{
+ ScanState *state = (ScanState *) data;
+ int max_columns;
+ switch (state->timer_state)
+ {
+ case SCAN_IDLE:
+/* happens if switch-break occurs before the timer fires, after SCAN_KEYS_DONE*/
+ return FALSE;
+ case SCAN_LINES:
+ deselect_line (state->scan_row);
+ state->scan_row = (++state->scan_row < MAX_ROWS) ? state->scan_row : 0;
+ select_line (state->scan_row);
+ g_print ("line %d\n", state->scan_row);
+ break;
+ case SCAN_KEYS:
+ deselect_key (state->scan_row, state->scan_column);
+ if (state->scan_row == MAX_ROWS-1) max_columns = 2;
+ else max_columns = MAX_COLUMNS; /* last row has only two keys */
+ state->scan_column = (++state->scan_column < max_columns) ? state->scan_column : 0;
+ select_key (state->scan_row, state->scan_column);
+ g_print ("row %d\n", state->scan_column);
+ break;
+ case SCAN_LINES_DONE:
+ case SCAN_KEYS_DONE:
+ return FALSE;
+ default:
+ }
+ return TRUE;
+}
+
+static void
+scan_start (unsigned int timestamp)
+{
+ ScanState *state = scan_state();
+ switch (state->timer_state)
+ {
+ case SCAN_IDLE:
+ state->timer_state = SCAN_LINES;
+ state->scan_column = 0;
+ state->scan_row = 0;
+ g_timeout_add_full (G_PRIORITY_HIGH_IDLE, 600, increment_scan, state, NULL);
+ select_line (state->scan_row);
+ break;
+ case SCAN_LINES_DONE:
+ state->timer_state = SCAN_KEYS;
+ g_timeout_add_full (G_PRIORITY_HIGH_IDLE, 600, increment_scan, state, NULL);
+ deselect_line (state->scan_row);
+ select_key (state->scan_row, state->scan_column);
+ break;
+ case SCAN_KEYS_DONE:
+ gtk_button_clicked (buttons[state->scan_row][state->scan_column]);
+ deselect_key (state->scan_row, state->scan_column);
+ state->timer_state = SCAN_IDLE;
+ break;
+ default:
+ g_print("unexpected state for 'scan start'\n");
+ }
+}
+
+static void
+scan_stop (unsigned int timestamp)
+{
+ /* find the element correspondin to this event's timestamp */
+ ScanState *state = scan_state();
+ switch (state->timer_state)
+ {
+ case SCAN_LINES:
+ state->timer_state = SCAN_LINES_DONE;
+ break;
+ case SCAN_KEYS:
+ state->timer_state = SCAN_KEYS_DONE;
+ g_timeout_add_full (G_PRIORITY_HIGH_IDLE, 1200, timeout_scan, state, NULL);
+ break;
+ case SCAN_IDLE:
+ break;
+ default:
+ g_print("unexpected state for 'scan stop'\n");
+ }
+}
+