main.c 29.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
/* 
 * 
 * Copyright (C) 2009-2011 Colomban Wendling <ban@herbesfolles.org>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 */
19

20 21
#include "config.h"

22
#include <stdarg.h>
23 24
#include <glib/gi18n.h>
#include <gtk/gtk.h>
25 26 27
#include <ovcc/ovcc.h>
#include <ovcc/ovccclient.h>

28 29
#include "utils.h"
#include "tiles.h"
30
#include "ovccgtk-board.h"
31
#include "ovccgtk-client.h"
32
#include "ovccgtk-connection-dialog.h"
33
#include "ovccgtk-player-list.h"
34

35

36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
#define GAME_TILES    "tiles.xml"
#define GAME_TILESET  "tileset.xml"


/* bad copy of the bot implemented in libovcc's test */

static gboolean
do_try_place_tile (OVCCBoard    *board,
                   OVCCPosition *pos,
                   OVCCTile     *tile,
                   gpointer      user_data)
{
  gsize     i;
  gboolean  keep_doing = TRUE;
  OVCCTile *new_tile = user_data;
  
  g_return_val_if_fail (new_tile != NULL, FALSE);
  
54
  for (i = 0; keep_doing && i < 4; i++) {
55 56 57 58
    OVCCPosition npos = *pos;
    int          r;
    
    switch (i) {
59 60 61 62
      case 0: npos.y--; break;
      case 1: npos.y++; break;
      case 2: npos.x--; break;
      case 3: npos.x++; break;
63
    }
64
    for (r = 0; keep_doing && r < 4; r++) {
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
      ovcc_tile_rotate (new_tile, 1);
      if (ovcc_board_add_tile_check (board, new_tile, &npos)) {
        ovcc_board_add_tile (board, new_tile, &npos);
        keep_doing = FALSE;
      }
    }
  }
  
  return keep_doing;
}

static gboolean
try_place_tile (OVCCBoard *board,
                OVCCTile  *tile)
{
  return ovcc_board_foreach (board, do_try_place_tile, tile) == FALSE;
}

static gboolean
test_board (OVCCBoard *board,
            OVCCStack *stack)
{
  OVCCTile *tile;
  gsize     n_success = 0;
  gsize     n_failed  = 0;
  
  while (NULL != (tile = ovcc_stack_pop (stack))) {
    if (! try_place_tile (board, tile)) {
      g_warning ("Failed to place tile %u", ovcc_tile_get_id (tile));
      n_failed ++;
    } else {
      n_success ++;
    }
98
    g_object_unref (tile);
99 100 101 102 103 104 105 106 107
  }
  
  g_debug ("%zu tiles over %zu placed (%.3u%%)",
           n_success, (n_success + n_failed),
           n_success * 100 / (n_success + n_failed));
  
  return n_failed == 0;
}

108 109 110 111 112 113 114 115 116 117 118 119 120
static gboolean
game_do_try_place_tile (OVCCBoard    *board,
                        OVCCPosition *pos,
                        OVCCTile     *tile,
                        gpointer      user_data)
{
  gsize     i;
  gboolean  keep_doing = TRUE;
  OVCCGame *game = user_data;
  OVCCTile *new_tile = ovcc_game_get_current_tile (game);
  
  g_return_val_if_fail (new_tile != NULL, FALSE);
  
121
  for (i = 0; keep_doing && i < 4; i++) {
122 123 124 125
    OVCCPosition npos = *pos;
    int          r;
    
    switch (i) {
126 127 128 129
      case 0: npos.y--; break;
      case 1: npos.y++; break;
      case 2: npos.x--; break;
      case 3: npos.x++; break;
130
    }
131
    for (r = 0; keep_doing && r < 4; r++) {
132 133
      if (ovcc_game_place_tile (game, ovcc_game_get_current_player (game), &npos)) {
        keep_doing = FALSE;
134 135
      } else {
        ovcc_tile_rotate (new_tile, 1);
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
      }
    }
  }
  
  return keep_doing;
}

static gboolean
game_try_place_tile (OVCCGame  *game,
                     OVCCTile  *tile)
{
  OVCCBoard *board;
  
  board = ovcc_game_get_board (game);
  return ovcc_board_foreach (board, game_do_try_place_tile, game) == FALSE;
}

153 154 155 156 157 158
struct _PlaceNextData {
  OVCCGame *game;
  guint     n_success;
  guint     n_failed;
};

159
static gboolean
160
place_next (gpointer data)
161
{
162 163
  struct _PlaceNextData  *pdata = data;
  OVCCTile               *tile;
164
  
165 166 167 168 169 170 171 172 173 174 175 176 177 178
  tile = ovcc_game_get_current_tile (pdata->game);
  if (! tile) {
    g_debug ("%u tiles over %u placed (%.3u%%)",
             pdata->n_success, (pdata->n_success + pdata->n_failed),
             (pdata->n_success + pdata->n_failed) > 0
              ? pdata->n_success * 100 / (pdata->n_success + pdata->n_failed)
              : 0);
    return FALSE;
  }
  if (! game_try_place_tile (pdata->game, tile)) {
    g_warning ("Failed to place tile %u", ovcc_tile_get_id (tile));
    pdata->n_failed ++;
  } else {
    pdata->n_success ++;
179 180
  }
  
181 182 183 184 185 186 187
  return TRUE;
}

static void
test_game (OVCCGame *game)
{
  struct _PlaceNextData *pdata;
188
  
189 190 191 192 193
  pdata = g_malloc (sizeof *pdata);
  pdata->game = game;
  pdata->n_success = 0;
  pdata->n_failed = 0;
  g_timeout_add_full (G_PRIORITY_DEFAULT, 100, place_next, pdata, g_free);
194 195
}

196 197
static OVCCStack *
create_stack (void)
198 199 200 201 202 203
{
  gchar        *path;
  OVCCTilesDef *tiles;
  GFile        *tiles_file;
  OVCCTileSet  *set;
  GFile        *set_file;
204
  OVCCStack    *stack = NULL;
205 206
  GError       *err = NULL;
  
207
  tiles = ovcc_tiles_def_new ();
208 209 210
  path = data_get_path (GAME_TILES);
  tiles_file = g_file_new_for_path (path);
  g_free (path);
211
  if (! ovcc_tiles_def_load (tiles, tiles_file, &err)) {
212 213 214
    msg_error (_("Failed to load tiles: %s"), err->message);
    g_error_free (err);
  } else {
215
    set = ovcc_tile_set_new ();
216 217 218
    path = data_get_path (GAME_TILESET);
    set_file = g_file_new_for_path (path);
    g_free (path);
219
    if (! ovcc_tile_set_load (set, tiles, set_file, &err)) {
220 221 222 223 224 225
      msg_error (_("Failed to load tile set: %s"), err->message);
      g_error_free (err);
    } else {
      stack = ovcc_stack_new_from_tileset (set);
    }
    g_object_unref (set_file);
226
    g_object_unref (set);
227 228
  }
  g_object_unref (tiles_file);
229
  g_object_unref (tiles);
230
  
231 232 233 234 235 236 237 238 239 240 241
  return stack;
}

static OVCCBoard *
create_board (void)
{
  OVCCStack    *stack;
  OVCCBoard    *board = NULL;
  
  stack = create_stack ();
  if (stack) {
242 243 244 245 246
    OVCCTile *tile;
    
    tile = ovcc_stack_pop (stack);
    board = ovcc_board_new (tile);
    g_object_unref (tile);
247 248
    test_board (board, stack);
  }
249
  g_object_unref (stack);
250
  
251 252 253
  return board;
}

254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272
static void
on_unplaceable_tile (OVCCGame *game,
                     OVCCTile *tile,
                     gpointer  data)
{
  g_warning ("Cannot place tile #%u, reshaking.", ovcc_tile_get_id (tile));
}

static OVCCGame *
create_game (void)
{
  gchar        *path;
  OVCCTilesDef *tiles;
  GFile        *tiles_file;
  OVCCTileSet  *set;
  GFile        *set_file;
  OVCCGame     *game = NULL;
  GError       *err = NULL;
  
273
  tiles = ovcc_tiles_def_new ();
274 275 276
  path = data_get_path (GAME_TILES);
  tiles_file = g_file_new_for_path (path);
  g_free (path);
277
  if (! ovcc_tiles_def_load (tiles, tiles_file, &err)) {
278 279 280
    msg_error (_("Failed to load tiles: %s"), err->message);
    g_error_free (err);
  } else {
281
    set = ovcc_tile_set_new ();
282 283 284
    path = data_get_path (GAME_TILESET);
    set_file = g_file_new_for_path (path);
    g_free (path);
285
    if (! ovcc_tile_set_load (set, tiles, set_file, &err)) {
286 287 288
      msg_error (_("Failed to load tile set: %s"), err->message);
      g_error_free (err);
    } else {
289
      game = ovcc_game_new (set, NULL);
290 291 292 293 294 295
      g_signal_connect (game, "unplaceable-tile",
                        G_CALLBACK (on_unplaceable_tile), NULL);
      ovcc_game_add_player (game, ovcc_player_new ("A"), NULL);
      ovcc_game_add_player (game, ovcc_player_new ("B"), NULL);
    }
    g_object_unref (set_file);
296
    g_object_unref (set);
297 298
  }
  g_object_unref (tiles_file);
299
  g_object_unref (tiles);
300 301 302 303
  
  return game;
}

304
/* end of bad game fake */
305

306 307 308 309 310





311
typedef struct _App App;
312 313 314

struct _App
{
315
  GtkWidget      *main_window;
316
  GtkWidget      *statusbar;
317 318 319 320
  GtkWidget      *tile_counter_label;
  GtkWidget      *current_tile_image;
  GtkWidget      *board_container;
  GtkWidget      *board;
321
  
322
  GtkWidget      *player_list;
323
  GtkWidget      *connection_dialog;
324
  GCancellable   *connection_cancel;
325
  
326 327
  OVCCGtkClient  *client;
  OVCCGame       *game;
328 329 330 331 332 333 334 335 336
  
  struct {
    struct {
      GtkAction  *new;
      GtkAction  *connect;
      GtkAction  *disconnect;
      GtkAction  *start;
      GtkAction  *rotate_tile_clockwise;
      GtkAction  *rotate_tile_counterclockwise;
337
      GtkAction  *finish_turn;
338 339 340 341 342 343 344 345 346
    } game;
    struct {
      GtkAction  *quit;
      GtkAction  *preferences;
    } app;
    struct {
      GtkAction  *about;
    } help;
  } actions;
347 348
};

349 350


351 352 353 354 355 356 357 358 359 360 361 362 363
static gint
show_message (GtkWindow      *parent,
              GtkMessageType  type,
              GtkButtonsType  buttons,
              const gchar    *secondary_text,
              const gchar    *primary_fmt,
              ...)
{
  gint          response;
  GtkWidget    *dialog;
  const gchar  *primary;
  va_list       ap;
  
364 365
  g_return_val_if_fail (parent == NULL || GTK_IS_WINDOW (parent), 0);
  g_return_val_if_fail (primary_fmt != NULL, 0);
366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387
  
  va_start (ap, primary_fmt);
  primary = g_strdup_vprintf (primary_fmt, ap);
  va_end (ap);
  dialog = g_object_new (GTK_TYPE_MESSAGE_DIALOG,
                         "message-type", type,
                         "buttons", buttons,
                         "text", primary,
                         "secondary-text", secondary_text,
                         NULL);
  
  if (parent != NULL) {
    gtk_window_set_transient_for (GTK_WINDOW (dialog), parent);
  }
  
  response = gtk_dialog_run (GTK_DIALOG (dialog));
  gtk_widget_destroy (dialog);
  
  return response;
}


388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405
static void
app_set_status (App          *app,
                const gchar  *fmt,
                ...)
{
  gchar  *status;
  va_list ap;
  
  va_start (ap, fmt);
  status = g_strdup_vprintf (fmt, ap);
  va_end (ap);
  
  gtk_statusbar_pop (GTK_STATUSBAR (app->statusbar), 0);
  gtk_statusbar_push (GTK_STATUSBAR (app->statusbar), 0, status);
  g_free (status);
}


406 407 408 409 410 411 412 413 414 415
static gboolean
load_ui (GtkBuilder  *builder,
         const gchar *filename)
{
  GError   *err     = NULL;
  gboolean  loaded  = FALSE;
  gchar    *path    = data_get_path (filename);
  
  loaded = gtk_builder_add_from_file (builder, path, &err) > 0;
  if (! loaded) {
416 417 418
    show_message (NULL, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
                  err->message, _("Failed to load UI file \"%s\""), path);
    g_error_free (err);
419 420 421 422 423 424 425
  }
  g_free (path);
  
  return loaded;
}


426 427 428
static void
update_tile_image (App *app)
{
429
  OVCCTile *tile = NULL;
430 431
  guint     tiles_left;
  gchar    *label;
432
  
433 434 435 436 437 438 439 440 441
  if (app->game) {
    tile = ovcc_game_get_current_tile (app->game);
    tiles_left = ovcc_game_get_n_tiles_left (app->game);
    label = g_strdup_printf (g_dngettext (NULL, "%u tile left",
                                                "%u tiles left", tiles_left),
                             tiles_left);
    gtk_label_set_text (GTK_LABEL (app->tile_counter_label), label);
    g_free (label);
  }
442
  if (tile) {
443
    GdkPixbuf *pix;
444
    
445
    pix = tile_get_pixbuf (tile, -1);
446 447 448 449 450 451 452 453 454 455 456 457 458 459 460
    gtk_image_set_from_pixbuf (GTK_IMAGE (app->current_tile_image), pix);
    g_object_unref (pix);
  } else {
    gtk_image_set_from_stock (GTK_IMAGE (app->current_tile_image),
                              GTK_STOCK_MISSING_IMAGE,
                              GTK_ICON_SIZE_LARGE_TOOLBAR);
  }
}

static void
rotate_current_tile (App *app,
                     gint wise)
{
  OVCCTile *tile;
  
461
  tile = ovcc_game_get_current_tile (app->game);
462 463 464 465 466 467
  if (tile) {
    ovcc_tile_rotate (tile, wise);
  }
  update_tile_image (app);
}

468 469 470 471 472 473 474 475
static gboolean
app_leave (App *app)
{
  if (! app->client) {
    return TRUE;
  }
  
  if (app->game && ovcc_game_get_state (app->game) == OVCC_GAME_STATE_STARTED &&
476
      g_slist_length (ovcc_game_get_players (app->game)) > 1 &&
477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524
      show_message (GTK_WINDOW (app->main_window), GTK_MESSAGE_QUESTION,
                    GTK_BUTTONS_YES_NO,
                    _("The game is not finished and you are not the only "
                      "player on it. Do you really want to abort the game "
                      "and leave it?"),
                    _("Do you really want to leave?")) != GTK_RESPONSE_YES) {
    return FALSE;
  } else {
    GError *err = NULL;
    
    if (! ovccclient_client_leave (OVCCCLIENT_CLIENT (app->client), &err)) {
      app_set_status (app, "Ignoring disconnection error: %s", err->message);
      g_error_free (err);
    } else {
      app_set_status (app, "Disconnected!");
    }
    g_object_unref (app->client);
    app->client = NULL;
    app->game = NULL;
    
    gtk_widget_destroy (app->board);
    app->board = NULL;
    update_tile_image (app);
    
    gtk_action_set_sensitive (app->actions.game.disconnect, FALSE);
    gtk_action_set_sensitive (app->actions.game.start, FALSE);
    gtk_action_set_sensitive (app->actions.game.rotate_tile_clockwise, FALSE);
    gtk_action_set_sensitive (app->actions.game.rotate_tile_counterclockwise, FALSE);
    gtk_action_set_sensitive (app->actions.game.connect, TRUE);
    gtk_action_set_sensitive (app->actions.game.new, TRUE);
    
    return TRUE;
  }
}

static gboolean
app_quit (App *app)
{
  if (app_leave (app)) {
    gtk_main_quit ();
    
    return TRUE;
  } else {
    return FALSE;
  }
}

G_MODULE_EXPORT gboolean
525 526 527
main_window_delete_event_handler (GtkWidget *window,
                                  GdkEvent  *event,
                                  App       *app)
528 529 530 531 532 533 534 535 536 537 538
{
  return ! app_quit (app);
}

G_MODULE_EXPORT void
game_quit_activate_handler (GtkAction *action,
                            App       *app)
{
  app_quit (app);
}

539
G_MODULE_EXPORT void
540 541
rotate_tile_clockwise_activate_handler (GtkAction *action,
                                        App       *app)
542
{
543
  rotate_current_tile (app, 1);
544 545 546
}

G_MODULE_EXPORT void
547 548
rotate_tile_counterclockwise_activate_handler (GtkAction *action,
                                               App       *app)
549
{
550
  rotate_current_tile (app, -1);
551 552
}

553 554 555 556 557 558 559 560 561 562 563 564 565 566 567
G_MODULE_EXPORT void
game_start_activate_handler (GtkAction *action,
                             App       *app)
{
  if (app->game && ovcc_game_get_state (app->game) == OVCC_GAME_STATE_STOPPED) {
    GError *err = NULL;
    
    if (! ovcc_game_start (app->game, &err)) {
      show_message (GTK_WINDOW (app->main_window), GTK_MESSAGE_WARNING,
                    GTK_BUTTONS_CLOSE, err->message, "Cannot start game");
      g_error_free (err);
    }
  }
}

568
static void
569
finish_turn (App *app)
570
{
571
  gtk_action_set_sensitive (app->actions.game.finish_turn, FALSE);
572
  g_signal_emit_by_name (app->game, "turn-finished");
573 574 575
  /* set board mode to tile for it to stop showing pawn placement infos  */
  ovccgtk_board_set_state (OVCCGTK_BOARD (app->board),
                           OVCCGTK_BOARD_STATE_TILE);
576 577 578
}

G_MODULE_EXPORT void
579 580
game_finish_turn_activate_handler (GtkAction *action,
                                   App       *app)
581 582 583 584 585 586 587 588 589 590 591 592
{
  finish_turn (app);
}

static void
on_pawn_add_request (OVCCGtkBoard        *board,
                     const OVCCPosition  *pos,
                     OVCCTileObject      *obj,
                     OVCCPawnKind         kind,
                     App                 *app)
{
  OVCCPlayer *player;
593
  
594 595 596 597 598 599
  g_debug ("Add pawn request @ [%d,%d]", pos->x, pos->y);
  
  player = ovccclient_client_get_player (OVCCCLIENT_CLIENT (app->client));
  if (ovcc_player_place_pawn (player, kind, obj, pos)) {
    g_debug ("OK, placed.");
    finish_turn (app);
600 601 602
  }
}

603 604 605
static void
on_tile_add_request (OVCCGtkBoard        *board,
                     const OVCCPosition  *pos,
606
                     App                 *app)
607
{
608 609
  OVCCTile *tile;
  
610
  g_debug ("Add request @ [%d,%d]", pos->x, pos->y);
611
  tile = ovcc_game_get_current_tile (app->game);
612
  if (tile) {
613 614 615 616
    OVCCPlayer *player;
    
    player = ovccclient_client_get_player (OVCCCLIENT_CLIENT (app->client));
    if (ovcc_player_place_tile (player, pos)) {
617
      g_debug ("OK, placed.");
618 619
      /* TODO: if there is no more pawns, we could finish our turn here (or
       * somewhere sensible) */
620
      gtk_action_set_sensitive (app->actions.game.finish_turn, TRUE);
621 622
    }
  } else {
623 624
    show_message (GTK_WINDOW (app->main_window), GTK_MESSAGE_INFO,
                  GTK_BUTTONS_CLOSE, NULL, "Game already finished!");
625
  }
626 627
}

Colomban Wendling's avatar
Colomban Wendling committed
628
static void
629 630 631
on_current_tile_changed (GObject     *object,
                         GParamSpec  *pspec,
                         App         *app)
Colomban Wendling's avatar
Colomban Wendling committed
632 633 634 635 636
{
  /* update current tile image */
  update_tile_image (app);
}

637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661
static void
on_current_player_changed (OVCCGame    *game,
                           GParamSpec  *pspec,
                           App         *app)
{
  if (ovcc_game_get_state (game) == OVCC_GAME_STATE_STARTED) {
    OVCCPlayer *me;
    OVCCPlayer *player;
    
    me = ovccclient_client_get_player (OVCCCLIENT_CLIENT (app->client));
    player = ovcc_game_get_current_player (game);
    if (player != me) {
      app_set_status (app, _("It's the turn of %s"),
                      ovcc_player_get_nick (player));
    } else {
      app_set_status (app, _("It's your turn"));
    }
  }
}

static void
on_game_state_changed (OVCCGame    *game,
                       GParamSpec  *pspec,
                       App         *app)
{
662
  gboolean can_start = FALSE;
663
  gboolean started   = FALSE;
664
  
665 666 667 668 669 670 671 672 673 674 675
  switch (ovcc_game_get_state (game)) {
    case OVCC_GAME_STATE_ABORTED:
      app_set_status (app, _("Game aborted"));
      break;
    
    case OVCC_GAME_STATE_FINISHED:
      app_set_status (app, _("Game finished!"));
      break;
    
    case OVCC_GAME_STATE_STARTED:
      app_set_status (app, _("Game started!"));
676
      started = TRUE;
677 678 679 680
      break;
    
    case OVCC_GAME_STATE_STOPPED:
      app_set_status (app, _("Game stopped"));
681
      can_start = TRUE;
682 683
      break;
  }
684
  gtk_action_set_sensitive (app->actions.game.start, can_start);
685 686 687
  gtk_widget_set_sensitive (app->current_tile_image, started);
  gtk_action_set_sensitive (app->actions.game.rotate_tile_clockwise, started);
  gtk_action_set_sensitive (app->actions.game.rotate_tile_counterclockwise, started);
688 689
}

690 691 692 693 694 695 696
static void
game_pawn_placed_handler (OVCCGame       *game,
                          OVCCPawn       *pawn,
                          OVCCTileObject *obj,
                          OVCCPosition   *pos,
                          App            *app)
{
697 698 699 700 701 702 703 704 705 706 707 708 709
  /* for now just redraw the position */
  ovccgtk_board_queue_position_update (OVCCGTK_BOARD (app->board), pos);
}

static void
game_pawn_removed_handler (OVCCGame       *game,
                           OVCCPawn       *pawn,
                           OVCCTileObject *obj,
                           OVCCPosition   *pos,
                           App            *app)
{
  /* for now just redraw the position */
  ovccgtk_board_queue_position_update (OVCCGTK_BOARD (app->board), pos);
710 711
}

712 713 714 715 716 717 718
static void
client_place_tile_handler (OVCCClientClient  *client,
                           OVCCTile          *tile,
                           App               *app)
{
  ovccgtk_board_set_state (OVCCGTK_BOARD (app->board),
                           OVCCGTK_BOARD_STATE_TILE);
719
  gtk_widget_set_sensitive (app->current_tile_image, TRUE);
720 721
  gtk_action_set_sensitive (app->actions.game.rotate_tile_clockwise, TRUE);
  gtk_action_set_sensitive (app->actions.game.rotate_tile_counterclockwise, TRUE);
722 723 724 725 726 727 728 729 730
}

static void
client_place_pawn_handler (OVCCClientClient  *client,
                           OVCCPosition      *pos,
                           App               *app)
{
  ovccgtk_board_set_state (OVCCGTK_BOARD (app->board),
                           OVCCGTK_BOARD_STATE_PAWN);
731
  gtk_widget_set_sensitive (app->current_tile_image, FALSE);
732 733
  gtk_action_set_sensitive (app->actions.game.rotate_tile_clockwise, FALSE);
  gtk_action_set_sensitive (app->actions.game.rotate_tile_counterclockwise, FALSE);
734 735
}

736 737 738 739 740 741 742 743 744

static void
client_join_ready (GObject       *object,
                   GAsyncResult  *result,
                   gpointer       data)
{
  GError *err = NULL;
  App    *app = data;
  
745 746
  g_object_unref (app->connection_cancel);
  app->connection_cancel = NULL;
747
  if (! ovccgtk_client_join_finish (result, &err)) {
748 749 750 751 752 753 754 755 756 757 758
    if (g_error_matches (err, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
      ovccgtk_connection_dialog_set_message (OVCCGTK_CONNECTION_DIALOG (app->connection_dialog),
                                             GTK_MESSAGE_INFO,
                                             _("Connection was cancelled"),
                                             NULL);
    } else {
      ovccgtk_connection_dialog_set_message (OVCCGTK_CONNECTION_DIALOG (app->connection_dialog),
                                             GTK_MESSAGE_WARNING,
                                             _("Failed to connect to server"),
                                             err->message);
    }
759
    g_error_free (err);
760 761
    g_object_unref (app->client);
    app->client = NULL;
762 763 764
    /* make sure we validate again since we have a validation rule on
     * app->client == NULL */
    ovccgtk_connection_dialog_validate (OVCCGTK_CONNECTION_DIALOG (app->connection_dialog));
765
    gtk_action_set_sensitive (app->actions.game.connect, TRUE);
766
  } else {
767 768
    /* hide the dialog we come from on success */
    gtk_widget_hide (app->connection_dialog);
769 770
    gtk_action_set_sensitive (app->actions.game.new, FALSE);
    gtk_action_set_sensitive (app->actions.game.disconnect, TRUE);
771
    app_set_status (app, _("Connected!"));
772
    
773 774 775 776 777
    g_signal_connect (app->client, "place-tile",
                      G_CALLBACK (client_place_tile_handler), app);
    g_signal_connect (app->client, "place-pawn",
                      G_CALLBACK (client_place_pawn_handler), app);
    
778 779 780
    app->game = ovccclient_client_get_game (OVCCCLIENT_CLIENT (app->client));
    g_signal_connect (app->game, "notify::current-tile",
                      G_CALLBACK (on_current_tile_changed), app);
781 782 783 784
    g_signal_connect (app->game, "notify::current-player",
                      G_CALLBACK (on_current_player_changed), app);
    g_signal_connect (app->game, "notify::state",
                      G_CALLBACK (on_game_state_changed), app);
785 786
    g_signal_connect (app->game, "pawn-placed",
                      G_CALLBACK (game_pawn_placed_handler), app);
787 788
    g_signal_connect (app->game, "pawn-removed",
                      G_CALLBACK (game_pawn_removed_handler), app);
789
    
790 791 792 793 794
    ovccgtk_player_list_set_game (OVCCGTK_PLAYER_LIST (app->player_list),
                                  app->game);
    ovccgtk_player_list_set_default_player (OVCCGTK_PLAYER_LIST (app->player_list),
                                            ovccclient_client_get_player (OVCCCLIENT_CLIENT (app->client)));
    
795 796 797
    app->board = ovccgtk_board_new (ovcc_game_get_board (app->game));
    g_signal_connect (app->board, "add-tile",
                      G_CALLBACK (on_tile_add_request), app);
798 799
    g_signal_connect (app->board, "add-pawn",
                      G_CALLBACK (on_pawn_add_request), app);
800 801 802 803 804
    gtk_container_add (GTK_CONTAINER (app->board_container), app->board);
    gtk_widget_show (app->board);
    
    update_tile_image (app);
    
805
    gtk_action_set_sensitive (app->actions.game.start, TRUE);
806 807 808
  }
}

809 810 811 812
static void
connection_dialog_response_handler (GtkDialog  *dialog,
                                    gint        response,
                                    App        *app)
813
{
814 815 816 817 818 819 820 821 822 823 824 825 826 827 828
  OVCCGtkConnectionDialog *dlg = OVCCGTK_CONNECTION_DIALOG (dialog);
  
  switch (response) {
    case GTK_RESPONSE_ACCEPT:
    case GTK_RESPONSE_OK: {
      const gchar      *host;
      guint16           port;
      const gchar      *username;
      OVCCClientServer *server;
      
      host = ovccgtk_connection_dialog_get_hostname (dlg);
      port = ovccgtk_connection_dialog_get_port (dlg);
      username = ovccgtk_connection_dialog_get_username (dlg);
      
      /* FIXME! */
829
      gtk_action_set_sensitive (app->actions.game.connect, FALSE);
830 831 832 833 834 835
      gtk_dialog_set_response_sensitive (dialog, GTK_RESPONSE_OK, FALSE);
      ovccgtk_connection_dialog_set_message (dlg,
                                             GTK_MESSAGE_INFO,
                                             _("Connecting..."),
                                             NULL);
      
836 837 838 839 840 841
      if (app->connection_cancel) {
        g_warning ("connection cancellable isn't NULL, wtf?");
        g_object_unref (app->connection_cancel);
      }
      app->connection_cancel = g_cancellable_new ();
      
842 843
      server = ovccclient_server_new (host, port);
      app->client = ovccgtk_client_new (username);
844 845
      ovccgtk_client_join_async (app->client, server, app->connection_cancel,
                                 client_join_ready, app);
846 847 848 849
      g_object_unref (server);
    } break;
    
    default:
850 851 852
      if (app->connection_cancel) {
        g_cancellable_cancel (app->connection_cancel);
      }
853
      gtk_widget_hide (app->connection_dialog);
854
  }
855 856 857
}

static gboolean
858 859
connection_dialog_validate_handler (OVCCGtkConnectionDialog  *dialog,
                                    App                      *app)
860
{
861 862 863 864 865 866 867 868 869
  if (app->client != NULL) {
    ovccgtk_connection_dialog_set_message (dialog,
                                           GTK_MESSAGE_INFO,
                                           _("You are already connected to a server"),
                                           NULL);
    return FALSE;
  }
  
  return TRUE;
870 871 872
}

G_MODULE_EXPORT void
873 874
game_connect_activate_handler (GtkAction *action,
                               App       *app)
875
{
876 877 878 879 880 881
  OVCCGtkConnectionDialog *dialog = OVCCGTK_CONNECTION_DIALOG (app->connection_dialog);
  
  ovccgtk_connection_dialog_set_message (dialog,
                                         GTK_MESSAGE_INFO,
                                         _("Fill in the fields and hit Connect"),
                                         NULL);
882
  gtk_widget_show (app->connection_dialog);
883
  ovccgtk_connection_dialog_validate (dialog);
884 885
}

886 887 888 889 890 891 892
G_MODULE_EXPORT void
game_disconnect_activate_handler (GtkAction *action,
                                  App       *app)
{
  app_leave (app);
}

893 894 895 896 897 898 899 900 901 902 903 904
G_MODULE_EXPORT void
help_about_activate_handler (GtkAction *action,
                             App       *app)
{
  GtkWidget    *dialog;
  const gchar  *authors[] = {
    "Colomban Wendling <ban@herbesfolles.org>",
    NULL
  };
  
  dialog = g_object_new (GTK_TYPE_ABOUT_DIALOG,
                         "transient-for", app->main_window,
905
                         "logo-icon-name", "ovccgtk",
906
                         "program-name", _("OVCCGtk"),
907 908
                         "version", VERSION,
                         "comments", _("A GTK client for OVCC"),
909 910
                         "copyright", _("Copyright \xc2\xa9 2009-2013 Colomban Wendling"),
                         "license-type", GTK_LICENSE_GPL_3_0,
911 912 913 914 915 916 917
                         "translator-credits", _("translator-credits"),
                         "authors", authors,
                         NULL);
  gtk_dialog_run (GTK_DIALOG (dialog));
  gtk_widget_destroy (dialog);
}

918 919 920 921 922 923
static gboolean
ovcc_gtk (int     argc,
          char  **argv)
{
  GtkBuilder *builder;
  gboolean    success = FALSE;
924 925 926 927 928
  gchar      *icon_path;
  
  icon_path = data_get_path ("icons");
  gtk_icon_theme_append_search_path (gtk_icon_theme_get_default (), icon_path);
  g_free (icon_path);
929 930
  
  builder = gtk_builder_new ();
931
  if (load_ui (builder, "ovccgtk-application.ui")) {
932
    GtkWidget  *players_cnt;
933
    GtkWidget  *board_cnt;
934 935 936
    App        *app;
    
    app = g_new0 (App, 1);
937
    
938 939
    gtk_builder_connect_signals (builder, app);
    app->main_window = GTK_WIDGET (gtk_builder_get_object (builder, "main-window"));
940
    app->statusbar = GTK_WIDGET (gtk_builder_get_object (builder, "main-statusbar"));
941 942
    board_cnt = GTK_WIDGET (gtk_builder_get_object (builder, "board-container"));
    players_cnt = GTK_WIDGET (gtk_builder_get_object (builder, "players-container"));
943
    app->current_tile_image = GTK_WIDGET (gtk_builder_get_object (builder, "current-tile-image"));
944
    app->tile_counter_label = GTK_WIDGET (gtk_builder_get_object (builder, "tiles-left-counter-label"));
945 946 947 948
    
    app->board_container = gtk_viewport_new (NULL, NULL);
    gtk_container_add (GTK_CONTAINER (board_cnt), app->board_container);
    
949 950 951
    app->player_list = ovccgtk_player_list_new ();
    gtk_container_add (GTK_CONTAINER (players_cnt), app->player_list);
    
952 953
    /* connection dialog */
    app->connection_dialog = ovccgtk_connection_dialog_new (GTK_WINDOW (app->main_window));
954 955
    g_signal_connect_after (app->connection_dialog, "response",
                            G_CALLBACK (connection_dialog_response_handler), app);
956 957
    g_signal_connect (app->connection_dialog, "validate",
                      G_CALLBACK (connection_dialog_validate_handler), app);
958
    
959 960 961 962 963 964 965 966 967 968
    /* Actions */
    #define GET_ACTION(b, n) GTK_ACTION (gtk_builder_get_object ((b), (n)))
    app->actions.app.quit = NULL;
    app->actions.app.preferences = GET_ACTION (builder, "edit-preferences");
    app->actions.game.new = GET_ACTION (builder, "game-new");
    app->actions.game.connect = GET_ACTION (builder, "game-connect");
    app->actions.game.disconnect = GET_ACTION (builder, "game-disconnect");
    app->actions.game.start = GET_ACTION (builder, "game-start");
    app->actions.game.rotate_tile_clockwise = GET_ACTION (builder, "tile-rotate-clockwise");
    app->actions.game.rotate_tile_counterclockwise = GET_ACTION (builder, "tile-rotate-counterclockwise");
969
    app->actions.game.finish_turn = GET_ACTION (builder, "game-finish-turn");
970 971 972 973 974 975 976
    app->actions.help.about = NULL;
    #undef GET_ACTION
    
    gtk_action_set_sensitive (app->actions.game.disconnect, FALSE);
    gtk_action_set_sensitive (app->actions.game.start, FALSE);
    gtk_action_set_sensitive (app->actions.game.rotate_tile_clockwise, FALSE);
    gtk_action_set_sensitive (app->actions.game.rotate_tile_counterclockwise, FALSE);
977
    gtk_action_set_sensitive (app->actions.game.finish_turn, FALSE);
978
    
979 980
    app_set_status (app, _("Welcome to OVCCGTK!"));
    
981
    gtk_widget_show_all (app->main_window);
982 983
    gtk_main ();
    
984 985
    g_free (app);
    
986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003
    success = TRUE;
  }
  
  return success;
}

int
main (int     argc,
      char  **argv)
{
  int rv = 1;
  
  gtk_init (&argc, &argv);
  rv = ovcc_gtk (argc, argv) ? 0 : 1;
  
  return rv;
}