gnome-games r7157 - trunk/aisleriot
- From: chpe svn gnome org
- To: svn-commits-list gnome org
- Subject: gnome-games r7157 - trunk/aisleriot
- Date: Sun, 13 Jan 2008 22:07:06 +0000 (GMT)
Author: chpe
Date: Sun Jan 13 22:07:06 2008
New Revision: 7157
URL: http://svn.gnome.org/viewvc/gnome-games?rev=7157&view=rev
Log:
First stab at implementing keynav.
Modified:
trunk/aisleriot/board.c
trunk/aisleriot/board.h
Modified: trunk/aisleriot/board.c
==============================================================================
--- trunk/aisleriot/board.c (original)
+++ trunk/aisleriot/board.c Sun Jan 13 22:07:06 2008
@@ -1,6 +1,9 @@
/*
* Copyright  1998, 2003 Jonathan Blandford <jrb mit edu>
- * Copyright  2007 Christian Persch
+ * Copyright  2007, 2008 Christian Persch
+ *
+ * Some code copied from gtk+/gtk/gtkiconview:
+ * Copyright  2002, 2004 Anders Carlsson <andersca gnu org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,12 +22,16 @@
#include "config.h"
+#define ENABLE_KEYNAV
+
#include <string.h>
#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
#include <libgames-support/games-card-images.h>
#include <libgames-support/games-files.h>
+#include <libgames-support/games-marshal.h>
#include <libgames-support/games-pixbuf-utils.h>
#include <libgames-support/games-sound.h>
@@ -66,6 +73,12 @@
#define PIXBUF_DRAWING_LIKELIHOOD(cond) (cond)
#endif /* HAVE_HILDON */
+#if GLIB_CHECK_VERSION (2, 10, 0)
+#define I_(string) g_intern_static_string (string)
+#else
+#define I_(string) string
+#endif
+
typedef enum {
CURSOR_DEFAULT,
CURSOR_OPEN,
@@ -184,6 +197,16 @@
PROP_THEME
};
+enum
+{
+ ACTIVATE,
+ MOVE_CURSOR,
+ TOGGLE_SELECTION,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
static void get_slot_and_card_from_point (AisleriotBoard *board,
int x,
int y,
@@ -444,6 +467,17 @@
*_cardid = cardid > 0 ? cardid - 1 : -1;
}
+#if 0
+static Slot *
+get_slot_from_slot_and_direction (AisleriotBoard *board,
+ Slot *origin_slot,
+ GtkDirectionType direction)
+{
+// XXX FIXME;
+ return origin_slot;
+}
+#endif
+
static void
get_rect_by_slot_and_card (AisleriotBoard *board,
Slot *slot,
@@ -457,7 +491,7 @@
g_return_if_fail (slot != NULL && card_id >= -1);
- first_card_id = slot->cards->len - slot->exposed;
+ first_card_id = ((int) slot->cards->len) - ((int) slot->exposed);
if (card_id >= first_card_id) {
delta = card_id - first_card_id;
@@ -498,6 +532,7 @@
rect->height = rect->height + 2 * delta;
}
+#define PRINT_RECT(rect)g_print("Rect %s is %d x %d at (%d, %d)\n", #rect, rect->width, rect->height, rect->x, rect->y);
static void
get_focus_rect (AisleriotBoard *board,
GdkRectangle *rect)
@@ -511,6 +546,8 @@
priv->focus_card_id,
1, rect);
widen_rect (rect, 2 * priv->focus_line_width);
+
+ PRINT_RECT (rect);
}
static void
@@ -523,16 +560,23 @@
GtkWidget *widget = GTK_WIDGET (board);
int top_card_id;
+ g_print ("set-focus slot %d card %d \n", slot ? slot->id : -1, card_id);
+ show_focus = TRUE;
+
/* Sanitise */
top_card_id = slot ? ((int) slot->cards->len) - 1 : -1;
- card_id = MAX (card_id, top_card_id);
+ card_id = MIN (card_id, top_card_id);
+ g_print ("top-card-id %d card-id %d\n", top_card_id, card_id);
if (priv->focus_slot == slot &&
priv->focus_card_id == card_id)
return;
+ // FIXMEchpe take GTK_WIDGET_HAS_FOCUS into account before invalidating!
+
if (priv->focus_slot != NULL) {
if (priv->show_focus) {
+ g_print ("invalidating old focus rect\n");
gdk_window_invalidate_rect (widget->window, &priv->focus_rect, FALSE);
priv->show_focus = FALSE;
@@ -552,6 +596,7 @@
priv->show_focus = show_focus;
if (show_focus) {
+ g_print ("invalidating new focus rect\n");
get_focus_rect (board, &priv->focus_rect);
gdk_window_invalidate_rect (widget->window, &priv->focus_rect, FALSE);
}
@@ -574,7 +619,7 @@
priv->selection_slot,
priv->selection_start_card_id,
n_cards, rect);
- widen_rect (rect, 4 * priv->focus_line_width);
+ widen_rect (rect, 4 * priv->focus_line_width); // FIXMEchpe why?
}
static void
@@ -617,6 +662,7 @@
}
}
+#if 0
/* If a prefix of @cards is on top of @slot, set the selection to that prefix;
* else set selection to NULL.
*/
@@ -646,6 +692,7 @@
set_selection (board, NULL, -1);
}
+#endif
/* Slot functions */
@@ -685,7 +732,7 @@
/* We need to make sure the cards fit within the board, even
* when there are many of them. See bug #171417.
*/
- /* FIXMEchpe: check slot->exposed instead of cards->len? */
+ /* FIXMEchpe: check |cards->len - slot->exposed| instead of cards->len? */
pixeldx = 0;
if (cards->len > 1) {
double dx = 0, dy = 0;
@@ -843,6 +890,14 @@
/* helper functions */
+static void
+aisleriot_board_error_bell (AisleriotBoard *board)
+{
+#if GTK_CHECK_VERSION (2, 12, 0)
+ gtk_widget_error_bell (GTK_WIDGET (board));
+#endif
+}
+
/* Work out new sizes and spacings for the cards. */
static void
aisleriot_board_setup_geometry (AisleriotBoard *board)
@@ -1079,7 +1134,7 @@
slot_update_geometry (board, hslot);
slot_update_card_images (board, hslot);
-
+
set_cursor (board, CURSOR_CLOSED);
}
@@ -1319,6 +1374,428 @@
priv->last_clicked_card_id = -1;
}
+/* Keynav */
+
+#ifdef ENABLE_KEYNAV
+
+/** Keynav specification:
+ *
+ * Focus state consists of the slot that has the focus, and the card ID
+ * in that slot of the card that has the focus; -1 if the slot has no
+ * cards on it and is thus itself focused.
+ *
+ * Without modifiers, for focus movement:
+ * Left, Right: for right-extended slots, moves the focus to the card
+ * under/over the currently focused card on the same slot.
+ * Otherwise, or if the focused card is already the bottommost/topmost
+ * card of the slot, moves the focus to the topmost/bottommost card
+ * on the slot left/right to the currently focused slot
+ * Up, Down: for down-extended slots, moves the focus to the card
+ * under/over the currently focused card on the same slot.
+ * Otherwise, or if the focused card is already the bottommost/topmost
+ * card of the slot, moves the focus to the topmost/bottommost card
+ * on the slot over/under the currently focused slot
+ * Home, End: for down- or right-extended slots, moves the focus to the
+ * topmost/bottommost card of the slot
+ * PageUp, PageDown: act like <control>Up, <control>Down
+ * Space: selects the cards from the currently focused card to the topmost
+ * card of the slot, if this is allowed by the game
+ * Return: performs button press on the focused card. If no action was
+ * performed by this:
+ * If not in move mode, picks up the selected cards, enters move mode.
+ * If already in move mode, puts down the selected cards on top of
+ * the focused slot, if this is allowed by the game, and exits move mode
+ *
+ * With <control>, for focus movement:
+ * Left, Right: moves the focus to the topmost/bottommost card on the
+ * slot; if that card is already focused, moves the focus to the
+ * topmost/bottommost card on the slot left/right to the currently
+ * focused slot
+ * Up, Down: moves the focus to the topmost/bottommost card on the
+ * slot; if that card is already focused, moves the focus to the
+ * topmost/bottommost card on the slot over/under the currently
+ * focused slot
+ * Home, End: moves the focus to the bottommost/topmost card on the
+ * first/last slot
+ * Return: performs double-click on the focused card
+ *
+ * With <shift>: extends the selection; focus movement itself occurs
+ * like for the same key without modifiers.
+ * Left, Right: for right-extended slots, shrinks/extends the selection
+ * by one card, if this is allowed by the game
+ * Up, Down: for down-extended slots, extends/shrinks the selection
+ * by one card, if this is allowed by the game
+ * Home, End: for down- or right-extended slots, selects the topmost
+ * card/all cards on the slot, if this is allowed by the game
+ *
+ * With <control><shift>: extends selection like with <shift> alone,
+ * and moves focus like with <control> alone
+ *
+ *
+ * Notes:
+ * - if no slot is currently focused, any cursor moves set the focus to the
+ * topmost card on the first slot
+ */
+
+static void
+aisleriot_board_add_move_binding (GtkBindingSet *binding_set,
+ guint keyval,
+ guint modmask,
+ GtkMovementStep step,
+ gint count)
+{
+ gtk_binding_entry_add_signal (binding_set, keyval,
+ modmask,
+ "move-cursor", 2,
+ G_TYPE_ENUM, step,
+ G_TYPE_INT, count);
+
+ if (modmask & GDK_CONTROL_MASK)
+ return;
+
+ gtk_binding_entry_add_signal (binding_set, keyval,
+ modmask | GDK_CONTROL_MASK,
+ "move-cursor", 2,
+ G_TYPE_ENUM, step,
+ G_TYPE_INT, count);
+
+}
+
+static void
+aisleriot_board_add_move_and_select_binding (GtkBindingSet *binding_set,
+ guint keyval,
+ guint modmask,
+ GtkMovementStep step,
+ gint count)
+{
+ aisleriot_board_add_move_binding (binding_set, keyval, modmask, step, count);
+ aisleriot_board_add_move_binding (binding_set, keyval, modmask | GDK_SHIFT_MASK, step, count);
+}
+
+static gboolean
+aisleriot_board_move_cursor_in_slot (AisleriotBoard *board,
+ int count)
+{
+ AisleriotBoardPrivate *priv = board->priv;
+ Slot *focus_slot;
+ int new_focus_card_id, first_card_id;
+
+ focus_slot = priv->focus_slot;
+ first_card_id = ((int) focus_slot->cards->len) - ((int) focus_slot->exposed);
+ new_focus_card_id = priv->focus_card_id + count;
+ if (new_focus_card_id < first_card_id || new_focus_card_id >= (int) focus_slot->cards->len)
+ return FALSE;
+
+ set_focus (board, focus_slot, new_focus_card_id, TRUE);
+ return TRUE;
+}
+
+static gboolean
+aisleriot_board_move_cursor_start_end_in_slot (AisleriotBoard *board,
+ int count)
+{
+ AisleriotBoardPrivate *priv = board->priv;
+ Slot *focus_slot;
+ int new_focus_card_id;
+
+ g_print ("start-end-in-slot %d\n", count);
+
+ focus_slot = priv->focus_slot;
+ if (count > 0) {
+ new_focus_card_id = ((int) focus_slot->cards->len) - 1;
+ } else {
+ if (focus_slot->cards->len > 0) {
+ new_focus_card_id = ((int) focus_slot->cards->len) - ((int) focus_slot->exposed);
+ } else {
+ new_focus_card_id = -1;
+ }
+ }
+
+ g_assert (new_focus_card_id >= -1);
+
+ /* FIXMEchpe: maybe just eat it up silently? */
+ if (new_focus_card_id == priv->focus_card_id)
+ return FALSE;
+
+ set_focus (board, focus_slot, new_focus_card_id, TRUE);
+ return TRUE;
+}
+
+static gboolean
+aisleriot_board_extend_selection_in_slot (AisleriotBoard *board,
+ int count)
+{
+ AisleriotBoardPrivate *priv = board->priv;
+ Slot *focus_slot, *selection_slot;
+ int new_selection_start_card_id, first_card_id;
+
+ focus_slot = priv->focus_slot;
+ selection_slot = priv->selection_slot;
+ first_card_id = ((int) focus_slot->cards->len) - ((int) focus_slot->exposed);
+
+ if (selection_slot == focus_slot) {
+ new_selection_start_card_id = priv->selection_start_card_id + count;
+
+ /* Can only extend the selection if the focus is adjacent to the selection */
+ if (priv->focus_card_id - 1 > new_selection_start_card_id ||
+ priv->focus_card_id + 1 < new_selection_start_card_id)
+ return FALSE;
+ } else {
+ /* No selection yet */
+ new_selection_start_card_id = ((int) focus_slot->cards->len) + count;
+
+ /* Must have the topmost card focused */
+ if (new_selection_start_card_id != priv->focus_card_id)
+ return FALSE;
+ }
+
+ g_print ("extend-selection-in-slot count %d old-selection-start %d new-selection-start %d focus %d\n",
+ count, priv->selection_start_card_id, new_selection_start_card_id, priv->focus_card_id);
+
+ if (new_selection_start_card_id < first_card_id)
+ return FALSE;
+
+ /* If it's the top card, unselect all */
+ if (new_selection_start_card_id >= focus_slot->cards->len) {
+ set_selection (board, NULL, -1);
+ return TRUE;
+ }
+
+ if (!aisleriot_game_drag_valid (priv->game,
+ focus_slot->id,
+ focus_slot->cards->data + new_selection_start_card_id,
+ focus_slot->cards->len - new_selection_start_card_id))
+ return FALSE;
+
+ set_selection (board, focus_slot, new_selection_start_card_id);
+
+ /* Try to move the cursor too, but don't beep if that fails */
+ aisleriot_board_move_cursor_in_slot (board, count);
+ return TRUE;
+}
+
+/* @direction is in slot coordinates, not visual coordinates */
+static gboolean
+aisleriot_board_move_cursor_by_slot (AisleriotBoard *board,
+ GtkDirectionType direction)
+{
+ AisleriotBoardPrivate *priv = board->priv;
+// GtkWidget *widget = GTK_WIDGET (board);
+ GPtrArray *slots;
+ guint i, n_slots;
+ Slot *focus_slot;
+ guint new_focus_slot_index = 0;
+ Slot *new_focus_slot = NULL;
+ int new_focus_card_id;
+
+ /* Move visually, not logically */
+ if (gtk_widget_get_direction (GTK_WIDGET (board)) == GTK_TEXT_DIR_RTL) {
+ switch (direction) {
+ case GTK_DIR_RIGHT:
+ direction = GTK_DIR_LEFT;
+ break;
+ case GTK_DIR_LEFT:
+ direction = GTK_DIR_RIGHT;
+ break;
+ default:
+ break;
+ }
+ }
+
+ slots = aisleriot_game_get_slots (priv->game);
+ if (!slots || slots->len == 0)
+ return FALSE;
+
+ focus_slot = priv->focus_slot;
+
+ n_slots = slots->len;
+ for (i = 0; i < n_slots; ++i) {
+ if (g_ptr_array_index (slots, i) == focus_slot)
+ break;
+ }
+
+ g_assert (i < n_slots); /* the focus_slot EXISTS after all */
+
+ switch (direction) {
+ case GTK_DIR_RIGHT:
+ if (i < n_slots - 1) {
+ new_focus_slot_index = i + 1;
+ } else {
+ new_focus_slot_index = 0;
+ }
+ break;
+ case GTK_DIR_LEFT:
+ if (i > 0) {
+ new_focus_slot_index = i - 1;
+ } else {
+ new_focus_slot_index = n_slots - 1;
+ }
+ break;
+ case GTK_DIR_UP:
+ case GTK_DIR_DOWN:
+// new_focus_slot_index = find_closest_slot_in_direction
+ default:
+ return FALSE;
+ }
+
+ new_focus_slot = slots->pdata[new_focus_slot_index];
+ new_focus_card_id = ((int) new_focus_slot->cards->len) - 1;
+
+ set_focus (board, new_focus_slot, new_focus_card_id, TRUE);
+ return TRUE;
+}
+
+static gboolean
+aisleriot_board_move_cursor_start_end_by_slot (AisleriotBoard *board,
+ int count)
+{
+ AisleriotBoardPrivate *priv = board->priv;
+ GPtrArray *slots;
+ Slot *new_focus_slot;
+ int new_focus_card_id;
+
+ g_print ("start-end-by-slot %d\n", count);
+
+ slots = aisleriot_game_get_slots (priv->game);
+ if (!slots || slots->len == 0)
+ return FALSE;
+
+ if (count > 0) {
+ new_focus_slot = (Slot *) slots->pdata[slots->len - 1];
+ new_focus_card_id = ((int) new_focus_slot->cards->len) - 1;
+ } else {
+ new_focus_slot = (Slot *) slots->pdata[0];
+ new_focus_card_id = new_focus_slot->cards->len > 0 ? 0 : -1;
+ }
+
+ g_assert (new_focus_slot != NULL);
+ g_assert (new_focus_card_id >= -1);
+
+ set_focus (board, new_focus_slot, new_focus_card_id, TRUE);
+ return TRUE;
+}
+
+static gboolean
+aisleriot_board_move_cursor_left_right (AisleriotBoard *board,
+ int count,
+ gboolean is_control)
+{
+ AisleriotBoardPrivate *priv = board->priv;
+ GtkWidget *widget = GTK_WIDGET (board);
+ gboolean is_rtl;
+
+ g_print ("left-right %d\n", count);
+
+ is_rtl = (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL);
+
+ /* First try in-slot focus movement */
+ if (!is_control &&
+ priv->focus_slot->expanded_right &&
+ aisleriot_board_move_cursor_in_slot (board, is_rtl ? -count : count))
+ return TRUE;
+
+ /* Cannot move in-slot; move focused slot */
+ return aisleriot_board_move_cursor_by_slot (board,
+ count > 0 ? GTK_DIR_RIGHT
+ : GTK_DIR_LEFT);
+}
+
+static gboolean
+aisleriot_board_move_cursor_up_down (AisleriotBoard *board,
+ int count,
+ gboolean is_control)
+{
+ AisleriotBoardPrivate *priv = board->priv;
+ GtkWidget *widget = GTK_WIDGET (board);
+ gboolean is_rtl;
+
+ g_print ("up-down %d\n", count);
+
+ g_assert (priv->focus_slot != NULL);
+
+ is_rtl = (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL);
+
+ /* First try in-slot focus movement */
+ if (!is_control &&
+ priv->focus_slot->expanded_down &&
+ aisleriot_board_move_cursor_in_slot (board, is_rtl ? -count : count))
+ return TRUE;
+
+ /* Cannot move in-slot; move focused slot */
+ return aisleriot_board_move_cursor_by_slot (board,
+ count > 0 ? GTK_DIR_DOWN
+ : GTK_DIR_UP);
+}
+
+static gboolean
+aisleriot_board_extend_selection_left_right (AisleriotBoard *board,
+ int count)
+{
+ AisleriotBoardPrivate *priv = board->priv;
+
+ g_print ("extend-selection-left-right\n");
+ if (!priv->focus_slot->expanded_right)
+ return FALSE;
+
+ return aisleriot_board_extend_selection_in_slot (board, count);
+}
+
+static gboolean
+aisleriot_board_extend_selection_up_down (AisleriotBoard *board,
+ int count)
+{
+ AisleriotBoardPrivate *priv = board->priv;
+
+ g_print ("extend-selection-up-down\n");
+ if (!priv->focus_slot->expanded_down)
+ return FALSE;
+
+ return aisleriot_board_extend_selection_in_slot (board, count);
+}
+
+
+static gboolean
+aisleriot_board_extend_selection_start_end (AisleriotBoard *board,
+ int count)
+{
+ AisleriotBoardPrivate *priv = board->priv;
+
+ g_print ("extend-selection-start-end\n");
+
+ if (count > 0) {
+ set_selection (board, NULL, -1);
+ } else {
+ Slot *focus_slot;
+ int first_card_id;
+ GByteArray *cards;
+// int new_selection_start_card_id;
+
+ focus_slot = priv->focus_slot;
+ cards = focus_slot->cards;
+ first_card_id = ((int) cards->len) - ((int) focus_slot->exposed);
+
+/* if (priv->focus_card_id = ((int) cards->len - 1))
+ new_selection_start_card_id = priv->focus_card_id;
+ aisleriot_game_drag_valid (priv->game,
+ focus_slot->id,
+ focus_slot->cards->data + first_card_id,
+ focus_slot->cards->len - first_card_id)) {*/
+ if (priv->focus_card_id >= first_card_id &&
+ priv->focus_card_id == ((int) focus_slot->cards->len) - 1 &&
+ aisleriot_game_drag_valid (priv->game,
+ focus_slot->id,
+ focus_slot->cards->data + first_card_id,
+ focus_slot->cards->len - first_card_id)) {
+ set_selection (board, focus_slot, first_card_id);
+ }
+ }
+
+ aisleriot_board_move_cursor_start_end_in_slot (board, count);
+ return TRUE;
+}
+
+#endif /* ENABLE_KEYNAV */
+
/* Game state handling */
static void
@@ -1404,6 +1881,10 @@
priv->click_status = STATUS_NONE;
}
if (slot == priv->focus_slot) {
+ /* Try to keep the focus intact. If the focused card isn't there
+ * anymore, this will set the focus to the topmost card of there
+ * same slot, or the slot itself if there are no cards on it.
+ */
set_focus (board, slot, priv->focus_card_id, priv->show_focus);
}
if (slot == priv->highlight_slot) {
@@ -1415,6 +1896,243 @@
G_DEFINE_TYPE (AisleriotBoard, aisleriot_board, GTK_TYPE_DRAWING_AREA);
+/* AisleriotBoardClass methods */
+
+#ifdef ENABLE_KEYNAV
+
+static void
+aisleriot_board_activate (AisleriotBoard *board)
+{
+ AisleriotBoardPrivate *priv = board->priv;
+ GtkWidget *widget = GTK_WIDGET (board);
+ Slot *focus_slot = priv->focus_slot;
+ Slot *selection_slot = priv->selection_slot;
+ int selection_start_card_id = priv->selection_start_card_id;
+ guint state = 0;
+ gboolean moved;
+ guint8 *cards;
+ guint n_cards;
+
+ if (!GTK_WIDGET_HAS_FOCUS (widget))
+ return;
+
+ if (!focus_slot) {
+ aisleriot_board_error_bell (board);
+ return;
+ }
+
+ if (!gtk_get_current_event_state (&state))
+ state = 0;
+
+ /* Control-Activate is double-click */
+ if (state & GDK_CONTROL_MASK) {
+ aisleriot_game_record_move (priv->game, -1, NULL, 0);
+ if (aisleriot_game_button_double_clicked_lambda (priv->game, focus_slot->id)) {
+ aisleriot_game_end_move (priv->game);
+ } else {
+ aisleriot_game_discard_move (priv->game);
+ aisleriot_board_error_bell (board);
+ }
+
+ aisleriot_game_test_end_of_game (priv->game);
+
+ return;
+ }
+
+ /* Try single click action */
+ aisleriot_game_record_move (priv->game, -1, NULL, 0);
+
+ if (aisleriot_game_button_clicked_lambda (priv->game, focus_slot->id)) {
+ aisleriot_game_end_move (priv->game);
+ games_sound_play ("click");
+ aisleriot_game_test_end_of_game (priv->game);
+
+ return;
+ }
+
+ aisleriot_game_discard_move (priv->game);
+
+ /* If we have a selection, and the topmost card of a slot is focused,
+ * try to move the selected cards to the focused slot.
+ *
+ * NOTE: We cannot use aisleriot_game_drop_valid here since the
+ * game may not support the "droppable" feature.
+ */
+ if (selection_slot != NULL &&
+ selection_start_card_id >= 0 &&
+ priv->focus_card_id == ((int) focus_slot->cards->len) - 1) {
+ /* Remove the old selection. If the move doesn't succeed,
+ * we'll re-select them later.
+ */
+ set_selection (board, NULL, -1);
+
+ priv->click_status = STATUS_NONE;
+
+ aisleriot_game_record_move (priv->game,
+ selection_slot->id,
+ selection_slot->cards->data,
+ selection_slot->cards->len);
+
+ /* Store the cards, since the move could alter slot->cards! */
+ g_assert (selection_slot->cards->len >= selection_start_card_id);
+ n_cards = selection_slot->cards->len - selection_start_card_id;
+
+ cards = g_alloca (n_cards);
+ memcpy (cards,
+ selection_slot->cards->data + selection_start_card_id,
+ n_cards);
+
+ /* Now take the cards off of the origin slot. We'll update the slot geometry later */
+ g_byte_array_set_size (selection_slot->cards, selection_start_card_id);
+ selection_slot->needs_update = TRUE;
+
+ moved = aisleriot_game_drop_cards (priv->game,
+ selection_slot->id,
+ focus_slot->id,
+ cards,
+ n_cards);
+ if (moved) {
+ aisleriot_game_end_move (priv->game);
+
+ games_sound_play ("click");
+
+ /* Select the new topmost card */
+ set_focus (board, focus_slot, ((int) focus_slot->cards->len - 1), TRUE);
+
+ if (selection_slot->needs_update)
+ g_signal_emit_by_name (priv->game, "slot-changed", selection_slot); /* FIXMEchpe! */
+
+ aisleriot_game_test_end_of_game (priv->game);
+
+ return;
+ }
+
+ aisleriot_game_discard_move (priv->game);
+ aisleriot_game_slot_add_cards (priv->game, selection_slot, cards, n_cards);
+
+ g_print ("set-selection start %d len %d\n", selection_start_card_id, selection_slot->cards->len);
+ set_selection (board, selection_slot, selection_start_card_id);
+ }
+
+ aisleriot_board_error_bell (board);
+}
+
+static gboolean
+aisleriot_board_move_cursor (AisleriotBoard *board,
+ GtkMovementStep step,
+ int count)
+{
+ AisleriotBoardPrivate *priv = board->priv;
+ guint state;
+ gboolean is_control, is_shift, rv = FALSE;
+
+ if (!GTK_WIDGET_HAS_FOCUS (GTK_WIDGET (board)))
+ return FALSE;
+
+ g_return_val_if_fail (step == GTK_MOVEMENT_LOGICAL_POSITIONS ||
+ step == GTK_MOVEMENT_VISUAL_POSITIONS ||
+ step == GTK_MOVEMENT_DISPLAY_LINES ||
+ step == GTK_MOVEMENT_PAGES ||
+ step == GTK_MOVEMENT_BUFFER_ENDS, FALSE);
+
+ /* No focus? Set focus to the first/last slot */
+ /* FIXMEchpe: only if !shift ? */
+ /* This will always return TRUE, no need for keynav-failed handling */
+ if (!priv->focus_slot)
+ return aisleriot_board_move_cursor_start_end_by_slot (board, -count);
+
+// if (!priv->show_focus)
+ // show it; return
+
+ g_assert (priv->focus_slot != NULL);
+
+ if (!gtk_get_current_event_state (&state))
+ state = 0;
+
+ is_shift = (state & GDK_SHIFT_MASK) != 0;
+ is_control = (state & GDK_CONTROL_MASK) != 0;
+
+ switch (step) {
+ case GTK_MOVEMENT_LOGICAL_POSITIONS:
+ case GTK_MOVEMENT_VISUAL_POSITIONS:
+ if (is_shift) {
+ rv = aisleriot_board_extend_selection_left_right (board, count);
+ } else {
+ rv = aisleriot_board_move_cursor_left_right (board, count, is_control);
+ }
+ break;
+ case GTK_MOVEMENT_DISPLAY_LINES:
+ if (is_shift) {
+ rv = aisleriot_board_extend_selection_up_down (board, count);
+ } else {
+ rv = aisleriot_board_move_cursor_up_down (board, count, is_control);
+ }
+ break;
+ case GTK_MOVEMENT_PAGES:
+ if (!is_shift) {
+ rv = aisleriot_board_move_cursor_up_down (board, count, TRUE);
+ }
+ break;
+ case GTK_MOVEMENT_BUFFER_ENDS:
+ if (is_shift) {
+ rv = aisleriot_board_extend_selection_start_end (board, count);
+ } else if (is_control) {
+ rv = aisleriot_board_move_cursor_start_end_by_slot (board, count);
+ } else {
+ rv = aisleriot_board_move_cursor_start_end_in_slot (board, count);
+ }
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ if (!rv) {
+// gtk_widget_keynav_failed (widget, count > 0 ? GTK_DIR_TAB_FORWARD : GTK_DIR_TAB_BACKWARD)
+ }
+
+ return rv;
+}
+
+static void
+aisleriot_board_toggle_selection (AisleriotBoard *board)
+{
+ AisleriotBoardPrivate *priv = board->priv;
+ Slot *focus_slot;
+ int focus_card_id,new_selection_start_card_id;
+
+ focus_slot = priv->focus_slot;
+ focus_card_id = priv->focus_card_id;
+ if (focus_card_id < 0)
+ return;
+
+ if (priv->selection_slot == focus_slot &&
+ priv->selection_start_card_id <= focus_card_id) {
+ /* Truncate selection */
+ new_selection_start_card_id = focus_card_id + 1;
+ } else {
+ /* Extend selection */
+ new_selection_start_card_id = focus_card_id;
+ }
+
+ g_print ("select-card old-selection-start %d new-selection-start %d focus %d\n",
+ priv->selection_start_card_id, new_selection_start_card_id, focus_card_id);
+
+ if (new_selection_start_card_id < 0 ||
+ new_selection_start_card_id >= focus_slot->cards->len)
+ return;
+
+ if (!aisleriot_game_drag_valid (priv->game,
+ focus_slot->id,
+ focus_slot->cards->data + new_selection_start_card_id,
+ focus_slot->cards->len - new_selection_start_card_id))
+ return;
+
+ set_selection (board, focus_slot, new_selection_start_card_id);
+}
+
+#endif /* ENABLE_KEYNAV */
+
+/* GtkWidgetClass methods */
static void
aisleriot_board_realize (GtkWidget *widget)
{
@@ -1548,6 +2266,8 @@
"selection-color", &colour,
NULL);
+ /* FIXMEchpe: recalculate the focus_rect!!! */
+
if (colour != NULL) {
priv->selection_colour = *colour;
gdk_color_free (colour);
@@ -1561,6 +2281,7 @@
&priv->selection_colour);
/* FIXMEchpe: is this the right place? */
+ // XXX move to direction-changed signal handler
priv->is_rtl = gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL;
GTK_WIDGET_CLASS (aisleriot_board_parent_class)->style_set (widget, previous_style);
@@ -1585,6 +2306,14 @@
}
}
+static void
+aisleriot_board_size_request (GtkWidget *widget,
+ GtkRequisition *requisition)
+{
+ requisition->width = BOARD_MIN_WIDTH;
+ requisition->height = BOARD_MIN_HEIGHT;
+}
+
/* The gtkwidget.c focus in/out handlers queue a shallow draw;
* that's ok for us but maybe we want to optimise this a bit to
* only do it if we have a focus to draw/erase?
@@ -1596,11 +2325,14 @@
AisleriotBoard *board = AISLERIOT_BOARD (widget);
AisleriotBoardPrivate *priv = board->priv;
- /* If we're not showing focus, no need to redraw on focus change */
- if (!priv->show_focus)
- return FALSE;
+ /* Paint focus */
+ if (priv->show_focus &&
+ priv->focus_slot != NULL) {
+ gdk_window_invalidate_rect (widget->window, &priv->focus_rect, FALSE);
+ }
- return GTK_WIDGET_CLASS (aisleriot_board_parent_class)->focus_in_event (widget, event);
+ return FALSE;
+// return GTK_WIDGET_CLASS (aisleriot_board_parent_class)->focus_in_event (widget, event);
}
static gboolean
@@ -1612,11 +2344,14 @@
clear_state (board);
- /* If we're not showing focus, no need to redraw on focus change */
- if (!priv->show_focus)
- return FALSE;
+ /* Hide focus */
+ if (priv->show_focus &&
+ priv->focus_slot != NULL) {
+ gdk_window_invalidate_rect (widget->window, &priv->focus_rect, FALSE);
+ }
- return GTK_WIDGET_CLASS (aisleriot_board_parent_class)->focus_out_event (widget, event);
+ return FALSE;
+// return GTK_WIDGET_CLASS (aisleriot_board_parent_class)->focus_out_event (widget, event);
}
static gboolean
@@ -2221,14 +2956,37 @@
draw_focus:
- /* FIXMEchpe: Once we support focus, draw the focus here */
+#ifdef ENABLE_KEYNAV
+ if (G_UNLIKELY (priv->show_focus &&
+ priv->focus_slot != NULL &&
+ GTK_WIDGET_HAS_FOCUS (widget))) {
+ GdkRectangle *focus_rect = &priv->focus_rect;
+
+ /* Check whether this needs to be drawn */
+ if (gdk_region_rect_in (region, focus_rect) == GDK_OVERLAP_RECTANGLE_OUT)
+ goto expose_done;
+
+ gtk_paint_focus (widget->style,
+ widget->window,
+ GTK_WIDGET_STATE (widget),
+ &event->area,
+ widget,
+ "card-focus",
+ focus_rect->x,
+ focus_rect->y,
+ focus_rect->width,
+ focus_rect->height);
+ }
expose_done:
+#endif /* ENABLE_KEYNAV */
/* Parent class has no expose handler, no need to chain up */
return TRUE;
}
+/* GObjectClass methods */
+
static void
aisleriot_board_init (AisleriotBoard *board)
{
@@ -2362,7 +3120,11 @@
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+#ifdef ENABLE_KEYNAV
GtkBindingSet *binding_set;
+#endif
+
+ g_type_class_add_private (gobject_class, sizeof (AisleriotBoardPrivate));
gobject_class->constructor = aisleriot_board_constructor;
gobject_class->finalize = aisleriot_board_finalize;
@@ -2373,6 +3135,7 @@
widget_class->unrealize = aisleriot_board_unrealize;
widget_class->style_set = aisleriot_board_style_set;
widget_class->size_allocate = aisleriot_board_size_allocate;
+ widget_class->size_request = aisleriot_board_size_request;
widget_class->focus_in_event = aisleriot_board_focus_in;
widget_class->focus_out_event = aisleriot_board_focus_out;
widget_class->button_press_event = aisleriot_board_button_press;
@@ -2385,6 +3148,46 @@
widget_class->tap_and_hold = aisleriot_board_tap_and_hold;
#endif /* HAVE_MAEMO */
+#ifdef ENABLE_KEYNAV
+ klass->activate = aisleriot_board_activate;
+ klass->move_cursor = aisleriot_board_move_cursor;
+ klass->toggle_selection = aisleriot_board_toggle_selection;
+
+ /* Keybinding signals */
+ widget_class->activate_signal = signals[ACTIVATE] =
+ g_signal_new (I_("activate"),
+ G_TYPE_FROM_CLASS (gobject_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (AisleriotBoardClass, activate),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+
+ signals[MOVE_CURSOR] =
+ g_signal_new (I_("move-cursor"),
+ G_TYPE_FROM_CLASS (gobject_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (AisleriotBoardClass, move_cursor),
+ NULL, NULL,
+ games_marshal_BOOLEAN__ENUM_INT,
+ G_TYPE_BOOLEAN,
+ 2,
+ GTK_TYPE_MOVEMENT_STEP,
+ G_TYPE_INT);
+
+ signals[TOGGLE_SELECTION] =
+ g_signal_new (I_("toggle-selection"),
+ G_TYPE_FROM_CLASS (gobject_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (AisleriotBoardClass, toggle_selection),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+#endif /* ENABLE_KEYNAV */
+
+ /* Properties */
g_object_class_install_property
(gobject_class,
PROP_GAME,
@@ -2412,9 +3215,63 @@
GDK_TYPE_COLOR,
G_PARAM_READABLE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB));
+#ifdef ENABLE_KEYNAV
+ /* Keybindings */
binding_set = gtk_binding_set_by_class (klass);
- g_type_class_add_private (gobject_class, sizeof (AisleriotBoardPrivate));
+ /* Cursor movement */
+ aisleriot_board_add_move_and_select_binding (binding_set, GDK_Left, 0,
+ GTK_MOVEMENT_VISUAL_POSITIONS, -1);
+ aisleriot_board_add_move_and_select_binding (binding_set, GDK_KP_Left, 0,
+ GTK_MOVEMENT_VISUAL_POSITIONS, -1);
+
+ aisleriot_board_add_move_and_select_binding (binding_set, GDK_Right, 0,
+ GTK_MOVEMENT_VISUAL_POSITIONS, 1);
+ aisleriot_board_add_move_and_select_binding (binding_set, GDK_KP_Right, 0,
+ GTK_MOVEMENT_VISUAL_POSITIONS, 1);
+
+ aisleriot_board_add_move_and_select_binding (binding_set, GDK_Up, 0,
+ GTK_MOVEMENT_DISPLAY_LINES, -1);
+ aisleriot_board_add_move_and_select_binding (binding_set, GDK_KP_Up, 0,
+ GTK_MOVEMENT_DISPLAY_LINES, -1);
+
+ aisleriot_board_add_move_and_select_binding (binding_set, GDK_Down, 0,
+ GTK_MOVEMENT_DISPLAY_LINES, 1);
+ aisleriot_board_add_move_and_select_binding (binding_set, GDK_KP_Down, 0,
+ GTK_MOVEMENT_DISPLAY_LINES, 1);
+
+ aisleriot_board_add_move_and_select_binding (binding_set, GDK_Home, 0,
+ GTK_MOVEMENT_BUFFER_ENDS, -1);
+ aisleriot_board_add_move_and_select_binding (binding_set, GDK_KP_Home, 0,
+ GTK_MOVEMENT_BUFFER_ENDS, -1);
+
+ aisleriot_board_add_move_and_select_binding (binding_set, GDK_End, 0,
+ GTK_MOVEMENT_BUFFER_ENDS, 1);
+ aisleriot_board_add_move_and_select_binding (binding_set, GDK_KP_End, 0,
+ GTK_MOVEMENT_BUFFER_ENDS, 1);
+
+ aisleriot_board_add_move_binding (binding_set, GDK_Page_Up, 0,
+ GTK_MOVEMENT_PAGES, -1);
+ aisleriot_board_add_move_binding (binding_set, GDK_KP_Page_Up, 0,
+ GTK_MOVEMENT_PAGES, -1);
+
+ aisleriot_board_add_move_binding (binding_set, GDK_Page_Down, 0,
+ GTK_MOVEMENT_PAGES, 1);
+ aisleriot_board_add_move_binding (binding_set, GDK_KP_Page_Down, 0,
+ GTK_MOVEMENT_PAGES, 1);
+
+ /* Selection */
+ gtk_binding_entry_add_signal (binding_set, GDK_space, 0,
+ "toggle-selection", 0);
+
+ /* Activate */
+ gtk_binding_entry_add_signal (binding_set, GDK_Return, 0,
+ "activate", 0);
+ gtk_binding_entry_add_signal (binding_set, GDK_ISO_Enter, 0,
+ "activate", 0);
+ gtk_binding_entry_add_signal (binding_set, GDK_KP_Enter, 0,
+ "activate", 0);
+#endif /* ENABLE_KEYNAV */
}
/* public API */
Modified: trunk/aisleriot/board.h
==============================================================================
--- trunk/aisleriot/board.h (original)
+++ trunk/aisleriot/board.h Sun Jan 13 22:07:06 2008
@@ -34,6 +34,7 @@
typedef struct _AisleriotBoard AisleriotBoard;
typedef struct _AisleriotBoardPrivate AisleriotBoardPrivate;
+typedef struct _AisleriotBoardClass AisleriotBoardClass;
struct _AisleriotBoard {
GtkDrawingArea parent_instance;
@@ -42,7 +43,16 @@
AisleriotBoardPrivate *priv;
};
-typedef GtkDrawingAreaClass AisleriotBoardClass;
+struct _AisleriotBoardClass {
+ GtkDrawingAreaClass parent_class;
+
+ /* keybinding signals */
+ gboolean (* move_cursor) (AisleriotBoard *board,
+ GtkMovementStep step,
+ int count);
+ void (* activate) (AisleriotBoard *board);
+ void (* toggle_selection) (AisleriotBoard *board);
+};
GType aisleriot_board_get_type (void);
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]