2
* Copyright © 1998, 2003 Jonathan Blandford <jrb@mit.edu>
3
* Copyright © 2007, 2008, 2009, 2010 Christian Persch
5
* Some code copied from gtk+/gtk/gtkiconview (LGPL2+):
6
* Copyright © 2002, 2004 Anders Carlsson <andersca@gnu.org>
8
* This program is free software: you can redistribute it and/or modify
9
* it under the terms of the GNU General Public License as published by
10
* the Free Software Foundation, either version 3 of the License, or
11
* (at your option) any later version.
13
* This program is distributed in the hope that it will be useful,
14
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
* GNU General Public License for more details.
18
* You should have received a copy of the GNU General Public License
19
* along with this program. If not, see <http://www.gnu.org/licenses/>.
29
#include <gdk/gdkkeysyms.h>
31
#include <clutter/clutter.h>
34
#include "ar-marshal.h"
41
#include "slot-renderer.h"
42
#include "ar-card-textures-cache.h"
43
#include "ar-cursor.h"
45
#define AISLERIOT_BOARD_GET_PRIVATE(board)(G_TYPE_INSTANCE_GET_PRIVATE ((board), AISLERIOT_TYPE_BOARD, AisleriotBoardPrivate))
47
/* Enable keynav by default */
48
#ifndef DISABLE_KEYNAV
52
/* The limits for how much overlap there is between cards and
53
* how much is allowed to slip off the bottom or right.
55
#define MIN_DELTA (0.05)
57
/* The minimum size for the playing area. Almost completely arbitrary. */
58
#define BOARD_MIN_WIDTH 300
59
#define BOARD_MIN_HEIGHT 200
61
#define DOUBLE_TO_INT_CEIL(d) ((int) (d + 0.5))
63
#define I_(string) g_intern_static_string (string)
65
/* FIXMEchpe: file a bug to get an exported function like gtk_accelerator_get_default_mod_mask() for this? */
66
/* Copied from clutter-binding-pool.c */
67
#define CLUTTER_DEFAULT_MOD_MASK ((CLUTTER_SHIFT_MASK | \
68
CLUTTER_CONTROL_MASK | \
70
CLUTTER_SUPER_MASK | \
71
CLUTTER_HYPER_MASK | \
72
CLUTTER_META_MASK) | \
75
#pragma GCC poison GtkWidget
76
#pragma GCC poison widget
89
#define MOVE_CURSOR_LEFT_RIGHT 'h'
90
#define MOVE_CURSOR_LEFT_RIGHT_S "h"
91
#define MOVE_CURSOR_UP_DOWN 'v'
92
#define MOVE_CURSOR_UP_DOWN_S "v"
93
#define MOVE_CURSOR_PAGES 'p'
94
#define MOVE_CURSOR_PAGES_S "p"
95
#define MOVE_CURSOR_START_END 'e'
96
#define MOVE_CURSOR_START_END_S "e"
99
#define MOVE_LEFT_S "l"
100
#define MOVE_RIGHT 'r'
101
#define MOVE_RIGHT_S "r"
103
#endif /* ENABLE_KEYNAV */
105
struct _AisleriotBoardPrivate
111
ClutterActorBox allocation;
117
ArCardTexturesCache *textures;
122
/* The size of a slot in pixels. */
126
/* How much of the slot the card should take up */
127
double card_slot_ratio;
129
/* The offset of the cards within the slot. */
130
int xoffset, yoffset;
132
/* The offset within the window. */
138
guint32 last_click_time;
141
ArSlot *moving_cards_origin_slot;
142
int moving_cards_origin_card_id; /* The index of the card that was clicked on in hslot->cards; or -1 if the click wasn't on a card */
143
ClutterActor *moving_cards_group;
144
GByteArray *moving_cards;
146
/* A group to put animated cards above the slots */
147
ClutterActor *animation_layer;
149
/* The 'reveal card' action's slot and card link */
150
ArSlot *show_card_slot;
154
ArSlot *last_clicked_slot;
155
int last_clicked_card_id;
159
int focus_card_id; /* -1 for focused empty slot */
160
int focus_line_width;
162
GdkRectangle focus_rect;
165
ArSlot *selection_slot;
166
int selection_start_card_id;
169
ArSlot *highlight_slot;
171
/* Array RemovedCards to be dropped in animations */
172
GArray *removed_cards;
174
/* Idle handler where the slots will be compared for changes to
175
trigger animations */
176
guint check_animations_handler;
179
const char *status_message; /* interned */
182
guint droppable_supported : 1;
183
guint touchscreen_mode : 1;
184
guint show_focus : 1; /* whether the focus is drawn */
185
guint interior_focus : 1;
187
guint click_to_move : 1;
189
guint geometry_set : 1;
192
guint last_click_left_click : 1;
193
guint click_status : 4; /* enough bits for MoveStatus */
195
guint show_selection : 1;
196
guint show_highlight : 1;
197
guint show_status_messages : 1;
199
guint force_geometry_update : 1;
202
typedef struct _RemovedCard RemovedCard;
211
G_STATIC_ASSERT (LAST_STATUS < 16 /* 2^4 */);
232
#endif /* ENABLE_KEYNAV */
236
static guint signals[LAST_SIGNAL];
238
static void get_slot_and_card_from_point (AisleriotBoard *board,
243
static void slot_update_card_images (AisleriotBoard *board,
245
static void slot_update_card_images_full (AisleriotBoard *board,
247
gint highlight_start_card_id);
249
static void aisleriot_board_setup_geometry (AisleriotBoard *board);
252
set_cursor (AisleriotBoard *board,
255
g_signal_emit (board, signals[REQUEST_CURSOR], 0, (int) cursor);
258
/* If we are over a slot, set the cursor to the given cursor,
259
* otherwise use the default cursor. */
261
set_cursor_by_location (AisleriotBoard *board,
265
AisleriotBoardPrivate *priv = board->priv;
266
ArSlot *selection_slot = priv->selection_slot;
267
int selection_start_card_id = priv->selection_start_card_id;
270
gboolean drop_valid = FALSE;
271
ArCursorType cursor = AR_CURSOR_DEFAULT;
273
get_slot_and_card_from_point (board, x, y, &slot, &card_id);
275
if (priv->click_to_move &&
277
selection_slot != NULL &&
278
slot != selection_slot &&
279
selection_start_card_id >= 0) {
280
g_return_if_fail (selection_slot->cards->len > selection_start_card_id);
282
drop_valid = aisleriot_game_drop_valid (priv->game,
285
selection_slot->cards->data + selection_start_card_id,
286
selection_slot->cards->len - selection_start_card_id);
288
/* FIXMEchpe: special cursor when _drag_ is possible? */
291
cursor = AR_CURSOR_DROPPABLE;
292
} else if (slot != NULL &&
294
!CARD_GET_FACE_DOWN (CARD (slot->cards->data[card_id]))) {
295
if (priv->click_status == STATUS_NONE) {
296
cursor = AR_CURSOR_OPEN;
298
cursor = AR_CURSOR_CLOSED;
302
set_cursor (board, cursor);
308
set_status_message (AisleriotBoard *board,
311
AisleriotBoardPrivate *priv = board->priv;
313
if (g_strcmp0 (priv->status_message, message) == 0)
316
priv->status_message = g_intern_string (message);
318
g_signal_emit (board, signals[STATUS_MESSAGE], 0, priv->status_message);
324
get_slot_and_card_from_point (AisleriotBoard *board,
330
AisleriotBoardPrivate *priv = board->priv;
332
gboolean got_slot = FALSE;
340
slots = aisleriot_game_get_slots (priv->game);
342
n_slots = slots->len;
343
for (i = n_slots - 1; i >= 0; --i) {
344
ArSlot *hslot = slots->pdata[i];
346
/* if point is within our rectangle */
347
if (hslot->rect.x <= x && x <= hslot->rect.x + hslot->rect.width &&
348
hslot->rect.y <= y && y <= hslot->rect.y + hslot->rect.height) {
349
num_cards = hslot->cards->len;
351
if (got_slot == FALSE || num_cards > 0) {
352
/* if we support exposing more than one card,
353
* find the exact card */
357
if (hslot->pixeldx > 0)
358
depth += (x - hslot->rect.x) / hslot->pixeldx;
359
else if (hslot->pixeldx < 0)
360
depth += (hslot->rect.x + hslot->rect.width - x) / -hslot->pixeldx;
361
else if (hslot->pixeldy > 0)
362
depth += (y - hslot->rect.y) / hslot->pixeldy;
364
/* account for the last card getting much more display area
367
if (depth > hslot->exposed)
368
depth = hslot->exposed;
371
/* card = #cards in slot + card chosen (indexed in # exposed cards) - # exposed cards */
373
cardid = num_cards + depth - hslot->exposed;
375
/* this is the topmost slot with a card */
376
/* take it and run */
385
*_cardid = cardid > 0 ? cardid - 1 : -1;
391
test_slot_projection_intersects_x (ArSlot *slot,
395
return slot->rect.x <= x_end &&
396
slot->rect.x + slot->rect.width >= x_start;
400
get_slot_index_from_slot (AisleriotBoard *board,
403
AisleriotBoardPrivate *priv = board->priv;
408
g_assert (slot != NULL);
410
slots = aisleriot_game_get_slots (priv->game);
411
n_slots = slots->len;
412
g_assert (n_slots > 0);
414
for (slot_index = 0; slot_index < n_slots; ++slot_index) {
415
if (g_ptr_array_index (slots, slot_index) == slot)
419
g_assert (slot_index < n_slots); /* the slot EXISTS after all */
424
#endif /* ENABLE_KEYNAV */
427
get_rect_by_slot_and_card (AisleriotBoard *board,
433
AisleriotBoardPrivate *priv = board->priv;
435
int first_card_id, num;
437
g_return_if_fail (slot != NULL && card_id >= -1);
439
first_card_id = ((int) slot->cards->len) - ((int) slot->exposed);
441
if (card_id >= first_card_id) {
442
delta = card_id - first_card_id;
445
rect->x = slot->rect.x + delta * slot->pixeldx;
446
rect->y = slot->rect.y + delta * slot->pixeldy;
447
rect->width = priv->card_size.width + num * slot->pixeldx;
448
rect->height = priv->card_size.height + num * slot->pixeldy;
451
slot->expanded_right) {
452
rect->x += slot->rect.width - priv->card_size.width;
456
/* card_id == -1 or no card available, return the slot rect.
457
* Its size should be card_size.
466
widen_rect (GdkRectangle *rect,
474
rect->x = MAX (x, 0);
475
rect->y = MAX (y, 0);
476
rect->width = rect->width + 2 * delta;
477
rect->height = rect->height + 2 * delta;
481
get_focus_rect (AisleriotBoard *board,
484
AisleriotBoardPrivate *priv = board->priv;
486
if (!priv->focus_slot)
489
get_rect_by_slot_and_card (board,
493
widen_rect (rect, priv->focus_line_width + priv->focus_padding);
497
set_focus (AisleriotBoard *board,
502
AisleriotBoardPrivate *priv = board->priv;
503
// GtkWidget *widget = GTK_WIDGET (board);
507
top_card_id = slot ? ((int) slot->cards->len) - 1 : -1;
508
card_id = MIN (card_id, top_card_id);
510
if (priv->focus_slot == slot &&
511
priv->focus_card_id == card_id &&
512
priv->show_focus == show_focus)
515
if (priv->focus_slot != NULL) {
517
if (priv->show_focus &&
518
gtk_widget_has_focus (widget)) {
519
gdk_window_invalidate_rect (widget->window, &priv->focus_rect, FALSE);
521
priv->show_focus = FALSE;
525
priv->focus_slot = NULL;
526
priv->focus_card_id = -1;
529
priv->show_focus = show_focus;
534
priv->focus_slot = slot;
535
priv->focus_card_id = card_id;
539
gtk_widget_has_focus (widget)) {
540
get_focus_rect (board, &priv->focus_rect);
541
gdk_window_invalidate_rect (widget->window, &priv->focus_rect, FALSE);
546
/* Selection handling */
549
set_selection (AisleriotBoard *board,
552
gboolean show_selection)
554
AisleriotBoardPrivate *priv = board->priv;
556
if (priv->selection_slot == slot &&
557
priv->selection_start_card_id == card_id &&
558
priv->show_selection == show_selection)
561
if (priv->selection_slot != NULL) {
562
if (priv->show_selection) {
563
/* Clear selection card images */
564
slot_update_card_images_full (board, priv->selection_slot, G_MAXINT);
567
priv->selection_slot = NULL;
568
priv->selection_start_card_id = -1;
571
priv->show_selection = show_selection;
572
priv->selection_slot = slot;
573
priv->selection_start_card_id = card_id;
574
g_assert (slot != NULL || card_id == -1);
579
g_assert (card_id < 0 || card_id < slot->cards->len);
581
if (priv->show_selection) {
582
slot_update_card_images_full (board, slot, card_id);
589
slot_update_geometry (AisleriotBoard *board,
592
AisleriotBoardPrivate *priv = board->priv;
593
// GtkWidget *widget = GTK_WIDGET (board);
594
GdkRectangle old_rect;
596
int delta, xofs, yofs, pixeldx;
599
if (!priv->geometry_set)
603
old_rect = slot->rect;
605
card_step = ar_style_get_card_step (priv->style);
607
xofs = priv->xoffset;
608
yofs = priv->yoffset;
610
/* FIXMEchpe: what exactly is the purpose of the following lines? */
611
if (slot->expanded_right)
613
if (slot->expanded_down)
617
slot->rect.x = priv->xslotstep * (priv->width - slot->x) - priv->card_size.width - xofs + priv->xbaseoffset;
619
slot->rect.x = priv->xslotstep * slot->x + xofs + priv->xbaseoffset;
622
slot->rect.y = priv->yslotstep * slot->y + yofs; /* FIXMEchpe + priv->ybaseoffset; */
624
/* We need to make sure the cards fit within the board, even
625
* when there are many of them. See bug #171417.
627
/* FIXMEchpe: check |slot->exposed| instead of cards->len? */
629
if (cards->len > 1) {
630
double dx = 0, dy = 0;
631
double n_cards = cards->len - 1; /* FIXMEchpe: slot->exposed - 1 ? */
633
if (slot->expanded_down) {
634
double y_from_bottom, max_dy = card_step;
635
float allocation_height = priv->allocation.y2 - priv->allocation.y1;
638
max_dy = slot->expansion.dy;
640
/* Calculate the compressed_dy that will let us fit within the board */
642
y_from_bottom = ((double) (widget->allocation.height - slot->rect.y)) / ((double) priv->card_size.height);
644
y_from_bottom = ((double) (allocation_height - slot->rect.y)) / ((double) priv->card_size.height);
646
dy = (y_from_bottom - (1.0 - ar_style_get_card_overhang (priv->style))) / n_cards;
647
dy = CLAMP (dy, MIN_DELTA, max_dy);
648
} else if (slot->expanded_right) {
650
double x_from_left, max_dx = card_step;
653
max_dx = slot->expansion.dx;
655
x_from_left = ((double) slot->rect.x) / ((double) priv->card_size.width) + 1.0;
656
dx = (x_from_left - (1.0 - ar_style_get_card_overhang (priv->style))) / n_cards;
657
dx = CLAMP (dx, MIN_DELTA, max_dx);
659
slot->pixeldx = DOUBLE_TO_INT_CEIL (- dx * priv->card_size.width);
660
pixeldx = -slot->pixeldx;
662
double x_from_right, max_dx = card_step;
663
float allocation_width = priv->allocation.x2 - priv->allocation.x1;
666
max_dx = slot->expansion.dx;
669
x_from_right = ((double) (widget->allocation.width - slot->rect.x)) / ((double) priv->card_size.width);
671
x_from_right = ((double) (allocation_width - slot->rect.x)) / ((double) priv->card_size.width);
673
dx = (x_from_right - (1.0 - ar_style_get_card_overhang (priv->style))) / n_cards;
674
dx = CLAMP (dx, MIN_DELTA, max_dx);
676
pixeldx = slot->pixeldx = DOUBLE_TO_INT_CEIL (dx * priv->card_size.width);
680
slot->pixeldy = DOUBLE_TO_INT_CEIL (dy * priv->card_size.height);
682
slot->pixeldx = slot->pixeldy = 0;
685
slot->exposed = cards->len;
686
if (0 < slot->expansion_depth &&
687
slot->expansion_depth < slot->exposed) {
688
slot->exposed = slot->expansion_depth;
691
if ((slot->pixeldx == 0 &&
692
slot->pixeldy == 0 &&
693
slot->exposed > 1) ||
695
slot->exposed < 1)) {
699
delta = slot->exposed > 0 ? slot->exposed - 1 : 0;
701
slot->rect.width = priv->card_size.width + delta * pixeldx;
702
slot->rect.height = priv->card_size.height + delta * slot->pixeldy;
705
slot->rect.x -= slot->rect.width - priv->card_size.width;
708
if (slot->slot_renderer)
709
clutter_actor_set_position (slot->slot_renderer,
710
slot->rect.x, slot->rect.y);
712
slot->needs_update = FALSE;
716
check_animations_cb (gpointer user_data)
718
AisleriotBoard *board = user_data;
719
AisleriotBoardPrivate *priv = board->priv;
723
GArray *animations = g_array_new (FALSE, FALSE, sizeof (AisleriotAnimStart));
725
slots = aisleriot_game_get_slots (priv->game);
727
/* Find any cards that have been removed from the top of the
729
for (slot_num = 0; slot_num < slots->len; slot_num++) {
730
slot = slots->pdata[slot_num];
732
if (slot->old_cards->len > slot->cards->len) {
733
for (i = 0; i < slot->cards->len; i++) {
734
Card old_card = CARD (slot->old_cards->data[i]);
735
Card new_card = CARD (slot->cards->data[i]);
737
if (old_card.attr.suit != new_card.attr.suit
738
|| old_card.attr.rank != new_card.attr.rank)
742
if (i >= slot->cards->len) {
743
for (; i < slot->old_cards->len; i++) {
744
RemovedCard removed_card;
746
removed_card.card = CARD (slot->old_cards->data[i]);
747
aisleriot_game_get_card_offset (slot, i,
750
&removed_card.cardy);
751
removed_card.cardx += slot->rect.x;
752
removed_card.cardy += slot->rect.y;
753
removed_card.from_drag = FALSE;
754
g_array_append_val (priv->removed_cards, removed_card);
760
for (slot_num = 0; slot_num < slots->len; slot_num++) {
761
/* Number of extra cards that aren't visible to include in the
763
guint n_unexposed_animated_cards = 0;
765
slot = slots->pdata[slot_num];
767
g_array_set_size (animations, 0);
769
/* Check if the top card has been flipped over */
770
if (slot->old_cards->len >= slot->cards->len
771
&& slot->cards->len >= 1
772
&& !memcmp (slot->old_cards->data, slot->cards->data,
773
slot->cards->len - 1)) {
774
Card old_card = CARD (slot->old_cards->data[slot->cards->len - 1]);
775
Card new_card = CARD (slot->cards->data[slot->cards->len - 1]);
777
if (old_card.attr.suit == new_card.attr.suit
778
&& old_card.attr.rank == new_card.attr.rank
779
&& old_card.attr.face_down != new_card.attr.face_down) {
780
AisleriotAnimStart anim;
782
aisleriot_game_get_card_offset (slot, slot->cards->len - 1,
786
anim.cardx += slot->rect.x;
787
anim.cardy += slot->rect.y;
788
anim.old_card = old_card;
791
g_array_append_val (animations, anim);
793
/* Check if any cards have been added from the removed cards
795
} else if (slot->old_cards->len < slot->cards->len
796
&& !memcmp (slot->old_cards->data, slot->cards->data,
797
slot->old_cards->len)) {
798
for (i = MAX (slot->old_cards->len, slot->cards->len - slot->exposed);
799
i < slot->cards->len;
801
Card added_card = CARD (slot->cards->data[i]);
804
for (j = 0; j < priv->removed_cards->len; j++) {
805
RemovedCard *removed_card = &g_array_index (priv->removed_cards,
808
if (added_card.attr.suit == removed_card->card.attr.suit
809
&& added_card.attr.rank == removed_card->card.attr.rank) {
810
AisleriotAnimStart anim;
812
anim.cardx = removed_card->cardx;
813
anim.cardy = removed_card->cardy;
814
anim.old_card = removed_card->card;
815
anim.raise = !removed_card->from_drag;
817
g_array_append_val (animations, anim);
819
g_array_remove_index (priv->removed_cards, j);
826
/* Check if any extra unexposed cards are included in the
827
animation. There's no point in drawing these because they
828
will be hidden by the exposed cards but we don't want to draw
829
them at the slot either. This will for example happen in
830
Canfield when the discard pile is flipped over into the draw
832
if (animations->len > 0 && animations->len == slot->exposed)
834
AisleriotAnimStart *anim = &g_array_index (animations,
835
AisleriotAnimStart, 0);
837
n_unexposed_animated_cards = (slot->cards->len - slot->old_cards->len
840
if (n_unexposed_animated_cards > 0)
842
/* Set the bottom card of the first animation to be the
843
lowest unexposed card */
845
= CARD (slot->cards->data[slot->cards->len
847
- n_unexposed_animated_cards]);
848
anim->old_card.attr.face_down = !anim->old_card.attr.face_down;
853
aisleriot_slot_renderer_set_animations
854
(AISLERIOT_SLOT_RENDERER (slot->slot_renderer),
855
animations->len, (const AisleriotAnimStart *) animations->data,
856
n_unexposed_animated_cards);
858
if (slot->cards->len == 0) {
859
clutter_actor_lower_bottom (slot->slot_renderer);
861
clutter_actor_raise_top (slot->slot_renderer);
862
ClutterActor *animation_layer = CLUTTER_ACTOR(aisleriot_slot_renderer_get_animation_layer(AISLERIOT_SLOT_RENDERER(slot->slot_renderer)));
863
clutter_actor_raise_top (animation_layer);
866
/* Set the old cards back to the new cards */
867
aisleriot_game_reset_old_cards (slot);
870
g_array_set_size (priv->removed_cards, 0);
872
g_array_free (animations, TRUE);
874
priv->check_animations_handler = 0;
880
queue_check_animations (AisleriotBoard *board)
882
AisleriotBoardPrivate *priv = board->priv;
884
if (!ar_style_get_enable_animations (priv->style))
887
/* The animations are checked for in an idle handler so that this
888
function can be called whenever the scheme script makes changes
889
to the board but it won't actually check until control has been
890
given back to the glib main loop */
892
if (priv->check_animations_handler == 0)
893
/* Check for animations with a high priority to ensure that the
894
animations are setup before the stage is repainted. Clutter's
895
redraw priority is unfortunately higher than all of the idle
896
priorities so we need to use G_PRIORITY_DEFAULT */
897
priv->check_animations_handler =
898
clutter_threads_add_idle_full (G_PRIORITY_DEFAULT,
904
slot_update_card_images_full (AisleriotBoard *board,
906
gint highlight_start_card_id)
908
AisleriotBoardPrivate *priv = board->priv;
910
if (!priv->geometry_set)
913
if (slot->slot_renderer == NULL) {
914
slot->slot_renderer = aisleriot_slot_renderer_new (priv->style, priv->textures, slot);
915
g_object_ref_sink (slot->slot_renderer);
917
aisleriot_slot_renderer_set_animation_layer
918
(AISLERIOT_SLOT_RENDERER (slot->slot_renderer),
919
CLUTTER_CONTAINER (priv->animation_layer));
921
clutter_actor_set_position (slot->slot_renderer,
922
slot->rect.x, slot->rect.y);
924
clutter_container_add (CLUTTER_CONTAINER (board),
925
slot->slot_renderer, NULL);
927
clutter_actor_raise_top (priv->animation_layer);
930
aisleriot_slot_renderer_set_animations
931
(AISLERIOT_SLOT_RENDERER (slot->slot_renderer), 0, NULL, 0);
933
aisleriot_slot_renderer_set_highlight
934
(AISLERIOT_SLOT_RENDERER (slot->slot_renderer),
935
priv->show_highlight ? highlight_start_card_id : G_MAXINT);
939
slot_update_card_images (AisleriotBoard *board,
942
AisleriotBoardPrivate *priv = board->priv;
943
int highlight_start_card_id = G_MAXINT;
945
if (G_UNLIKELY (slot == priv->highlight_slot &&
946
priv->show_highlight)) {
947
highlight_start_card_id = slot->cards->len - 1;
948
} else if (G_UNLIKELY (slot == priv->selection_slot &&
949
priv->selection_start_card_id >= 0 &&
950
priv->show_selection)) {
951
highlight_start_card_id = priv->selection_start_card_id;
954
slot_update_card_images_full (board, slot, highlight_start_card_id);
957
/* helper functions */
960
aisleriot_board_error_bell (AisleriotBoard *board)
962
g_signal_emit (board, signals[ERROR_BELL], 0);
965
/* Work out new sizes and spacings for the cards. */
967
aisleriot_board_setup_geometry (AisleriotBoard *board)
969
AisleriotBoardPrivate *priv = board->priv;
970
ClutterActor *actor = CLUTTER_ACTOR (board);
976
if (!CLUTTER_ACTOR_IS_REALIZED (actor))
979
/* Nothing to do yet */
980
if (aisleriot_game_get_state (priv->game) <= GAME_LOADED)
983
theme = ar_style_get_card_theme (priv->style);
987
g_return_if_fail (priv->width > 0 && priv->height > 0);
990
priv->xslotstep = ((double) priv->allocation.x2 - priv->allocation.x1) / priv->width;
991
priv->yslotstep = ((double) priv->allocation.y2 - priv->allocation.y1) / priv->height;
993
ar_card_theme_set_size (theme,
996
priv->card_slot_ratio);
998
ar_card_theme_get_size (theme, &card_size);
999
priv->card_size = card_size;
1001
/* If the cards are too far apart, bunch them in the middle. */
1002
priv->xbaseoffset = 0;
1003
if (priv->xslotstep > (card_size.width * 3) / 2) {
1004
priv->xslotstep = (card_size.width * 3) / 2;
1005
/* FIXMEchpe: if there are expand-right slots, reserve the space for them instead? */
1007
priv->xbaseoffset = (widget->allocation.width - priv->xslotstep * priv->width) / 2;
1009
priv->xbaseoffset = (priv->allocation.x2 - priv->allocation.x1 - priv->xslotstep * priv->width) / 2;
1012
if (priv->yslotstep > (card_size.height * 3) / 2) {
1013
priv->yslotstep = (card_size.height * 3) / 2;
1014
/* FIXMEchpe: if there are expand-down slots, reserve the space for them instead?
1015
priv->ybaseoffset = (widget->allocation.height - priv->yslotstep * priv->height) / 2;
1019
priv->xoffset = (priv->xslotstep - card_size.width) / 2;
1020
priv->yoffset = (priv->yslotstep - card_size.height) / 2;
1022
/* NOTE! Updating the slots checks that geometry is set, so
1023
* we set it to TRUE already.
1025
priv->geometry_set = TRUE;
1027
/* Now recalculate the slot locations. */
1028
slots = aisleriot_game_get_slots (priv->game);
1030
n_slots = slots->len;
1031
for (i = 0; i < n_slots; ++i) {
1032
ArSlot *slot = slots->pdata[i];
1034
slot_update_geometry (board, slot);
1035
slot_update_card_images (board, slot);
1038
/* Update the focus and selection rects */
1039
get_focus_rect (board, &priv->focus_rect);
1043
drag_begin (AisleriotBoard *board)
1045
AisleriotBoardPrivate *priv = board->priv;
1047
int delta, height, width;
1049
int num_moving_cards;
1053
if (!priv->selection_slot ||
1054
priv->selection_start_card_id < 0) {
1055
priv->click_status = STATUS_NONE;
1059
priv->click_status = STATUS_IS_DRAG;
1061
hslot = priv->moving_cards_origin_slot = priv->selection_slot;
1062
priv->moving_cards_origin_card_id = priv->selection_start_card_id;
1064
num_moving_cards = hslot->cards->len - priv->moving_cards_origin_card_id;
1066
cards = hslot->cards;
1068
/* Save game state */
1069
aisleriot_game_record_move (priv->game, hslot->id,
1070
cards->data, cards->len);
1072
/* Unset the selection and focus. It'll be re-set if the drag is aborted */
1073
set_selection (board, NULL, -1, FALSE);
1074
set_focus (board, NULL, -1, FALSE);
1076
delta = hslot->exposed - num_moving_cards;
1078
/* (x,y) is the upper left edge of the topmost dragged card */
1079
x = hslot->rect.x + delta * hslot->pixeldx;
1081
hslot->expanded_right) {
1082
x += hslot->rect.width - priv->card_size.width;
1085
priv->last_click_x -= x;
1086
priv->last_click_y -= y = hslot->rect.y + delta * hslot->pixeldy;;
1088
g_byte_array_set_size (priv->moving_cards, 0);
1089
g_byte_array_append (priv->moving_cards,
1090
cards->data + priv->moving_cards_origin_card_id,
1091
cards->len - priv->moving_cards_origin_card_id);
1093
width = priv->card_size.width + (num_moving_cards - 1) * hslot->pixeldx;
1094
height = priv->card_size.height + (num_moving_cards - 1) * hslot->pixeldy;
1096
priv->moving_cards_group = g_object_ref_sink (clutter_group_new ());
1097
clutter_actor_set_position (priv->moving_cards_group, x, y);
1099
/* FIXMEchpe: RTL issue: this doesn't work right when we allow dragging of
1100
* more than one card from a expand-right slot. (But right now no game .scm
1104
width = priv->card_size.width;
1105
height = priv->card_size.height;
1107
for (i = 0; i < priv->moving_cards->len; ++i) {
1108
Card hcard = CARD (priv->moving_cards->data[i]);
1109
ClutterActor *card_tex;
1110
RemovedCard removed_card;
1112
removed_card.cardx = x;
1113
removed_card.cardy = y;
1114
removed_card.card = hcard;
1115
removed_card.from_drag = TRUE;
1117
g_array_append_val (priv->removed_cards, removed_card);
1119
card_tex = aisleriot_card_new (priv->textures, hcard, hcard);
1120
clutter_actor_set_position (card_tex, x, y);
1121
clutter_container_add (CLUTTER_CONTAINER (priv->moving_cards_group),
1124
x += hslot->pixeldx;
1125
y += hslot->pixeldy;
1128
/* Take the cards off of the stack */
1129
g_byte_array_set_size (cards, priv->moving_cards_origin_card_id);
1131
slot_update_geometry (board, hslot);
1132
slot_update_card_images (board, hslot);
1133
aisleriot_game_reset_old_cards (hslot);
1135
clutter_container_add (CLUTTER_CONTAINER (board),
1136
priv->moving_cards_group, NULL);
1138
if (hslot->cards->len == 0) {
1139
clutter_actor_lower_bottom (hslot->slot_renderer);
1142
set_cursor (board, AR_CURSOR_CLOSED);
1146
drag_end (AisleriotBoard *board,
1149
AisleriotBoardPrivate *priv = board->priv;
1151
if (priv->moving_cards_group != NULL) {
1152
clutter_actor_destroy (priv->moving_cards_group);
1153
g_object_unref (priv->moving_cards_group);
1154
priv->moving_cards_group = NULL;
1157
/* FIXMEchpe: check that slot->cards->len == moving_cards_origin_card_id !!! FIXMEchpe what to do if not, abort the game? */
1158
/* Add the origin cards back to the origin slot */
1160
priv->moving_cards_origin_slot != NULL &&
1161
priv->moving_cards->len > 0) {
1162
aisleriot_game_slot_add_cards (priv->game,
1163
priv->moving_cards_origin_slot,
1164
priv->moving_cards->data,
1165
priv->moving_cards->len);
1166
clutter_actor_raise_top (priv->moving_cards_origin_slot->slot_renderer);
1167
ClutterActor *animation_layer = CLUTTER_ACTOR(aisleriot_slot_renderer_get_animation_layer(AISLERIOT_SLOT_RENDERER(priv->moving_cards_origin_slot->slot_renderer)));
1168
clutter_actor_raise_top (animation_layer);
1171
priv->click_status = STATUS_NONE;
1172
priv->moving_cards_origin_slot = NULL;
1173
priv->moving_cards_origin_card_id = -1;
1177
cards_are_droppable (AisleriotBoard *board,
1180
AisleriotBoardPrivate *priv = board->priv;
1182
return slot != NULL &&
1183
priv->moving_cards_origin_slot &&
1184
aisleriot_game_drop_valid (priv->game,
1185
priv->moving_cards_origin_slot->id,
1187
priv->moving_cards->data,
1188
priv->moving_cards->len);
1192
find_drop_target (AisleriotBoard *board,
1196
AisleriotBoardPrivate *priv = board->priv;
1198
ArSlot *retval = NULL;
1200
gint min_distance = G_MAXINT;
1202
/* Find a target directly under the center of the card. */
1203
get_slot_and_card_from_point (board,
1204
x + priv->card_size.width / 2,
1205
y + priv->card_size.height / 2,
1206
&new_hslot, &new_cardid);
1208
if (cards_are_droppable (board, new_hslot))
1211
/* If that didn't work, look for a target at all 4 corners of the card. */
1212
for (i = 0; i < 4; i++) {
1213
get_slot_and_card_from_point (board,
1214
x + priv->card_size.width * (i / 2),
1215
y + priv->card_size.height * (i % 2),
1216
&new_hslot, &new_cardid);
1221
/* This skips corners we know are not droppable. */
1222
if (!priv->droppable_supported || cards_are_droppable (board, new_hslot)) {
1223
gint dx, dy, distance_squared;
1225
dx = new_hslot->rect.x + (new_cardid - 1) * new_hslot->pixeldx - x;
1226
dy = new_hslot->rect.y + (new_cardid - 1) * new_hslot->pixeldy - y;
1228
distance_squared = dx * dx + dy * dy;
1230
if (distance_squared <= min_distance) {
1232
min_distance = distance_squared;
1241
drop_moving_cards (AisleriotBoard *board,
1245
AisleriotBoardPrivate *priv = board->priv;
1247
gboolean moved = FALSE;
1250
hslot = find_drop_target (board,
1251
x - priv->last_click_x,
1252
y - priv->last_click_y);
1254
/* Reposition the removed cards so that they are relative to the
1256
for (i = 0; i < priv->removed_cards->len; i++) {
1257
RemovedCard *removed_card = &g_array_index (priv->removed_cards,
1260
if (removed_card->from_drag) {
1261
removed_card->cardx += x - priv->last_click_x;
1262
removed_card->cardy += y - priv->last_click_y;
1267
moved = aisleriot_game_drop_cards (priv->game,
1268
priv->moving_cards_origin_slot->id,
1270
priv->moving_cards->data,
1271
priv->moving_cards->len);
1275
aisleriot_game_end_move (priv->game);
1276
ar_sound_play ("click");
1278
aisleriot_game_discard_move (priv->game);
1279
ar_sound_play ("slide");
1282
drag_end (board, moved);
1285
aisleriot_game_test_end_of_game (priv->game);
1289
highlight_drop_target (AisleriotBoard *board,
1292
AisleriotBoardPrivate *priv = board->priv;
1293
ArSlot *old_slot = priv->highlight_slot;
1295
if (slot == old_slot)
1298
/* Need to set the highlight slot even when we the cards aren't droppable
1299
* since that can happen when the game doesn't support FEATURE_DROPPABLE.
1301
priv->highlight_slot = slot;
1303
/* Invalidate the old highlight rect */
1304
if (old_slot != NULL &&
1305
priv->show_highlight) {
1306
/* FIXMEchpe only update the topmost card? */
1307
/* It's ok to call this directly here, since the old highlight_slot cannot
1308
* have been the same as the current selection_slot!
1310
slot_update_card_images_full (board, old_slot, G_MAXINT);
1313
if (!cards_are_droppable (board, slot))
1316
if (!priv->show_highlight)
1319
/* Prepare the highlight pixbuf/pixmaps */
1321
/* FIXMEchpe only update the topmost card? */
1322
/* It's ok to call this directly, since the highlight slot is always
1323
* different from the selection slot!
1325
slot_update_card_images_full (board, slot, ((int) slot->cards->len) - 1);
1329
reveal_card (AisleriotBoard *board,
1333
AisleriotBoardPrivate *priv = board->priv;
1335
AisleriotSlotRenderer *renderer;
1337
if (priv->show_card_slot == slot)
1340
if (priv->show_card_slot != NULL) {
1341
if (priv->show_card_slot->slot_renderer) {
1342
renderer = AISLERIOT_SLOT_RENDERER (priv->show_card_slot->slot_renderer);
1343
aisleriot_slot_renderer_set_revealed_card (renderer, -1);
1345
priv->show_card_slot = NULL;
1346
priv->show_card_id = -1;
1347
priv->click_status = STATUS_NONE;
1350
if (!slot || cardid < 0 || cardid >= slot->cards->len - 1)
1353
card = CARD (slot->cards->data[cardid]);
1354
if (CARD_GET_FACE_DOWN (card))
1357
priv->show_card_slot = slot;
1358
priv->show_card_id = cardid;
1359
priv->click_status = STATUS_SHOW;
1361
renderer = AISLERIOT_SLOT_RENDERER (slot->slot_renderer);
1362
aisleriot_slot_renderer_set_revealed_card (renderer, cardid);
1366
clear_state (AisleriotBoard *board)
1368
AisleriotBoardPrivate *priv = board->priv;
1370
highlight_drop_target (board, NULL);
1371
drag_end (board, FALSE /* FIXMEchpe ? */);
1373
reveal_card (board, NULL, -1);
1375
priv->click_status = STATUS_NONE;
1376
priv->last_clicked_slot = NULL;
1377
priv->last_clicked_card_id = -1;
1380
/* Note: this unsets the selection! */
1382
aisleriot_board_move_selected_cards_to_slot (AisleriotBoard *board,
1385
AisleriotBoardPrivate *priv = board->priv;
1386
ArSlot *selection_slot = priv->selection_slot;
1387
int selection_start_card_id = priv->selection_start_card_id;
1392
if (!selection_slot ||
1393
priv->selection_start_card_id < 0)
1396
/* NOTE: We cannot use aisleriot_game_drop_valid here since the
1397
* game may not support the "droppable" feature.
1400
set_selection (board, NULL, -1, FALSE);
1402
priv->click_status = STATUS_NONE;
1404
aisleriot_game_record_move (priv->game,
1406
selection_slot->cards->data,
1407
selection_slot->cards->len);
1409
/* Store the cards, since the move could alter slot->cards! */
1410
g_assert (selection_slot->cards->len >= selection_start_card_id);
1411
n_cards = selection_slot->cards->len - selection_start_card_id;
1413
cards = g_alloca (n_cards);
1415
selection_slot->cards->data + selection_start_card_id,
1418
/* Now take the cards off of the origin slot. We'll update the slot geometry later */
1419
g_byte_array_set_size (selection_slot->cards, selection_start_card_id);
1420
selection_slot->needs_update = TRUE;
1422
moved = aisleriot_game_drop_cards (priv->game,
1428
aisleriot_game_end_move (priv->game);
1430
ar_sound_play ("click");
1432
if (selection_slot->needs_update)
1433
g_signal_emit_by_name (priv->game, "slot-changed", selection_slot); /* FIXMEchpe! */
1435
aisleriot_game_test_end_of_game (priv->game);
1437
/* Not moved; discard the move add the cards back to the origin slot */
1438
aisleriot_game_discard_move (priv->game);
1439
aisleriot_game_slot_add_cards (priv->game, selection_slot, cards, n_cards);
1447
#ifdef ENABLE_KEYNAV
1449
/** Keynav specification:
1451
* Focus state consists of the slot that has the focus, and the card
1452
* in that slot of the card that has the focus; if the slot has no cards
1453
* the focus is on the empty slot itself.
1455
* Without modifiers, for focus movement:
1456
* Left (Right): For right-extended slots, moves the focus to the card
1457
* under (over) the currently focused card on the same slot.
1458
* If the focused card is already the bottommost (topmost)
1459
* card of the slot, or the slot is not right-extended, moves the focus
1460
* to the topmost (bottommost) card on the slot left (right) to the currently
1461
* focused slot (wraps around from first to last slot and vv.)
1462
* Up (Down): For down-extended slots, moves the focus to the card
1463
* under (over) the currently focused card on the same slot.
1464
* If the focused card is already the bottommost (topmost) card
1465
* of the slot, or the slot is not down-extended, moves the focus to
1466
* the topmost (bottommost) card of the slot over (under) the currently
1467
* focused slot (wraps around vertically)
1468
* Home (End): For down- or right-extended slots, moves the focus to the
1469
* start (end) of the stack of face-up cards on the currently focused slot.
1470
* If the focus is already on that card, moves the focus over a stack of
1471
* face-down cards into the next stack of face-up cards (e.g. Athena), or
1472
* the bottommost (topmost) card on the slot if there are no such cards.
1473
* PageUp, PageDown: Acts like <control>Up, <control>Down
1474
* Space: selects the cards from the currently focused card to the topmost
1475
* card of the slot, if this is allowed by the game. If the focused card is
1476
* the first selected card already, unsets the selection.
1477
* Return: Performs the button press action on the focused card.
1478
* If no action was performed by that, moves the selected card(s) to the top
1479
* of the focused slot, if this is allowed by the game.
1481
* With <control>, for focus movement:
1482
* Left (Right): Moves the focus to the bottommost (topmost) card on the
1483
* slot; if that card is already focused, moves the focus to the
1484
* topmost/bottommost card on the slot left/right to the currently
1485
* focused slot (wraps around from first to last slot and vv.)
1486
* Up (Down): Moves the focus to the topmost (bottommost) card of the slot
1487
* over (under) the currently focused slot (wraps around vertically)
1488
* Home (End): moves the focus to the bottommost (topmost) card on the
1490
* Return: Performs the double-click action on the focused card
1492
* With <shift>: extends the selection; focus movement itself occurs
1493
* like for the same key without modifiers.
1494
* Left (Right): for right-extended slots, extends (shrinks) the selection
1495
* by one card, if this is allowed by the game
1496
* Up (Down): for down-extended slots, extends (shrinks) the selection
1497
* by one card, if this is allowed by the game
1498
* Home: extends the selection maximally in the focused slot, as allowed
1500
* End: shrinks the selection into nonexistence
1502
* With <control><shift>: extends selection like with <shift> alone,
1503
* and moves focus like with <control> alone
1505
* Other keyboard shortcuts:
1506
* <control>A: extends the selection maximally in the focused slot, as allowed
1508
* <shift><control>A: unsets the selection
1511
* If no slot is currently focused:
1512
* Right, Up, Down, PgUp, PgDown, Home: moves the focus to the bottommost card on the first
1514
* Left, End: moves the focus to the topmost card on the last slot
1518
aisleriot_board_add_move_binding (ClutterBindingPool *binding_pool,
1522
ClutterModifierType modifiers)
1524
clutter_binding_pool_install_closure (binding_pool,
1530
if (modifiers & CLUTTER_CONTROL_MASK)
1533
clutter_binding_pool_install_closure (binding_pool,
1536
modifiers | CLUTTER_CONTROL_MASK,
1541
aisleriot_board_add_move_and_select_binding (ClutterBindingPool *binding_pool,
1545
ClutterModifierType modifiers)
1547
aisleriot_board_add_move_binding (binding_pool, closure, action, keyval, modifiers);
1548
aisleriot_board_add_move_binding (binding_pool, closure, action, keyval, modifiers | CLUTTER_SHIFT_MASK);
1552
aisleriot_board_add_activate_binding (ClutterBindingPool *binding_pool,
1555
ClutterModifierType modifiers)
1557
clutter_binding_pool_install_closure (binding_pool,
1563
if (modifiers & CLUTTER_CONTROL_MASK)
1566
clutter_binding_pool_install_closure (binding_pool,
1569
modifiers | CLUTTER_CONTROL_MASK,
1574
aisleriot_board_move_cursor_in_slot (AisleriotBoard *board,
1577
AisleriotBoardPrivate *priv = board->priv;
1579
int new_focus_card_id, first_card_id;
1581
focus_slot = priv->focus_slot;
1582
first_card_id = ((int) focus_slot->cards->len) - ((int) focus_slot->exposed);
1583
new_focus_card_id = priv->focus_card_id + count;
1584
if (new_focus_card_id < first_card_id || new_focus_card_id >= (int) focus_slot->cards->len)
1587
set_focus (board, focus_slot, new_focus_card_id, TRUE);
1592
aisleriot_board_move_cursor_start_end_in_slot (AisleriotBoard *board,
1595
AisleriotBoardPrivate *priv = board->priv;
1596
ArSlot *focus_slot = priv->focus_slot;
1597
int first_card_id, top_card_id, new_focus_card_id;
1600
if (focus_slot->cards->len == 0)
1603
g_assert (priv->focus_card_id >= 0);
1605
/* Moves the cursor to the first/last card above/below a face-down
1606
* card, or the start/end of the slot if there are no face-down cards
1607
* between the currently focused card and the slot start/end.
1608
* (Jumping over face-down cards and landing on a non-face-down card
1609
* happens e.g. in Athena.)
1611
cards = focus_slot->cards->data;
1612
top_card_id = ((int) focus_slot->cards->len) - 1;
1613
first_card_id = ((int) focus_slot->cards->len) - ((int) focus_slot->exposed);
1614
new_focus_card_id = priv->focus_card_id;
1616
/* Set new_focus_card_id to the index of the last face-down card
1617
* in the run of face-down cards.
1620
new_focus_card_id += count;
1621
} while (new_focus_card_id >= first_card_id &&
1622
new_focus_card_id <= top_card_id &&
1623
CARD_GET_FACE_DOWN (((Card) cards[new_focus_card_id])));
1625
/* We went one too far */
1626
new_focus_card_id -= count;
1628
/* Now get to the start/end of the run of face-up cards */
1630
new_focus_card_id += count;
1631
} while (new_focus_card_id >= first_card_id &&
1632
new_focus_card_id <= top_card_id &&
1633
!CARD_GET_FACE_DOWN (((Card) cards[new_focus_card_id])));
1635
if (new_focus_card_id < first_card_id ||
1636
new_focus_card_id > top_card_id ||
1637
CARD_GET_FACE_DOWN (((Card) cards[new_focus_card_id]))) {
1638
/* We went one too far */
1639
new_focus_card_id -= count;
1642
new_focus_card_id = CLAMP (new_focus_card_id, first_card_id, top_card_id);
1643
set_focus (board, focus_slot, new_focus_card_id, TRUE);
1649
aisleriot_board_extend_selection_in_slot (AisleriotBoard *board,
1652
AisleriotBoardPrivate *priv = board->priv;
1653
ArSlot *focus_slot, *selection_slot;
1654
int new_selection_start_card_id, first_card_id;
1656
focus_slot = priv->focus_slot;
1657
selection_slot = priv->selection_slot;
1658
first_card_id = ((int) focus_slot->cards->len) - ((int) focus_slot->exposed);
1660
if (selection_slot == focus_slot) {
1661
new_selection_start_card_id = priv->selection_start_card_id + count;
1663
/* Can only extend the selection if the focus is adjacent to the selection */
1664
if (priv->focus_card_id - 1 > new_selection_start_card_id ||
1665
priv->focus_card_id + 1 < new_selection_start_card_id)
1668
/* No selection yet */
1669
new_selection_start_card_id = ((int) focus_slot->cards->len) + count;
1671
/* Must have the topmost card focused */
1672
if (new_selection_start_card_id != priv->focus_card_id)
1676
if (new_selection_start_card_id < first_card_id)
1679
/* If it's the top card, unselect all */
1680
if (new_selection_start_card_id >= focus_slot->cards->len) {
1681
set_selection (board, NULL, -1, FALSE);
1685
if (!aisleriot_game_drag_valid (priv->game,
1687
focus_slot->cards->data + new_selection_start_card_id,
1688
focus_slot->cards->len - new_selection_start_card_id))
1691
set_selection (board, focus_slot, new_selection_start_card_id, TRUE);
1693
/* Try to move the cursor too, but don't beep if that fails */
1694
aisleriot_board_move_cursor_in_slot (board, count);
1699
aisleriot_board_extend_selection_in_slot_maximal (AisleriotBoard *board)
1701
AisleriotBoardPrivate *priv = board->priv;
1702
ArSlot *focus_slot = priv->focus_slot;
1703
int new_selection_start_card_id, n_selected;
1706
new_selection_start_card_id = ((int) focus_slot->cards->len) - 1;
1707
while (new_selection_start_card_id >= 0) {
1708
if (!aisleriot_game_drag_valid (priv->game,
1710
focus_slot->cards->data + new_selection_start_card_id,
1711
focus_slot->cards->len - new_selection_start_card_id))
1715
--new_selection_start_card_id;
1718
if (n_selected == 0)
1721
set_selection (board, focus_slot, new_selection_start_card_id + 1, TRUE);
1726
aisleriot_board_move_cursor_left_right_by_slot (AisleriotBoard *board,
1730
AisleriotBoardPrivate *priv = board->priv;
1733
ArSlot *focus_slot, *new_focus_slot;
1734
int focus_slot_index, new_focus_slot_index;
1735
int new_focus_slot_topmost_card_id, new_focus_card_id;
1738
slots = aisleriot_game_get_slots (priv->game);
1739
if (!slots || slots->len == 0)
1742
n_slots = slots->len;
1744
focus_slot = priv->focus_slot;
1745
g_assert (focus_slot != NULL);
1747
focus_slot_index = get_slot_index_from_slot (board, focus_slot);
1750
is_rtl = priv->is_rtl;
1752
new_focus_slot_index = focus_slot_index - count;
1754
new_focus_slot_index = focus_slot_index + count;
1758
if (new_focus_slot_index < 0 ||
1759
new_focus_slot_index >= n_slots) {
1765
GtkDirectionType direction;
1768
direction = GTK_DIR_RIGHT;
1770
direction = GTK_DIR_LEFT;
1773
if (!gtk_widget_keynav_failed (widget, direction)) {
1774
return gtk_widget_child_focus (gtk_widget_get_toplevel (widget), direction);
1779
if (new_focus_slot_index < 0) {
1780
new_focus_slot_index = ((int) n_slots) - 1;
1782
new_focus_slot_index = 0;
1786
g_assert (new_focus_slot_index >= 0 && new_focus_slot_index < n_slots);
1788
new_focus_slot = slots->pdata[new_focus_slot_index];
1789
new_focus_slot_topmost_card_id = ((int) new_focus_slot->cards->len) - 1;
1791
if (new_focus_slot->expanded_right) {
1792
if ((is_rtl && count < 0) ||
1793
(!is_rtl && count > 0)) {
1794
if (new_focus_slot->cards->len > 0) {
1795
new_focus_card_id = ((int) new_focus_slot->cards->len) - ((int) new_focus_slot->exposed);
1797
new_focus_card_id = -1;
1800
new_focus_card_id = new_focus_slot_topmost_card_id;
1803
/* Just take the topmost card */
1804
new_focus_card_id = new_focus_slot_topmost_card_id;
1807
set_focus (board, new_focus_slot, new_focus_card_id, TRUE);
1812
aisleriot_board_move_cursor_up_down_by_slot (AisleriotBoard *board,
1815
AisleriotBoardPrivate *priv = board->priv;
1818
ArSlot *focus_slot, *new_focus_slot;
1819
int focus_slot_index, new_focus_slot_index;
1820
int new_focus_slot_topmost_card_id, new_focus_card_id;
1823
slots = aisleriot_game_get_slots (priv->game);
1824
if (!slots || slots->len == 0)
1827
n_slots = slots->len;
1829
focus_slot = priv->focus_slot;
1830
g_assert (focus_slot != NULL);
1832
x_start = focus_slot->rect.x;
1833
x_end = x_start + focus_slot->rect.width;
1835
focus_slot_index = get_slot_index_from_slot (board, focus_slot);
1837
new_focus_slot_index = focus_slot_index;
1839
new_focus_slot_index += count;
1840
} while (new_focus_slot_index >= 0 &&
1841
new_focus_slot_index < n_slots &&
1842
!test_slot_projection_intersects_x (slots->pdata[new_focus_slot_index], x_start, x_end));
1844
if (new_focus_slot_index < 0 || new_focus_slot_index == n_slots) {
1846
GtkWidget *widget = GTK_WIDGET (board);
1847
GtkDirectionType direction;
1850
direction = GTK_DIR_DOWN;
1852
direction = GTK_DIR_UP;
1855
if (!gtk_widget_keynav_failed (widget, direction)) {
1856
return gtk_widget_child_focus (gtk_widget_get_toplevel (widget), direction);
1862
new_focus_slot_index = -1;
1864
new_focus_slot_index = n_slots;
1868
new_focus_slot_index += count;
1869
} while (new_focus_slot_index != focus_slot_index &&
1870
!test_slot_projection_intersects_x (slots->pdata[new_focus_slot_index], x_start, x_end));
1873
g_assert (new_focus_slot_index >= 0 && new_focus_slot_index < n_slots);
1875
new_focus_slot = slots->pdata[new_focus_slot_index];
1876
new_focus_slot_topmost_card_id = ((int) new_focus_slot->cards->len) - 1;
1878
if (new_focus_slot->expanded_down) {
1880
if (new_focus_slot->cards->len > 0) {
1881
new_focus_card_id = ((int) new_focus_slot->cards->len) - ((int) new_focus_slot->exposed);
1883
new_focus_card_id = -1;
1886
new_focus_card_id = new_focus_slot_topmost_card_id;
1889
/* Just take the topmost card */
1890
new_focus_card_id = new_focus_slot_topmost_card_id;
1893
set_focus (board, new_focus_slot, new_focus_card_id, TRUE);
1898
aisleriot_board_move_cursor_start_end_by_slot (AisleriotBoard *board,
1901
AisleriotBoardPrivate *priv = board->priv;
1903
ArSlot *new_focus_slot;
1904
int new_focus_card_id;
1906
slots = aisleriot_game_get_slots (priv->game);
1907
if (!slots || slots->len == 0)
1911
new_focus_slot = (ArSlot *) slots->pdata[slots->len - 1];
1912
new_focus_card_id = ((int) new_focus_slot->cards->len) - 1;
1914
new_focus_slot = (ArSlot *) slots->pdata[0];
1915
if (new_focus_slot->cards->len > 0) {
1916
new_focus_card_id = ((int) new_focus_slot->cards->len) - ((int) new_focus_slot->exposed);
1918
new_focus_card_id = -1;
1922
g_assert (new_focus_slot != NULL);
1923
g_assert (new_focus_card_id >= -1);
1925
set_focus (board, new_focus_slot, new_focus_card_id, TRUE);
1930
aisleriot_board_move_cursor_left_right (AisleriotBoard *board,
1932
gboolean is_control)
1934
AisleriotBoardPrivate *priv = board->priv;
1936
/* First try in-slot focus movement */
1938
priv->focus_slot->expanded_right &&
1939
aisleriot_board_move_cursor_in_slot (board, priv->is_rtl ? -count : count))
1942
/* Cannot move in-slot; move focused slot */
1943
return aisleriot_board_move_cursor_left_right_by_slot (board, count, TRUE);
1947
aisleriot_board_move_cursor_up_down (AisleriotBoard *board,
1949
gboolean is_control)
1951
AisleriotBoardPrivate *priv = board->priv;
1953
g_assert (priv->focus_slot != NULL);
1955
/* First try in-slot focus movement */
1957
priv->focus_slot->expanded_down &&
1958
aisleriot_board_move_cursor_in_slot (board, count))
1961
/* Cannot move in-slot; move focused slot */
1962
return aisleriot_board_move_cursor_up_down_by_slot (board, count);
1966
aisleriot_board_extend_selection_left_right (AisleriotBoard *board,
1969
AisleriotBoardPrivate *priv = board->priv;
1971
if (!priv->focus_slot->expanded_right)
1974
return aisleriot_board_extend_selection_in_slot (board, count);
1978
aisleriot_board_extend_selection_up_down (AisleriotBoard *board,
1981
AisleriotBoardPrivate *priv = board->priv;
1983
if (!priv->focus_slot->expanded_down)
1986
return aisleriot_board_extend_selection_in_slot (board, count);
1990
aisleriot_board_extend_selection_start_end (AisleriotBoard *board,
1993
AisleriotBoardPrivate *priv = board->priv;
1994
ArSlot *focus_slot = priv->focus_slot;
1995
int new_focus_card_id;
1998
/* Can only shrink the selection if the focus is on the selected slot,
1999
* and the focused card is on or below the start of the selection.
2001
if (priv->selection_slot == focus_slot &&
2002
priv->selection_start_card_id >= priv->focus_card_id) {
2003
set_selection (board, NULL, -1, FALSE);
2004
new_focus_card_id = ((int) focus_slot->cards->len);
2006
aisleriot_board_error_bell (board);
2011
if (!aisleriot_board_extend_selection_in_slot_maximal (board)) {
2012
set_selection (board, NULL, -1, FALSE);
2013
aisleriot_board_error_bell (board);
2017
new_focus_card_id = priv->selection_start_card_id;
2020
set_focus (board, focus_slot, new_focus_card_id, TRUE);
2024
#endif /* ENABLE_KEYNAV */
2026
/* Game state handling */
2029
game_type_changed_cb (AisleriotGame *game,
2030
AisleriotBoard *board)
2032
AisleriotBoardPrivate *priv = board->priv;
2035
features = aisleriot_game_get_features (game);
2037
priv->droppable_supported = ((features & FEATURE_DROPPABLE) != 0);
2038
priv->show_highlight = priv->droppable_supported;
2042
game_cleared_cb (AisleriotGame *game,
2043
AisleriotBoard *board)
2045
AisleriotBoardPrivate *priv = board->priv;
2047
priv->geometry_set = FALSE;
2049
/* So we don't re-add the cards to the now-dead slot */
2050
priv->highlight_slot = NULL;
2051
priv->last_clicked_slot = NULL;
2052
priv->moving_cards_origin_slot = NULL;
2053
priv->selection_slot = NULL;
2054
priv->show_card_slot = NULL;
2056
clear_state (board);
2060
game_new_cb (AisleriotGame *game,
2061
AisleriotBoard *board)
2063
AisleriotBoardPrivate *priv = board->priv;
2065
clear_state (board);
2067
set_focus (board, NULL, -1, FALSE);
2068
set_selection (board, NULL, -1, FALSE);
2070
aisleriot_game_get_geometry (game, &priv->width, &priv->height);
2072
aisleriot_board_setup_geometry (board);
2075
g_print ("{ %.3f , %.3f /* %s */ },\n",
2076
priv->width, priv->height,
2077
aisleriot_game_get_game_file (priv->game));
2080
/* Check for animations so that the differences will be reset */
2081
queue_check_animations (board);
2085
slot_changed_cb (AisleriotGame *game,
2087
AisleriotBoard *board)
2089
AisleriotBoardPrivate *priv = board->priv;
2091
slot_update_geometry (board, slot);
2092
slot_update_card_images (board, slot);
2094
if (slot == priv->moving_cards_origin_slot) {
2098
if (slot == priv->selection_slot) {
2099
set_selection (board, NULL, -1, FALSE);
2101
/* If this slot changes while we're in a click cycle, abort the action.
2102
* That prevents a problem where the cards that were selected and
2103
* about to be dragged have vanished because of autoplay; c.f. bug #449767.
2104
* Note: we don't use clear_state() here since we only want to disable
2105
* the highlight and revealed card if that particular slot changed, see
2106
* the code above. And we don't clear last_clicked_slot/card_id either, so
2107
* a double-click will still work.
2109
priv->click_status = STATUS_NONE;
2111
if (slot == priv->focus_slot) {
2112
/* Try to keep the focus intact. If the focused card isn't there
2113
* anymore, this will set the focus to the topmost card of there
2114
* same slot, or the slot itself if there are no cards on it.
2115
* If the slot was empty but now isn't, we set the focus to the
2118
if (priv->focus_card_id < 0) {
2119
set_focus (board, slot, ((int) slot->cards->len) - 1, priv->show_focus);
2121
set_focus (board, slot, priv->focus_card_id, priv->show_focus);
2124
if (slot == priv->highlight_slot) {
2125
highlight_drop_target (board, NULL);
2128
queue_check_animations (board);
2131
/* Style handling */
2134
aisleriot_board_sync_style (ArStyle *style,
2136
AisleriotBoard *board)
2138
AisleriotBoardPrivate *priv = board->priv;
2139
const char *pspec_name;
2140
gboolean update_geometry = FALSE, redraw_focus = FALSE;
2142
g_assert (style == priv->style);
2144
if (pspec != NULL) {
2145
pspec_name = pspec->name;
2150
if (pspec_name == NULL || pspec_name == I_(AR_STYLE_PROP_CARD_THEME)) {
2153
theme = ar_style_get_card_theme (style);
2154
if (theme != NULL) {
2155
ar_card_textures_cache_set_theme (priv->textures, theme);
2157
priv->geometry_set = FALSE;
2159
update_geometry |= TRUE;
2163
if (pspec_name == NULL || pspec_name == I_(AR_STYLE_PROP_CARD_SLOT_RATIO)) {
2164
double card_slot_ratio;
2166
card_slot_ratio = ar_style_get_card_slot_ratio (style);
2168
update_geometry |= (card_slot_ratio != priv->card_slot_ratio);
2170
priv->card_slot_ratio = card_slot_ratio;
2173
if (pspec_name == NULL || pspec_name == I_(AR_STYLE_PROP_CARD_OVERHANG)) {
2174
update_geometry |= TRUE;
2177
if (pspec_name == NULL || pspec_name == I_(AR_STYLE_PROP_CARD_STEP)) {
2178
update_geometry |= TRUE;
2181
if (pspec_name == NULL || pspec_name == I_(AR_STYLE_PROP_INTERIOR_FOCUS)) {
2182
gboolean interior_focus;
2184
interior_focus = ar_style_get_interior_focus (style);
2186
redraw_focus = (interior_focus != priv->interior_focus);
2188
priv->interior_focus = interior_focus;
2191
if (pspec_name == NULL || pspec_name == I_(AR_STYLE_PROP_FOCUS_LINE_WIDTH)) {
2192
int focus_line_width;
2194
focus_line_width = ar_style_get_focus_line_width (style);
2196
redraw_focus = (focus_line_width != priv->focus_line_width);
2198
priv->focus_line_width = focus_line_width;
2201
if (pspec_name == NULL || pspec_name == I_(AR_STYLE_PROP_FOCUS_PADDING)) {
2204
focus_padding = ar_style_get_focus_padding (style);
2206
redraw_focus = (focus_padding != priv->focus_padding);
2208
priv->focus_padding = focus_padding;
2211
if (pspec_name == NULL || pspec_name == I_(AR_STYLE_PROP_RTL)) {
2214
is_rtl = ar_style_get_rtl (style);
2216
update_geometry |= (is_rtl != priv->is_rtl);
2218
priv->is_rtl = is_rtl;
2220
/* FIXMEchpe: necessary? */
2221
priv->force_geometry_update = TRUE;
2224
if (pspec_name == NULL || pspec_name == I_(AR_STYLE_PROP_ENABLE_ANIMATIONS)) {
2225
/* FIXMEchpe: abort animations-in-progress if the setting is now OFF */
2228
if (pspec_name == NULL || pspec_name == I_(AR_STYLE_PROP_CLICK_TO_MOVE)) {
2229
gboolean click_to_move;
2231
click_to_move = ar_style_get_click_to_move (style);
2232
if (click_to_move != priv->click_to_move) {
2233
/* Clear the selection. Do this before setting the new value,
2234
* since otherwise selection won't get cleared correctly.
2236
set_selection (board, NULL, -1, FALSE);
2238
priv->click_to_move = click_to_move;
2240
/* FIXMEchpe: we used to queue a redraw here. WHY?? Check that it's safe not to. */
2244
if (pspec_name == NULL || pspec_name == I_(AR_STYLE_PROP_SHOW_STATUS_MESSAGES)) {
2245
gboolean show_status_messages;
2247
show_status_messages = ar_style_get_show_status_messages (priv->style);
2249
if (show_status_messages != priv->show_status_messages) {
2250
priv->show_status_messages = show_status_messages;
2252
if (!show_status_messages) {
2254
set_status_message (board, NULL);
2259
/* FIXMEchpe: queue a relayout instead? */
2260
if (update_geometry) {
2261
aisleriot_board_setup_geometry (board);
2265
/* FIXMEchpe: do redraw the focus! */
2269
/* Class implementation */
2271
G_DEFINE_TYPE (AisleriotBoard, aisleriot_board, CLUTTER_TYPE_GROUP);
2273
/* AisleriotBoardClass methods */
2275
#ifdef ENABLE_KEYNAV
2278
aisleriot_board_activate (AisleriotBoard *board,
2281
ClutterModifierType modifiers)
2283
AisleriotBoardPrivate *priv = board->priv; ArSlot *focus_slot = priv->focus_slot;
2284
ArSlot *selection_slot = priv->selection_slot;
2285
int selection_start_card_id = priv->selection_start_card_id;
2288
if (!gtk_widget_has_focus (widget))
2292
ar_debug_print (AR_DEBUG_GAME_KEYNAV,
2293
"board ::activate keyval %x modifiers %x\n",
2297
aisleriot_board_error_bell (board);
2301
/* Focus not shown? Show it, and do nothing else */
2302
if (!priv->show_focus) {
2303
set_focus (board, focus_slot, priv->focus_card_id, TRUE);
2307
/* Control-Activate is double-click */
2308
if (modifiers & CLUTTER_CONTROL_MASK) {
2309
aisleriot_game_record_move (priv->game, -1, NULL, 0);
2310
if (aisleriot_game_button_double_clicked_lambda (priv->game, focus_slot->id)) {
2311
aisleriot_game_end_move (priv->game);
2313
aisleriot_game_discard_move (priv->game);
2314
aisleriot_board_error_bell (board);
2317
aisleriot_game_test_end_of_game (priv->game);
2322
/* Try single click action */
2323
aisleriot_game_record_move (priv->game, -1, NULL, 0);
2325
if (aisleriot_game_button_clicked_lambda (priv->game, focus_slot->id)) {
2326
aisleriot_game_end_move (priv->game);
2327
ar_sound_play ("click");
2328
aisleriot_game_test_end_of_game (priv->game);
2333
aisleriot_game_discard_move (priv->game);
2335
/* If we have a selection, and the topmost card of a slot is focused,
2336
* try to move the selected cards to the focused slot.
2337
* Note that this needs to be tested even if selection_slot == focus_slot !
2339
* NOTE: We cannot use aisleriot_game_drop_valid here since the
2340
* game may not support the "droppable" feature.
2342
if (selection_slot != NULL &&
2343
selection_start_card_id >= 0 &&
2344
priv->focus_card_id == ((int) focus_slot->cards->len) - 1) {
2345
if (aisleriot_board_move_selected_cards_to_slot (board, focus_slot)) {
2346
/* Select the new topmost card */
2347
set_focus (board, focus_slot, ((int) focus_slot->cards->len - 1), TRUE);
2352
/* Trying to move the cards has unset the selection; re-select them */
2353
set_selection (board, selection_slot, selection_start_card_id, TRUE);
2356
aisleriot_board_error_bell (board);
2360
aisleriot_board_move_cursor (AisleriotBoard *board,
2363
ClutterModifierType modifiers)
2365
AisleriotBoardPrivate *priv = board->priv;
2366
gboolean is_control, is_shift, moved = FALSE;
2371
if (!gtk_widget_has_focus (widget))
2376
count = (action[1] == MOVE_LEFT ? -1 : 1);
2378
ar_debug_print (AR_DEBUG_GAME_KEYNAV,
2379
"board ::move-cursor keyval %x modifiers %x step '%c' count %d\n",
2383
/* No focus? Set focus to the first/last slot */
2384
/* This will always return TRUE, no need for keynav-failed handling */
2385
if (!priv->focus_slot) {
2387
case MOVE_CURSOR_UP_DOWN:
2388
case MOVE_CURSOR_PAGES:
2389
/* Focus the first slot */
2390
return aisleriot_board_move_cursor_start_end_by_slot (board, -1);
2391
case MOVE_CURSOR_LEFT_RIGHT:
2392
/* Move as if we'd been on the last/first slot */
2393
if (!priv->is_rtl) {
2394
return aisleriot_board_move_cursor_start_end_by_slot (board, -count);
2398
return aisleriot_board_move_cursor_start_end_by_slot (board, count);
2402
g_assert (priv->focus_slot != NULL);
2404
is_shift = (modifiers & CLUTTER_SHIFT_MASK) != 0;
2405
is_control = (modifiers & CLUTTER_CONTROL_MASK) != 0;
2408
case MOVE_CURSOR_LEFT_RIGHT:
2410
moved = aisleriot_board_extend_selection_left_right (board, count);
2412
moved = aisleriot_board_move_cursor_left_right (board, count, is_control);
2415
case MOVE_CURSOR_UP_DOWN:
2417
moved = aisleriot_board_extend_selection_up_down (board, count);
2419
moved = aisleriot_board_move_cursor_up_down (board, count, is_control);
2422
case MOVE_CURSOR_PAGES:
2424
moved = aisleriot_board_move_cursor_up_down (board, count, TRUE);
2427
case MOVE_CURSOR_START_END:
2429
moved = aisleriot_board_extend_selection_start_end (board, count);
2430
} else if (is_control) {
2431
moved = aisleriot_board_move_cursor_start_end_by_slot (board, count);
2433
moved = aisleriot_board_move_cursor_start_end_in_slot (board, count);
2437
g_assert_not_reached ();
2442
!priv->show_focus) {
2443
set_focus (board, priv->focus_slot, priv->focus_card_id, TRUE);
2450
aisleriot_board_select_all (AisleriotBoard *board,
2453
ClutterModifierType modifiers)
2455
AisleriotBoardPrivate *priv = board->priv;
2456
ArSlot *focus_slot = priv->focus_slot;
2458
ar_debug_print (AR_DEBUG_GAME_KEYNAV,
2459
"board ::select-all keyval %x modifiers %x\n",
2463
focus_slot->cards->len == 0 ||
2464
!aisleriot_board_extend_selection_in_slot_maximal (board)) {
2465
set_selection (board, NULL, -1, FALSE);
2466
aisleriot_board_error_bell (board);
2471
aisleriot_board_deselect_all (AisleriotBoard *board,
2474
ClutterModifierType modifiers)
2476
ar_debug_print (AR_DEBUG_GAME_KEYNAV,
2477
"board ::deselect-all keyval %x modifiers %x\n",
2480
set_selection (board, NULL, -1, FALSE);
2484
aisleriot_board_toggle_selection (AisleriotBoard *board,
2487
ClutterModifierType modifiers)
2489
AisleriotBoardPrivate *priv = board->priv;
2493
ar_debug_print (AR_DEBUG_GAME_KEYNAV,
2494
"board ::toggle-selection keyval %x modifiers %x\n",
2497
focus_slot = priv->focus_slot;
2501
focus_card_id = priv->focus_card_id;
2503
/* Focus not shown? Show it, and proceed */
2504
if (!priv->show_focus) {
2505
set_focus (board, focus_slot, focus_card_id, TRUE);
2508
if (focus_card_id < 0) {
2509
aisleriot_board_error_bell (board);
2513
/* If the selection isn't currently showing, don't truncate it.
2514
* Otherwise we get unexpected results when clicking on some cards
2515
* (which selects them but doesn't show the selection) and then press
2516
* Space or Shift-Up/Down etc.
2518
if (priv->selection_slot == focus_slot &&
2519
priv->selection_start_card_id == focus_card_id &&
2520
priv->show_selection) {
2521
set_selection (board, NULL, -1, FALSE);
2525
if (!aisleriot_game_drag_valid (priv->game,
2527
focus_slot->cards->data + focus_card_id,
2528
focus_slot->cards->len - focus_card_id)) {
2529
aisleriot_board_error_bell (board);
2533
set_selection (board, focus_slot, focus_card_id, TRUE);
2536
#endif /* ENABLE_KEYNAV */
2539
/* ClutterActorClass impl */
2543
aisleriot_board_realize (GtkWidget *widget)
2545
AisleriotBoard *board = AISLERIOT_BOARD (widget);
2546
// AisleriotBoardPrivate *priv = board->priv;
2548
GTK_WIDGET_CLASS (aisleriot_board_parent_class)->realize (widget);
2550
aisleriot_board_setup_geometry (board);
2554
aisleriot_board_unrealize (GtkWidget *widget)
2556
AisleriotBoard *board = AISLERIOT_BOARD (widget);
2557
AisleriotBoardPrivate *priv = board->priv;
2559
priv->geometry_set = FALSE;
2561
clear_state (board);
2563
GTK_WIDGET_CLASS (aisleriot_board_parent_class)->unrealize (widget);
2568
aisleriot_board_allocate (ClutterActor *actor,
2569
const ClutterActorBox *box,
2570
ClutterAllocationFlags flags)
2572
AisleriotBoard *board = AISLERIOT_BOARD (actor);
2573
AisleriotBoardPrivate *priv = board->priv;
2576
is_same = clutter_actor_box_equal (box, &priv->allocation);
2578
ar_debug_print (AR_DEBUG_GAME_SIZING,
2579
"board ::allocate (%f / %f)-(%f / %f) => %f x %f is-same %s force-update %s\n",
2580
box->x1, box->y1, box->x2, box->y2,
2581
box->x2 - box->x1, box->y2 - box->y1,
2582
is_same ? "t" : "f",
2583
priv->force_geometry_update ? "t" : "f");
2585
CLUTTER_ACTOR_CLASS (aisleriot_board_parent_class)->allocate (actor, box, flags);
2587
priv->allocation = *box;
2589
if (is_same && !priv->force_geometry_update)
2592
priv->force_geometry_update = FALSE;
2594
/* FIXMEchpe: just queue this instead maybe? */
2595
aisleriot_board_setup_geometry (board);
2599
aisleriot_board_get_preferred_width (ClutterActor *actor,
2602
float *natural_width_p)
2604
ar_debug_print (AR_DEBUG_GAME_SIZING,
2605
"board ::get-preferred-width\n");
2607
*min_width_p = BOARD_MIN_WIDTH;
2608
*natural_width_p = 3 * BOARD_MIN_WIDTH;
2612
aisleriot_board_get_preferred_height (ClutterActor *actor,
2614
float *min_height_p,
2615
float *natural_height_p)
2617
ar_debug_print (AR_DEBUG_GAME_SIZING,
2618
"board ::get-preferred-height\n");
2620
*min_height_p = BOARD_MIN_HEIGHT;
2621
*natural_height_p = 3 * BOARD_MIN_HEIGHT;
2624
#ifdef ENABLE_KEYNAV
2627
aisleriot_board_focus (AisleriotBoard *board,
2630
AisleriotBoardPrivate *priv = board->priv;
2632
if (!priv->focus_slot) {
2633
return aisleriot_board_move_cursor_start_end_by_slot (board, -count);
2637
if (aisleriot_board_move_cursor_left_right_by_slot (board, count, FALSE))
2645
aisleriot_board_key_press (ClutterActor *actor,
2646
ClutterKeyEvent *event)
2648
ClutterBindingPool *pool;
2650
ar_debug_print (AR_DEBUG_GAME_EVENTS,
2651
"board ::key-press keyval %x modifiers %x\n",
2652
event->keyval, event->modifier_state);
2654
pool = clutter_binding_pool_get_for_class (CLUTTER_ACTOR_GET_CLASS (actor));
2655
g_assert (pool != NULL);
2657
return clutter_binding_pool_activate (pool,
2659
event->modifier_state,
2663
#endif /* ENABLE_KEYNAV */
2666
/* The gtkwidget.c focus in/out handlers queue a shallow draw;
2667
* that's ok for us but maybe we want to optimise this a bit to
2668
* only do it if we have a focus to draw/erase?
2671
aisleriot_board_focus_in (GtkWidget *widget,
2672
GdkEventFocus *event)
2674
#ifdef ENABLE_KEYNAV
2675
AisleriotBoard *board = AISLERIOT_BOARD (widget);
2676
AisleriotBoardPrivate *priv = board->priv;
2679
if (priv->show_focus &&
2680
priv->focus_slot != NULL) {
2681
gdk_window_invalidate_rect (widget->window, &priv->focus_rect, FALSE);
2683
#endif /* ENABLE_KEYNAV */
2689
aisleriot_board_focus_out (GtkWidget *widget,
2690
GdkEventFocus *event)
2692
AisleriotBoard *board = AISLERIOT_BOARD (widget);
2693
#ifdef ENABLE_KEYNAV
2694
AisleriotBoardPrivate *priv = board->priv;
2695
#endif /* ENABLE_KEYNAV */
2697
clear_state (board);
2699
#ifdef ENABLE_KEYNAV
2701
if (priv->show_focus &&
2702
priv->focus_slot != NULL) {
2703
gdk_window_invalidate_rect (widget->window, &priv->focus_rect, FALSE);
2705
#endif /* ENABLE_KEYNAV */
2709
#endif /* FIXMEchpe */
2712
aisleriot_board_button_press (ClutterActor *actor,
2713
ClutterButtonEvent *event)
2715
AisleriotBoard *board = AISLERIOT_BOARD (actor);
2716
AisleriotBoardPrivate *priv = board->priv;
2720
gboolean drag_valid;
2722
gboolean is_double_click, show_focus;
2724
ar_debug_print (AR_DEBUG_GAME_EVENTS,
2725
"board ::button-press @(%f / %f) button %d click-count %d modifiers %x\n",
2727
event->button, event->click_count,
2728
event->modifier_state);
2730
/* NOTE: It's ok to just return instead of chaining up, since the
2731
* parent classes have no class closure for this event.
2734
/* FIXMEchpe: check event coordinate handling (float vs int!) */
2737
/* FIXMEchpe: we might be able to use ClutterButtonEvent::click_count for double-click detection! */
2738
/* ignore the gdk synthetic double/triple click events */
2739
if (event->type != GDK_BUTTON_PRESS)
2743
/* Don't do anything if a modifier is pressed */
2744
/* FIXMEchpe: is there anything like gtk_accelerator_get_default_mod_mask() in clutter? */
2745
state = event->modifier_state & CLUTTER_DEFAULT_MOD_MASK;
2749
button = event->button;
2751
/* We're only interested in left, middle and right-clicks */
2752
if (button < 1 || button > 3)
2755
/* If we already have a click, ignore this new one */
2756
if (priv->click_status != STATUS_NONE) {
2760
/* If the game hasn't started yet, start it now */
2761
aisleriot_game_start (priv->game);
2763
get_slot_and_card_from_point (board, event->x, event->y, &hslot, &cardid);
2765
is_double_click = button == 2 ||
2766
(priv->last_click_left_click &&
2767
(event->time - priv->last_click_time <= ar_style_get_double_click_time (priv->style)) &&
2768
priv->last_clicked_slot == hslot &&
2769
priv->last_clicked_card_id == cardid);
2771
priv->last_click_x = event->x;
2772
priv->last_click_y = event->y;
2773
priv->last_clicked_slot = hslot;
2774
priv->last_clicked_card_id = cardid;
2775
priv->last_click_time = event->time;
2776
priv->last_click_left_click = button == 1;
2779
set_focus (board, NULL, -1, FALSE);
2780
set_selection (board, NULL, -1, FALSE);
2782
priv->click_status = STATUS_NONE;
2787
set_cursor (board, AR_CURSOR_CLOSED);
2789
/* First check if it's a right-click: if so, we reveal the card and do nothing else */
2791
/* Don't change the selection here! */
2792
reveal_card (board, hslot, cardid);
2797
/* Clear revealed card */
2798
reveal_card (board, NULL, -1);
2800
/* We can't let Gdk do the double-click detection; since the entire playing
2801
* area is one big widget it can't distinguish between single-clicks on two
2802
* different cards and a double-click on one card.
2804
if (is_double_click) {
2805
ArSlot *clicked_slot = hslot;
2807
priv->click_status = STATUS_NONE;
2809
/* Reset this since otherwise 3 clicks will be interpreted as 2 double-clicks */
2810
priv->last_click_left_click = FALSE;
2812
aisleriot_game_record_move (priv->game, -1, NULL, 0);
2813
if (aisleriot_game_button_double_clicked_lambda (priv->game, clicked_slot->id)) {
2814
aisleriot_game_end_move (priv->game);
2816
aisleriot_game_discard_move (priv->game);
2819
aisleriot_game_test_end_of_game (priv->game);
2821
set_cursor (board, AR_CURSOR_OPEN);
2826
/* button == 1 from now on */
2828
if (priv->selection_slot == NULL)
2831
/* In click-to-move mode, we need to test whether moving the selected cards
2832
* to this slot does a move. Note that it is necessary to do this both if
2833
* the clicked slot is the selection_slot (and the clicked card the topmost
2834
* card below the selection), and if it's not the selection_slot, since some
2835
* games depend on this behaviour (e.g. Treize). See bug #565560.
2837
* Note that aisleriot_board_move_selected_cards_to_slot unsets the selection,
2838
* so we need to fall through to set_selection if no move was done.
2840
if (priv->click_to_move &&
2841
priv->selection_start_card_id >= 0 &&
2842
(hslot != priv->selection_slot || cardid + 1 == priv->selection_start_card_id)) {
2844
/* Try to move the selected cards to the clicked slot */
2845
if (aisleriot_board_move_selected_cards_to_slot (board, hslot))
2848
/* Move failed if this wasn't the selection_slot slot */
2849
if (hslot != priv->selection_slot) {
2850
aisleriot_board_error_bell (board);
2854
if (hslot != priv->selection_slot ||
2855
cardid != priv->selection_start_card_id)
2858
/* Single click on the selected slot & card, we take that to mean to deselect,
2859
* but only in click-to-move mode.
2861
if (priv->click_to_move) {
2862
set_selection (board, NULL, -1, FALSE);
2864
/* Reveal the card on left click */
2865
reveal_card (board, hslot, cardid);
2873
drag_valid = aisleriot_game_drag_valid (priv->game,
2875
hslot->cards->data + cardid,
2876
hslot->cards->len - cardid);
2882
set_selection (board, hslot, cardid, priv->click_to_move);
2883
priv->click_status = priv->click_to_move ? STATUS_NOT_DRAG : STATUS_MAYBE_DRAG;
2885
set_selection (board, NULL, -1, FALSE);
2886
priv->click_status = STATUS_NOT_DRAG;
2889
/* If we're already showing focus or just clicked on the
2890
* card with the (hidden) focus, show the focus on the
2893
show_focus = priv->show_focus ||
2894
(hslot == priv->focus_slot &&
2895
cardid == priv->focus_card_id);
2896
set_focus (board, hslot, cardid, show_focus);
2898
/* Reveal the card on left click */
2899
if (priv->click_to_move) {
2900
reveal_card (board, hslot, cardid);
2907
aisleriot_board_button_release (ClutterActor *actor,
2908
ClutterButtonEvent *event)
2910
AisleriotBoard *board = AISLERIOT_BOARD (actor);
2911
AisleriotBoardPrivate *priv = board->priv;
2914
ar_debug_print (AR_DEBUG_GAME_EVENTS,
2915
"board ::button-release @(%f / %f) button %d click-count %d modifiers %x\n",
2917
event->button, event->click_count,
2918
event->modifier_state);
2920
/* FIXMEchpe: check event coordinate handling (float vs int!) */
2922
/* NOTE: It's ok to just return instead of chaining up, since the
2923
* parent classes have no class closure for this event.
2926
/* We just abort any action on button release, even if the button-up
2927
* is not the one that started the action. This greatly simplifies the code,
2928
* and is also the right thing to do, anyway.
2931
/* state = event->state & gtk_accelerator_get_default_mod_mask (); */
2933
switch (priv->click_status) {
2935
reveal_card (board, NULL, -1);
2938
case STATUS_IS_DRAG:
2939
highlight_drop_target (board, NULL);
2940
drop_moving_cards (board, event->x, event->y);
2943
case STATUS_MAYBE_DRAG:
2944
case STATUS_NOT_DRAG: {
2948
/* Don't do the action if the mouse moved away from the clicked slot; see bug #329183 */
2949
get_slot_and_card_from_point (board, event->x, event->y, &slot, &card_id);
2950
if (!slot || slot != priv->last_clicked_slot)
2953
aisleriot_game_record_move (priv->game, -1, NULL, 0);
2954
if (aisleriot_game_button_clicked_lambda (priv->game, slot->id)) {
2955
aisleriot_game_end_move (priv->game);
2956
ar_sound_play_for_event ("click", (GdkEvent *) event);
2958
aisleriot_game_discard_move (priv->game);
2961
aisleriot_game_test_end_of_game (priv->game);
2970
priv->click_status = STATUS_NONE;
2972
set_cursor_by_location (board, event->x, event->y);
2978
aisleriot_board_motion (ClutterActor *actor,
2979
ClutterMotionEvent *event)
2981
AisleriotBoard *board = AISLERIOT_BOARD (actor);
2982
AisleriotBoardPrivate *priv = board->priv;
2984
ar_debug_print (AR_DEBUG_GAME_EVENTS,
2985
"board ::motion @(%f / %f) modifiers %x\n",
2987
event->modifier_state);
2989
/* FIXMEchpe: check event coordinate handling (float vs int!) */
2991
/* NOTE: It's ok to just return instead of chaining up, since the
2992
* parent classes have no class closure for this event.
2995
if (priv->show_status_messages) {
2996
ArSlot *slot = NULL;
2999
get_slot_and_card_from_point (board, event->x, event->y, &slot, &cardid);
3000
if (slot != NULL && ar_slot_get_slot_type (slot) != AR_SLOT_UNKNOWN) {
3003
text = ar_slot_get_hint_string (slot, cardid);
3004
set_status_message (board, text);
3007
set_status_message (board, NULL);
3011
if (priv->click_status == STATUS_IS_DRAG) {
3015
x = event->x - priv->last_click_x;
3016
y = event->y - priv->last_click_y;
3018
slot = find_drop_target (board, x, y);
3019
highlight_drop_target (board, slot);
3021
clutter_actor_set_position (priv->moving_cards_group, x, y);
3022
clutter_actor_raise_top (priv->moving_cards_group);
3024
set_cursor (board, AR_CURSOR_CLOSED);
3025
} else if (priv->click_status == STATUS_MAYBE_DRAG &&
3026
ar_style_check_dnd_drag_threshold (priv->style,
3033
set_cursor_by_location (board, event->x, event->y);
3040
aisleriot_board_enter (ClutterActor *actor,
3041
ClutterCrossingEvent *event)
3043
ar_debug_print (AR_DEBUG_GAME_EVENTS,
3044
"board ::enter @(%f / %f)\n",
3045
event->x, event->y);
3047
/* NOTE: It's ok to just return instead of chaining up, since the
3048
* parent classes have no class closure for this event.
3055
aisleriot_board_leave (ClutterActor *actor,
3056
ClutterCrossingEvent *event)
3058
AisleriotBoard *board = AISLERIOT_BOARD (actor);
3060
ar_debug_print (AR_DEBUG_GAME_EVENTS,
3061
"board ::leave @(%f / %f)\n",
3062
event->x, event->y);
3064
/* NOTE: It's ok to just return instead of chaining up, since the
3065
* parent classes have no class closure for this event.
3068
set_cursor (board, AR_CURSOR_DEFAULT);
3074
aisleriot_board_key_focus_in (ClutterActor *actor)
3076
ar_debug_print (AR_DEBUG_GAME_EVENTS,
3077
"board ::key-focus-in\n");
3081
aisleriot_board_key_focus_out (ClutterActor *actor)
3083
ar_debug_print (AR_DEBUG_GAME_EVENTS,
3084
"board ::key-focus-out\n");
3087
/* GObjectClass methods */
3090
aisleriot_board_init (AisleriotBoard *board)
3092
ClutterActor *actor = CLUTTER_ACTOR (board);
3093
AisleriotBoardPrivate *priv;
3095
priv = board->priv = AISLERIOT_BOARD_GET_PRIVATE (board);
3097
priv->textures = ar_card_textures_cache_new ();
3099
memset (&priv->allocation, 0, sizeof (ClutterActorBox));
3101
/* We want to receive events! */
3102
clutter_actor_set_reactive (actor, TRUE);
3104
priv->force_geometry_update = FALSE;
3106
priv->click_to_move = FALSE;
3107
priv->show_selection = FALSE;
3108
priv->show_status_messages = FALSE;
3110
priv->show_card_id = -1;
3112
priv->moving_cards = g_byte_array_sized_new (SLOT_CARDS_N_PREALLOC);
3114
priv->removed_cards = g_array_new (FALSE, FALSE, sizeof (RemovedCard));
3116
priv->animation_layer = g_object_ref_sink (clutter_group_new ());
3117
clutter_container_add (CLUTTER_CONTAINER (board),
3118
priv->animation_layer, NULL);
3122
aisleriot_board_finalize (GObject *object)
3124
AisleriotBoard *board = AISLERIOT_BOARD (object);
3125
AisleriotBoardPrivate *priv = board->priv;
3127
g_signal_handlers_disconnect_matched (priv->game,
3128
G_SIGNAL_MATCH_DATA,
3129
0, 0, NULL, NULL, board);
3130
g_object_unref (priv->game);
3132
g_array_free (priv->removed_cards, TRUE);
3134
g_byte_array_free (priv->moving_cards, TRUE);
3136
if (priv->style != NULL) {
3137
g_signal_handlers_disconnect_by_func (priv->style,
3138
G_CALLBACK (aisleriot_board_sync_style),
3141
g_object_unref (priv->style);
3145
screen = gtk_widget_get_settings (widget);
3146
g_signal_handlers_disconnect_matched (settings, G_SIGNAL_MATCH_DATA,
3151
G_OBJECT_CLASS (aisleriot_board_parent_class)->finalize (object);
3155
aisleriot_board_dispose (GObject *object)
3157
AisleriotBoard *board = AISLERIOT_BOARD (object);
3158
AisleriotBoardPrivate *priv = board->priv;
3160
if (priv->textures) {
3161
g_object_unref (priv->textures);
3162
priv->textures = NULL;
3165
if (priv->animation_layer) {
3166
g_object_unref (priv->animation_layer);
3167
priv->animation_layer = NULL;
3170
if (priv->check_animations_handler) {
3171
g_source_remove (priv->check_animations_handler);
3172
priv->check_animations_handler = 0;
3175
G_OBJECT_CLASS (aisleriot_board_parent_class)->dispose (object);
3179
aisleriot_board_set_property (GObject *object,
3181
const GValue *value,
3184
AisleriotBoard *board = AISLERIOT_BOARD (object);
3185
AisleriotBoardPrivate *priv = board->priv;
3189
priv->game = AISLERIOT_GAME (g_value_dup_object (value));
3191
g_signal_connect (priv->game, "game-type",
3192
G_CALLBACK (game_type_changed_cb), board);
3193
g_signal_connect (priv->game, "game-cleared",
3194
G_CALLBACK (game_cleared_cb), board);
3195
g_signal_connect (priv->game, "game-new",
3196
G_CALLBACK (game_new_cb), board);
3197
g_signal_connect (priv->game, "slot-changed",
3198
G_CALLBACK (slot_changed_cb), board);
3203
priv->style = g_value_dup_object (value);
3205
aisleriot_board_sync_style (priv->style, NULL, board);
3206
g_signal_connect (priv->style, "notify",
3207
G_CALLBACK (aisleriot_board_sync_style), board);
3211
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
3216
aisleriot_board_class_init (AisleriotBoardClass *klass)
3218
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
3219
ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
3220
#ifdef ENABLE_KEYNAV
3221
ClutterBindingPool *binding_pool;
3225
g_type_class_add_private (gobject_class, sizeof (AisleriotBoardPrivate));
3227
gobject_class->dispose = aisleriot_board_dispose;
3228
gobject_class->finalize = aisleriot_board_finalize;
3229
gobject_class->set_property = aisleriot_board_set_property;
3232
widget_class->realize = aisleriot_board_realize;
3233
widget_class->unrealize = aisleriot_board_unrealize;
3234
widget_class->focus_in_event = aisleriot_board_focus_in;
3235
widget_class->focus_out_event = aisleriot_board_focus_out;
3238
actor_class->allocate = aisleriot_board_allocate;
3239
actor_class->get_preferred_width = aisleriot_board_get_preferred_width;
3240
actor_class->get_preferred_height = aisleriot_board_get_preferred_height;
3241
#ifdef ENABLE_KEYNAV
3242
actor_class->key_press_event = aisleriot_board_key_press;
3243
#endif /* ENABLE_KEYNAV */
3244
actor_class->button_press_event = aisleriot_board_button_press;
3245
actor_class->button_release_event = aisleriot_board_button_release;
3246
actor_class->motion_event = aisleriot_board_motion;
3247
actor_class->enter_event = aisleriot_board_enter;
3248
actor_class->leave_event = aisleriot_board_leave;
3249
actor_class->key_focus_in = aisleriot_board_key_focus_in;
3250
actor_class->key_focus_out = aisleriot_board_key_focus_out;
3253
signals[REQUEST_CURSOR] =
3254
g_signal_new (I_("request-cursor"),
3255
G_TYPE_FROM_CLASS (gobject_class),
3256
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
3257
G_STRUCT_OFFSET (AisleriotBoardClass, request_cursor),
3259
g_cclosure_marshal_VOID__INT,
3264
signals[ERROR_BELL] =
3265
g_signal_new (I_("error-bell"),
3266
G_TYPE_FROM_CLASS (gobject_class),
3267
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
3268
G_STRUCT_OFFSET (AisleriotBoardClass, error_bell),
3270
g_cclosure_marshal_VOID__VOID,
3274
signals[STATUS_MESSAGE] =
3275
g_signal_new (I_("status-message"),
3276
G_TYPE_FROM_CLASS (gobject_class),
3277
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
3278
G_STRUCT_OFFSET (AisleriotBoardClass, status_message),
3280
g_cclosure_marshal_VOID__STRING,
3283
G_TYPE_STRING | G_SIGNAL_TYPE_STATIC_SCOPE);
3286
g_signal_new (I_("focus"),
3287
G_TYPE_FROM_CLASS (gobject_class),
3288
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
3289
G_STRUCT_OFFSET (AisleriotBoardClass, focus),
3291
g_cclosure_marshal_VOID__INT,
3296
#ifdef ENABLE_KEYNAV
3297
klass->focus = aisleriot_board_focus;
3298
klass->activate = aisleriot_board_activate;
3299
klass->move_cursor = aisleriot_board_move_cursor;
3300
klass->select_all = aisleriot_board_select_all;
3301
klass->deselect_all = aisleriot_board_deselect_all;
3302
klass->toggle_selection = aisleriot_board_toggle_selection;
3304
/* Keybinding signals */
3306
widget_class->activate_signal =
3309
g_signal_new (I_("activate"),
3310
G_TYPE_FROM_CLASS (gobject_class),
3311
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
3312
G_STRUCT_OFFSET (AisleriotBoardClass, activate),
3314
ar_marshal_BOOLEAN__STRING_UINT_ENUM,
3317
G_TYPE_STRING | G_SIGNAL_TYPE_STATIC_SCOPE,
3319
CLUTTER_TYPE_MODIFIER_TYPE);
3321
signals[MOVE_CURSOR] =
3322
g_signal_new (I_("move-cursor"),
3323
G_TYPE_FROM_CLASS (gobject_class),
3324
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
3325
G_STRUCT_OFFSET (AisleriotBoardClass, move_cursor),
3327
ar_marshal_BOOLEAN__STRING_UINT_ENUM,
3330
G_TYPE_STRING | G_SIGNAL_TYPE_STATIC_SCOPE,
3332
CLUTTER_TYPE_MODIFIER_TYPE);
3334
signals[TOGGLE_SELECTION] =
3335
g_signal_new (I_("toggle-selection"),
3336
G_TYPE_FROM_CLASS (gobject_class),
3337
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
3338
G_STRUCT_OFFSET (AisleriotBoardClass, toggle_selection),
3340
ar_marshal_BOOLEAN__STRING_UINT_ENUM,
3343
G_TYPE_STRING | G_SIGNAL_TYPE_STATIC_SCOPE,
3345
CLUTTER_TYPE_MODIFIER_TYPE);
3347
signals[SELECT_ALL] =
3348
g_signal_new (I_("select-all"),
3349
G_TYPE_FROM_CLASS (gobject_class),
3350
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
3351
G_STRUCT_OFFSET (AisleriotBoardClass, select_all),
3353
ar_marshal_BOOLEAN__STRING_UINT_ENUM,
3356
G_TYPE_STRING | G_SIGNAL_TYPE_STATIC_SCOPE,
3358
CLUTTER_TYPE_MODIFIER_TYPE);
3360
signals[DESELECT_ALL] =
3361
g_signal_new (I_("deselect-all"),
3362
G_TYPE_FROM_CLASS (gobject_class),
3363
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
3364
G_STRUCT_OFFSET (AisleriotBoardClass, deselect_all),
3366
ar_marshal_BOOLEAN__STRING_UINT_ENUM,
3369
G_TYPE_STRING | G_SIGNAL_TYPE_STATIC_SCOPE,
3371
CLUTTER_TYPE_MODIFIER_TYPE);
3372
#endif /* ENABLE_KEYNAV */
3375
g_object_class_install_property
3378
g_param_spec_object ("game", NULL, NULL,
3379
AISLERIOT_TYPE_GAME,
3381
G_PARAM_CONSTRUCT_ONLY |
3382
G_PARAM_STATIC_STRINGS));
3384
g_object_class_install_property
3387
g_param_spec_object ("style", NULL, NULL,
3390
G_PARAM_CONSTRUCT_ONLY |
3391
G_PARAM_STATIC_STRINGS));
3393
#ifdef ENABLE_KEYNAV
3395
binding_pool = clutter_binding_pool_get_for_class (klass);
3397
/* Cursor movement */
3398
closure = g_signal_type_cclosure_new (G_TYPE_FROM_CLASS (klass),
3399
G_STRUCT_OFFSET (AisleriotBoardClass, move_cursor));
3401
aisleriot_board_add_move_and_select_binding (binding_pool, closure,
3402
I_(MOVE_CURSOR_LEFT_RIGHT_S MOVE_LEFT_S),
3404
aisleriot_board_add_move_and_select_binding (binding_pool, closure,
3405
I_(MOVE_CURSOR_LEFT_RIGHT_S MOVE_LEFT_S),
3406
CLUTTER_KP_Left, 0);
3408
aisleriot_board_add_move_and_select_binding (binding_pool, closure,
3409
I_(MOVE_CURSOR_LEFT_RIGHT_S MOVE_RIGHT_S),
3411
aisleriot_board_add_move_and_select_binding (binding_pool, closure,
3412
I_(MOVE_CURSOR_LEFT_RIGHT_S MOVE_RIGHT_S),
3413
CLUTTER_KP_Right, 0);
3415
aisleriot_board_add_move_and_select_binding (binding_pool, closure,
3416
I_(MOVE_CURSOR_UP_DOWN_S MOVE_LEFT_S),
3418
aisleriot_board_add_move_and_select_binding (binding_pool, closure,
3419
I_(MOVE_CURSOR_UP_DOWN_S MOVE_LEFT_S),
3422
aisleriot_board_add_move_and_select_binding (binding_pool, closure,
3423
I_(MOVE_CURSOR_UP_DOWN_S MOVE_RIGHT_S),
3425
aisleriot_board_add_move_and_select_binding (binding_pool, closure,
3426
I_(MOVE_CURSOR_UP_DOWN_S MOVE_RIGHT_S),
3427
CLUTTER_KP_Down, 0);
3429
aisleriot_board_add_move_and_select_binding (binding_pool, closure,
3430
I_(MOVE_CURSOR_START_END_S MOVE_LEFT_S),
3432
aisleriot_board_add_move_and_select_binding (binding_pool, closure,
3433
I_(MOVE_CURSOR_START_END_S MOVE_LEFT_S),
3434
CLUTTER_KP_Home, 0);
3435
aisleriot_board_add_move_and_select_binding (binding_pool, closure,
3436
I_(MOVE_CURSOR_START_END_S MOVE_LEFT_S),
3439
aisleriot_board_add_move_and_select_binding (binding_pool, closure,
3440
I_(MOVE_CURSOR_START_END_S MOVE_RIGHT_S),
3442
aisleriot_board_add_move_and_select_binding (binding_pool, closure,
3443
I_(MOVE_CURSOR_START_END_S MOVE_RIGHT_S),
3446
aisleriot_board_add_move_binding (binding_pool, closure,
3447
I_(MOVE_CURSOR_PAGES_S MOVE_LEFT_S),
3448
CLUTTER_Page_Up, 0);
3449
aisleriot_board_add_move_binding (binding_pool, closure,
3450
I_(MOVE_CURSOR_PAGES_S MOVE_LEFT_S),
3451
CLUTTER_KP_Page_Up, 0);
3453
aisleriot_board_add_move_binding (binding_pool, closure,
3454
I_(MOVE_CURSOR_PAGES_S MOVE_RIGHT_S),
3455
CLUTTER_Page_Down, 0);
3456
aisleriot_board_add_move_binding (binding_pool, closure,
3457
I_(MOVE_CURSOR_PAGES_S MOVE_RIGHT_S),
3458
CLUTTER_KP_Page_Down, 0);
3460
g_closure_unref (closure);
3463
closure = g_signal_type_cclosure_new (G_TYPE_FROM_CLASS (klass),
3464
G_STRUCT_OFFSET (AisleriotBoardClass, toggle_selection));
3466
clutter_binding_pool_install_closure (binding_pool,
3467
I_("toggle-selection"),
3471
clutter_binding_pool_install_closure (binding_pool,
3472
I_("toggle-selection"),
3476
g_closure_unref (closure);
3478
closure = g_signal_type_cclosure_new (G_TYPE_FROM_CLASS (klass),
3479
G_STRUCT_OFFSET (AisleriotBoardClass, select_all));
3480
clutter_binding_pool_install_closure (binding_pool,
3483
CLUTTER_CONTROL_MASK,
3485
g_closure_unref (closure);
3487
closure = g_signal_type_cclosure_new (G_TYPE_FROM_CLASS (klass),
3488
G_STRUCT_OFFSET (AisleriotBoardClass, deselect_all));
3489
clutter_binding_pool_install_closure (binding_pool,
3492
CLUTTER_CONTROL_MASK | CLUTTER_SHIFT_MASK,
3494
g_closure_unref (closure);
3497
closure = g_signal_type_cclosure_new (G_TYPE_FROM_CLASS (klass),
3498
G_STRUCT_OFFSET (AisleriotBoardClass, activate));
3500
aisleriot_board_add_activate_binding (binding_pool, closure, CLUTTER_Return, 0);
3501
aisleriot_board_add_activate_binding (binding_pool, closure, CLUTTER_ISO_Enter, 0);
3502
aisleriot_board_add_activate_binding (binding_pool, closure, CLUTTER_KP_Enter, 0);
3504
g_closure_unref (closure);
3505
#endif /* ENABLE_KEYNAV */
3511
aisleriot_board_new (ArStyle *style,
3512
AisleriotGame *game)
3514
return g_object_new (AISLERIOT_TYPE_BOARD,
3521
aisleriot_board_abort_move (AisleriotBoard *board)
3523
clear_state (board);