~taylorp36/ubuntu/wily/aisleriot/bug-1490189

« back to all changes in this revision

Viewing changes to src/board.c

  • Committer: Package Import Robot
  • Author(s): Jeremy Bicha
  • Date: 2011-09-26 12:54:22 UTC
  • mfrom: (1.1.1 upstream)
  • Revision ID: package-import@ubuntu.com-20110926125422-fhnx2xyc0qcpgyh2
Tags: 1:3.2.0-0ubuntu1
* New upstream stable release.
  - Distribute a copy of the LGPL
  - Update card theme install info for Debian, Ubuntu & OpenSUSE
  - Build help with yelp-tools instead of gnome-doc-utils
  - Fix game restart
  - Translation updates
* debian/control: Build-depends on yelp-tools
* debian/copyright: Fixed a few lintian warnings
* 03_update_theme_install_data.patch: Dropped, applied upstream

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/*
2
 
 * Copyright © 1998, 2003 Jonathan Blandford <jrb@mit.edu>
3
 
 * Copyright © 2007, 2008, 2009, 2010 Christian Persch
4
 
 *
5
 
 * Some code copied from gtk+/gtk/gtkiconview (LGPL2+):
6
 
 * Copyright © 2002, 2004  Anders Carlsson <andersca@gnu.org>
7
 
 *
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.
12
 
 *
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.
17
 
 *
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/>.
20
 
 */
21
 
 
22
 
#include <config.h>
23
 
 
24
 
#include "board.h"
25
 
 
26
 
#include <string.h>
27
 
 
28
 
#include <gtk/gtk.h>
29
 
#include <gdk/gdkkeysyms.h>
30
 
 
31
 
#include <clutter/clutter.h>
32
 
 
33
 
#include "ar-debug.h"
34
 
#include "ar-marshal.h"
35
 
#include "ar-sound.h"
36
 
 
37
 
#include "conf.h"
38
 
 
39
 
#include "game.h"
40
 
#include "card.h"
41
 
#include "slot-renderer.h"
42
 
#include "ar-card-textures-cache.h"
43
 
#include "ar-cursor.h"
44
 
 
45
 
#define AISLERIOT_BOARD_GET_PRIVATE(board)(G_TYPE_INSTANCE_GET_PRIVATE ((board), AISLERIOT_TYPE_BOARD, AisleriotBoardPrivate))
46
 
 
47
 
/* Enable keynav by default */
48
 
#ifndef DISABLE_KEYNAV
49
 
#define ENABLE_KEYNAV
50
 
#endif
51
 
 
52
 
/* The limits for how much overlap there is between cards and
53
 
 * how much is allowed to slip off the bottom or right.
54
 
 */
55
 
#define MIN_DELTA (0.05)
56
 
 
57
 
/* The minimum size for the playing area. Almost completely arbitrary. */
58
 
#define BOARD_MIN_WIDTH 300
59
 
#define BOARD_MIN_HEIGHT 200
60
 
 
61
 
#define DOUBLE_TO_INT_CEIL(d) ((int) (d + 0.5))
62
 
 
63
 
#define I_(string) g_intern_static_string (string)
64
 
  
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 | \
69
 
                                   CLUTTER_MOD1_MASK    | \
70
 
                                   CLUTTER_SUPER_MASK   | \
71
 
                                   CLUTTER_HYPER_MASK   | \
72
 
                                   CLUTTER_META_MASK)   | \
73
 
                                  CLUTTER_RELEASE_MASK)
74
 
 
75
 
#pragma GCC poison GtkWidget
76
 
#pragma GCC poison widget
77
 
 
78
 
typedef enum {
79
 
  STATUS_NONE,
80
 
  STATUS_MAYBE_DRAG,
81
 
  STATUS_NOT_DRAG,
82
 
  STATUS_IS_DRAG,
83
 
  STATUS_SHOW,
84
 
  LAST_STATUS
85
 
} MoveStatus;
86
 
 
87
 
#ifdef ENABLE_KEYNAV
88
 
 
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"
97
 
 
98
 
#define MOVE_LEFT     'l'
99
 
#define MOVE_LEFT_S   "l"
100
 
#define MOVE_RIGHT    'r'
101
 
#define MOVE_RIGHT_S  "r"
102
 
 
103
 
#endif /* ENABLE_KEYNAV */
104
 
 
105
 
struct _AisleriotBoardPrivate
106
 
{
107
 
  AisleriotGame *game;
108
 
 
109
 
  ArStyle *style;
110
 
 
111
 
  ClutterActorBox allocation;
112
 
 
113
 
  /* Card theme */
114
 
  CardSize card_size;
115
 
 
116
 
  /* Cards cache */
117
 
  ArCardTexturesCache *textures;
118
 
 
119
 
  double width;
120
 
  double height;
121
 
 
122
 
  /* The size of a slot in pixels. */
123
 
  double xslotstep;
124
 
  double yslotstep;
125
 
 
126
 
  /* How much of the slot the card should take up */
127
 
  double card_slot_ratio;
128
 
 
129
 
  /* The offset of the cards within the slot. */
130
 
  int xoffset, yoffset;
131
 
 
132
 
  /* The offset within the window. */
133
 
  int xbaseoffset;
134
 
 
135
 
  /* Button press */
136
 
  int last_click_x;
137
 
  int last_click_y;
138
 
  guint32 last_click_time;
139
 
 
140
 
  /* Moving cards */
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;
145
 
 
146
 
  /* A group to put animated cards above the slots */
147
 
  ClutterActor *animation_layer;
148
 
 
149
 
  /* The 'reveal card' action's slot and card link */
150
 
  ArSlot *show_card_slot;
151
 
  int show_card_id;
152
 
 
153
 
  /* Click data */
154
 
  ArSlot *last_clicked_slot;
155
 
  int last_clicked_card_id;
156
 
 
157
 
  /* Focus handling */
158
 
  ArSlot *focus_slot;
159
 
  int focus_card_id; /* -1 for focused empty slot */
160
 
  int focus_line_width;
161
 
  int focus_padding;
162
 
  GdkRectangle focus_rect;
163
 
 
164
 
  /* Selection */
165
 
  ArSlot *selection_slot;
166
 
  int selection_start_card_id;
167
 
 
168
 
  /* Highlight */
169
 
  ArSlot *highlight_slot;
170
 
 
171
 
  /* Array RemovedCards to be dropped in animations */
172
 
  GArray *removed_cards;
173
 
 
174
 
  /* Idle handler where the slots will be compared for changes to
175
 
     trigger animations */
176
 
  guint check_animations_handler;
177
 
 
178
 
  /* Status message */
179
 
  const char *status_message; /* interned */
180
 
 
181
 
  /* Bit field */
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;
186
 
 
187
 
  guint click_to_move : 1;
188
 
 
189
 
  guint geometry_set : 1;
190
 
  guint is_rtl : 1;
191
 
 
192
 
  guint last_click_left_click : 1;
193
 
  guint click_status : 4; /* enough bits for MoveStatus */
194
 
 
195
 
  guint show_selection : 1;
196
 
  guint show_highlight : 1;
197
 
  guint show_status_messages : 1;
198
 
 
199
 
  guint force_geometry_update : 1;
200
 
};
201
 
 
202
 
typedef struct _RemovedCard RemovedCard;
203
 
 
204
 
struct _RemovedCard
205
 
{
206
 
  Card card;
207
 
  gint cardx, cardy;
208
 
  gboolean from_drag;
209
 
};
210
 
 
211
 
G_STATIC_ASSERT (LAST_STATUS < 16 /* 2^4 */);
212
 
 
213
 
enum
214
 
{
215
 
  PROP_0,
216
 
  PROP_GAME,
217
 
  PROP_STYLE
218
 
};
219
 
 
220
 
enum
221
 
{
222
 
  REQUEST_CURSOR,
223
 
  ERROR_BELL,
224
 
  STATUS_MESSAGE,
225
 
  FOCUS,
226
 
#ifdef ENABLE_KEYNAV
227
 
  ACTIVATE,
228
 
  MOVE_CURSOR,
229
 
  TOGGLE_SELECTION,
230
 
  SELECT_ALL,
231
 
  DESELECT_ALL,
232
 
#endif /* ENABLE_KEYNAV */
233
 
  LAST_SIGNAL
234
 
};
235
 
 
236
 
static guint signals[LAST_SIGNAL];
237
 
 
238
 
static void get_slot_and_card_from_point (AisleriotBoard *board,
239
 
                                          int x,
240
 
                                          int y,
241
 
                                          ArSlot **slot,
242
 
                                          int *_cardid);
243
 
static void slot_update_card_images      (AisleriotBoard *board,
244
 
                                          ArSlot *slot);
245
 
static void slot_update_card_images_full (AisleriotBoard *board,
246
 
                                          ArSlot *slot,
247
 
                                          gint highlight_start_card_id);
248
 
 
249
 
static void aisleriot_board_setup_geometry (AisleriotBoard *board);
250
 
 
251
 
static void
252
 
set_cursor (AisleriotBoard *board,
253
 
            ArCursorType cursor)
254
 
{
255
 
  g_signal_emit (board, signals[REQUEST_CURSOR], 0, (int) cursor);
256
 
}
257
 
 
258
 
/* If we are over a slot, set the cursor to the given cursor,
259
 
 * otherwise use the default cursor. */
260
 
static void
261
 
set_cursor_by_location (AisleriotBoard *board,
262
 
                        int x,
263
 
                        int y)
264
 
{
265
 
  AisleriotBoardPrivate *priv = board->priv;
266
 
  ArSlot *selection_slot = priv->selection_slot;
267
 
  int selection_start_card_id = priv->selection_start_card_id;
268
 
  ArSlot *slot;
269
 
  int card_id;
270
 
  gboolean drop_valid = FALSE;
271
 
  ArCursorType cursor = AR_CURSOR_DEFAULT;
272
 
 
273
 
  get_slot_and_card_from_point (board, x, y, &slot, &card_id);
274
 
 
275
 
  if (priv->click_to_move &&
276
 
      slot != NULL &&
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);
281
 
 
282
 
    drop_valid = aisleriot_game_drop_valid (priv->game,
283
 
                                            selection_slot->id,
284
 
                                            slot->id,
285
 
                                            selection_slot->cards->data + selection_start_card_id,
286
 
                                            selection_slot->cards->len - selection_start_card_id);
287
 
  }
288
 
  /* FIXMEchpe: special cursor when _drag_ is possible? */
289
 
 
290
 
  if (drop_valid) {
291
 
    cursor = AR_CURSOR_DROPPABLE;
292
 
  } else if (slot != NULL &&
293
 
             card_id >= 0 &&
294
 
             !CARD_GET_FACE_DOWN (CARD (slot->cards->data[card_id]))) {
295
 
    if (priv->click_status == STATUS_NONE) {
296
 
      cursor = AR_CURSOR_OPEN;
297
 
    } else {
298
 
      cursor = AR_CURSOR_CLOSED;
299
 
    }
300
 
  }
301
 
 
302
 
  set_cursor (board, cursor);
303
 
}
304
 
 
305
 
/* status message */
306
 
 
307
 
static void
308
 
set_status_message (AisleriotBoard *board,
309
 
                    const char *message)
310
 
{
311
 
  AisleriotBoardPrivate *priv = board->priv;
312
 
 
313
 
  if (g_strcmp0 (priv->status_message, message) == 0)
314
 
    return;
315
 
 
316
 
  priv->status_message = g_intern_string (message);
317
 
 
318
 
  g_signal_emit (board, signals[STATUS_MESSAGE], 0, priv->status_message);
319
 
}
320
 
 
321
 
/* Slot helpers */
322
 
 
323
 
static void
324
 
get_slot_and_card_from_point (AisleriotBoard *board,
325
 
                              int x,
326
 
                              int y,
327
 
                              ArSlot **slot,
328
 
                              int *_cardid)
329
 
{
330
 
  AisleriotBoardPrivate *priv = board->priv;
331
 
  GPtrArray *slots;
332
 
  gboolean got_slot = FALSE;
333
 
  int num_cards;
334
 
  int i, n_slots;
335
 
  int cardid;
336
 
 
337
 
  *slot = NULL;
338
 
  cardid = -1;
339
 
 
340
 
  slots = aisleriot_game_get_slots (priv->game);
341
 
 
342
 
  n_slots = slots->len;
343
 
  for (i = n_slots - 1; i >= 0; --i) {
344
 
    ArSlot *hslot = slots->pdata[i];
345
 
 
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;
350
 
 
351
 
      if (got_slot == FALSE || num_cards > 0) {
352
 
        /* if we support exposing more than one card,
353
 
         * find the exact card  */
354
 
 
355
 
        gint depth = 1;
356
 
 
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;
363
 
 
364
 
        /* account for the last card getting much more display area
365
 
         * or no cards */
366
 
 
367
 
        if (depth > hslot->exposed)
368
 
          depth = hslot->exposed;
369
 
        *slot = hslot;
370
 
 
371
 
        /* card = #cards in slot + card chosen (indexed in # exposed cards) - # exposed cards */
372
 
 
373
 
        cardid = num_cards + depth - hslot->exposed;
374
 
 
375
 
        /* this is the topmost slot with a card */
376
 
        /* take it and run */
377
 
        if (num_cards > 0)
378
 
          break;
379
 
 
380
 
        got_slot = TRUE;
381
 
      }
382
 
    }
383
 
  }
384
 
 
385
 
  *_cardid = cardid > 0 ? cardid - 1 : -1;
386
 
}
387
 
 
388
 
#ifdef ENABLE_KEYNAV
389
 
 
390
 
static gboolean
391
 
test_slot_projection_intersects_x (ArSlot *slot,
392
 
                                   int x_start,
393
 
                                   int x_end)
394
 
{
395
 
  return slot->rect.x <= x_end &&
396
 
         slot->rect.x + slot->rect.width >= x_start;
397
 
}
398
 
 
399
 
static int
400
 
get_slot_index_from_slot (AisleriotBoard *board,
401
 
                          ArSlot *slot)
402
 
{
403
 
  AisleriotBoardPrivate *priv = board->priv;
404
 
  GPtrArray *slots;
405
 
  guint n_slots;
406
 
  int slot_index;
407
 
 
408
 
  g_assert (slot != NULL);
409
 
 
410
 
  slots = aisleriot_game_get_slots (priv->game);
411
 
  n_slots = slots->len;
412
 
  g_assert (n_slots > 0);
413
 
 
414
 
  for (slot_index = 0; slot_index < n_slots; ++slot_index) {
415
 
    if (g_ptr_array_index (slots, slot_index) == slot)
416
 
      break;
417
 
  }
418
 
 
419
 
  g_assert (slot_index < n_slots); /* the slot EXISTS after all */
420
 
 
421
 
  return slot_index;
422
 
}
423
 
 
424
 
#endif /* ENABLE_KEYNAV */
425
 
 
426
 
static void
427
 
get_rect_by_slot_and_card (AisleriotBoard *board,
428
 
                           ArSlot *slot,
429
 
                           int card_id,
430
 
                           int num_cards,
431
 
                           GdkRectangle *rect)
432
 
{
433
 
  AisleriotBoardPrivate *priv = board->priv;
434
 
  guint delta;
435
 
  int first_card_id, num;
436
 
 
437
 
  g_return_if_fail (slot != NULL && card_id >= -1);
438
 
 
439
 
  first_card_id = ((int) slot->cards->len) - ((int) slot->exposed);
440
 
 
441
 
  if (card_id >= first_card_id) {
442
 
    delta = card_id - first_card_id;
443
 
    num = num_cards - 1;
444
 
 
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;
449
 
        
450
 
    if (priv->is_rtl &&
451
 
        slot->expanded_right) {
452
 
      rect->x += slot->rect.width - priv->card_size.width;
453
 
    }
454
 
 
455
 
  } else {
456
 
    /* card_id == -1 or no card available, return the slot rect.
457
 
     * Its size should be card_size.
458
 
     */
459
 
    *rect = slot->rect;
460
 
  }
461
 
}
462
 
 
463
 
/* Focus handling */
464
 
 
465
 
static void
466
 
widen_rect (GdkRectangle *rect,
467
 
            int delta)
468
 
{
469
 
  int x, y;
470
 
 
471
 
  x = rect->x - delta;
472
 
  y = rect->y - delta;
473
 
 
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;
478
 
}
479
 
 
480
 
static void
481
 
get_focus_rect (AisleriotBoard *board,
482
 
                GdkRectangle *rect)
483
 
{
484
 
  AisleriotBoardPrivate *priv = board->priv;
485
 
 
486
 
  if (!priv->focus_slot)
487
 
    return;
488
 
 
489
 
  get_rect_by_slot_and_card (board,
490
 
                             priv->focus_slot,
491
 
                             priv->focus_card_id,
492
 
                             1, rect);
493
 
  widen_rect (rect, priv->focus_line_width + priv->focus_padding);
494
 
}
495
 
 
496
 
static void
497
 
set_focus (AisleriotBoard *board,
498
 
           ArSlot *slot,
499
 
           int card_id,
500
 
           gboolean show_focus)
501
 
{
502
 
  AisleriotBoardPrivate *priv = board->priv;
503
 
//   GtkWidget *widget = GTK_WIDGET (board);
504
 
  int top_card_id;
505
 
 
506
 
  /* Sanitise */
507
 
  top_card_id = slot ? ((int) slot->cards->len) - 1 : -1;
508
 
  card_id = MIN (card_id, top_card_id);
509
 
 
510
 
  if (priv->focus_slot == slot &&
511
 
      priv->focus_card_id == card_id &&
512
 
      priv->show_focus == show_focus)
513
 
    return;
514
 
 
515
 
  if (priv->focus_slot != NULL) {
516
 
#ifdef FIXMEchpe
517
 
    if (priv->show_focus &&
518
 
        gtk_widget_has_focus (widget)) {
519
 
      gdk_window_invalidate_rect (widget->window, &priv->focus_rect, FALSE);
520
 
    
521
 
      priv->show_focus = FALSE;
522
 
    }
523
 
#endif
524
 
 
525
 
    priv->focus_slot = NULL;
526
 
    priv->focus_card_id = -1;
527
 
  }
528
 
 
529
 
  priv->show_focus = show_focus;
530
 
 
531
 
  if (!slot)
532
 
    return;
533
 
 
534
 
  priv->focus_slot = slot;
535
 
  priv->focus_card_id = card_id;
536
 
 
537
 
#ifdef FIXMEchpe
538
 
  if (show_focus &&
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);
542
 
  }
543
 
#endif
544
 
}
545
 
 
546
 
/* Selection handling */
547
 
 
548
 
static void
549
 
set_selection (AisleriotBoard *board,
550
 
               ArSlot *slot,
551
 
               int card_id,
552
 
               gboolean show_selection)
553
 
{
554
 
  AisleriotBoardPrivate *priv = board->priv;
555
 
 
556
 
  if (priv->selection_slot == slot &&
557
 
      priv->selection_start_card_id == card_id &&
558
 
      priv->show_selection == show_selection)
559
 
    return;
560
 
 
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);
565
 
    }
566
 
 
567
 
    priv->selection_slot = NULL;
568
 
    priv->selection_start_card_id = -1;
569
 
  }
570
 
 
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);
575
 
 
576
 
  if (!slot)
577
 
    return;
578
 
 
579
 
  g_assert (card_id < 0 || card_id < slot->cards->len);
580
 
 
581
 
  if (priv->show_selection) {
582
 
    slot_update_card_images_full (board, slot, card_id);
583
 
  }
584
 
}
585
 
 
586
 
/* Slot functions */
587
 
 
588
 
static void
589
 
slot_update_geometry (AisleriotBoard *board,
590
 
                      ArSlot *slot)
591
 
{
592
 
  AisleriotBoardPrivate *priv = board->priv;
593
 
//   GtkWidget *widget = GTK_WIDGET (board);
594
 
  GdkRectangle old_rect;
595
 
  GByteArray *cards;
596
 
  int delta, xofs, yofs, pixeldx;
597
 
  double card_step;
598
 
 
599
 
  if (!priv->geometry_set)
600
 
    return;
601
 
 
602
 
  cards = slot->cards;
603
 
  old_rect = slot->rect;
604
 
 
605
 
  card_step = ar_style_get_card_step (priv->style);
606
 
 
607
 
  xofs = priv->xoffset;
608
 
  yofs = priv->yoffset;
609
 
 
610
 
  /* FIXMEchpe: what exactly is the purpose of the following lines? */
611
 
  if (slot->expanded_right)
612
 
    xofs = yofs;
613
 
  if (slot->expanded_down)
614
 
    yofs = xofs;
615
 
 
616
 
  if (priv->is_rtl) {
617
 
    slot->rect.x = priv->xslotstep * (priv->width - slot->x) - priv->card_size.width - xofs + priv->xbaseoffset;
618
 
  } else {
619
 
    slot->rect.x = priv->xslotstep * slot->x + xofs + priv->xbaseoffset;
620
 
  }
621
 
 
622
 
  slot->rect.y = priv->yslotstep * slot->y + yofs; /* FIXMEchpe + priv->ybaseoffset; */
623
 
 
624
 
  /* We need to make sure the cards fit within the board, even
625
 
   * when there are many of them. See bug #171417.
626
 
   */
627
 
  /* FIXMEchpe: check |slot->exposed| instead of cards->len? */
628
 
  pixeldx = 0;
629
 
  if (cards->len > 1) {
630
 
    double dx = 0, dy = 0;
631
 
    double n_cards = cards->len - 1; /* FIXMEchpe: slot->exposed - 1 ? */
632
 
 
633
 
    if (slot->expanded_down) {
634
 
      double y_from_bottom, max_dy = card_step;
635
 
      float allocation_height = priv->allocation.y2 - priv->allocation.y1;
636
 
 
637
 
      if (slot->dy_set)
638
 
        max_dy = slot->expansion.dy;
639
 
 
640
 
      /* Calculate the compressed_dy that will let us fit within the board */
641
 
#ifdef FIXMEchpe
642
 
      y_from_bottom = ((double) (widget->allocation.height - slot->rect.y)) / ((double) priv->card_size.height);
643
 
#else
644
 
      y_from_bottom = ((double) (allocation_height - slot->rect.y)) / ((double) priv->card_size.height);
645
 
#endif
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) {
649
 
      if (priv->is_rtl) {
650
 
        double x_from_left, max_dx = card_step;
651
 
 
652
 
        if (slot->dx_set)
653
 
          max_dx = slot->expansion.dx;
654
 
 
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);
658
 
 
659
 
        slot->pixeldx = DOUBLE_TO_INT_CEIL (- dx * priv->card_size.width);
660
 
        pixeldx = -slot->pixeldx;
661
 
      } else {
662
 
        double x_from_right, max_dx = card_step;
663
 
        float allocation_width = priv->allocation.x2 - priv->allocation.x1;
664
 
 
665
 
        if (slot->dx_set)
666
 
          max_dx = slot->expansion.dx;
667
 
 
668
 
#ifdef FIXMEchpe
669
 
        x_from_right = ((double) (widget->allocation.width - slot->rect.x)) / ((double) priv->card_size.width);
670
 
#else
671
 
        x_from_right = ((double) (allocation_width - slot->rect.x)) / ((double) priv->card_size.width);
672
 
#endif
673
 
        dx = (x_from_right - (1.0 - ar_style_get_card_overhang (priv->style))) / n_cards;
674
 
        dx = CLAMP (dx, MIN_DELTA, max_dx);
675
 
 
676
 
        pixeldx = slot->pixeldx = DOUBLE_TO_INT_CEIL (dx * priv->card_size.width);        
677
 
      }
678
 
    }
679
 
 
680
 
    slot->pixeldy = DOUBLE_TO_INT_CEIL (dy * priv->card_size.height);
681
 
  } else {
682
 
    slot->pixeldx = slot->pixeldy = 0;
683
 
  }
684
 
 
685
 
  slot->exposed = cards->len;
686
 
  if (0 < slot->expansion_depth &&
687
 
      slot->expansion_depth < slot->exposed) {
688
 
    slot->exposed = slot->expansion_depth;
689
 
  }
690
 
 
691
 
  if ((slot->pixeldx == 0 &&
692
 
       slot->pixeldy == 0 &&
693
 
       slot->exposed > 1) ||
694
 
      (cards->len > 0 &&
695
 
       slot->exposed < 1)) {
696
 
    slot->exposed = 1;
697
 
  }
698
 
 
699
 
  delta = slot->exposed > 0 ? slot->exposed - 1 : 0;
700
 
 
701
 
  slot->rect.width = priv->card_size.width + delta * pixeldx;
702
 
  slot->rect.height = priv->card_size.height + delta * slot->pixeldy;
703
 
 
704
 
  if (priv->is_rtl) {
705
 
    slot->rect.x -= slot->rect.width - priv->card_size.width;
706
 
  }
707
 
 
708
 
  if (slot->slot_renderer)
709
 
    clutter_actor_set_position (slot->slot_renderer,
710
 
                                slot->rect.x, slot->rect.y);
711
 
 
712
 
  slot->needs_update = FALSE;
713
 
}
714
 
 
715
 
static gboolean
716
 
check_animations_cb (gpointer user_data)
717
 
{
718
 
  AisleriotBoard *board = user_data;
719
 
  AisleriotBoardPrivate *priv = board->priv;
720
 
  GPtrArray *slots;
721
 
  int slot_num, i;
722
 
  ArSlot *slot;
723
 
  GArray *animations = g_array_new (FALSE, FALSE, sizeof (AisleriotAnimStart));
724
 
 
725
 
  slots = aisleriot_game_get_slots (priv->game);
726
 
 
727
 
  /* Find any cards that have been removed from the top of the
728
 
     slots */
729
 
  for (slot_num = 0; slot_num < slots->len; slot_num++) {
730
 
    slot = slots->pdata[slot_num];
731
 
 
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]);
736
 
 
737
 
        if (old_card.attr.suit != new_card.attr.suit
738
 
            || old_card.attr.rank != new_card.attr.rank)
739
 
          break;
740
 
      }
741
 
 
742
 
      if (i >= slot->cards->len) {
743
 
        for (; i < slot->old_cards->len; i++) {
744
 
          RemovedCard removed_card;
745
 
 
746
 
          removed_card.card = CARD (slot->old_cards->data[i]);
747
 
          aisleriot_game_get_card_offset (slot, i,
748
 
                                          TRUE,
749
 
                                          &removed_card.cardx,
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);
755
 
        }
756
 
      }
757
 
    }
758
 
  }
759
 
 
760
 
  for (slot_num = 0; slot_num < slots->len; slot_num++) {
761
 
    /* Number of extra cards that aren't visible to include in the
762
 
       animation */
763
 
    guint n_unexposed_animated_cards = 0;
764
 
 
765
 
    slot = slots->pdata[slot_num];
766
 
 
767
 
    g_array_set_size (animations, 0);
768
 
 
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]);
776
 
 
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;
781
 
 
782
 
        aisleriot_game_get_card_offset (slot, slot->cards->len - 1,
783
 
                                        FALSE,
784
 
                                        &anim.cardx,
785
 
                                        &anim.cardy);
786
 
        anim.cardx += slot->rect.x;
787
 
        anim.cardy += slot->rect.y;
788
 
        anim.old_card = old_card;
789
 
        anim.raise = TRUE;
790
 
 
791
 
        g_array_append_val (animations, anim);
792
 
      }
793
 
      /* Check if any cards have been added from the removed cards
794
 
         pile */
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;
800
 
           i++) {
801
 
        Card added_card = CARD (slot->cards->data[i]);
802
 
        int j;
803
 
 
804
 
        for (j = 0; j < priv->removed_cards->len; j++) {
805
 
          RemovedCard *removed_card = &g_array_index (priv->removed_cards,
806
 
                                                      RemovedCard, j);
807
 
 
808
 
          if (added_card.attr.suit == removed_card->card.attr.suit
809
 
              && added_card.attr.rank == removed_card->card.attr.rank) {
810
 
            AisleriotAnimStart anim;
811
 
 
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;
816
 
 
817
 
            g_array_append_val (animations, anim);
818
 
 
819
 
            g_array_remove_index (priv->removed_cards, j);
820
 
 
821
 
            break;
822
 
          }
823
 
        }
824
 
      }
825
 
 
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
831
 
         pile */
832
 
      if (animations->len > 0 && animations->len == slot->exposed)
833
 
        {
834
 
          AisleriotAnimStart *anim = &g_array_index (animations,
835
 
                                                     AisleriotAnimStart, 0);
836
 
 
837
 
          n_unexposed_animated_cards = (slot->cards->len - slot->old_cards->len
838
 
                                        - slot->exposed);
839
 
 
840
 
          if (n_unexposed_animated_cards > 0)
841
 
            {
842
 
              /* Set the bottom card of the first animation to be the
843
 
                 lowest unexposed card */
844
 
              anim->old_card
845
 
                = CARD (slot->cards->data[slot->cards->len
846
 
                                          - animations->len
847
 
                                          - n_unexposed_animated_cards]);
848
 
              anim->old_card.attr.face_down = !anim->old_card.attr.face_down;
849
 
            }
850
 
        }
851
 
    }
852
 
 
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);
857
 
 
858
 
    if (slot->cards->len == 0) {
859
 
      clutter_actor_lower_bottom (slot->slot_renderer);
860
 
    } else {
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);
864
 
    }
865
 
 
866
 
    /* Set the old cards back to the new cards */
867
 
    aisleriot_game_reset_old_cards (slot);
868
 
  }
869
 
 
870
 
  g_array_set_size (priv->removed_cards, 0);
871
 
 
872
 
  g_array_free (animations, TRUE);
873
 
 
874
 
  priv->check_animations_handler = 0;
875
 
 
876
 
  return FALSE;
877
 
}
878
 
 
879
 
static void
880
 
queue_check_animations (AisleriotBoard *board)
881
 
{
882
 
  AisleriotBoardPrivate *priv = board->priv;
883
 
 
884
 
  if (!ar_style_get_enable_animations (priv->style))
885
 
    return;
886
 
 
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 */
891
 
 
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,
899
 
                                     check_animations_cb,
900
 
                                     board, NULL);
901
 
}
902
 
 
903
 
static void
904
 
slot_update_card_images_full (AisleriotBoard *board,
905
 
                              ArSlot *slot,
906
 
                              gint highlight_start_card_id)
907
 
{
908
 
  AisleriotBoardPrivate *priv = board->priv;
909
 
 
910
 
  if (!priv->geometry_set)
911
 
    return;
912
 
 
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);
916
 
 
917
 
    aisleriot_slot_renderer_set_animation_layer
918
 
      (AISLERIOT_SLOT_RENDERER (slot->slot_renderer),
919
 
       CLUTTER_CONTAINER (priv->animation_layer));
920
 
 
921
 
    clutter_actor_set_position (slot->slot_renderer,
922
 
                                slot->rect.x, slot->rect.y);
923
 
 
924
 
    clutter_container_add (CLUTTER_CONTAINER (board),
925
 
                           slot->slot_renderer, NULL);
926
 
 
927
 
    clutter_actor_raise_top (priv->animation_layer);
928
 
  }
929
 
 
930
 
  aisleriot_slot_renderer_set_animations
931
 
    (AISLERIOT_SLOT_RENDERER (slot->slot_renderer), 0, NULL, 0);
932
 
 
933
 
  aisleriot_slot_renderer_set_highlight
934
 
    (AISLERIOT_SLOT_RENDERER (slot->slot_renderer),
935
 
     priv->show_highlight ? highlight_start_card_id : G_MAXINT);
936
 
}
937
 
 
938
 
static void
939
 
slot_update_card_images (AisleriotBoard *board,
940
 
                         ArSlot *slot)
941
 
{
942
 
  AisleriotBoardPrivate *priv = board->priv;
943
 
  int highlight_start_card_id = G_MAXINT;
944
 
 
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;
952
 
  }
953
 
 
954
 
  slot_update_card_images_full (board, slot, highlight_start_card_id);
955
 
}
956
 
 
957
 
/* helper functions */
958
 
 
959
 
static void
960
 
aisleriot_board_error_bell (AisleriotBoard *board)
961
 
{
962
 
  g_signal_emit (board, signals[ERROR_BELL], 0);
963
 
}
964
 
 
965
 
/* Work out new sizes and spacings for the cards. */
966
 
static void
967
 
aisleriot_board_setup_geometry (AisleriotBoard *board)
968
 
{
969
 
  AisleriotBoardPrivate *priv = board->priv;
970
 
  ClutterActor *actor = CLUTTER_ACTOR (board);
971
 
  ArCardTheme *theme;
972
 
  GPtrArray *slots;
973
 
  guint i, n_slots;
974
 
  CardSize card_size;
975
 
 
976
 
  if (!CLUTTER_ACTOR_IS_REALIZED (actor))
977
 
    return;
978
 
 
979
 
  /* Nothing to do yet */
980
 
  if (aisleriot_game_get_state (priv->game) <= GAME_LOADED)
981
 
    return;
982
 
 
983
 
  theme = ar_style_get_card_theme (priv->style);
984
 
  if (theme == NULL)
985
 
    return;
986
 
 
987
 
  g_return_if_fail (priv->width > 0 && priv->height > 0);
988
 
 
989
 
  // FIXMEchpe
990
 
  priv->xslotstep = ((double) priv->allocation.x2 - priv->allocation.x1) / priv->width;
991
 
  priv->yslotstep = ((double) priv->allocation.y2 - priv->allocation.y1) / priv->height;
992
 
 
993
 
  ar_card_theme_set_size (theme,
994
 
                             priv->xslotstep,
995
 
                             priv->yslotstep,
996
 
                             priv->card_slot_ratio);
997
 
 
998
 
  ar_card_theme_get_size (theme, &card_size);
999
 
  priv->card_size = card_size;
1000
 
 
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? */
1006
 
#ifdef FIXMEchpe
1007
 
    priv->xbaseoffset = (widget->allocation.width - priv->xslotstep * priv->width) / 2;
1008
 
#else
1009
 
    priv->xbaseoffset = (priv->allocation.x2 - priv->allocation.x1 - priv->xslotstep * priv->width) / 2;
1010
 
#endif
1011
 
  }
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;
1016
 
    */
1017
 
  }
1018
 
 
1019
 
  priv->xoffset = (priv->xslotstep - card_size.width) / 2;
1020
 
  priv->yoffset = (priv->yslotstep - card_size.height) / 2;
1021
 
 
1022
 
  /* NOTE! Updating the slots checks that geometry is set, so
1023
 
   * we set it to TRUE already.
1024
 
   */
1025
 
  priv->geometry_set = TRUE;
1026
 
 
1027
 
  /* Now recalculate the slot locations. */
1028
 
  slots = aisleriot_game_get_slots (priv->game);
1029
 
 
1030
 
  n_slots = slots->len;
1031
 
  for (i = 0; i < n_slots; ++i) {
1032
 
    ArSlot *slot = slots->pdata[i];
1033
 
 
1034
 
    slot_update_geometry (board, slot);
1035
 
    slot_update_card_images (board, slot);
1036
 
  }
1037
 
 
1038
 
  /* Update the focus and selection rects */
1039
 
  get_focus_rect (board, &priv->focus_rect);
1040
 
}
1041
 
 
1042
 
static void
1043
 
drag_begin (AisleriotBoard *board)
1044
 
{
1045
 
  AisleriotBoardPrivate *priv = board->priv;
1046
 
  ArSlot *hslot;
1047
 
  int delta, height, width;
1048
 
  int x, y;
1049
 
  int num_moving_cards;
1050
 
  guint i;
1051
 
  GByteArray *cards;
1052
 
 
1053
 
  if (!priv->selection_slot ||
1054
 
      priv->selection_start_card_id < 0) {
1055
 
    priv->click_status = STATUS_NONE;
1056
 
    return;
1057
 
  }
1058
 
 
1059
 
  priv->click_status = STATUS_IS_DRAG;
1060
 
 
1061
 
  hslot = priv->moving_cards_origin_slot = priv->selection_slot;
1062
 
  priv->moving_cards_origin_card_id = priv->selection_start_card_id;
1063
 
 
1064
 
  num_moving_cards = hslot->cards->len - priv->moving_cards_origin_card_id;
1065
 
 
1066
 
  cards = hslot->cards;
1067
 
 
1068
 
  /* Save game state */
1069
 
  aisleriot_game_record_move (priv->game, hslot->id,
1070
 
                              cards->data, cards->len);
1071
 
 
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);
1075
 
 
1076
 
  delta = hslot->exposed - num_moving_cards;
1077
 
 
1078
 
  /* (x,y) is the upper left edge of the topmost dragged card */
1079
 
  x = hslot->rect.x + delta * hslot->pixeldx;
1080
 
  if (priv->is_rtl &&
1081
 
      hslot->expanded_right) {
1082
 
    x += hslot->rect.width - priv->card_size.width;
1083
 
  }
1084
 
 
1085
 
  priv->last_click_x -= x;
1086
 
  priv->last_click_y -= y = hslot->rect.y + delta * hslot->pixeldy;;
1087
 
 
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);
1092
 
 
1093
 
  width = priv->card_size.width + (num_moving_cards - 1) * hslot->pixeldx;
1094
 
  height = priv->card_size.height + (num_moving_cards - 1) * hslot->pixeldy;
1095
 
 
1096
 
  priv->moving_cards_group = g_object_ref_sink (clutter_group_new ());
1097
 
  clutter_actor_set_position (priv->moving_cards_group, x, y);
1098
 
 
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
1101
 
   * does allow that.)
1102
 
   */
1103
 
  x = y = 0;
1104
 
  width = priv->card_size.width;
1105
 
  height = priv->card_size.height;
1106
 
 
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;
1111
 
 
1112
 
    removed_card.cardx = x;
1113
 
    removed_card.cardy = y;
1114
 
    removed_card.card = hcard;
1115
 
    removed_card.from_drag = TRUE;
1116
 
 
1117
 
    g_array_append_val (priv->removed_cards, removed_card);
1118
 
 
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),
1122
 
                           card_tex, NULL);
1123
 
 
1124
 
    x += hslot->pixeldx;
1125
 
    y += hslot->pixeldy;
1126
 
  }
1127
 
 
1128
 
  /* Take the cards off of the stack */
1129
 
  g_byte_array_set_size (cards, priv->moving_cards_origin_card_id);
1130
 
 
1131
 
  slot_update_geometry (board, hslot);
1132
 
  slot_update_card_images (board, hslot);
1133
 
  aisleriot_game_reset_old_cards (hslot);
1134
 
 
1135
 
  clutter_container_add (CLUTTER_CONTAINER (board),
1136
 
                         priv->moving_cards_group, NULL);
1137
 
 
1138
 
  if (hslot->cards->len == 0) {
1139
 
    clutter_actor_lower_bottom (hslot->slot_renderer);
1140
 
  }
1141
 
 
1142
 
  set_cursor (board, AR_CURSOR_CLOSED);
1143
 
}
1144
 
 
1145
 
static void
1146
 
drag_end (AisleriotBoard *board,
1147
 
          gboolean moved)
1148
 
{
1149
 
  AisleriotBoardPrivate *priv = board->priv;
1150
 
 
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;
1155
 
  }
1156
 
 
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 */
1159
 
  if (!moved &&
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);
1169
 
  }
1170
 
 
1171
 
  priv->click_status = STATUS_NONE;
1172
 
  priv->moving_cards_origin_slot = NULL;
1173
 
  priv->moving_cards_origin_card_id = -1;
1174
 
}
1175
 
 
1176
 
static gboolean
1177
 
cards_are_droppable (AisleriotBoard *board,
1178
 
                     ArSlot *slot)
1179
 
{
1180
 
  AisleriotBoardPrivate *priv = board->priv;
1181
 
 
1182
 
  return slot != NULL &&
1183
 
         priv->moving_cards_origin_slot &&
1184
 
         aisleriot_game_drop_valid (priv->game,
1185
 
                                    priv->moving_cards_origin_slot->id,
1186
 
                                    slot->id,
1187
 
                                    priv->moving_cards->data,
1188
 
                                    priv->moving_cards->len);
1189
 
}
1190
 
 
1191
 
static ArSlot *
1192
 
find_drop_target (AisleriotBoard *board,
1193
 
                  gint x,
1194
 
                  gint y)
1195
 
{
1196
 
  AisleriotBoardPrivate *priv = board->priv;
1197
 
  ArSlot *new_hslot;
1198
 
  ArSlot *retval = NULL;
1199
 
  gint i, new_cardid;
1200
 
  gint min_distance = G_MAXINT;
1201
 
 
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);
1207
 
 
1208
 
  if (cards_are_droppable (board, new_hslot))
1209
 
    return new_hslot;
1210
 
 
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);
1217
 
 
1218
 
    if (!new_hslot)
1219
 
      continue;
1220
 
 
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;
1224
 
 
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;
1227
 
 
1228
 
      distance_squared = dx * dx + dy * dy;
1229
 
 
1230
 
      if (distance_squared <= min_distance) {
1231
 
        retval = new_hslot;
1232
 
        min_distance = distance_squared;
1233
 
      }
1234
 
    }
1235
 
  }
1236
 
 
1237
 
  return retval;
1238
 
}
1239
 
 
1240
 
static void
1241
 
drop_moving_cards (AisleriotBoard *board,
1242
 
                   gint x,
1243
 
                   gint y)
1244
 
{
1245
 
  AisleriotBoardPrivate *priv = board->priv;
1246
 
  ArSlot *hslot;
1247
 
  gboolean moved = FALSE;
1248
 
  guint i;
1249
 
 
1250
 
  hslot = find_drop_target (board,
1251
 
                            x - priv->last_click_x,
1252
 
                            y - priv->last_click_y);
1253
 
 
1254
 
  /* Reposition the removed cards so that they are relative to the
1255
 
     cursor position */
1256
 
  for (i = 0; i < priv->removed_cards->len; i++) {
1257
 
    RemovedCard *removed_card = &g_array_index (priv->removed_cards,
1258
 
                                                RemovedCard, i);
1259
 
 
1260
 
    if (removed_card->from_drag) {
1261
 
      removed_card->cardx += x - priv->last_click_x;
1262
 
      removed_card->cardy += y - priv->last_click_y;
1263
 
    }
1264
 
  }
1265
 
 
1266
 
  if (hslot) {
1267
 
    moved = aisleriot_game_drop_cards (priv->game,
1268
 
                                       priv->moving_cards_origin_slot->id,
1269
 
                                       hslot->id,
1270
 
                                       priv->moving_cards->data,
1271
 
                                       priv->moving_cards->len);
1272
 
  }
1273
 
 
1274
 
  if (moved) {
1275
 
    aisleriot_game_end_move (priv->game);
1276
 
    ar_sound_play ("click");
1277
 
  } else {
1278
 
    aisleriot_game_discard_move (priv->game);
1279
 
    ar_sound_play ("slide");
1280
 
  }
1281
 
 
1282
 
  drag_end (board, moved);
1283
 
 
1284
 
  if (moved)
1285
 
    aisleriot_game_test_end_of_game (priv->game);
1286
 
}
1287
 
 
1288
 
static void
1289
 
highlight_drop_target (AisleriotBoard *board,
1290
 
                       ArSlot *slot)
1291
 
{
1292
 
  AisleriotBoardPrivate *priv = board->priv;
1293
 
  ArSlot *old_slot = priv->highlight_slot;
1294
 
 
1295
 
  if (slot == old_slot)
1296
 
    return;
1297
 
 
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.
1300
 
   */
1301
 
  priv->highlight_slot = slot;
1302
 
 
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!
1309
 
     */
1310
 
    slot_update_card_images_full (board, old_slot, G_MAXINT);
1311
 
  }
1312
 
 
1313
 
  if (!cards_are_droppable (board, slot))
1314
 
    return;
1315
 
 
1316
 
  if (!priv->show_highlight)
1317
 
    return;
1318
 
 
1319
 
  /* Prepare the highlight pixbuf/pixmaps */
1320
 
 
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!
1324
 
   */
1325
 
  slot_update_card_images_full (board, slot, ((int) slot->cards->len) - 1);
1326
 
}
1327
 
 
1328
 
static void
1329
 
reveal_card (AisleriotBoard *board,
1330
 
             ArSlot *slot,
1331
 
             int cardid)
1332
 
{
1333
 
  AisleriotBoardPrivate *priv = board->priv;
1334
 
  Card card;
1335
 
  AisleriotSlotRenderer *renderer;
1336
 
 
1337
 
  if (priv->show_card_slot == slot)
1338
 
    return;
1339
 
 
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);
1344
 
    }
1345
 
    priv->show_card_slot = NULL;
1346
 
    priv->show_card_id = -1;
1347
 
    priv->click_status = STATUS_NONE;
1348
 
  }
1349
 
 
1350
 
  if (!slot || cardid < 0 || cardid >= slot->cards->len - 1)
1351
 
    return;
1352
 
 
1353
 
  card = CARD (slot->cards->data[cardid]);
1354
 
  if (CARD_GET_FACE_DOWN (card))
1355
 
    return;
1356
 
 
1357
 
  priv->show_card_slot = slot;
1358
 
  priv->show_card_id = cardid;
1359
 
  priv->click_status = STATUS_SHOW;
1360
 
 
1361
 
  renderer = AISLERIOT_SLOT_RENDERER (slot->slot_renderer);
1362
 
  aisleriot_slot_renderer_set_revealed_card (renderer, cardid);
1363
 
}
1364
 
 
1365
 
static void
1366
 
clear_state (AisleriotBoard *board)
1367
 
{
1368
 
  AisleriotBoardPrivate *priv = board->priv;
1369
 
 
1370
 
  highlight_drop_target (board, NULL);
1371
 
  drag_end (board, FALSE /* FIXMEchpe ? */);
1372
 
 
1373
 
  reveal_card (board, NULL, -1);
1374
 
 
1375
 
  priv->click_status = STATUS_NONE;
1376
 
  priv->last_clicked_slot = NULL;
1377
 
  priv->last_clicked_card_id = -1;
1378
 
}
1379
 
 
1380
 
/* Note: this unsets the selection! */
1381
 
static gboolean
1382
 
aisleriot_board_move_selected_cards_to_slot (AisleriotBoard *board,
1383
 
                                             ArSlot *hslot)
1384
 
{
1385
 
  AisleriotBoardPrivate *priv = board->priv;
1386
 
  ArSlot *selection_slot = priv->selection_slot;
1387
 
  int selection_start_card_id = priv->selection_start_card_id;
1388
 
  gboolean moved;
1389
 
  guint8 *cards;
1390
 
  guint n_cards;
1391
 
 
1392
 
  if (!selection_slot ||
1393
 
      priv->selection_start_card_id < 0)
1394
 
    return FALSE;
1395
 
 
1396
 
  /* NOTE: We cannot use aisleriot_game_drop_valid here since the
1397
 
   * game may not support the "droppable" feature.
1398
 
   */
1399
 
 
1400
 
  set_selection (board, NULL, -1, FALSE);
1401
 
 
1402
 
  priv->click_status = STATUS_NONE;
1403
 
 
1404
 
  aisleriot_game_record_move (priv->game,
1405
 
                              selection_slot->id,
1406
 
                              selection_slot->cards->data,
1407
 
                              selection_slot->cards->len);
1408
 
 
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;
1412
 
 
1413
 
  cards = g_alloca (n_cards);
1414
 
  memcpy (cards,
1415
 
          selection_slot->cards->data + selection_start_card_id,
1416
 
          n_cards);
1417
 
 
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;
1421
 
 
1422
 
  moved = aisleriot_game_drop_cards (priv->game,
1423
 
                                      selection_slot->id,
1424
 
                                      hslot->id,
1425
 
                                      cards,
1426
 
                                      n_cards);
1427
 
  if (moved) {
1428
 
    aisleriot_game_end_move (priv->game);
1429
 
 
1430
 
    ar_sound_play ("click");
1431
 
 
1432
 
    if (selection_slot->needs_update)
1433
 
      g_signal_emit_by_name (priv->game, "slot-changed", selection_slot); /* FIXMEchpe! */
1434
 
 
1435
 
    aisleriot_game_test_end_of_game (priv->game);
1436
 
  } else {
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);
1440
 
  }
1441
 
 
1442
 
  return moved;
1443
 
}
1444
 
 
1445
 
/* Keynav */
1446
 
 
1447
 
#ifdef ENABLE_KEYNAV
1448
 
 
1449
 
/** Keynav specification:
1450
 
 *
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.
1454
 
 *
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.
1480
 
 *
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
1489
 
 *   first (last) slot
1490
 
 * Return: Performs the double-click action on the focused card
1491
 
 *
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
1499
 
 *   by the game
1500
 
 * End: shrinks the selection into nonexistence
1501
 
 * 
1502
 
 * With <control><shift>: extends selection like with <shift> alone,
1503
 
 *   and moves focus like with <control> alone
1504
 
 *
1505
 
 * Other keyboard shortcuts:
1506
 
 * <control>A: extends the selection maximally in the focused slot, as allowed
1507
 
 *   by the game
1508
 
 * <shift><control>A: unsets the selection
1509
 
 *
1510
 
 * Notes:
1511
 
 * If no slot is currently focused:
1512
 
 * Right, Up, Down, PgUp, PgDown, Home: moves the focus to the bottommost card on the first
1513
 
 *   slot
1514
 
 * Left, End: moves the focus to the topmost card on the last slot
1515
 
 */
1516
 
 
1517
 
static void
1518
 
aisleriot_board_add_move_binding (ClutterBindingPool *binding_pool,
1519
 
                                  GClosure           *closure,
1520
 
                                  const char         *action,
1521
 
                                  guint               keyval,
1522
 
                                  ClutterModifierType modifiers)
1523
 
{
1524
 
  clutter_binding_pool_install_closure (binding_pool,
1525
 
                                        action,
1526
 
                                        keyval,
1527
 
                                        modifiers,
1528
 
                                        closure);
1529
 
 
1530
 
  if (modifiers & CLUTTER_CONTROL_MASK)
1531
 
    return;
1532
 
 
1533
 
  clutter_binding_pool_install_closure (binding_pool,
1534
 
                                        action,
1535
 
                                        keyval,
1536
 
                                        modifiers | CLUTTER_CONTROL_MASK,
1537
 
                                        closure);
1538
 
}
1539
 
 
1540
 
static void
1541
 
aisleriot_board_add_move_and_select_binding (ClutterBindingPool *binding_pool,
1542
 
                                             GClosure           *closure,
1543
 
                                             const char         *action,
1544
 
                                             guint               keyval,
1545
 
                                             ClutterModifierType modifiers)
1546
 
{
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);
1549
 
}
1550
 
 
1551
 
static void
1552
 
aisleriot_board_add_activate_binding (ClutterBindingPool *binding_pool,
1553
 
                                      GClosure           *closure,
1554
 
                                      guint               keyval,
1555
 
                                      ClutterModifierType modifiers)
1556
 
{
1557
 
  clutter_binding_pool_install_closure (binding_pool,
1558
 
                                        I_("activate"),
1559
 
                                        keyval,
1560
 
                                        modifiers,
1561
 
                                        closure);
1562
 
 
1563
 
  if (modifiers & CLUTTER_CONTROL_MASK)
1564
 
    return;
1565
 
 
1566
 
  clutter_binding_pool_install_closure (binding_pool,
1567
 
                                        I_("activate"),
1568
 
                                        keyval,
1569
 
                                        modifiers | CLUTTER_CONTROL_MASK,
1570
 
                                        closure);
1571
 
}
1572
 
 
1573
 
static gboolean
1574
 
aisleriot_board_move_cursor_in_slot (AisleriotBoard *board,
1575
 
                                     int count)
1576
 
{
1577
 
  AisleriotBoardPrivate *priv = board->priv;
1578
 
  ArSlot *focus_slot;
1579
 
  int new_focus_card_id, first_card_id;
1580
 
 
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)
1585
 
    return FALSE;
1586
 
 
1587
 
  set_focus (board, focus_slot, new_focus_card_id, TRUE);
1588
 
  return TRUE;
1589
 
}
1590
 
 
1591
 
static gboolean
1592
 
aisleriot_board_move_cursor_start_end_in_slot (AisleriotBoard *board,
1593
 
                                               int count)
1594
 
{
1595
 
  AisleriotBoardPrivate *priv = board->priv;
1596
 
  ArSlot *focus_slot = priv->focus_slot;
1597
 
  int first_card_id, top_card_id, new_focus_card_id;
1598
 
  guint8 *cards;
1599
 
 
1600
 
  if (focus_slot->cards->len == 0)
1601
 
    return FALSE;
1602
 
 
1603
 
  g_assert (priv->focus_card_id >= 0);
1604
 
 
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.)
1610
 
   */
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;
1615
 
 
1616
 
  /* Set new_focus_card_id to the index of the last face-down card
1617
 
    * in the run of face-down cards.
1618
 
    */
1619
 
  do {
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])));
1624
 
 
1625
 
  /* We went one too far */
1626
 
  new_focus_card_id -= count;
1627
 
 
1628
 
  /* Now get to the start/end of the run of face-up cards */
1629
 
  do {
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])));
1634
 
 
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;
1640
 
  }
1641
 
 
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);
1644
 
 
1645
 
  return TRUE;
1646
 
}
1647
 
 
1648
 
static gboolean
1649
 
aisleriot_board_extend_selection_in_slot (AisleriotBoard *board,
1650
 
                                          int count)
1651
 
{
1652
 
  AisleriotBoardPrivate *priv = board->priv;
1653
 
  ArSlot *focus_slot, *selection_slot;
1654
 
  int new_selection_start_card_id, first_card_id;
1655
 
 
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);
1659
 
 
1660
 
  if (selection_slot == focus_slot) {
1661
 
    new_selection_start_card_id = priv->selection_start_card_id + count;
1662
 
 
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)
1666
 
      return FALSE;
1667
 
  } else {
1668
 
    /* No selection yet */
1669
 
    new_selection_start_card_id = ((int) focus_slot->cards->len) + count;
1670
 
 
1671
 
    /* Must have the topmost card focused */
1672
 
    if (new_selection_start_card_id != priv->focus_card_id)
1673
 
      return FALSE;
1674
 
  }
1675
 
 
1676
 
  if (new_selection_start_card_id < first_card_id)
1677
 
    return FALSE;
1678
 
 
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);
1682
 
    return TRUE;
1683
 
  }
1684
 
    
1685
 
  if (!aisleriot_game_drag_valid (priv->game,
1686
 
                                  focus_slot->id,
1687
 
                                  focus_slot->cards->data + new_selection_start_card_id,
1688
 
                                  focus_slot->cards->len - new_selection_start_card_id))
1689
 
    return FALSE;
1690
 
 
1691
 
  set_selection (board, focus_slot, new_selection_start_card_id, TRUE);
1692
 
 
1693
 
  /* Try to move the cursor too, but don't beep if that fails */
1694
 
  aisleriot_board_move_cursor_in_slot (board, count);
1695
 
  return TRUE;
1696
 
}
1697
 
 
1698
 
static gboolean
1699
 
aisleriot_board_extend_selection_in_slot_maximal (AisleriotBoard *board)
1700
 
{
1701
 
  AisleriotBoardPrivate *priv = board->priv;
1702
 
  ArSlot *focus_slot = priv->focus_slot;
1703
 
  int new_selection_start_card_id, n_selected;
1704
 
 
1705
 
  n_selected = 0;
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,
1709
 
                                    focus_slot->id,
1710
 
                                    focus_slot->cards->data + new_selection_start_card_id,
1711
 
                                    focus_slot->cards->len - new_selection_start_card_id))
1712
 
      break;
1713
 
 
1714
 
    ++n_selected;
1715
 
    --new_selection_start_card_id;
1716
 
  }
1717
 
 
1718
 
  if (n_selected == 0)
1719
 
    return FALSE;
1720
 
 
1721
 
  set_selection (board, focus_slot, new_selection_start_card_id + 1, TRUE);
1722
 
  return TRUE;
1723
 
}
1724
 
 
1725
 
static gboolean
1726
 
aisleriot_board_move_cursor_left_right_by_slot (AisleriotBoard *board,
1727
 
                                                int count,
1728
 
                                                gboolean wrap)
1729
 
{
1730
 
  AisleriotBoardPrivate *priv = board->priv;
1731
 
  GPtrArray *slots;
1732
 
  guint n_slots;
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;
1736
 
  gboolean is_rtl;
1737
 
 
1738
 
  slots = aisleriot_game_get_slots (priv->game);
1739
 
  if (!slots || slots->len == 0)
1740
 
    return FALSE;
1741
 
 
1742
 
  n_slots = slots->len;
1743
 
 
1744
 
  focus_slot = priv->focus_slot;
1745
 
  g_assert (focus_slot != NULL);
1746
 
 
1747
 
  focus_slot_index = get_slot_index_from_slot (board, focus_slot);
1748
 
 
1749
 
  /* Move visually */
1750
 
  is_rtl = priv->is_rtl;
1751
 
  if (priv->is_rtl) {
1752
 
    new_focus_slot_index = focus_slot_index - count;
1753
 
  } else {
1754
 
    new_focus_slot_index = focus_slot_index + count;
1755
 
  }
1756
 
 
1757
 
  /* Wrap-around? */
1758
 
  if (new_focus_slot_index < 0 ||
1759
 
      new_focus_slot_index >= n_slots) {
1760
 
    if (!wrap)
1761
 
      return FALSE;
1762
 
 
1763
 
#ifdef FIXMEchpe
1764
 
{
1765
 
    GtkDirectionType direction;
1766
 
 
1767
 
    if (count > 0) {
1768
 
      direction = GTK_DIR_RIGHT;
1769
 
    } else {
1770
 
      direction = GTK_DIR_LEFT;
1771
 
    }
1772
 
 
1773
 
    if (!gtk_widget_keynav_failed (widget, direction)) {
1774
 
       return gtk_widget_child_focus (gtk_widget_get_toplevel (widget), direction);
1775
 
    }
1776
 
}
1777
 
#endif // FIXMEchpe
1778
 
 
1779
 
    if (new_focus_slot_index < 0) {
1780
 
      new_focus_slot_index = ((int) n_slots) - 1;
1781
 
    } else {
1782
 
      new_focus_slot_index = 0;
1783
 
    }
1784
 
  }
1785
 
 
1786
 
  g_assert (new_focus_slot_index >= 0 && new_focus_slot_index < n_slots);
1787
 
 
1788
 
  new_focus_slot = slots->pdata[new_focus_slot_index];
1789
 
  new_focus_slot_topmost_card_id = ((int) new_focus_slot->cards->len) - 1;
1790
 
 
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);
1796
 
      } else {
1797
 
        new_focus_card_id = -1;
1798
 
      }
1799
 
    } else {
1800
 
      new_focus_card_id = new_focus_slot_topmost_card_id;
1801
 
    }
1802
 
  } else {
1803
 
    /* Just take the topmost card */
1804
 
    new_focus_card_id = new_focus_slot_topmost_card_id;
1805
 
  }
1806
 
 
1807
 
  set_focus (board, new_focus_slot, new_focus_card_id, TRUE);
1808
 
  return TRUE;
1809
 
}
1810
 
 
1811
 
static gboolean
1812
 
aisleriot_board_move_cursor_up_down_by_slot (AisleriotBoard *board,
1813
 
                                             int count)
1814
 
{
1815
 
  AisleriotBoardPrivate *priv = board->priv;
1816
 
  GPtrArray *slots;
1817
 
  guint n_slots;
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;
1821
 
  int x_start, x_end;
1822
 
 
1823
 
  slots = aisleriot_game_get_slots (priv->game);
1824
 
  if (!slots || slots->len == 0)
1825
 
    return FALSE;
1826
 
 
1827
 
  n_slots = slots->len;
1828
 
 
1829
 
  focus_slot = priv->focus_slot;
1830
 
  g_assert (focus_slot != NULL);
1831
 
 
1832
 
  x_start = focus_slot->rect.x;
1833
 
  x_end = x_start + focus_slot->rect.width;
1834
 
 
1835
 
  focus_slot_index = get_slot_index_from_slot (board, focus_slot);
1836
 
 
1837
 
  new_focus_slot_index = focus_slot_index;
1838
 
  do {
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));
1843
 
 
1844
 
  if (new_focus_slot_index < 0 || new_focus_slot_index == n_slots) {
1845
 
#ifdef FIXMEchpe
1846
 
    GtkWidget *widget = GTK_WIDGET (board);
1847
 
    GtkDirectionType direction;
1848
 
 
1849
 
    if (count > 0) {
1850
 
      direction = GTK_DIR_DOWN;
1851
 
    } else {
1852
 
      direction = GTK_DIR_UP;
1853
 
    }
1854
 
 
1855
 
    if (!gtk_widget_keynav_failed (widget, direction)) {
1856
 
       return gtk_widget_child_focus (gtk_widget_get_toplevel (widget), direction);
1857
 
    }
1858
 
#endif // FIXMEchpe
1859
 
 
1860
 
    /* Wrap around */
1861
 
    if (count > 0) {
1862
 
      new_focus_slot_index = -1;
1863
 
    } else {
1864
 
      new_focus_slot_index = n_slots;
1865
 
    }
1866
 
 
1867
 
    do {
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));
1871
 
  }
1872
 
 
1873
 
  g_assert (new_focus_slot_index >= 0 && new_focus_slot_index < n_slots);
1874
 
 
1875
 
  new_focus_slot = slots->pdata[new_focus_slot_index];
1876
 
  new_focus_slot_topmost_card_id = ((int) new_focus_slot->cards->len) - 1;
1877
 
 
1878
 
  if (new_focus_slot->expanded_down) {
1879
 
    if (count > 0) {
1880
 
      if (new_focus_slot->cards->len > 0) {
1881
 
        new_focus_card_id = ((int) new_focus_slot->cards->len) - ((int) new_focus_slot->exposed);
1882
 
      } else {
1883
 
        new_focus_card_id = -1;
1884
 
      }
1885
 
    } else {
1886
 
      new_focus_card_id = new_focus_slot_topmost_card_id;
1887
 
    }
1888
 
  } else {
1889
 
    /* Just take the topmost card */
1890
 
    new_focus_card_id = new_focus_slot_topmost_card_id;
1891
 
  }
1892
 
 
1893
 
  set_focus (board, new_focus_slot, new_focus_card_id, TRUE);
1894
 
  return TRUE;
1895
 
}
1896
 
 
1897
 
static gboolean
1898
 
aisleriot_board_move_cursor_start_end_by_slot (AisleriotBoard *board,
1899
 
                                               int count)
1900
 
{
1901
 
  AisleriotBoardPrivate *priv = board->priv;
1902
 
  GPtrArray *slots;
1903
 
  ArSlot *new_focus_slot;
1904
 
  int new_focus_card_id;
1905
 
 
1906
 
  slots = aisleriot_game_get_slots (priv->game);
1907
 
  if (!slots || slots->len == 0)
1908
 
    return FALSE;
1909
 
 
1910
 
  if (count > 0) {
1911
 
    new_focus_slot = (ArSlot *) slots->pdata[slots->len - 1];
1912
 
    new_focus_card_id = ((int) new_focus_slot->cards->len) - 1;
1913
 
  } else {
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);
1917
 
    } else {
1918
 
      new_focus_card_id = -1;
1919
 
    }
1920
 
  }
1921
 
 
1922
 
  g_assert (new_focus_slot != NULL);
1923
 
  g_assert (new_focus_card_id >= -1);
1924
 
 
1925
 
  set_focus (board, new_focus_slot, new_focus_card_id, TRUE);
1926
 
  return TRUE;
1927
 
}
1928
 
 
1929
 
static gboolean
1930
 
aisleriot_board_move_cursor_left_right (AisleriotBoard *board,
1931
 
                                        int count,
1932
 
                                        gboolean is_control)
1933
 
{
1934
 
  AisleriotBoardPrivate *priv = board->priv;
1935
 
 
1936
 
  /* First try in-slot focus movement */
1937
 
  if (!is_control &&
1938
 
      priv->focus_slot->expanded_right &&
1939
 
      aisleriot_board_move_cursor_in_slot (board, priv->is_rtl ? -count : count))
1940
 
    return TRUE;
1941
 
 
1942
 
  /* Cannot move in-slot; move focused slot */
1943
 
  return aisleriot_board_move_cursor_left_right_by_slot (board, count, TRUE);
1944
 
}
1945
 
 
1946
 
static gboolean
1947
 
aisleriot_board_move_cursor_up_down (AisleriotBoard *board,
1948
 
                                     int count,
1949
 
                                     gboolean is_control)
1950
 
{
1951
 
  AisleriotBoardPrivate *priv = board->priv;
1952
 
 
1953
 
  g_assert (priv->focus_slot != NULL);
1954
 
 
1955
 
  /* First try in-slot focus movement */
1956
 
  if (!is_control &&
1957
 
      priv->focus_slot->expanded_down &&
1958
 
      aisleriot_board_move_cursor_in_slot (board, count))
1959
 
    return TRUE;
1960
 
 
1961
 
  /* Cannot move in-slot; move focused slot */
1962
 
  return aisleriot_board_move_cursor_up_down_by_slot (board, count);
1963
 
}
1964
 
 
1965
 
static gboolean
1966
 
aisleriot_board_extend_selection_left_right (AisleriotBoard *board,
1967
 
                                             int count)
1968
 
{
1969
 
  AisleriotBoardPrivate *priv = board->priv;
1970
 
 
1971
 
  if (!priv->focus_slot->expanded_right)
1972
 
    return FALSE;
1973
 
 
1974
 
  return aisleriot_board_extend_selection_in_slot (board, count);
1975
 
}
1976
 
 
1977
 
static gboolean
1978
 
aisleriot_board_extend_selection_up_down (AisleriotBoard *board,
1979
 
                                          int count)
1980
 
{
1981
 
  AisleriotBoardPrivate *priv = board->priv;
1982
 
 
1983
 
  if (!priv->focus_slot->expanded_down)
1984
 
    return FALSE;
1985
 
 
1986
 
  return aisleriot_board_extend_selection_in_slot (board, count);
1987
 
}
1988
 
 
1989
 
static gboolean
1990
 
aisleriot_board_extend_selection_start_end (AisleriotBoard *board,
1991
 
                                            int count)
1992
 
{
1993
 
  AisleriotBoardPrivate *priv = board->priv;
1994
 
  ArSlot *focus_slot = priv->focus_slot;
1995
 
  int new_focus_card_id;
1996
 
 
1997
 
  if (count > 0) {
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.
2000
 
     */
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);
2005
 
    } else {
2006
 
      aisleriot_board_error_bell (board);
2007
 
      return FALSE;
2008
 
    }
2009
 
 
2010
 
  } else {
2011
 
    if (!aisleriot_board_extend_selection_in_slot_maximal (board)) {
2012
 
      set_selection (board, NULL, -1, FALSE);
2013
 
      aisleriot_board_error_bell (board);
2014
 
      return FALSE;
2015
 
    }
2016
 
 
2017
 
    new_focus_card_id = priv->selection_start_card_id;
2018
 
  }
2019
 
 
2020
 
  set_focus (board, focus_slot, new_focus_card_id, TRUE);
2021
 
  return TRUE;
2022
 
}
2023
 
 
2024
 
#endif /* ENABLE_KEYNAV */
2025
 
 
2026
 
/* Game state handling */
2027
 
 
2028
 
static void
2029
 
game_type_changed_cb (AisleriotGame *game,
2030
 
                      AisleriotBoard *board)
2031
 
{
2032
 
  AisleriotBoardPrivate *priv = board->priv;
2033
 
  guint features;
2034
 
 
2035
 
  features = aisleriot_game_get_features (game);
2036
 
 
2037
 
  priv->droppable_supported = ((features & FEATURE_DROPPABLE) != 0);
2038
 
  priv->show_highlight = priv->droppable_supported;
2039
 
}
2040
 
 
2041
 
static void
2042
 
game_cleared_cb (AisleriotGame *game,
2043
 
                 AisleriotBoard *board)
2044
 
{
2045
 
  AisleriotBoardPrivate *priv = board->priv;
2046
 
 
2047
 
  priv->geometry_set = FALSE;
2048
 
 
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;
2055
 
 
2056
 
  clear_state (board);
2057
 
}
2058
 
 
2059
 
static void
2060
 
game_new_cb (AisleriotGame *game,
2061
 
             AisleriotBoard *board)
2062
 
{
2063
 
  AisleriotBoardPrivate *priv = board->priv;
2064
 
 
2065
 
  clear_state (board);
2066
 
 
2067
 
  set_focus (board, NULL, -1, FALSE);
2068
 
  set_selection (board, NULL, -1, FALSE);
2069
 
 
2070
 
  aisleriot_game_get_geometry (game, &priv->width, &priv->height);
2071
 
 
2072
 
  aisleriot_board_setup_geometry (board);
2073
 
 
2074
 
#if 0
2075
 
  g_print ("{ %.3f , %.3f /* %s */ },\n",
2076
 
           priv->width, priv->height,
2077
 
           aisleriot_game_get_game_file (priv->game));
2078
 
#endif
2079
 
 
2080
 
  /* Check for animations so that the differences will be reset */
2081
 
  queue_check_animations (board);
2082
 
}
2083
 
 
2084
 
static void
2085
 
slot_changed_cb (AisleriotGame *game,
2086
 
                 ArSlot *slot,
2087
 
                 AisleriotBoard *board)
2088
 
{
2089
 
  AisleriotBoardPrivate *priv = board->priv;
2090
 
 
2091
 
  slot_update_geometry (board, slot);
2092
 
  slot_update_card_images (board, slot);
2093
 
 
2094
 
  if (slot == priv->moving_cards_origin_slot) {
2095
 
    /* PANIC! */
2096
 
    /* FIXMEchpe */
2097
 
  }
2098
 
  if (slot == priv->selection_slot) {
2099
 
    set_selection (board, NULL, -1, FALSE);
2100
 
 
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.
2108
 
     */
2109
 
    priv->click_status = STATUS_NONE;
2110
 
  }
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
2116
 
     * topmost card.
2117
 
     */
2118
 
    if (priv->focus_card_id < 0) {
2119
 
      set_focus (board, slot, ((int) slot->cards->len) - 1, priv->show_focus);
2120
 
    } else {
2121
 
      set_focus (board, slot, priv->focus_card_id, priv->show_focus);
2122
 
    }
2123
 
  }
2124
 
  if (slot == priv->highlight_slot) {
2125
 
    highlight_drop_target (board, NULL);
2126
 
  }
2127
 
 
2128
 
  queue_check_animations (board);
2129
 
}
2130
 
 
2131
 
/* Style handling */
2132
 
 
2133
 
static void
2134
 
aisleriot_board_sync_style (ArStyle *style,
2135
 
                            GParamSpec *pspec,
2136
 
                            AisleriotBoard *board)
2137
 
{
2138
 
  AisleriotBoardPrivate *priv = board->priv;
2139
 
  const char *pspec_name;
2140
 
  gboolean update_geometry = FALSE, redraw_focus = FALSE;
2141
 
 
2142
 
  g_assert (style == priv->style);
2143
 
 
2144
 
  if (pspec != NULL) {
2145
 
    pspec_name = pspec->name;
2146
 
  } else {
2147
 
    pspec_name = NULL;
2148
 
  }
2149
 
 
2150
 
  if (pspec_name == NULL || pspec_name == I_(AR_STYLE_PROP_CARD_THEME)) {
2151
 
    ArCardTheme *theme;
2152
 
 
2153
 
    theme = ar_style_get_card_theme (style);
2154
 
    if (theme != NULL) {
2155
 
      ar_card_textures_cache_set_theme (priv->textures, theme);
2156
 
 
2157
 
      priv->geometry_set = FALSE;
2158
 
 
2159
 
      update_geometry |= TRUE;
2160
 
    }
2161
 
  }
2162
 
 
2163
 
  if (pspec_name == NULL || pspec_name == I_(AR_STYLE_PROP_CARD_SLOT_RATIO)) {
2164
 
    double card_slot_ratio;
2165
 
 
2166
 
    card_slot_ratio = ar_style_get_card_slot_ratio (style);
2167
 
 
2168
 
    update_geometry |= (card_slot_ratio != priv->card_slot_ratio);
2169
 
 
2170
 
    priv->card_slot_ratio = card_slot_ratio;
2171
 
  }
2172
 
 
2173
 
  if (pspec_name == NULL || pspec_name == I_(AR_STYLE_PROP_CARD_OVERHANG)) {
2174
 
    update_geometry |= TRUE;
2175
 
  }
2176
 
 
2177
 
  if (pspec_name == NULL || pspec_name == I_(AR_STYLE_PROP_CARD_STEP)) {
2178
 
    update_geometry |= TRUE;
2179
 
  }
2180
 
 
2181
 
  if (pspec_name == NULL || pspec_name == I_(AR_STYLE_PROP_INTERIOR_FOCUS)) {
2182
 
    gboolean interior_focus;
2183
 
 
2184
 
    interior_focus = ar_style_get_interior_focus (style);
2185
 
 
2186
 
    redraw_focus = (interior_focus != priv->interior_focus);
2187
 
 
2188
 
    priv->interior_focus = interior_focus;
2189
 
  }
2190
 
 
2191
 
  if (pspec_name == NULL || pspec_name == I_(AR_STYLE_PROP_FOCUS_LINE_WIDTH)) {
2192
 
    int focus_line_width;
2193
 
 
2194
 
    focus_line_width = ar_style_get_focus_line_width (style);
2195
 
 
2196
 
    redraw_focus = (focus_line_width != priv->focus_line_width);
2197
 
 
2198
 
    priv->focus_line_width = focus_line_width;
2199
 
  }
2200
 
 
2201
 
  if (pspec_name == NULL || pspec_name == I_(AR_STYLE_PROP_FOCUS_PADDING)) {
2202
 
    int focus_padding;
2203
 
 
2204
 
    focus_padding = ar_style_get_focus_padding (style);
2205
 
 
2206
 
    redraw_focus = (focus_padding != priv->focus_padding);
2207
 
 
2208
 
    priv->focus_padding = focus_padding;
2209
 
  }
2210
 
 
2211
 
  if (pspec_name == NULL || pspec_name == I_(AR_STYLE_PROP_RTL)) {
2212
 
    gboolean is_rtl;
2213
 
 
2214
 
    is_rtl = ar_style_get_rtl (style);
2215
 
 
2216
 
    update_geometry |= (is_rtl != priv->is_rtl);
2217
 
 
2218
 
    priv->is_rtl = is_rtl;
2219
 
 
2220
 
    /* FIXMEchpe: necessary? */
2221
 
    priv->force_geometry_update = TRUE;
2222
 
  }
2223
 
 
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 */
2226
 
  }
2227
 
 
2228
 
  if (pspec_name == NULL || pspec_name == I_(AR_STYLE_PROP_CLICK_TO_MOVE)) {
2229
 
    gboolean click_to_move;
2230
 
 
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.
2235
 
      */
2236
 
      set_selection (board, NULL, -1, FALSE);
2237
 
 
2238
 
      priv->click_to_move = click_to_move;
2239
 
 
2240
 
      /* FIXMEchpe: we used to queue a redraw here. WHY?? Check that it's safe not to. */
2241
 
    }
2242
 
  }
2243
 
 
2244
 
  if (pspec_name == NULL || pspec_name == I_(AR_STYLE_PROP_SHOW_STATUS_MESSAGES)) {
2245
 
    gboolean show_status_messages;
2246
 
 
2247
 
    show_status_messages = ar_style_get_show_status_messages (priv->style);
2248
 
 
2249
 
    if (show_status_messages != priv->show_status_messages) {
2250
 
      priv->show_status_messages = show_status_messages;
2251
 
 
2252
 
      if (!show_status_messages) {
2253
 
        /* Clear message */
2254
 
        set_status_message (board, NULL);
2255
 
      }
2256
 
    }
2257
 
  }
2258
 
 
2259
 
  /* FIXMEchpe: queue a relayout instead? */
2260
 
  if (update_geometry) {
2261
 
    aisleriot_board_setup_geometry (board);
2262
 
  }
2263
 
 
2264
 
  if (redraw_focus) {
2265
 
    /* FIXMEchpe: do redraw the focus! */
2266
 
  }
2267
 
}
2268
 
 
2269
 
/* Class implementation */
2270
 
 
2271
 
G_DEFINE_TYPE (AisleriotBoard, aisleriot_board, CLUTTER_TYPE_GROUP);
2272
 
 
2273
 
/* AisleriotBoardClass methods */
2274
 
 
2275
 
#ifdef ENABLE_KEYNAV
2276
 
 
2277
 
static void
2278
 
aisleriot_board_activate (AisleriotBoard *board,
2279
 
                          const char *action,
2280
 
                          guint keyval,
2281
 
                          ClutterModifierType modifiers)
2282
 
{
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;
2286
 
 
2287
 
#ifdef FIXMEchpe
2288
 
  if (!gtk_widget_has_focus (widget))
2289
 
    return;
2290
 
#endif
2291
 
 
2292
 
  ar_debug_print (AR_DEBUG_GAME_KEYNAV,
2293
 
                      "board ::activate keyval %x modifiers %x\n",
2294
 
                      keyval, modifiers);
2295
 
 
2296
 
  if (!focus_slot) {
2297
 
    aisleriot_board_error_bell (board);
2298
 
    return;
2299
 
  }
2300
 
 
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);
2304
 
    return;
2305
 
  }
2306
 
 
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);
2312
 
    } else {
2313
 
      aisleriot_game_discard_move (priv->game);
2314
 
      aisleriot_board_error_bell (board);
2315
 
    }
2316
 
 
2317
 
    aisleriot_game_test_end_of_game (priv->game);
2318
 
 
2319
 
    return;
2320
 
  }
2321
 
 
2322
 
  /* Try single click action */
2323
 
  aisleriot_game_record_move (priv->game, -1, NULL, 0);
2324
 
 
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);
2329
 
 
2330
 
    return;
2331
 
  }
2332
 
 
2333
 
  aisleriot_game_discard_move (priv->game);
2334
 
 
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 !
2338
 
   *
2339
 
   * NOTE: We cannot use aisleriot_game_drop_valid here since the
2340
 
   * game may not support the "droppable" feature.
2341
 
   */
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);
2348
 
 
2349
 
      return;
2350
 
    }
2351
 
 
2352
 
    /* Trying to move the cards has unset the selection; re-select them */
2353
 
    set_selection (board, selection_slot, selection_start_card_id, TRUE);
2354
 
  }
2355
 
 
2356
 
  aisleriot_board_error_bell (board);
2357
 
}
2358
 
 
2359
 
static gboolean
2360
 
aisleriot_board_move_cursor (AisleriotBoard *board,
2361
 
                             const char *action,
2362
 
                             guint keyval,
2363
 
                             ClutterModifierType modifiers)
2364
 
{
2365
 
  AisleriotBoardPrivate *priv = board->priv;
2366
 
  gboolean is_control, is_shift, moved = FALSE;
2367
 
  char step;
2368
 
  int count;
2369
 
 
2370
 
#ifdef FIXMEchpe
2371
 
  if (!gtk_widget_has_focus (widget))
2372
 
    return FALSE;
2373
 
#endif
2374
 
 
2375
 
  step = action[0];
2376
 
  count = (action[1] == MOVE_LEFT ? -1 : 1);
2377
 
 
2378
 
  ar_debug_print (AR_DEBUG_GAME_KEYNAV,
2379
 
                      "board ::move-cursor keyval %x modifiers %x step '%c' count %d\n",
2380
 
                      keyval, modifiers,
2381
 
                      step, count);
2382
 
 
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) {
2386
 
    switch (step) {
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);
2395
 
        }
2396
 
        /* fall-through */
2397
 
      default:
2398
 
        return aisleriot_board_move_cursor_start_end_by_slot (board, count);
2399
 
    }
2400
 
  }
2401
 
 
2402
 
  g_assert (priv->focus_slot != NULL);
2403
 
 
2404
 
  is_shift = (modifiers & CLUTTER_SHIFT_MASK) != 0;
2405
 
  is_control = (modifiers & CLUTTER_CONTROL_MASK) != 0;
2406
 
 
2407
 
  switch (step) {
2408
 
    case MOVE_CURSOR_LEFT_RIGHT:
2409
 
      if (is_shift) {
2410
 
        moved = aisleriot_board_extend_selection_left_right (board, count);
2411
 
      } else {
2412
 
        moved = aisleriot_board_move_cursor_left_right (board, count, is_control);
2413
 
      }
2414
 
      break;
2415
 
    case MOVE_CURSOR_UP_DOWN:
2416
 
      if (is_shift) {
2417
 
        moved = aisleriot_board_extend_selection_up_down (board, count);
2418
 
      } else {
2419
 
        moved = aisleriot_board_move_cursor_up_down (board, count, is_control);
2420
 
      }
2421
 
      break;
2422
 
    case MOVE_CURSOR_PAGES:
2423
 
      if (!is_shift) {
2424
 
        moved = aisleriot_board_move_cursor_up_down (board, count, TRUE);
2425
 
      }
2426
 
      break;
2427
 
    case MOVE_CURSOR_START_END:
2428
 
      if (is_shift) {
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);
2432
 
      } else {
2433
 
        moved = aisleriot_board_move_cursor_start_end_in_slot (board, count);
2434
 
      }
2435
 
      break;
2436
 
    default:
2437
 
      g_assert_not_reached ();
2438
 
  }
2439
 
 
2440
 
  /* Show focus */
2441
 
  if (!moved &&
2442
 
      !priv->show_focus) {
2443
 
    set_focus (board, priv->focus_slot, priv->focus_card_id, TRUE);
2444
 
  }
2445
 
 
2446
 
  return moved;
2447
 
}
2448
 
 
2449
 
static void
2450
 
aisleriot_board_select_all (AisleriotBoard *board,
2451
 
                            const char *action,
2452
 
                            guint keyval,
2453
 
                            ClutterModifierType modifiers)
2454
 
{
2455
 
  AisleriotBoardPrivate *priv = board->priv;
2456
 
  ArSlot *focus_slot = priv->focus_slot;
2457
 
 
2458
 
  ar_debug_print (AR_DEBUG_GAME_KEYNAV,
2459
 
                      "board ::select-all keyval %x modifiers %x\n",
2460
 
                      keyval, modifiers);
2461
 
 
2462
 
  if (!focus_slot ||
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);
2467
 
  }
2468
 
}
2469
 
 
2470
 
static void
2471
 
aisleriot_board_deselect_all (AisleriotBoard *board,
2472
 
                              const char *action,
2473
 
                              guint keyval,
2474
 
                              ClutterModifierType modifiers)
2475
 
{
2476
 
  ar_debug_print (AR_DEBUG_GAME_KEYNAV,
2477
 
                      "board ::deselect-all keyval %x modifiers %x\n",
2478
 
                      keyval, modifiers);
2479
 
 
2480
 
  set_selection (board, NULL, -1, FALSE);
2481
 
}
2482
 
 
2483
 
static void
2484
 
aisleriot_board_toggle_selection (AisleriotBoard *board,
2485
 
                                  const char *action,
2486
 
                                  guint keyval,
2487
 
                                  ClutterModifierType modifiers)
2488
 
{
2489
 
  AisleriotBoardPrivate *priv = board->priv;
2490
 
  ArSlot *focus_slot;
2491
 
  int focus_card_id;
2492
 
 
2493
 
  ar_debug_print (AR_DEBUG_GAME_KEYNAV,
2494
 
                      "board ::toggle-selection keyval %x modifiers %x\n",
2495
 
                      keyval, modifiers);
2496
 
 
2497
 
  focus_slot = priv->focus_slot;
2498
 
  if (!focus_slot)
2499
 
    return;
2500
 
 
2501
 
  focus_card_id = priv->focus_card_id;
2502
 
 
2503
 
  /* Focus not shown? Show it, and proceed */
2504
 
  if (!priv->show_focus) {
2505
 
    set_focus (board, focus_slot, focus_card_id, TRUE);
2506
 
  }
2507
 
 
2508
 
  if (focus_card_id < 0) {
2509
 
    aisleriot_board_error_bell (board);
2510
 
    return;
2511
 
  }
2512
 
 
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.
2517
 
   */
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);
2522
 
    return;
2523
 
  }
2524
 
 
2525
 
  if (!aisleriot_game_drag_valid (priv->game,
2526
 
                                  focus_slot->id,
2527
 
                                  focus_slot->cards->data + focus_card_id,
2528
 
                                  focus_slot->cards->len - focus_card_id)) {
2529
 
    aisleriot_board_error_bell (board);
2530
 
    return;
2531
 
  }
2532
 
 
2533
 
  set_selection (board, focus_slot, focus_card_id, TRUE);
2534
 
}
2535
 
 
2536
 
#endif /* ENABLE_KEYNAV */
2537
 
 
2538
 
 
2539
 
/* ClutterActorClass impl */
2540
 
 
2541
 
#if 0
2542
 
static void
2543
 
aisleriot_board_realize (GtkWidget *widget)
2544
 
{
2545
 
  AisleriotBoard *board = AISLERIOT_BOARD (widget);
2546
 
//   AisleriotBoardPrivate *priv = board->priv;
2547
 
 
2548
 
  GTK_WIDGET_CLASS (aisleriot_board_parent_class)->realize (widget);
2549
 
 
2550
 
  aisleriot_board_setup_geometry (board);
2551
 
}
2552
 
 
2553
 
static void
2554
 
aisleriot_board_unrealize (GtkWidget *widget)
2555
 
{
2556
 
  AisleriotBoard *board = AISLERIOT_BOARD (widget);
2557
 
  AisleriotBoardPrivate *priv = board->priv;
2558
 
 
2559
 
  priv->geometry_set = FALSE;
2560
 
 
2561
 
  clear_state (board);
2562
 
 
2563
 
  GTK_WIDGET_CLASS (aisleriot_board_parent_class)->unrealize (widget);
2564
 
}
2565
 
#endif
2566
 
 
2567
 
static void
2568
 
aisleriot_board_allocate (ClutterActor *actor,
2569
 
                          const ClutterActorBox *box,
2570
 
                          ClutterAllocationFlags flags)
2571
 
{
2572
 
  AisleriotBoard *board = AISLERIOT_BOARD (actor);
2573
 
  AisleriotBoardPrivate *priv = board->priv;
2574
 
  gboolean is_same;
2575
 
 
2576
 
  is_same = clutter_actor_box_equal (box, &priv->allocation);
2577
 
 
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");
2584
 
 
2585
 
  CLUTTER_ACTOR_CLASS (aisleriot_board_parent_class)->allocate (actor, box, flags);
2586
 
 
2587
 
  priv->allocation = *box;
2588
 
 
2589
 
  if (is_same && !priv->force_geometry_update)
2590
 
    return;
2591
 
 
2592
 
  priv->force_geometry_update = FALSE;
2593
 
 
2594
 
  /* FIXMEchpe: just queue this instead maybe? */
2595
 
  aisleriot_board_setup_geometry (board);
2596
 
}
2597
 
 
2598
 
static void
2599
 
aisleriot_board_get_preferred_width (ClutterActor *actor,
2600
 
                                     float for_height,
2601
 
                                     float *min_width_p,
2602
 
                                     float *natural_width_p)
2603
 
{
2604
 
  ar_debug_print (AR_DEBUG_GAME_SIZING,
2605
 
                      "board ::get-preferred-width\n");
2606
 
 
2607
 
  *min_width_p = BOARD_MIN_WIDTH;
2608
 
  *natural_width_p = 3 * BOARD_MIN_WIDTH;
2609
 
}
2610
 
 
2611
 
static void
2612
 
aisleriot_board_get_preferred_height (ClutterActor *actor,
2613
 
                                      float for_width,
2614
 
                                      float *min_height_p,
2615
 
                                      float *natural_height_p)
2616
 
{
2617
 
  ar_debug_print (AR_DEBUG_GAME_SIZING,
2618
 
                      "board ::get-preferred-height\n");
2619
 
 
2620
 
  *min_height_p = BOARD_MIN_HEIGHT;
2621
 
  *natural_height_p = 3 * BOARD_MIN_HEIGHT;
2622
 
}
2623
 
 
2624
 
#ifdef ENABLE_KEYNAV
2625
 
 
2626
 
static gboolean
2627
 
aisleriot_board_focus (AisleriotBoard *board,
2628
 
                       int count)
2629
 
{
2630
 
  AisleriotBoardPrivate *priv = board->priv;
2631
 
 
2632
 
  if (!priv->focus_slot) {
2633
 
    return aisleriot_board_move_cursor_start_end_by_slot (board, -count);
2634
 
  }
2635
 
 
2636
 
#if 0
2637
 
  if (aisleriot_board_move_cursor_left_right_by_slot (board, count, FALSE))
2638
 
    return TRUE;
2639
 
#endif
2640
 
 
2641
 
  return FALSE;
2642
 
}
2643
 
 
2644
 
static gboolean
2645
 
aisleriot_board_key_press (ClutterActor *actor,
2646
 
                           ClutterKeyEvent *event)
2647
 
{
2648
 
  ClutterBindingPool *pool;
2649
 
 
2650
 
  ar_debug_print (AR_DEBUG_GAME_EVENTS,
2651
 
                      "board ::key-press keyval %x modifiers %x\n",
2652
 
                      event->keyval, event->modifier_state);
2653
 
 
2654
 
  pool = clutter_binding_pool_get_for_class (CLUTTER_ACTOR_GET_CLASS (actor));
2655
 
  g_assert (pool != NULL);
2656
 
 
2657
 
  return clutter_binding_pool_activate (pool,
2658
 
                                        event->keyval,
2659
 
                                        event->modifier_state,
2660
 
                                        G_OBJECT (actor));
2661
 
}
2662
 
 
2663
 
#endif /* ENABLE_KEYNAV */
2664
 
 
2665
 
#ifdef FIXMEchpe
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?
2669
 
 */
2670
 
static gboolean
2671
 
aisleriot_board_focus_in (GtkWidget *widget,
2672
 
                          GdkEventFocus *event)
2673
 
{
2674
 
#ifdef ENABLE_KEYNAV
2675
 
  AisleriotBoard *board = AISLERIOT_BOARD (widget);
2676
 
  AisleriotBoardPrivate *priv = board->priv;
2677
 
 
2678
 
  /* Paint focus */
2679
 
  if (priv->show_focus &&
2680
 
      priv->focus_slot != NULL) {
2681
 
    gdk_window_invalidate_rect (widget->window, &priv->focus_rect, FALSE);
2682
 
  }
2683
 
#endif /* ENABLE_KEYNAV */
2684
 
 
2685
 
  return FALSE;
2686
 
}
2687
 
 
2688
 
static gboolean
2689
 
aisleriot_board_focus_out (GtkWidget *widget,
2690
 
                           GdkEventFocus *event)
2691
 
{
2692
 
  AisleriotBoard *board = AISLERIOT_BOARD (widget);
2693
 
#ifdef ENABLE_KEYNAV
2694
 
  AisleriotBoardPrivate *priv = board->priv;
2695
 
#endif /* ENABLE_KEYNAV */
2696
 
 
2697
 
  clear_state (board);
2698
 
 
2699
 
#ifdef ENABLE_KEYNAV
2700
 
  /* Hide focus */
2701
 
  if (priv->show_focus &&
2702
 
      priv->focus_slot != NULL) {
2703
 
    gdk_window_invalidate_rect (widget->window, &priv->focus_rect, FALSE);
2704
 
  }
2705
 
#endif /* ENABLE_KEYNAV */
2706
 
 
2707
 
  return FALSE;
2708
 
}
2709
 
#endif /* FIXMEchpe */
2710
 
 
2711
 
static gboolean
2712
 
aisleriot_board_button_press (ClutterActor *actor,
2713
 
                              ClutterButtonEvent *event)
2714
 
{
2715
 
  AisleriotBoard *board = AISLERIOT_BOARD (actor);
2716
 
  AisleriotBoardPrivate *priv = board->priv;
2717
 
  ArSlot *hslot;
2718
 
  int cardid;
2719
 
  guint32 button;
2720
 
  gboolean drag_valid;
2721
 
  guint state;
2722
 
  gboolean is_double_click, show_focus;
2723
 
 
2724
 
  ar_debug_print (AR_DEBUG_GAME_EVENTS,
2725
 
                      "board ::button-press @(%f / %f) button %d click-count %d modifiers %x\n",
2726
 
                      event->x, event->y,
2727
 
                      event->button, event->click_count,
2728
 
                      event->modifier_state);
2729
 
 
2730
 
  /* NOTE: It's ok to just return instead of chaining up, since the
2731
 
   * parent classes have no class closure for this event.
2732
 
   */
2733
 
 
2734
 
  /* FIXMEchpe: check event coordinate handling (float vs int!) */
2735
 
 
2736
 
#ifdef FIXMEchpe
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)
2740
 
    return FALSE;
2741
 
#endif
2742
 
 
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;
2746
 
  if (state != 0)
2747
 
    return FALSE;
2748
 
 
2749
 
  button = event->button;
2750
 
 
2751
 
  /* We're only interested in left, middle and right-clicks */
2752
 
  if (button < 1 || button > 3)
2753
 
    return FALSE;
2754
 
 
2755
 
  /* If we already have a click, ignore this new one */
2756
 
  if (priv->click_status != STATUS_NONE) {
2757
 
    return FALSE;
2758
 
  }
2759
 
 
2760
 
  /* If the game hasn't started yet, start it now */
2761
 
  aisleriot_game_start (priv->game);
2762
 
 
2763
 
  get_slot_and_card_from_point (board, event->x, event->y, &hslot, &cardid);
2764
 
 
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);
2770
 
 
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;
2777
 
 
2778
 
  if (!hslot) {
2779
 
    set_focus (board, NULL, -1, FALSE);
2780
 
    set_selection (board, NULL, -1, FALSE);
2781
 
 
2782
 
    priv->click_status = STATUS_NONE;
2783
 
 
2784
 
    return FALSE;
2785
 
  }
2786
 
 
2787
 
  set_cursor (board, AR_CURSOR_CLOSED);
2788
 
 
2789
 
  /* First check if it's a right-click: if so, we reveal the card and do nothing else */
2790
 
  if (button == 3) {
2791
 
    /* Don't change the selection here! */
2792
 
    reveal_card (board, hslot, cardid);
2793
 
 
2794
 
    return TRUE;
2795
 
  }
2796
 
 
2797
 
  /* Clear revealed card */
2798
 
  reveal_card (board, NULL, -1);
2799
 
 
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.
2803
 
   */
2804
 
  if (is_double_click) {
2805
 
    ArSlot *clicked_slot = hslot;
2806
 
 
2807
 
    priv->click_status = STATUS_NONE;
2808
 
 
2809
 
    /* Reset this since otherwise 3 clicks will be interpreted as 2 double-clicks */
2810
 
    priv->last_click_left_click = FALSE;
2811
 
 
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);
2815
 
    } else {
2816
 
      aisleriot_game_discard_move (priv->game);
2817
 
    }
2818
 
 
2819
 
    aisleriot_game_test_end_of_game (priv->game);
2820
 
 
2821
 
    set_cursor (board, AR_CURSOR_OPEN);
2822
 
 
2823
 
    return TRUE;
2824
 
  }
2825
 
 
2826
 
  /* button == 1 from now on */
2827
 
 
2828
 
  if (priv->selection_slot == NULL)
2829
 
    goto set_selection;
2830
 
 
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.
2836
 
   *
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.
2839
 
    */
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)) {
2843
 
 
2844
 
    /* Try to move the selected cards to the clicked slot */
2845
 
    if (aisleriot_board_move_selected_cards_to_slot (board, hslot))
2846
 
      return TRUE;
2847
 
 
2848
 
    /* Move failed if this wasn't the selection_slot slot */
2849
 
    if (hslot != priv->selection_slot) {
2850
 
      aisleriot_board_error_bell (board);
2851
 
    }
2852
 
  }
2853
 
 
2854
 
  if (hslot != priv->selection_slot ||
2855
 
      cardid != priv->selection_start_card_id)
2856
 
    goto set_selection;
2857
 
    
2858
 
  /* Single click on the selected slot & card, we take that to mean to deselect,
2859
 
   * but only in click-to-move mode.
2860
 
   */
2861
 
  if (priv->click_to_move) {
2862
 
    set_selection (board, NULL, -1, FALSE);
2863
 
 
2864
 
    /* Reveal the card on left click */
2865
 
    reveal_card (board, hslot, cardid);
2866
 
 
2867
 
    return TRUE;
2868
 
  }
2869
 
 
2870
 
set_selection:
2871
 
 
2872
 
  if (cardid >= 0) {
2873
 
    drag_valid = aisleriot_game_drag_valid (priv->game,
2874
 
                                            hslot->id,
2875
 
                                            hslot->cards->data + cardid,
2876
 
                                            hslot->cards->len - cardid);
2877
 
  } else {
2878
 
    drag_valid = FALSE;
2879
 
  }
2880
 
 
2881
 
  if (drag_valid) {
2882
 
    set_selection (board, hslot, cardid, priv->click_to_move);
2883
 
    priv->click_status = priv->click_to_move ? STATUS_NOT_DRAG : STATUS_MAYBE_DRAG;
2884
 
  } else {
2885
 
    set_selection (board, NULL, -1, FALSE);
2886
 
    priv->click_status = STATUS_NOT_DRAG;
2887
 
  }
2888
 
 
2889
 
  /* If we're already showing focus or just clicked on the
2890
 
   * card with the (hidden) focus, show the focus on the
2891
 
   * clicked card.
2892
 
   */
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);
2897
 
 
2898
 
  /* Reveal the card on left click */
2899
 
  if (priv->click_to_move) {
2900
 
    reveal_card (board, hslot, cardid);
2901
 
  }
2902
 
 
2903
 
  return FALSE;
2904
 
}
2905
 
 
2906
 
static gboolean
2907
 
aisleriot_board_button_release (ClutterActor *actor,
2908
 
                                ClutterButtonEvent *event)
2909
 
{
2910
 
  AisleriotBoard *board = AISLERIOT_BOARD (actor);
2911
 
  AisleriotBoardPrivate *priv = board->priv;
2912
 
  /* guint state; */
2913
 
 
2914
 
  ar_debug_print (AR_DEBUG_GAME_EVENTS,
2915
 
                      "board ::button-release @(%f / %f) button %d click-count %d modifiers %x\n",
2916
 
                      event->x, event->y,
2917
 
                      event->button, event->click_count,
2918
 
                      event->modifier_state);
2919
 
 
2920
 
  /* FIXMEchpe: check event coordinate handling (float vs int!) */
2921
 
 
2922
 
  /* NOTE: It's ok to just return instead of chaining up, since the
2923
 
   * parent classes have no class closure for this event.
2924
 
   */
2925
 
 
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.
2929
 
   */
2930
 
 
2931
 
  /* state = event->state & gtk_accelerator_get_default_mod_mask (); */
2932
 
 
2933
 
  switch (priv->click_status) {
2934
 
    case STATUS_SHOW:
2935
 
      reveal_card (board, NULL, -1);
2936
 
      break;
2937
 
 
2938
 
    case STATUS_IS_DRAG:
2939
 
      highlight_drop_target (board, NULL);
2940
 
      drop_moving_cards (board, event->x, event->y);
2941
 
      break;
2942
 
 
2943
 
    case STATUS_MAYBE_DRAG:
2944
 
    case STATUS_NOT_DRAG: {
2945
 
      ArSlot *slot;
2946
 
      int card_id;
2947
 
 
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)
2951
 
        break;
2952
 
 
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);
2957
 
      } else {
2958
 
        aisleriot_game_discard_move (priv->game);
2959
 
      }
2960
 
 
2961
 
      aisleriot_game_test_end_of_game (priv->game);
2962
 
 
2963
 
      break;
2964
 
    }
2965
 
 
2966
 
    case STATUS_NONE:
2967
 
      break;
2968
 
  }
2969
 
 
2970
 
  priv->click_status = STATUS_NONE;
2971
 
 
2972
 
  set_cursor_by_location (board, event->x, event->y);
2973
 
 
2974
 
  return TRUE;
2975
 
}
2976
 
 
2977
 
static gboolean
2978
 
aisleriot_board_motion (ClutterActor *actor,
2979
 
                        ClutterMotionEvent *event)
2980
 
{
2981
 
  AisleriotBoard *board = AISLERIOT_BOARD (actor);
2982
 
  AisleriotBoardPrivate *priv = board->priv;
2983
 
 
2984
 
  ar_debug_print (AR_DEBUG_GAME_EVENTS,
2985
 
                      "board ::motion @(%f / %f) modifiers %x\n",
2986
 
                      event->x, event->y,
2987
 
                      event->modifier_state);
2988
 
 
2989
 
  /* FIXMEchpe: check event coordinate handling (float vs int!) */
2990
 
 
2991
 
  /* NOTE: It's ok to just return instead of chaining up, since the
2992
 
   * parent classes have no class closure for this event.
2993
 
   */
2994
 
 
2995
 
  if (priv->show_status_messages) {
2996
 
    ArSlot *slot = NULL;
2997
 
    int cardid = -1;
2998
 
 
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) {
3001
 
      char *text;
3002
 
 
3003
 
      text = ar_slot_get_hint_string (slot, cardid);
3004
 
      set_status_message (board, text);
3005
 
      g_free (text);
3006
 
    } else {
3007
 
      set_status_message (board, NULL);
3008
 
    }
3009
 
  }
3010
 
 
3011
 
  if (priv->click_status == STATUS_IS_DRAG) {
3012
 
    ArSlot *slot;
3013
 
    int x, y;
3014
 
 
3015
 
    x = event->x - priv->last_click_x;
3016
 
    y = event->y - priv->last_click_y;
3017
 
 
3018
 
    slot = find_drop_target (board, x, y);
3019
 
    highlight_drop_target (board, slot);
3020
 
 
3021
 
    clutter_actor_set_position (priv->moving_cards_group, x, y);
3022
 
    clutter_actor_raise_top (priv->moving_cards_group);
3023
 
 
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,
3027
 
                                                priv->last_click_x,
3028
 
                                                priv->last_click_y,
3029
 
                                                event->x,
3030
 
                                                event->y)) {
3031
 
    drag_begin (board);
3032
 
  } else {
3033
 
    set_cursor_by_location (board, event->x, event->y);
3034
 
  }
3035
 
 
3036
 
  return FALSE;
3037
 
}
3038
 
 
3039
 
static gboolean
3040
 
aisleriot_board_enter (ClutterActor *actor,
3041
 
                       ClutterCrossingEvent *event)
3042
 
{
3043
 
  ar_debug_print (AR_DEBUG_GAME_EVENTS,
3044
 
                      "board ::enter @(%f / %f)\n",
3045
 
                      event->x, event->y);
3046
 
 
3047
 
  /* NOTE: It's ok to just return instead of chaining up, since the
3048
 
   * parent classes have no class closure for this event.
3049
 
   */
3050
 
 
3051
 
  return FALSE;
3052
 
}
3053
 
 
3054
 
static gboolean
3055
 
aisleriot_board_leave (ClutterActor *actor,
3056
 
                       ClutterCrossingEvent *event)
3057
 
{
3058
 
  AisleriotBoard *board = AISLERIOT_BOARD (actor);
3059
 
 
3060
 
  ar_debug_print (AR_DEBUG_GAME_EVENTS,
3061
 
                      "board ::leave @(%f / %f)\n",
3062
 
                      event->x, event->y);
3063
 
 
3064
 
  /* NOTE: It's ok to just return instead of chaining up, since the
3065
 
   * parent classes have no class closure for this event.
3066
 
   */
3067
 
 
3068
 
  set_cursor (board, AR_CURSOR_DEFAULT);
3069
 
 
3070
 
  return FALSE;
3071
 
}
3072
 
 
3073
 
static void
3074
 
aisleriot_board_key_focus_in (ClutterActor *actor)
3075
 
{
3076
 
  ar_debug_print (AR_DEBUG_GAME_EVENTS,
3077
 
                      "board ::key-focus-in\n");
3078
 
}
3079
 
 
3080
 
static void
3081
 
aisleriot_board_key_focus_out (ClutterActor *actor)
3082
 
{
3083
 
  ar_debug_print (AR_DEBUG_GAME_EVENTS,
3084
 
                      "board ::key-focus-out\n");
3085
 
}
3086
 
 
3087
 
/* GObjectClass methods */
3088
 
 
3089
 
static void
3090
 
aisleriot_board_init (AisleriotBoard *board)
3091
 
{
3092
 
  ClutterActor *actor = CLUTTER_ACTOR (board);
3093
 
  AisleriotBoardPrivate *priv;
3094
 
 
3095
 
  priv = board->priv = AISLERIOT_BOARD_GET_PRIVATE (board);
3096
 
 
3097
 
  priv->textures = ar_card_textures_cache_new ();
3098
 
 
3099
 
  memset (&priv->allocation, 0, sizeof (ClutterActorBox));
3100
 
 
3101
 
  /* We want to receive events! */
3102
 
  clutter_actor_set_reactive (actor, TRUE);
3103
 
 
3104
 
  priv->force_geometry_update = FALSE;
3105
 
 
3106
 
  priv->click_to_move = FALSE;
3107
 
  priv->show_selection = FALSE;
3108
 
  priv->show_status_messages = FALSE;
3109
 
 
3110
 
  priv->show_card_id = -1;
3111
 
 
3112
 
  priv->moving_cards = g_byte_array_sized_new (SLOT_CARDS_N_PREALLOC);
3113
 
 
3114
 
  priv->removed_cards = g_array_new (FALSE, FALSE, sizeof (RemovedCard));
3115
 
 
3116
 
  priv->animation_layer = g_object_ref_sink (clutter_group_new ());
3117
 
  clutter_container_add (CLUTTER_CONTAINER (board),
3118
 
                         priv->animation_layer, NULL);
3119
 
}
3120
 
 
3121
 
static void
3122
 
aisleriot_board_finalize (GObject *object)
3123
 
{
3124
 
  AisleriotBoard *board = AISLERIOT_BOARD (object);
3125
 
  AisleriotBoardPrivate *priv = board->priv;
3126
 
 
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);
3131
 
 
3132
 
  g_array_free (priv->removed_cards, TRUE);
3133
 
 
3134
 
  g_byte_array_free (priv->moving_cards, TRUE);
3135
 
 
3136
 
  if (priv->style != NULL) {
3137
 
    g_signal_handlers_disconnect_by_func (priv->style,
3138
 
                                          G_CALLBACK (aisleriot_board_sync_style),
3139
 
                                          board);
3140
 
 
3141
 
    g_object_unref (priv->style);
3142
 
  }
3143
 
 
3144
 
#if 0
3145
 
  screen = gtk_widget_get_settings (widget);
3146
 
  g_signal_handlers_disconnect_matched (settings, G_SIGNAL_MATCH_DATA,
3147
 
                                        0, 0, NULL, NULL,
3148
 
                                        widget);
3149
 
#endif
3150
 
 
3151
 
  G_OBJECT_CLASS (aisleriot_board_parent_class)->finalize (object);
3152
 
}
3153
 
 
3154
 
static void
3155
 
aisleriot_board_dispose (GObject *object)
3156
 
{
3157
 
  AisleriotBoard *board = AISLERIOT_BOARD (object);
3158
 
  AisleriotBoardPrivate *priv = board->priv;
3159
 
 
3160
 
  if (priv->textures) {
3161
 
    g_object_unref (priv->textures);
3162
 
    priv->textures = NULL;
3163
 
  }
3164
 
 
3165
 
  if (priv->animation_layer) {
3166
 
    g_object_unref (priv->animation_layer);
3167
 
    priv->animation_layer = NULL;
3168
 
  }
3169
 
 
3170
 
  if (priv->check_animations_handler) {
3171
 
    g_source_remove (priv->check_animations_handler);
3172
 
    priv->check_animations_handler = 0;
3173
 
  }
3174
 
 
3175
 
  G_OBJECT_CLASS (aisleriot_board_parent_class)->dispose (object);
3176
 
}
3177
 
 
3178
 
static void
3179
 
aisleriot_board_set_property (GObject *object,
3180
 
                              guint prop_id,
3181
 
                              const GValue *value,
3182
 
                              GParamSpec *pspec)
3183
 
{
3184
 
  AisleriotBoard *board = AISLERIOT_BOARD (object);
3185
 
  AisleriotBoardPrivate *priv = board->priv;
3186
 
 
3187
 
  switch (prop_id) {
3188
 
    case PROP_GAME:
3189
 
      priv->game = AISLERIOT_GAME (g_value_dup_object (value));
3190
 
 
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);
3199
 
 
3200
 
      break;
3201
 
 
3202
 
    case PROP_STYLE:
3203
 
      priv->style = g_value_dup_object (value);
3204
 
 
3205
 
      aisleriot_board_sync_style (priv->style, NULL, board);
3206
 
      g_signal_connect (priv->style, "notify",
3207
 
                        G_CALLBACK (aisleriot_board_sync_style), board);
3208
 
      break;
3209
 
 
3210
 
    default:
3211
 
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
3212
 
  }
3213
 
}
3214
 
 
3215
 
static void
3216
 
aisleriot_board_class_init (AisleriotBoardClass *klass)
3217
 
{
3218
 
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
3219
 
  ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
3220
 
#ifdef ENABLE_KEYNAV
3221
 
  ClutterBindingPool *binding_pool;
3222
 
  GClosure *closure;
3223
 
#endif
3224
 
 
3225
 
  g_type_class_add_private (gobject_class, sizeof (AisleriotBoardPrivate));
3226
 
 
3227
 
  gobject_class->dispose = aisleriot_board_dispose;
3228
 
  gobject_class->finalize = aisleriot_board_finalize;
3229
 
  gobject_class->set_property = aisleriot_board_set_property;
3230
 
 
3231
 
#ifdef FIXMEchpe
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;
3236
 
#endif // FIXMEchpe
3237
 
 
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;
3251
 
 
3252
 
 
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),
3258
 
                  NULL, NULL,
3259
 
                  g_cclosure_marshal_VOID__INT,
3260
 
                  G_TYPE_NONE,
3261
 
                  1,
3262
 
                  G_TYPE_INT);
3263
 
 
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),
3269
 
                  NULL, NULL,
3270
 
                  g_cclosure_marshal_VOID__VOID,
3271
 
                  G_TYPE_NONE,
3272
 
                  0);
3273
 
 
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),
3279
 
                  NULL, NULL,
3280
 
                  g_cclosure_marshal_VOID__STRING,
3281
 
                  G_TYPE_NONE,
3282
 
                  1,
3283
 
                  G_TYPE_STRING | G_SIGNAL_TYPE_STATIC_SCOPE);
3284
 
 
3285
 
  signals[FOCUS] =
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),
3290
 
                  NULL, NULL,
3291
 
                  g_cclosure_marshal_VOID__INT,
3292
 
                  G_TYPE_BOOLEAN,
3293
 
                  1,
3294
 
                  G_TYPE_INT);
3295
 
 
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;
3303
 
 
3304
 
  /* Keybinding signals */
3305
 
#ifdef FIXMEchpe
3306
 
  widget_class->activate_signal =
3307
 
#endif
3308
 
  signals[ACTIVATE] =
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),
3313
 
                  NULL, NULL,
3314
 
                  ar_marshal_BOOLEAN__STRING_UINT_ENUM,
3315
 
                  G_TYPE_BOOLEAN,
3316
 
                  3,
3317
 
                  G_TYPE_STRING | G_SIGNAL_TYPE_STATIC_SCOPE,
3318
 
                  G_TYPE_UINT,
3319
 
                  CLUTTER_TYPE_MODIFIER_TYPE);
3320
 
 
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),
3326
 
                  NULL, NULL,
3327
 
                  ar_marshal_BOOLEAN__STRING_UINT_ENUM,
3328
 
                  G_TYPE_BOOLEAN,
3329
 
                  3,
3330
 
                  G_TYPE_STRING | G_SIGNAL_TYPE_STATIC_SCOPE,
3331
 
                  G_TYPE_UINT,
3332
 
                  CLUTTER_TYPE_MODIFIER_TYPE);
3333
 
 
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),
3339
 
                  NULL, NULL,
3340
 
                  ar_marshal_BOOLEAN__STRING_UINT_ENUM,
3341
 
                  G_TYPE_BOOLEAN,
3342
 
                  3,
3343
 
                  G_TYPE_STRING | G_SIGNAL_TYPE_STATIC_SCOPE,
3344
 
                  G_TYPE_UINT,
3345
 
                  CLUTTER_TYPE_MODIFIER_TYPE);
3346
 
 
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),
3352
 
                  NULL, NULL,
3353
 
                  ar_marshal_BOOLEAN__STRING_UINT_ENUM,
3354
 
                  G_TYPE_BOOLEAN,
3355
 
                  3,
3356
 
                  G_TYPE_STRING | G_SIGNAL_TYPE_STATIC_SCOPE,
3357
 
                  G_TYPE_UINT,
3358
 
                  CLUTTER_TYPE_MODIFIER_TYPE);
3359
 
 
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),
3365
 
                  NULL, NULL,
3366
 
                  ar_marshal_BOOLEAN__STRING_UINT_ENUM,
3367
 
                  G_TYPE_BOOLEAN,
3368
 
                  3,
3369
 
                  G_TYPE_STRING | G_SIGNAL_TYPE_STATIC_SCOPE,
3370
 
                  G_TYPE_UINT,
3371
 
                  CLUTTER_TYPE_MODIFIER_TYPE);
3372
 
#endif /* ENABLE_KEYNAV */
3373
 
 
3374
 
  /* Properties */
3375
 
  g_object_class_install_property
3376
 
    (gobject_class,
3377
 
     PROP_GAME,
3378
 
     g_param_spec_object ("game", NULL, NULL,
3379
 
                          AISLERIOT_TYPE_GAME,
3380
 
                          G_PARAM_WRITABLE |
3381
 
                          G_PARAM_CONSTRUCT_ONLY |
3382
 
                          G_PARAM_STATIC_STRINGS));
3383
 
 
3384
 
  g_object_class_install_property
3385
 
    (gobject_class,
3386
 
     PROP_STYLE,
3387
 
     g_param_spec_object ("style", NULL, NULL,
3388
 
                          AR_TYPE_STYLE,
3389
 
                          G_PARAM_WRITABLE |
3390
 
                          G_PARAM_CONSTRUCT_ONLY |
3391
 
                          G_PARAM_STATIC_STRINGS));
3392
 
 
3393
 
#ifdef ENABLE_KEYNAV
3394
 
  /* Keybindings */
3395
 
  binding_pool = clutter_binding_pool_get_for_class (klass);
3396
 
 
3397
 
  /* Cursor movement */
3398
 
  closure = g_signal_type_cclosure_new (G_TYPE_FROM_CLASS (klass),
3399
 
                                        G_STRUCT_OFFSET (AisleriotBoardClass, move_cursor));
3400
 
 
3401
 
  aisleriot_board_add_move_and_select_binding (binding_pool, closure,
3402
 
                                               I_(MOVE_CURSOR_LEFT_RIGHT_S MOVE_LEFT_S),
3403
 
                                               CLUTTER_Left, 0);
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);
3407
 
 
3408
 
  aisleriot_board_add_move_and_select_binding (binding_pool, closure,
3409
 
                                               I_(MOVE_CURSOR_LEFT_RIGHT_S MOVE_RIGHT_S),
3410
 
                                               CLUTTER_Right, 0);
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);
3414
 
  
3415
 
  aisleriot_board_add_move_and_select_binding (binding_pool, closure,
3416
 
                                               I_(MOVE_CURSOR_UP_DOWN_S MOVE_LEFT_S),
3417
 
                                               CLUTTER_Up, 0);
3418
 
  aisleriot_board_add_move_and_select_binding (binding_pool, closure,
3419
 
                                               I_(MOVE_CURSOR_UP_DOWN_S MOVE_LEFT_S),
3420
 
                                               CLUTTER_KP_Up, 0);
3421
 
 
3422
 
  aisleriot_board_add_move_and_select_binding (binding_pool, closure,
3423
 
                                               I_(MOVE_CURSOR_UP_DOWN_S MOVE_RIGHT_S),
3424
 
                                               CLUTTER_Down, 0);
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);
3428
 
 
3429
 
  aisleriot_board_add_move_and_select_binding (binding_pool, closure,
3430
 
                                               I_(MOVE_CURSOR_START_END_S MOVE_LEFT_S),
3431
 
                                               CLUTTER_Home, 0);
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),
3437
 
                                               CLUTTER_Begin, 0);
3438
 
 
3439
 
  aisleriot_board_add_move_and_select_binding (binding_pool, closure,
3440
 
                                               I_(MOVE_CURSOR_START_END_S MOVE_RIGHT_S),
3441
 
                                               CLUTTER_End, 0);
3442
 
  aisleriot_board_add_move_and_select_binding (binding_pool, closure,
3443
 
                                               I_(MOVE_CURSOR_START_END_S MOVE_RIGHT_S),
3444
 
                                               CLUTTER_KP_End, 0);
3445
 
 
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);
3452
 
 
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);
3459
 
 
3460
 
  g_closure_unref (closure);
3461
 
 
3462
 
  /* Selection */
3463
 
  closure = g_signal_type_cclosure_new (G_TYPE_FROM_CLASS (klass),
3464
 
                                        G_STRUCT_OFFSET (AisleriotBoardClass, toggle_selection));
3465
 
 
3466
 
  clutter_binding_pool_install_closure (binding_pool,
3467
 
                                        I_("toggle-selection"),
3468
 
                                        CLUTTER_space,
3469
 
                                        0,
3470
 
                                       closure);
3471
 
  clutter_binding_pool_install_closure (binding_pool,
3472
 
                                        I_("toggle-selection"),
3473
 
                                        CLUTTER_KP_Space,
3474
 
                                        0,
3475
 
                                        closure);
3476
 
  g_closure_unref (closure);
3477
 
 
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,
3481
 
                                        I_("select-all"),
3482
 
                                        CLUTTER_a,
3483
 
                                        CLUTTER_CONTROL_MASK,
3484
 
                                        closure);
3485
 
  g_closure_unref (closure);
3486
 
 
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,
3490
 
                                        I_("deselect-all"),
3491
 
                                        CLUTTER_a,
3492
 
                                        CLUTTER_CONTROL_MASK | CLUTTER_SHIFT_MASK,
3493
 
                                        closure);
3494
 
  g_closure_unref (closure);
3495
 
 
3496
 
  /* Activate */
3497
 
  closure = g_signal_type_cclosure_new (G_TYPE_FROM_CLASS (klass),
3498
 
                                        G_STRUCT_OFFSET (AisleriotBoardClass, activate));
3499
 
 
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);
3503
 
 
3504
 
  g_closure_unref (closure);
3505
 
#endif /* ENABLE_KEYNAV */
3506
 
}
3507
 
 
3508
 
/* public API */
3509
 
 
3510
 
ClutterActor *
3511
 
aisleriot_board_new (ArStyle *style,
3512
 
                     AisleriotGame *game)
3513
 
{
3514
 
  return g_object_new (AISLERIOT_TYPE_BOARD,
3515
 
                       "style", style,
3516
 
                       "game", game,
3517
 
                       NULL);
3518
 
}
3519
 
 
3520
 
void
3521
 
aisleriot_board_abort_move (AisleriotBoard *board)
3522
 
{
3523
 
  clear_state (board);
3524
 
}