From 7294fa2d3e23fb2d1ab2eddf5a940128187239fc Mon Sep 17 00:00:00 2001 From: Karuna Grewal Date: Sun, 18 Mar 2018 18:28:21 +0530 Subject: [PATCH] Split five-or-more.c The current code existing in five-or-more.c has been split into: * five-or-more-app.c : Here all the callbacks to the application are connected, followed by the general setting related to the application like background color etc. are moved to this file * game-area.c: Drawing area related code ,say, binding signals to callbacks, or rendering the graphics, controlling the animation are handled here. * balls-preview.c: This has the necessary functions for previewing. Getters are used to access the static variables used across the appliaction which weren't currently required since all the code was mash-up into a single file. https://bugzilla.gnome.org/show_bug.cgi?id=794273 --- po/POTFILES.in | 3 + src/Makefile.am | 9 +- src/balls-preview.c | 168 ++++ src/balls-preview.h | 35 + src/five-or-more-app.c | 695 +++++++++++++++++ src/five-or-more-app.h | 51 ++ src/five-or-more.c | 1668 +--------------------------------------- src/five-or-more.h | 57 -- src/game-area.c | 1057 +++++++++++++++++++++++++ src/game-area.h | 55 ++ src/meson.build | 6 + 11 files changed, 2084 insertions(+), 1720 deletions(-) create mode 100644 src/balls-preview.c create mode 100644 src/balls-preview.h create mode 100644 src/five-or-more-app.c create mode 100644 src/five-or-more-app.h delete mode 100644 src/five-or-more.h create mode 100644 src/game-area.c create mode 100644 src/game-area.h diff --git a/po/POTFILES.in b/po/POTFILES.in index f354a02..980c576 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -9,3 +9,6 @@ data/org.gnome.five-or-more.gschema.xml data/translatable_themes.h src/five-or-more.c src/games-scores-dialog.c +src/five-or-more-app.c +src/game-area.c +src/balls-preview.c diff --git a/src/Makefile.am b/src/Makefile.am index 12edd52..79d7d95 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -2,7 +2,6 @@ bin_PROGRAMS = five-or-more five_or_more_SOURCES = \ five-or-more.c \ - five-or-more.h \ games-file-list.c \ games-file-list.h \ games-gridframe.c \ @@ -16,7 +15,13 @@ five_or_more_SOURCES = \ games-scores-dialog.c \ games-scores-dialog.h \ games-scores-backend.c \ - games-scores-backend.h + games-scores-backend.h \ + five-or-more-app.h \ + five-or-more-app.c \ + balls-preview.c \ + balls-preview.h \ + game-area.h \ + game-area.c five_or_more_CPPFLAGS = \ -I$(top_srcdir) \ diff --git a/src/balls-preview.c b/src/balls-preview.c new file mode 100644 index 0000000..c68e3e0 --- /dev/null +++ b/src/balls-preview.c @@ -0,0 +1,168 @@ +/* -*- mode:C; indent-tabs-mode:t; tab-width:8; c-basic-offset:8; -*- */ + +/* + * Color lines for GNOME + * Copyright © 1999 Free Software Foundation + * Authors: Robert Szokovacs + * Szabolcs Ban + * Karuna Grewal + * Copyright © 2007 Christian Persch + * + * This game 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 2, 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 . + */ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "games-preimage.h" +#include "balls-preview.h" +#include "game-area.h" + +#define MAXNPIECES 10 +#define PREVIEW_IMAGE_WIDTH 20 +#define PREVIEW_IMAGE_HEIGHT 20 + +static GtkImage* preview_images[MAXNPIECES]; +static GdkPixbuf* preview_pixbufs[MAXNPIECES]; +static int preview[MAXNPIECES]; + +void +init_preview (void) +{ + int i; + gint npieces = get_npieces(); + for (i = 0; i < npieces; i++) { + preview[i] = g_rand_int_range (*get_rgen(), 1, get_ncolors() + 1); + } +} + +void +draw_preview (void) +{ + guint i; + gint npieces = get_npieces(); + /* This function can be called before the images are initialized */ + if (!GTK_IS_IMAGE (preview_images[0])) + return; + + for (i = 0; i < MAXNPIECES; i++) { + if (i < npieces) + gtk_image_set_from_pixbuf (preview_images[i], preview_pixbufs[preview[i] - 1]); + else + gtk_image_clear (preview_images[i]); + } +} + +void +refresh_preview_surfaces (void) +{ + guint i; + GdkPixbuf *scaled = NULL; + GtkWidget *widget = GTK_WIDGET (preview_images[0]); + GtkStyleContext *context; + GdkRGBA bg; + cairo_t *cr; + GdkRectangle preview_rect; + cairo_surface_t *blank_preview_surface = NULL; + /* The balls rendered to a size appropriate for the preview. */ + cairo_surface_t *preview_surface = NULL; + + context = gtk_widget_get_style_context (widget); + gtk_style_context_get_background_color (context, gtk_style_context_get_state (context), &bg); + + /* Like the refresh_pixmaps() function, we may be called before + * the window is ready. */ + if (PREVIEW_IMAGE_HEIGHT == 0) + return; + + preview_rect.x = 0; + preview_rect.y = 0; + preview_rect.width = PREVIEW_IMAGE_WIDTH; + preview_rect.height = PREVIEW_IMAGE_HEIGHT; + + /* We create pixmaps for each of the ball colours and then + * set them as the background for each widget in the preview array. + * This code assumes that each preview window is identical. */ + GamesPreimage *ball_preimage = get_ball_preimage(); + if (ball_preimage) + scaled = games_preimage_render (ball_preimage, 4 * PREVIEW_IMAGE_WIDTH, + 7 * PREVIEW_IMAGE_HEIGHT); + + if (!scaled) { + scaled = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, + 4 * PREVIEW_IMAGE_WIDTH, 7 * PREVIEW_IMAGE_HEIGHT); + gdk_pixbuf_fill (scaled, 0x00000000); + } + + for (i = 0; i < 7; i++) { + preview_surface = gdk_window_create_similar_image_surface (gtk_widget_get_window (widget), + CAIRO_FORMAT_ARGB32, + PREVIEW_IMAGE_WIDTH, PREVIEW_IMAGE_HEIGHT, 1); + cr = cairo_create (preview_surface); + gdk_cairo_set_source_rgba (cr, &bg); + gdk_cairo_rectangle (cr, &preview_rect); + cairo_fill (cr); + + gdk_cairo_set_source_pixbuf (cr, scaled, 0, -1.0 * PREVIEW_IMAGE_HEIGHT * i); + cairo_mask (cr, cairo_get_source (cr)); + + if (preview_pixbufs[i]) + g_object_unref (preview_pixbufs[i]); + + preview_pixbufs[i] = gdk_pixbuf_get_from_surface (preview_surface, 0, 0, + PREVIEW_IMAGE_WIDTH, PREVIEW_IMAGE_HEIGHT); + + cairo_destroy (cr); + cairo_surface_destroy (preview_surface); + } + + blank_preview_surface = gdk_window_create_similar_surface (gtk_widget_get_window (widget), + CAIRO_CONTENT_COLOR_ALPHA, + PREVIEW_IMAGE_WIDTH, PREVIEW_IMAGE_HEIGHT); + cr = cairo_create (blank_preview_surface); + gdk_cairo_set_source_rgba (cr, &bg); + gdk_cairo_rectangle (cr, &preview_rect); + cairo_fill (cr); + + cairo_surface_destroy (blank_preview_surface); + cairo_destroy (cr); + g_object_unref (scaled); +} + +GtkImage ** +get_preview_images() +{ + return preview_images; +} + +int * +get_preview() +{ + return preview; +} + +GdkPixbuf ** +get_preview_pixbufs() +{ + return preview_pixbufs; +} \ No newline at end of file diff --git a/src/balls-preview.h b/src/balls-preview.h new file mode 100644 index 0000000..13fa64f --- /dev/null +++ b/src/balls-preview.h @@ -0,0 +1,35 @@ +/* + * Color lines for GNOME + * Copyright © 1999 Free Software Foundation + * Authors: Robert Szokovacs + * Szabolcs Ban + * Karuna Grewal + * Copyright © 2007 Christian Persch + * + * This game 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 2, 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 . + */ + +#ifndef BALLS_PREVIEW_H +#define BALLS_PREVIEW_H + +#include + +void init_preview (void); +void draw_preview (void); +void refresh_preview_surfaces (void); +GtkImage **get_preview_images (); +int * get_preview (); +GdkPixbuf **get_preview_pixbufs (); + +#endif \ No newline at end of file diff --git a/src/five-or-more-app.c b/src/five-or-more-app.c new file mode 100644 index 0000000..8c94ddc --- /dev/null +++ b/src/five-or-more-app.c @@ -0,0 +1,695 @@ +/* -*- mode:C; indent-tabs-mode:t; tab-width:8; c-basic-offset:8; -*- */ + +/* + * Color lines for GNOME + * Copyright © 1999 Free Software Foundation + * Authors: Robert Szokovacs + * Szabolcs Ban + * Karuna Grewal + * Copyright © 2007 Christian Persch + * + * This game 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 2, 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 . + */ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "games-file-list.h" +#include "games-preimage.h" +#include "games-gridframe.h" +#include "games-scores.h" +#include "games-scores-dialog.h" +#include "game-area.h" +#include "five-or-more-app.h" +#include "balls-preview.h" + +#define DEFAULT_GAME_SIZE MEDIUM +#define KEY_SIZE "size" +#define MAXNPIECES 10 +#define KEY_MOVE_TIMEOUT "move-timeout" +#define KEY_BACKGROUND_COLOR "background-color" +#define KEY_BALL_THEME "ball-theme" + +static const GamesScoresCategory scorecats[] = { + { "Small", NC_("board size", "Small") }, + { "Medium", NC_("board size", "Medium") }, + { "Large", NC_("board size", "Large") } +}; + +enum { + UNSET = 0, + SMALL = 1, + MEDIUM, + LARGE, + MAX_SIZE, +}; + +static background backgnd = { { 0, 0, 0, 0}, NULL, 0 }; +static GtkWidget *gridframe; +static GtkWidget *app, *headerbar, *restart_game_dialog, *pref_dialog; + +static char *ball_filename; +static gint game_size = UNSET; +static gboolean pref_dialog_done = FALSE; +static GamesFileList *theme_file_list = NULL; +static GtkWidget *size_radio_s, *size_radio_m, *size_radio_l; + +static int move_timeout = 100; +static gboolean window_is_fullscreen = FALSE, window_is_maximized = FALSE; +static gint window_width = 0, window_height = 0; +static GamesScores *highscores; +static GSettings *settings; +static GtkBuilder *builder; +static GtkBuilder *builder_preferences; + +static guint score = 0; +static GtkWidget *scorelabel; + +/*getter funcs*/ + +GSettings ** +get_settings () +{ + return &settings; +} + +const GamesScoresCategory * +get_scorecats () +{ + return scorecats; +} + +gint * +get_game_size () +{ + return &game_size; +} + +GtkWidget * +get_gridframe () +{ + return gridframe; +} + +char * +get_ball_filename () +{ + return ball_filename; +} + +background +get_backgnd () +{ + return backgnd; +} + +int +get_move_timeout () +{ + return move_timeout; +} + +GamesScores * +get_highscores() +{ + return highscores; +} + +void +update_score (guint value) +{ + char string[20]; + if(value) + score += value; + else + score = value; + g_snprintf (string, 19, "%d", score); + gtk_label_set_text (GTK_LABEL (scorelabel), string); +} + +void +set_status_message (gchar * message) +{ + gtk_header_bar_set_subtitle (GTK_HEADER_BAR (headerbar), message); +} + +static void +set_backgnd_color (const gchar * str) +{ + if (!str) + str = g_strdup ("#000000"); + + if (str != backgnd.name) { + g_free (backgnd.name); + backgnd.name = g_strdup (str); + } + + if (!gdk_rgba_parse (&backgnd.color, backgnd.name)) { + gdk_rgba_parse (&backgnd.color, "#000000"); + } +} + +static void +show_scores (gint pos) +{ + static GtkWidget *dialog; + + if (dialog == NULL) { + dialog = games_scores_dialog_new (GTK_WINDOW (app), highscores, _("Five or More Scores")); + games_scores_dialog_set_category_description (GAMES_SCORES_DIALOG + (dialog), _("_Board size:")); + } + + if (pos > 0) { + games_scores_dialog_set_hilight (GAMES_SCORES_DIALOG (dialog), pos); + } + + gtk_window_present (GTK_WINDOW (dialog)); + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_hide (dialog); +} + +static void +load_properties (void) +{ + gchar *buf; + ball_filename = g_settings_get_string (settings, KEY_BALL_THEME); + + move_timeout = g_settings_get_int (settings, KEY_MOVE_TIMEOUT); + if (move_timeout <= 0) + move_timeout = 100; + + buf = g_settings_get_string (settings, KEY_BACKGROUND_COLOR); /* FIXMEchpe? */ + set_backgnd_color (buf); + g_free (buf); + load_theme (); +} + +void +game_over (void) +{ + int pos; + + set_status_message (_("Game Over!")); + if (score > 0) + pos = games_scores_add_plain_score (highscores, score); + show_scores (pos); +} + +static void +start_game (void) +{ + set_status_message (_("Match five objects of the same type in a row to score!")); + refresh_screen (); + update_score(0); + set_inmove (0); +} + +static void +conf_value_changed_cb (GSettings *settings, gchar *key) +{ + if (strcmp (key, KEY_BACKGROUND_COLOR) == 0) { + gchar *color; + color = g_settings_get_string (settings, KEY_BACKGROUND_COLOR); + if (color != NULL) { + set_backgnd_color (color); + g_free (color); + } + } else if (strcmp (key, KEY_BALL_THEME) == 0) { + gchar *theme_tmp = NULL; + + theme_tmp = g_settings_get_string (settings, KEY_BALL_THEME); + if (theme_tmp) { + if (strcmp (theme_tmp, ball_filename) != 0) { + g_free (ball_filename); + ball_filename = theme_tmp; + load_theme (); + refresh_screen (); + } else + g_free (theme_tmp); + } + /* FIXME apply in the prefs dialog GUI */ + } else if (strcmp (key, KEY_MOVE_TIMEOUT) == 0) { + gint timeout_tmp; + + timeout_tmp = g_settings_get_int (settings, KEY_MOVE_TIMEOUT); + timeout_tmp = CLAMP (timeout_tmp, 1, 1000); + if (timeout_tmp != move_timeout) + move_timeout = timeout_tmp; + + } else if (strcmp (key, KEY_SIZE) == 0) { + gint size_tmp; + size_tmp = g_settings_get_int (settings, KEY_SIZE); + + if (size_tmp != game_size) { + set_sizes (size_tmp); + reset_game (); + start_game (); + } + } +} + +static void +init_config (void) +{ + g_signal_connect (settings, "changed", + G_CALLBACK (conf_value_changed_cb), NULL); + + game_size = g_settings_get_int (settings, KEY_SIZE); + if (game_size == UNSET) + game_size = DEFAULT_GAME_SIZE; + + game_size = CLAMP (game_size, SMALL, MAX_SIZE - 1); + + set_sizes (game_size); +} + +static gboolean +window_size_allocate_cb (GtkWidget *widget, GdkRectangle *allocation) +{ + if (!window_is_maximized && !window_is_fullscreen) + gtk_window_get_size (GTK_WINDOW (widget), &window_width, &window_height); + + return FALSE; +} + +static gboolean +window_state_event_cb (GtkWidget *widget, GdkEventWindowState *event) +{ + if ((event->changed_mask & GDK_WINDOW_STATE_MAXIMIZED) != 0) + window_is_maximized = (event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) != 0; + if ((event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN) != 0) + window_is_fullscreen = (event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN) != 0; + return FALSE; +} + + +void +game_new_callback (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + reset_game (); + start_game (); +} + +void +game_top_ten_callback (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + show_scores (0); +} + + +static void +set_selection (GtkWidget * widget, char *data) +{ + const gchar *entry; + + entry = games_file_list_get_nth (theme_file_list, + gtk_combo_box_get_active (GTK_COMBO_BOX + (widget))); + g_settings_set_string (settings, KEY_BALL_THEME, entry); +} + + + +void +pref_dialog_response (GtkDialog * dialog, gint response, gpointer data) +{ + gtk_widget_hide (GTK_WIDGET (dialog)); +} + +static void +bg_color_callback (GtkWidget * widget, gpointer data) +{ + GdkRGBA c; + char str[64]; + + gtk_color_chooser_get_rgba (GTK_COLOR_CHOOSER (widget), &c); + + g_snprintf (str, sizeof (str), "#%04x%04x%04x", (int) (c.red * 65535 + 0.5), (int) (c.green * 65535 + 0.5), (int) (c.blue * 65535 + 0.5)); + + g_settings_set_string (settings, KEY_BACKGROUND_COLOR, str); + //area,h + load_theme (); + //area.h + refresh_screen (); +} + +static void +size_callback (GtkWidget * widget, gpointer data) +{ + GtkWidget *size_radio, *content_area, *label; + + game_size = g_settings_get_int (settings, KEY_SIZE); + if (pref_dialog_done && game_size != GPOINTER_TO_INT (data) && !restart_game_dialog) { + GtkDialogFlags flags = GTK_DIALOG_DESTROY_WITH_PARENT; + + restart_game_dialog = gtk_message_dialog_new (GTK_WINDOW (pref_dialog), + GTK_MESSAGE_WARNING, + flags, + GTK_BUTTONS_NONE, + _("Are you sure you want to restart the game?")); + + gtk_dialog_add_buttons (GTK_DIALOG (restart_game_dialog), + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_Restart"), GTK_RESPONSE_OK, + NULL); + + gint result = gtk_dialog_run (GTK_DIALOG (restart_game_dialog)); + gtk_widget_destroy (restart_game_dialog); + + switch (result) { + case GTK_RESPONSE_OK: + g_settings_set_int (settings, KEY_SIZE, GPOINTER_TO_INT (data)); + break; + case GTK_RESPONSE_CANCEL: + switch (game_size) { + case SMALL: + size_radio = size_radio_s; + break; + case MEDIUM: + size_radio = size_radio_m; + break; + case LARGE: + size_radio = size_radio_l; + break; + } + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (size_radio), TRUE); + } + + restart_game_dialog = NULL; + } +} + +static GtkWidget * +fill_menu (void) +{ + gchar *pixmap_dir; + + if (theme_file_list) + g_object_unref (theme_file_list); + + pixmap_dir = g_build_filename (DATA_DIRECTORY, "themes", NULL); + theme_file_list = games_file_list_new_images (pixmap_dir, NULL); + g_free (pixmap_dir); + games_file_list_transform_basename (theme_file_list); + + return games_file_list_create_widget (theme_file_list, ball_filename, + GAMES_FILE_LIST_REMOVE_EXTENSION | + GAMES_FILE_LIST_REPLACE_UNDERSCORES); +} + +static void +set_fast_moves_callback (GtkWidget * widget, gpointer * data) +{ + gboolean is_on = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)); + gint timeout = is_on ? 10 : 100; + g_settings_set_int (settings, KEY_MOVE_TIMEOUT, timeout); +} + +void +game_props_callback (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + gchar *ui_path; + GError *error = NULL; + GtkWidget *omenu; + GtkWidget *grid; + GtkWidget *color_button; + GtkWidget *size_radio; + GtkWidget *fast_moves_checkbutton; + + if (!pref_dialog) { + ui_path = g_build_filename (DATA_DIRECTORY, "five-or-more-preferences.ui", NULL); + builder_preferences = gtk_builder_new (); + gtk_builder_add_from_file (builder_preferences, ui_path, &error); + g_free (ui_path); + + if (error) { + g_critical ("Unable to load the user interface file: %s", error->message); + g_error_free (error); + g_assert_not_reached (); + } + + pref_dialog = GTK_WIDGET (gtk_builder_get_object (builder_preferences, "preferences_dialog")); + g_signal_connect (pref_dialog, "response", + G_CALLBACK (pref_dialog_response), NULL); + g_signal_connect (pref_dialog, "delete-event", + G_CALLBACK (gtk_widget_hide), NULL); + + grid = GTK_WIDGET (gtk_builder_get_object (builder_preferences, "grid1")); + omenu = fill_menu (); + gtk_widget_show_all (GTK_WIDGET (omenu)); + gtk_grid_attach (GTK_GRID (grid), GTK_WIDGET (omenu), 1, 0, 1, 1); + g_signal_connect (omenu, "changed", + G_CALLBACK (set_selection), NULL); + + color_button = GTK_WIDGET (gtk_builder_get_object (builder_preferences, "colorbutton1")); + gtk_color_chooser_set_rgba (GTK_COLOR_CHOOSER (color_button), &backgnd.color); + g_signal_connect (color_button, "color-set", + G_CALLBACK (bg_color_callback), NULL); + + size_radio = GTK_WIDGET (gtk_builder_get_object (builder_preferences, "radiobutton_small")); + size_radio_s = size_radio; + g_signal_connect (size_radio, "clicked", + G_CALLBACK (size_callback), GINT_TO_POINTER (1)); + if (game_size == SMALL) + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (size_radio), TRUE); + + size_radio = GTK_WIDGET (gtk_builder_get_object (builder_preferences, "radiobutton_medium")); + size_radio_m = size_radio; + g_signal_connect (size_radio, "clicked", + G_CALLBACK (size_callback), GINT_TO_POINTER (2)); + if (game_size == MEDIUM) + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (size_radio), TRUE); + + size_radio = GTK_WIDGET (gtk_builder_get_object (builder_preferences, "radiobutton_large")); + size_radio_l = size_radio; + g_signal_connect (size_radio, "clicked", + G_CALLBACK (size_callback), GINT_TO_POINTER (3)); + if (game_size == LARGE) + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (size_radio), TRUE); + + fast_moves_checkbutton = GTK_WIDGET (gtk_builder_get_object (builder_preferences, "checkbutton_fast_moves")); + if (move_timeout == 10) { + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (fast_moves_checkbutton), TRUE); + } + + g_signal_connect (fast_moves_checkbutton, "clicked", + G_CALLBACK (set_fast_moves_callback), NULL); + + g_object_unref (G_OBJECT (builder_preferences)); + + pref_dialog_done = TRUE; + gtk_window_set_transient_for (GTK_WINDOW (pref_dialog), GTK_WINDOW (app)); + } + gtk_dialog_run (GTK_DIALOG (pref_dialog)); +} + +void +game_help_callback (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + GError *error = NULL; + + gtk_show_uri (gtk_widget_get_screen (GTK_WIDGET (app)), "help:five-or-more", gtk_get_current_event_time (), &error); + if (error) + g_warning ("Failed to show help: %s", error->message); + g_clear_error (&error); +} + +void +game_about_callback (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + const gchar *authors[] = { "Robert Szokovacs ", + "Szabolcs B\xc3\xa1n ", + NULL + }; + + const gchar *documenters[] = { "Tiffany Antopolski", + "Lanka Rathnayaka", + NULL + }; + + gtk_show_about_dialog (GTK_WINDOW (app), + "program-name", _("Five or More"), + "version", VERSION, + "comments", _("GNOME port of the once-popular Color Lines game"), + "copyright", + "Copyright © 1997–2008 Free Software Foundation, Inc.\n Copyright © 2013–2014 Michael Catanzaro", + "license-type", GTK_LICENSE_GPL_2_0, + "authors", authors, + "documenters", documenters, + "translator-credits", _("translator-credits"), + "logo-icon-name", "five-or-more", + "website", "https://wiki.gnome.org/Apps/Five%20or%20more", + NULL); +} + +void +game_quit_callback (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + g_application_quit (G_APPLICATION (user_data)); +} + +void +startup_cb (GApplication *application) +{ + gchar *ui_path; + GtkWidget *hbox; + GtkWidget *preview_hbox; + GtkWidget *new_game_button; + guint i; + GError *error = NULL; + + GActionEntry app_actions[] = { + { "new-game", game_new_callback }, + { "scores", game_top_ten_callback }, + { "preferences", game_props_callback }, + { "help", game_help_callback }, + { "about", game_about_callback }, + { "quit", game_quit_callback } + }; + + g_action_map_add_action_entries (G_ACTION_MAP (application), + app_actions, G_N_ELEMENTS (app_actions), + application); + + gtk_application_add_accelerator (GTK_APPLICATION (application), "N", "app.new-game", NULL); + + settings = g_settings_new ("org.gnome.five-or-more"); + + highscores = games_scores_new ("five-or-more", + scorecats, G_N_ELEMENTS (scorecats), + "board size", NULL, + 0 /* default category */, + GAMES_SCORES_STYLE_PLAIN_DESCENDING); + init_config (); + builder = gtk_builder_new (); + ui_path = g_build_filename (DATA_DIRECTORY, "menu.ui", NULL); + gtk_builder_add_from_file (builder, ui_path, &error); + g_free (ui_path); + + if (error) { + g_critical ("Unable to load the application menu file: %s", error->message); + g_error_free (error); + } else { + gtk_application_set_app_menu (GTK_APPLICATION (application), + G_MENU_MODEL (gtk_builder_get_object (builder, "appmenu"))); + } + + ui_path = g_build_filename (DATA_DIRECTORY, "five-or-more.ui", NULL); + gtk_builder_add_from_file (builder, ui_path, &error); + g_free (ui_path); + + if (error) { + g_critical ("Unable to load the user interface file: %s", error->message); + g_error_free (error); + g_assert_not_reached (); + } + + app = GTK_WIDGET (gtk_builder_get_object (builder, "glines_window")); + gtk_window_set_icon_name (GTK_WINDOW (app), "five-or-more"); + g_signal_connect (GTK_WINDOW (app), "size-allocate", G_CALLBACK (window_size_allocate_cb), NULL); + g_signal_connect (GTK_WINDOW (app), "window-state-event", G_CALLBACK (window_state_event_cb), NULL); + gtk_window_set_default_size (GTK_WINDOW (app), g_settings_get_int (settings, "window-width"), g_settings_get_int (settings, "window-height")); + if (g_settings_get_boolean (settings, "window-is-maximized")) + gtk_window_maximize (GTK_WINDOW (app)); + gtk_container_set_border_width (GTK_CONTAINER (app), 20); + gtk_application_add_window (GTK_APPLICATION (application), GTK_WINDOW (app)); + + headerbar = GTK_WIDGET (gtk_builder_get_object (builder, "headerbar")); + + GtkImage **preview_images = get_preview_images(); + preview_hbox = GTK_WIDGET (gtk_builder_get_object (builder, "preview_hbox")); + for (i = 0; i < MAXNPIECES; i++) { + //preview images getter req this is defiend in src or here + preview_images[i] = GTK_IMAGE (gtk_image_new ()); + gtk_box_pack_start (GTK_BOX (preview_hbox), GTK_WIDGET (preview_images[i]), FALSE, FALSE, 0); + gtk_widget_realize (GTK_WIDGET (preview_images[i])); + } + + scorelabel = GTK_WIDGET (gtk_builder_get_object (builder, "scorelabel")); + + hbox = GTK_WIDGET (gtk_builder_get_object (builder, "hbox")); + + GtkWidget *draw_area = game_area_init (); + + gridframe = games_grid_frame_new (get_hfieldsize(), get_vfieldsize()); + games_grid_frame_set_padding (GAMES_GRID_FRAME (gridframe), 1, 1); + gtk_container_add (GTK_CONTAINER (gridframe), draw_area); + gtk_box_pack_start (GTK_BOX (hbox), gridframe, TRUE, TRUE, 0); + + new_game_button = GTK_WIDGET (gtk_builder_get_object (builder, "new_game_button")); + + load_properties (); + + gtk_builder_connect_signals (builder, NULL); + + g_object_unref (G_OBJECT (builder)); +} + + +void +activate_cb (GApplication *application) +{ + reset_game (); + gtk_widget_show_all (app); + start_game (); +} + +void +shutdown_cb (GApplication *application) +{ + int i = 0; + GtkImage **preview_images = get_preview_images(); + GdkPixbuf **preview_pixbufs = get_preview_pixbufs(); + for (i = 0; i < G_N_ELEMENTS (preview_images); i++) + if (preview_pixbufs[i]) + g_object_unref (preview_pixbufs[i]); + + GamesPreimage *ball_preimage = get_ball_preimage(); + g_clear_object (&ball_preimage); + g_object_unref (highscores); + + g_settings_set_int (settings, "window-width", window_width); + g_settings_set_int (settings, "window-height", window_height); + g_settings_set_boolean (settings, "window-is-maximized", window_is_maximized); +} + +void +set_application_callbacks(GtkApplication *application) +{ + g_signal_connect (application, "startup", G_CALLBACK (startup_cb), NULL); + g_signal_connect (application, "activate", G_CALLBACK (activate_cb), NULL); + g_signal_connect (application, "shutdown", G_CALLBACK (shutdown_cb), NULL); +} \ No newline at end of file diff --git a/src/five-or-more-app.h b/src/five-or-more-app.h new file mode 100644 index 0000000..6e9a6b6 --- /dev/null +++ b/src/five-or-more-app.h @@ -0,0 +1,51 @@ +/* + * Color lines for GNOME + * Copyright © 1999 Free Software Foundation + * Authors: Robert Szokovacs + * Szabolcs Ban + * Karuna Grewal + * Copyright © 2007 Christian Persch + * + * This game 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 2, 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 . + */ + +#ifndef FIVE_OR_MORE_APP_H +#define FIVE_OR_MORE_APP_H + +#include +#include "games-scores.h" + +typedef struct _background background; +struct _background{ + GdkRGBA color; + gchar *name; + gint set; +} ; + +GamesScores *get_highscores (); +GtkWidget *get_gridframe (); +char *get_ball_filename (); +background get_backgnd (); +void set_status_message (gchar * message); +void game_over (void); +void set_application_callbacks (GtkApplication *application); +gint *get_game_size (); +const GamesScoresCategory *get_scorecats (); +GSettings **get_settings (); +int get_move_timeout (); +void game_props_callback (GSimpleAction *action, + GVariant *parameter, + gpointer user_data); +void update_score (guint value); +#endif \ No newline at end of file diff --git a/src/five-or-more.c b/src/five-or-more.c index f2a3dc7..cc9e1fe 100644 --- a/src/five-or-more.c +++ b/src/five-or-more.c @@ -34,1662 +34,8 @@ #include #include -#include "five-or-more.h" -#include "games-file-list.h" -#include "games-preimage.h" -#include "games-gridframe.h" -#include "games-scores.h" -#include "games-scores-dialog.h" - -#define KEY_BACKGROUND_COLOR "background-color" -#define KEY_BALL_THEME "ball-theme" -#define KEY_MOVE_TIMEOUT "move-timeout" -#define KEY_SIZE "size" - -#define KEY_SAVED_SCORE "score" -#define KEY_SAVED_FIELD "field" -#define KEY_SAVED_PREVIEW "preview" - -#define MAXNPIECES 10 -#define MAXFIELDSIZE 30 -#define DEFAULT_GAME_SIZE MEDIUM -#define DEFAULT_BALL_THEME "balls.svg" - -#define PREVIEW_IMAGE_WIDTH 20 -#define PREVIEW_IMAGE_HEIGHT 20 - -enum { - UNSET = 0, - SMALL = 1, - MEDIUM, - LARGE, - MAX_SIZE, -}; - -/* Keep these in sync with the enum above. */ -static const gint field_sizes[MAX_SIZE][4] = { - {-1, -1, -1, -1}, /* This is a dummy entry. */ - {7, 7, 5, 3}, /* SMALL */ - {9, 9, 7, 3}, /* MEDIUM */ - {20, 15, 7, 7} /* LARGE */ -}; - -static const GamesScoresCategory scorecats[] = { - { "Small", NC_("board size", "Small") }, - { "Medium", NC_("board size", "Medium") }, - { "Large", NC_("board size", "Large") } -}; - -static GamesScores *highscores; -static GSettings *settings; -static GtkBuilder *builder; -static GtkBuilder *builder_preferences; - -static gint hfieldsize; -static gint vfieldsize; -static gint ncolors; -static gint npieces; -static gint game_size = UNSET; -static gboolean pref_dialog_done = FALSE; - -static GRand *rgen; - -static GtkWidget *draw_area; -static GtkWidget *app, *headerbar, *pref_dialog, *gridframe, *restart_game_dialog; -static GtkWidget *size_radio_s, *size_radio_m, *size_radio_l; - -static gint window_width = 0, window_height = 0; -static gboolean window_is_fullscreen = FALSE, window_is_maximized = FALSE; - -static field_props field[MAXFIELDSIZE * MAXFIELDSIZE]; - -/* Pre-rendering image data prepared from file. */ -static GamesPreimage *ball_preimage = NULL; -/* The tile images with balls rendered on them. */ -static cairo_surface_t *ball_surface = NULL; - -static GtkImage* preview_images[MAXNPIECES]; -static GdkPixbuf* preview_pixbufs[MAXNPIECES]; - -/* A cairo_surface_t of a blank tile. */ -static cairo_surface_t *blank_surface = NULL; - -static GamesFileList *theme_file_list = NULL; - -static int active = -1; -static int target = -1; -static int inmove = 0; -static guint score = 0; -static int cursor_x = MAXFIELDSIZE / 2; -static int cursor_y = MAXFIELDSIZE / 2; -static gboolean show_cursor = FALSE; - -static int boxsize; - -static int move_timeout = 100; -static int animate_id = 0; -static int preview[MAXNPIECES]; -static char *ball_filename; -static GtkWidget *scorelabel; -static scoretable sctab[] = - { {5, 10}, {6, 12}, {7, 18}, {8, 28}, {9, 42}, {10, 82}, {11, 108}, {12, - 138}, - {13, 172}, {14, 210}, {0, 0} }; - -static struct { - GdkRGBA color; - gchar *name; - gint set; -} backgnd = { { 0, 0, 0, 0}, NULL, 0 }; - -static gchar *warning_message = NULL; - -static void -set_status_message (gchar * message) -{ - gtk_header_bar_set_subtitle (GTK_HEADER_BAR (headerbar), message); -} - -static void -show_image_warning (gchar * message) -{ - GtkWidget *dialog; - GtkWidget *button; - - dialog = gtk_message_dialog_new (GTK_WINDOW (app), - GTK_DIALOG_MODAL, - GTK_MESSAGE_WARNING, - GTK_BUTTONS_CLOSE, - "%s",_("Could not load theme")); - - gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), - "%s", message); - - button = gtk_dialog_add_button (GTK_DIALOG (dialog), - _("Preferences"), GTK_RESPONSE_ACCEPT); - - gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT); - - g_signal_connect (button, "clicked", G_CALLBACK (game_props_callback), - NULL); - - gtk_dialog_run (GTK_DIALOG (dialog)); - gtk_widget_destroy (dialog); -} - -static GamesPreimage * -load_image (gchar * fname) -{ - GamesPreimage *preimage; - gchar *path; - GError *error = NULL; - - path = g_build_filename (DATA_DIRECTORY, "themes", fname, NULL); - if (!g_file_test (path, G_FILE_TEST_EXISTS)) { - warning_message = g_strdup_printf (_("Unable to locate file:\n%s\n\n" - "The default theme will be loaded instead."), - fname); - - path = g_build_filename (DATA_DIRECTORY, "themes", "balls.svg", NULL); - if (!g_file_test (path, G_FILE_TEST_EXISTS)) { - g_free (warning_message); - warning_message = g_strdup_printf (_("Unable to locate file:\n%s\n\n" - "Please check that Five or More is installed correctly."), - fname); - } - } - - preimage = games_preimage_new_from_file (path, &error); - g_free (path); - - if (error) { - warning_message = g_strdup (error->message); - g_error_free (error); - } - - return preimage; -} - -static void -refresh_pixmaps (void) -{ - cairo_t *cr, *cr_blank; - GdkPixbuf *ball_pixbuf = NULL; - - /* Since we get called both by configure and after loading an image. - * it is possible the pixmaps aren't initialised. If they aren't - * we don't do anything. */ - if (!ball_surface) - return; - - if (!boxsize) - return; - - if (ball_preimage) { - ball_pixbuf = games_preimage_render (ball_preimage, 4 * boxsize, - 7 * boxsize); - - /* Handle rendering problems. */ - if (!ball_pixbuf) { - g_object_unref (ball_preimage); - ball_preimage = NULL; - - if (!warning_message) { - warning_message = g_strdup ("The selected theme failed to render.\n\n" - "Please check that Five or More is installed correctly."); - } - } - } - - if (!ball_pixbuf) { - ball_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, - 4 * boxsize, 7 * boxsize); - gdk_pixbuf_fill (ball_pixbuf, 0x00000000); - } - - if (warning_message) - show_image_warning (warning_message); - g_free (warning_message); - warning_message = NULL; - - cr = cairo_create (ball_surface); - gdk_cairo_set_source_rgba (cr, &backgnd.color); - - cairo_rectangle (cr, 0, 0, boxsize * 4, boxsize * 7); - cairo_fill (cr); - - gdk_cairo_set_source_pixbuf (cr, ball_pixbuf, 0, 0); - cairo_mask (cr, cairo_get_source (cr)); - g_object_unref (ball_pixbuf); - - cairo_destroy (cr); - - cr_blank = cairo_create (blank_surface); - gdk_cairo_set_source_rgba (cr_blank, &backgnd.color); - - cairo_rectangle (cr_blank, 0, 0, boxsize, boxsize); - cairo_fill (cr_blank); - - cairo_destroy (cr_blank); -} - -static void -refresh_preview_surfaces (void) -{ - guint i; - GdkPixbuf *scaled = NULL; - GtkWidget *widget = GTK_WIDGET (preview_images[0]); - GtkStyleContext *context; - GdkRGBA bg; - cairo_t *cr; - GdkRectangle preview_rect; - cairo_surface_t *blank_preview_surface = NULL; - /* The balls rendered to a size appropriate for the preview. */ - cairo_surface_t *preview_surface = NULL; - - context = gtk_widget_get_style_context (widget); - gtk_style_context_get_background_color (context, gtk_style_context_get_state (context), &bg); - - /* Like the refresh_pixmaps() function, we may be called before - * the window is ready. */ - if (PREVIEW_IMAGE_HEIGHT == 0) - return; - - preview_rect.x = 0; - preview_rect.y = 0; - preview_rect.width = PREVIEW_IMAGE_WIDTH; - preview_rect.height = PREVIEW_IMAGE_HEIGHT; - - /* We create pixmaps for each of the ball colours and then - * set them as the background for each widget in the preview array. - * This code assumes that each preview window is identical. */ - - if (ball_preimage) - scaled = games_preimage_render (ball_preimage, 4 * PREVIEW_IMAGE_WIDTH, - 7 * PREVIEW_IMAGE_HEIGHT); - - if (!scaled) { - scaled = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, - 4 * PREVIEW_IMAGE_WIDTH, 7 * PREVIEW_IMAGE_HEIGHT); - gdk_pixbuf_fill (scaled, 0x00000000); - } - - for (i = 0; i < 7; i++) { - preview_surface = gdk_window_create_similar_image_surface (gtk_widget_get_window (widget), - CAIRO_FORMAT_ARGB32, - PREVIEW_IMAGE_WIDTH, PREVIEW_IMAGE_HEIGHT, 1); - cr = cairo_create (preview_surface); - gdk_cairo_set_source_rgba (cr, &bg); - gdk_cairo_rectangle (cr, &preview_rect); - cairo_fill (cr); - - gdk_cairo_set_source_pixbuf (cr, scaled, 0, -1.0 * PREVIEW_IMAGE_HEIGHT * i); - cairo_mask (cr, cairo_get_source (cr)); - - if (preview_pixbufs[i]) - g_object_unref (preview_pixbufs[i]); - - preview_pixbufs[i] = gdk_pixbuf_get_from_surface (preview_surface, 0, 0, - PREVIEW_IMAGE_WIDTH, PREVIEW_IMAGE_HEIGHT); - - cairo_destroy (cr); - cairo_surface_destroy (preview_surface); - } - - blank_preview_surface = gdk_window_create_similar_surface (gtk_widget_get_window (widget), - CAIRO_CONTENT_COLOR_ALPHA, - PREVIEW_IMAGE_WIDTH, PREVIEW_IMAGE_HEIGHT); - cr = cairo_create (blank_preview_surface); - gdk_cairo_set_source_rgba (cr, &bg); - gdk_cairo_rectangle (cr, &preview_rect); - cairo_fill (cr); - - cairo_surface_destroy (blank_preview_surface); - cairo_destroy (cr); - g_object_unref (scaled); -} - -static void -draw_all_balls (GtkWidget * widget) -{ - gdk_window_invalidate_rect (gtk_widget_get_window (widget), NULL, FALSE); -} - -static void -start_animation (void) -{ - int ms; - - ms = (inmove ? move_timeout : 100); - if (animate_id == 0) - animate_id = g_timeout_add (ms, animate, draw_area); -} - -void -set_inmove (int i) -{ - if (inmove != i) { - inmove = i; - if (animate_id) - g_source_remove (animate_id); - animate_id = 0; - start_animation (); - } -} - -static void -reset_game (void) -{ - int i; - - for (i = 0; i < hfieldsize * vfieldsize; i++) { - field[i].color = 0; - field[i].phase = 0; - field[i].active = 0; - field[i].pathsearch = -1; - } - score = 0; - init_preview (); - init_new_balls (npieces, -1); -} - -static void -refresh_screen (void) -{ - draw_all_balls (draw_area); - draw_preview (); -} - -static void -start_game (void) -{ - char string[20]; - - set_status_message (_("Match five objects of the same type in a row to score!")); - refresh_screen (); - active = -1; - target = -1; - inmove = -1; - g_snprintf (string, 19, "%d", score); - gtk_label_set_text (GTK_LABEL (scorelabel), string); - set_inmove (0); -} - -static void -reset_pathsearch (void) -{ - int i; - - for (i = 0; i < hfieldsize * vfieldsize; i++) - field[i].pathsearch = -1; -} - -void -init_preview (void) -{ - int i; - - for (i = 0; i < npieces; i++) { - preview[i] = g_rand_int_range (rgen, 1, ncolors + 1); - } -} - -void -draw_preview (void) -{ - guint i; - - /* This function can be called before the images are initialized */ - if (!GTK_IS_IMAGE (preview_images[0])) - return; - - for (i = 0; i < MAXNPIECES; i++) { - if (i < npieces) - gtk_image_set_from_pixbuf (preview_images[i], preview_pixbufs[preview[i] - 1]); - else - gtk_image_clear (preview_images[i]); - } -} - -void -game_new_callback (GSimpleAction *action, - GVariant *parameter, - gpointer user_data) -{ - reset_game (); - start_game (); -} - -static void -show_scores (gint pos) -{ - static GtkWidget *dialog; - - if (dialog == NULL) { - dialog = games_scores_dialog_new (GTK_WINDOW (app), highscores, _("Five or More Scores")); - games_scores_dialog_set_category_description (GAMES_SCORES_DIALOG - (dialog), _("_Board size:")); - } - - if (pos > 0) { - games_scores_dialog_set_hilight (GAMES_SCORES_DIALOG (dialog), pos); - } - - gtk_window_present (GTK_WINDOW (dialog)); - gtk_dialog_run (GTK_DIALOG (dialog)); - gtk_widget_hide (dialog); -} - -static void -game_over (void) -{ - int pos; - - set_status_message (_("Game Over!")); - if (score > 0) - pos = games_scores_add_plain_score (highscores, score); - show_scores (pos); -} - -static int -spaces_left (void) -{ - int i, j; - - j = 0; - - for (i = 0; i < hfieldsize * vfieldsize; i++) { - if (field[i].color == 0) - j++; - } - - return j; -} - -static int -check_gameover (void) -{ - if (spaces_left () > 0) - return 1; - - game_over (); - - return -1; -} - -int -init_new_balls (int num, int prev) -{ - int i, j = -1; - gfloat num_boxes = hfieldsize * vfieldsize; - for (i = 0; i < num;) { - j = g_rand_int_range (rgen, 0, num_boxes); - if (field[j].color == 0) { - field[j].color = (prev == -1) ? - g_rand_int_range (rgen, 1, ncolors + 1) : preview[prev]; - i++; - } - } - return j; -} - -void -draw_box (GtkWidget * widget, int x, int y) -{ - gtk_widget_queue_draw_area (widget, x * boxsize, y * boxsize, - boxsize, boxsize); -} - -static int -route (int num) -{ - int i; - int flag = 0; - - if (field[target].pathsearch == num) - return 1; - for (i = 0; i < hfieldsize * vfieldsize; i++) { - if (field[i].pathsearch == num) { - flag = 1; - if ((i / hfieldsize > 0) && (field[i - hfieldsize].pathsearch == -1) - && (field[i - hfieldsize].color == 0)) - field[i - hfieldsize].pathsearch = num + 1; - if ((i / hfieldsize < vfieldsize - 1) - && (field[i + hfieldsize].pathsearch == -1) - && (field[i + hfieldsize].color == 0)) - field[i + hfieldsize].pathsearch = num + 1; - if ((i % hfieldsize > 0) && (field[i - 1].pathsearch == -1) - && (field[i - 1].color == 0)) - field[i - 1].pathsearch = num + 1; - if ((i % hfieldsize < hfieldsize - 1) && (field[i + 1].pathsearch == -1) - && (field[i + 1].color == 0)) { - field[i + 1].pathsearch = num + 1; - } - } - } - if (flag == 0) - return 0; - return 2; -} - -static void -fix_route (int num, int pos) -{ - int i; - - for (i = 0; i < hfieldsize * vfieldsize; i++) - if ((i != pos) && (field[i].pathsearch == num)) - field[i].pathsearch = -1; - if (num < 2) - return; - if ((pos / hfieldsize > 0) - && (field[pos - hfieldsize].pathsearch == num - 1)) - fix_route (num - 1, pos - hfieldsize); - if ((pos % hfieldsize > 0) && (field[pos - 1].pathsearch == num - 1)) - fix_route (num - 1, pos - 1); - if ((pos / hfieldsize < vfieldsize - 1) - && (field[pos + hfieldsize].pathsearch == num - 1)) - fix_route (num - 1, pos + hfieldsize); - if ((pos % hfieldsize < hfieldsize - 1) - && (field[pos + 1].pathsearch == num - 1)) - fix_route (num - 1, pos + 1); -} - -int -find_route (void) -{ - int i = 0; - int j = 0; - - field[active].pathsearch = i; - while ((j = route (i))) { - if (j == 1) { - fix_route (i, target); - return 1; - } - i++; - } - return 0; -} - -static void -deactivate (GtkWidget * widget, int x, int y) -{ - field[active].active = 0; - field[active].phase = 0; - draw_box (widget, x, y); - active = -1; -} - -static void -cell_clicked (GtkWidget * widget, int fx, int fy) -{ - int x, y; - - set_status_message (NULL); - if (field[fx + fy * hfieldsize].color == 0) { - /* Clicked on an empty field */ - - if ((active != -1) && (inmove != 1)) { - /* We have an active ball, and it's not - * already in move. Therefore we should begin - * the moving sequence */ - - target = fx + fy * hfieldsize; - if (find_route ()) { - /* We found a route to the new position */ - - set_inmove (1); - } else { - /* Can't move there! */ - set_status_message (_("You can’t move there!")); - reset_pathsearch (); - target = -1; - } - } - } else { - /* Clicked on a ball */ - - if ((fx + fy * hfieldsize) == active) { - /* It's active, so let's deactivate it! */ - - deactivate (widget, fx, fy); - } else { - /* It's not active, so let's activate it! */ - - if (active != -1) { - /* There is an other active ball, we should deactivate it first! */ - - x = active % hfieldsize; - y = active / hfieldsize; - deactivate (widget, x, y); - } - - active = fx + fy * hfieldsize; - field[active].active = 1; - start_animation(); - } - } - -} - -static gint -button_press_event (GtkWidget * widget, GdkEvent * event) -{ - int x, y; - int fx, fy; - - if (inmove) - return TRUE; - - /* Ignore the 2BUTTON and 3BUTTON events. */ - if (event->type != GDK_BUTTON_PRESS) - return TRUE; - - if (show_cursor) { - show_cursor = FALSE; - draw_box (draw_area, cursor_x, cursor_y); - } - - fx = event->button.x / boxsize; - fy = event->button.y / boxsize; - - /* If we click on the outer border pixels, the previous calculation - * will be wrong and we must correct. */ - fx = CLAMP (fx, 0, hfieldsize - 1); - fy = CLAMP (fy, 0, vfieldsize - 1); - - cursor_x = fx; - cursor_y = fy; - - cell_clicked (widget, fx, fy); - - return TRUE; -} - -static void -move_cursor (int dx, int dy) -{ - int old_x = cursor_x; - int old_y = cursor_y; - - if (!show_cursor) { - show_cursor = TRUE; - draw_box (draw_area, cursor_x, cursor_y); - } - - cursor_x = cursor_x + dx; - if (cursor_x < 0) - cursor_x = 0; - if (cursor_x >= hfieldsize) - cursor_x = hfieldsize - 1; - cursor_y = cursor_y + dy; - if (cursor_y < 0) - cursor_y = 0; - if (cursor_y >= vfieldsize) - cursor_y = vfieldsize - 1; - - if (cursor_x == old_x && cursor_y == old_y) - return; - - draw_box (draw_area, old_x, old_y); - draw_box (draw_area, cursor_x, cursor_y); -} - -static gboolean -key_press_event (GtkWidget * widget, GdkEventKey * event, void *d) -{ - guint key; - - key = event->keyval; - - switch (key) { - case GDK_KEY_Left: - case GDK_KEY_KP_Left: - move_cursor (-1, 0); - break; - case GDK_KEY_Right: - case GDK_KEY_KP_Right: - move_cursor (1, 0); - break; - case GDK_KEY_Up: - case GDK_KEY_KP_Up: - move_cursor (0, -1); - break; - case GDK_KEY_Down: - case GDK_KEY_KP_Down: - move_cursor (0, 1); - break; - case GDK_KEY_Home: - case GDK_KEY_KP_Home: - move_cursor (-999, 0); - break; - case GDK_KEY_End: - case GDK_KEY_KP_End: - move_cursor (999, 0); - break; - case GDK_KEY_Page_Up: - case GDK_KEY_KP_Page_Up: - move_cursor (0, -999); - break; - case GDK_KEY_Page_Down: - case GDK_KEY_KP_Page_Down: - move_cursor (0, 999); - break; - - case GDK_KEY_space: - case GDK_KEY_Return: - case GDK_KEY_KP_Enter: - if (show_cursor) - cell_clicked (widget, cursor_x, cursor_y); - break; - } - - return TRUE; -} - -static void -draw_grid (cairo_t *cr) -{ - GdkRGBA color; - guint w, h; - guint i; - - w = gtk_widget_get_allocated_width(draw_area); - h = gtk_widget_get_allocated_height(draw_area); - - gdk_rgba_parse (&color, "#525F6C"); - gdk_cairo_set_source_rgba (cr, &color); - cairo_set_line_width (cr, 1.0); - - for (i = boxsize; i < w; i = i + boxsize) - { - cairo_move_to (cr, i + 0.5, 0 + 0.5); - cairo_line_to (cr, i + 0.5, h + 0.5); - } - - for (i = boxsize; i < h; i = i + boxsize) - { - cairo_move_to (cr, 0 + 0.5, i + 0.5); - cairo_line_to (cr, w + 0.5, i + 0.5); - } - - cairo_rectangle (cr, 0.5, 0.5, w - 0.5, h - 0.5); - cairo_stroke (cr); -} - -static gboolean -field_draw_callback (GtkWidget * widget, cairo_t *cr) -{ - guint i, j, idx; - GdkRGBA cursorColor; - - for (i = 0; i < vfieldsize; i++) { - for (j = 0; j < hfieldsize; j++) { - int phase, color; - - idx = j + i * hfieldsize; - - cairo_rectangle (cr, j * boxsize, i * boxsize, boxsize, boxsize); - - if (field[idx].color != 0) { - phase = field[idx].phase; - color = field[idx].color - 1; - - phase = ABS (ABS (3 - phase) - 3); - - cairo_set_source_surface (cr, ball_surface, - (1.0 * j - phase) * boxsize, - (1.0 * i - color) * boxsize); - } else { - cairo_set_source_surface (cr, blank_surface, 1.0 * j * boxsize, 1.0 * i * boxsize); - } - - cairo_fill (cr); - } - } - - /* Cursor */ - if (show_cursor) { - if (((backgnd.color.red + backgnd.color.green + backgnd.color.blue) / 3) > 0.5) - gdk_rgba_parse (&cursorColor, "#000000"); - else - gdk_rgba_parse (&cursorColor, "#FFFFFF"); - - gdk_cairo_set_source_rgba (cr, &cursorColor); - cairo_set_line_width (cr, 1.0); - cairo_rectangle (cr, - cursor_x * boxsize + 1.5, cursor_y * boxsize + 1.5, - boxsize - 2.5, boxsize - 2.5); - cairo_stroke (cr); - } - - draw_grid (cr); - - return FALSE; -} - -static int -addscore (int num) -{ - gchar string[20]; - int i = 0; - int retval; - - while ((sctab[i].num != num) && (sctab[i].num != 0)) - i++; - if (sctab[i].num == 0) { - num-=5; - retval = sctab[num%5].score + 72 * (num/5) + (num-5)*24 ; - } else - retval = sctab[i].score; - - score += retval; - g_snprintf (string, 19, "%d", score); - gtk_label_set_text (GTK_LABEL (scorelabel), string); - - return retval; -} - - -static void -tag_list (int *list, int len) -{ - int i; - - for (i = 0; i < len; i++) - field[list[i]].tag = 1; -} - -static int -find_lines (int num) -{ - int count = 1; - int subcount = 0; - int x = num % hfieldsize; - int y = num / hfieldsize; - int list[hfieldsize]; - - /* Horizontal */ - - x++; - while ((x <= hfieldsize - 1) - && (field[x + y * hfieldsize].color == field[num].color)) { - list[subcount] = x + y * hfieldsize; - subcount++; - x++; - } - x = num % hfieldsize - 1; - while ((x >= 0) && (field[x + y * hfieldsize].color == field[num].color)) { - list[subcount] = x + y * hfieldsize; - subcount++; - x--; - } - if (subcount >= 4) { - field[num].tag = 1; - tag_list (list, subcount); - count += subcount; - } - subcount = 0; - - /* Vertical */ - - x = num % hfieldsize; - y++; - while ((y <= vfieldsize - 1) - && (field[x + y * hfieldsize].color == field[num].color)) { - list[subcount] = x + y * hfieldsize; - subcount++; - y++; - } - y = num / hfieldsize - 1; - while ((y >= 0) && (field[x + y * hfieldsize].color == field[num].color)) { - list[subcount] = x + y * hfieldsize; - subcount++; - y--; - } - if (subcount >= 4) { - field[num].tag = 1; - tag_list (list, subcount); - count += subcount; - } - subcount = 0; - - /* Diagonal ++ */ - - x = num % hfieldsize + 1; - y = num / hfieldsize + 1; - while ((y <= vfieldsize - 1) && (x <= hfieldsize - 1) - && (field[x + y * hfieldsize].color == field[num].color)) { - list[subcount] = x + y * hfieldsize; - subcount++; - y++; - x++; - } - x = num % hfieldsize - 1; - y = num / hfieldsize - 1; - while ((y >= 0) && (x >= 0) - && (field[x + y * hfieldsize].color == field[num].color)) { - list[subcount] = x + y * hfieldsize; - subcount++; - y--; - x--; - } - if (subcount >= 4) { - field[num].tag = 1; - tag_list (list, subcount); - count += subcount; - } - subcount = 0; - - /* Diagonal +- */ - - x = num % hfieldsize + 1; - y = num / hfieldsize - 1; - while ((y >= 0) && (x <= hfieldsize - 1) - && (field[x + y * hfieldsize].color == field[num].color)) { - list[subcount] = x + y * hfieldsize; - subcount++; - y--; - x++; - } - x = num % hfieldsize - 1; - y = num / hfieldsize + 1; - while ((y <= vfieldsize - 1) && (x >= 0) - && (field[x + y * hfieldsize].color == field[num].color)) { - list[subcount] = x + y * hfieldsize; - subcount++; - y++; - x--; - } - if (subcount >= 4) { - field[num].tag = 1; - tag_list (list, subcount); - count += subcount; - } - - return count; -} - -static void -clear_tags (void) -{ - int i; - - for (i = 0; i < hfieldsize * vfieldsize; i++) - field[i].tag = 0; -} - -static int -kill_tags (GtkWidget * widget) -{ - int i, j; - - j = 0; - for (i = 0; i < hfieldsize * vfieldsize; i++) { - if (field[i].tag == 1) { - j++; - field[i].color = 0; - field[i].phase = 0; - field[i].active = 0; - draw_box (widget, i % hfieldsize, i / hfieldsize); - } - } - - return j; -} - -static gboolean -check_goal (GtkWidget * w, int target) -{ - gboolean lines_cleared; - int count; - - clear_tags (); - - count = find_lines (target); - lines_cleared = count >= 5; - - if (lines_cleared) { - kill_tags (w); - addscore (count); - } - reset_pathsearch (); - - return lines_cleared; -} - -gint -animate (gpointer gp) -{ - GtkWidget *widget = GTK_WIDGET (gp); - int x, y; - int newactive = 0; - - x = active % hfieldsize; - y = active / hfieldsize; - - if (active == -1) { - animate_id = 0; - return FALSE; - } - - if (inmove != 0) { - if ((x > 0) - && (field[active - 1].pathsearch == field[active].pathsearch + 1)) - newactive = active - 1; - else if ((x < hfieldsize - 1) - && (field[active + 1].pathsearch == - field[active].pathsearch + 1)) - newactive = active + 1; - else if ((y > 0) - && (field[active - hfieldsize].pathsearch == - field[active].pathsearch + 1)) - newactive = active - hfieldsize; - else if ((y < vfieldsize - 1) - && (field[active + hfieldsize].pathsearch == - field[active].pathsearch + 1)) - newactive = active + hfieldsize; - else { - set_inmove (0); - } - draw_box (widget, x, y); - x = newactive % hfieldsize; - y = newactive / hfieldsize; - field[newactive].phase = field[active].phase; - field[newactive].color = field[active].color; - field[active].phase = 0; - field[active].color = 0; - field[active].active = 0; - if (newactive == target) { - target = -1; - set_inmove (0); - active = -1; - field[newactive].phase = 0; - field[newactive].active = 0; - draw_box (widget, x, y); - reset_pathsearch (); - if (!check_goal (widget, newactive)) { - gboolean fullline; - int balls[npieces]; - int spaces; - - clear_tags (); - fullline = FALSE; - spaces = spaces_left (); - if (spaces > npieces) - spaces = npieces; - for (x = 0; x < spaces; x++) { - int tmp = init_new_balls (1, x); - draw_box (widget, tmp % hfieldsize, tmp / hfieldsize); - balls[x] = tmp; - fullline = (find_lines (balls[x]) >= 5) || fullline; - } - if (fullline) - addscore (kill_tags (widget)); - if (check_gameover () == -1) - return FALSE; - init_preview (); - draw_preview (); - } - return TRUE; - } - field[newactive].active = 1; - active = newactive; - } - - field[active].phase++; - if (field[active].phase >= 4) - field[active].phase = 0; - draw_box (widget, x, y); - - return TRUE; -} - -void -game_top_ten_callback (GSimpleAction *action, - GVariant *parameter, - gpointer user_data) -{ - show_scores (0); -} - -void -game_about_callback (GSimpleAction *action, - GVariant *parameter, - gpointer user_data) -{ - const gchar *authors[] = { "Robert Szokovacs ", - "Szabolcs B\xc3\xa1n ", - NULL - }; - - const gchar *documenters[] = { "Tiffany Antopolski", - "Lanka Rathnayaka", - NULL - }; - - gtk_show_about_dialog (GTK_WINDOW (app), - "program-name", _("Five or More"), - "version", VERSION, - "comments", _("GNOME port of the once-popular Color Lines game"), - "copyright", - "Copyright © 1997–2008 Free Software Foundation, Inc.\n Copyright © 2013–2014 Michael Catanzaro", - "license-type", GTK_LICENSE_GPL_2_0, - "authors", authors, - "documenters", documenters, - "translator-credits", _("translator-credits"), - "logo-icon-name", "five-or-more", - "website", "https://wiki.gnome.org/Apps/Five%20or%20more", - NULL); -} - -static void -set_backgnd_color (const gchar * str) -{ - if (!str) - str = g_strdup ("#000000"); - - if (str != backgnd.name) { - g_free (backgnd.name); - backgnd.name = g_strdup (str); - } - - if (!gdk_rgba_parse (&backgnd.color, backgnd.name)) { - gdk_rgba_parse (&backgnd.color, "#000000"); - } -} - -static void -set_sizes (gint size) -{ - hfieldsize = field_sizes[size][0]; - vfieldsize = field_sizes[size][1]; - ncolors = field_sizes[size][2]; - npieces = field_sizes[size][3]; - game_size = size; - games_scores_set_category (highscores, scorecats[size - 1].key); - - g_settings_set_int (settings, KEY_SIZE, size); - - if (gridframe) - games_grid_frame_set (GAMES_GRID_FRAME (gridframe), - hfieldsize, vfieldsize); -} - -static void -load_theme (void) -{ - if (ball_preimage) - g_object_unref (ball_preimage); - ball_preimage = load_image (ball_filename); - - refresh_pixmaps (); - refresh_preview_surfaces (); -} - -static void -conf_value_changed_cb (GSettings *settings, gchar *key) -{ - if (strcmp (key, KEY_BACKGROUND_COLOR) == 0) { - gchar *color; - - color = g_settings_get_string (settings, KEY_BACKGROUND_COLOR); - if (color != NULL) { - set_backgnd_color (color); - g_free (color); - } - } else if (strcmp (key, KEY_BALL_THEME) == 0) { - gchar *theme_tmp = NULL; - - theme_tmp = g_settings_get_string (settings, KEY_BALL_THEME); - if (theme_tmp) { - if (strcmp (theme_tmp, ball_filename) != 0) { - g_free (ball_filename); - ball_filename = theme_tmp; - load_theme (); - refresh_screen (); - } else - g_free (theme_tmp); - } - /* FIXME apply in the prefs dialog GUI */ - } else if (strcmp (key, KEY_MOVE_TIMEOUT) == 0) { - gint timeout_tmp; - - timeout_tmp = g_settings_get_int (settings, KEY_MOVE_TIMEOUT); - timeout_tmp = CLAMP (timeout_tmp, 1, 1000); - if (timeout_tmp != move_timeout) - move_timeout = timeout_tmp; - - } else if (strcmp (key, KEY_SIZE) == 0) { - gint size_tmp; - size_tmp = g_settings_get_int (settings, KEY_SIZE); - - if (size_tmp != game_size) { - set_sizes (size_tmp); - reset_game (); - start_game (); - } - } -} - -static void -bg_color_callback (GtkWidget * widget, gpointer data) -{ - GdkRGBA c; - char str[64]; - - gtk_color_chooser_get_rgba (GTK_COLOR_CHOOSER (widget), &c); - - g_snprintf (str, sizeof (str), "#%04x%04x%04x", (int) (c.red * 65535 + 0.5), (int) (c.green * 65535 + 0.5), (int) (c.blue * 65535 + 0.5)); - - g_settings_set_string (settings, KEY_BACKGROUND_COLOR, str); - - load_theme (); - refresh_screen (); -} - -static void -size_callback (GtkWidget * widget, gpointer data) -{ - GtkWidget *size_radio, *content_area, *label; - - game_size = g_settings_get_int (settings, KEY_SIZE); - if (pref_dialog_done && game_size != GPOINTER_TO_INT (data) && !restart_game_dialog) { - GtkDialogFlags flags = GTK_DIALOG_DESTROY_WITH_PARENT; - - restart_game_dialog = gtk_message_dialog_new (GTK_WINDOW (pref_dialog), - GTK_MESSAGE_WARNING, - flags, - GTK_BUTTONS_NONE, - _("Are you sure you want to restart the game?")); - - gtk_dialog_add_buttons (GTK_DIALOG (restart_game_dialog), - _("_Cancel"), GTK_RESPONSE_CANCEL, - _("_Restart"), GTK_RESPONSE_OK, - NULL); - - gint result = gtk_dialog_run (GTK_DIALOG (restart_game_dialog)); - gtk_widget_destroy (restart_game_dialog); - - switch (result) { - case GTK_RESPONSE_OK: - g_settings_set_int (settings, KEY_SIZE, GPOINTER_TO_INT (data)); - break; - case GTK_RESPONSE_CANCEL: - switch (game_size) { - case SMALL: - size_radio = size_radio_s; - break; - case MEDIUM: - size_radio = size_radio_m; - break; - case LARGE: - size_radio = size_radio_l; - break; - } - gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (size_radio), TRUE); - } - - restart_game_dialog = NULL; - } -} - -static void -set_selection (GtkWidget * widget, char *data) -{ - const gchar *entry; - - entry = games_file_list_get_nth (theme_file_list, - gtk_combo_box_get_active (GTK_COMBO_BOX - (widget))); - g_settings_set_string (settings, KEY_BALL_THEME, entry); -} - -static GtkWidget * -fill_menu (void) -{ - gchar *pixmap_dir; - - if (theme_file_list) - g_object_unref (theme_file_list); - - pixmap_dir = g_build_filename (DATA_DIRECTORY, "themes", NULL); - theme_file_list = games_file_list_new_images (pixmap_dir, NULL); - g_free (pixmap_dir); - games_file_list_transform_basename (theme_file_list); - - return games_file_list_create_widget (theme_file_list, ball_filename, - GAMES_FILE_LIST_REMOVE_EXTENSION | - GAMES_FILE_LIST_REPLACE_UNDERSCORES); -} - -static void -set_fast_moves_callback (GtkWidget * widget, gpointer * data) -{ - gboolean is_on = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)); - gint timeout = is_on ? 10 : 100; - g_settings_set_int (settings, KEY_MOVE_TIMEOUT, timeout); -} - -void -pref_dialog_response (GtkDialog * dialog, gint response, gpointer data) -{ - gtk_widget_hide (GTK_WIDGET (dialog)); -} - -void -game_props_callback (GSimpleAction *action, - GVariant *parameter, - gpointer user_data) -{ - gchar *ui_path; - GError *error = NULL; - GtkWidget *omenu; - GtkWidget *grid; - GtkWidget *color_button; - GtkWidget *size_radio; - GtkWidget *fast_moves_checkbutton; - - if (!pref_dialog) { - ui_path = g_build_filename (DATA_DIRECTORY, "five-or-more-preferences.ui", NULL); - builder_preferences = gtk_builder_new (); - gtk_builder_add_from_file (builder_preferences, ui_path, &error); - g_free (ui_path); - - if (error) { - g_critical ("Unable to load the user interface file: %s", error->message); - g_error_free (error); - g_assert_not_reached (); - } - - pref_dialog = GTK_WIDGET (gtk_builder_get_object (builder_preferences, "preferences_dialog")); - g_signal_connect (pref_dialog, "response", - G_CALLBACK (pref_dialog_response), NULL); - g_signal_connect (pref_dialog, "delete-event", - G_CALLBACK (gtk_widget_hide), NULL); - - grid = GTK_WIDGET (gtk_builder_get_object (builder_preferences, "grid1")); - omenu = fill_menu (); - gtk_widget_show_all (GTK_WIDGET (omenu)); - gtk_grid_attach (GTK_GRID (grid), GTK_WIDGET (omenu), 1, 0, 1, 1); - g_signal_connect (omenu, "changed", - G_CALLBACK (set_selection), NULL); - - color_button = GTK_WIDGET (gtk_builder_get_object (builder_preferences, "colorbutton1")); - gtk_color_chooser_set_rgba (GTK_COLOR_CHOOSER (color_button), &backgnd.color); - g_signal_connect (color_button, "color-set", - G_CALLBACK (bg_color_callback), NULL); - - size_radio = GTK_WIDGET (gtk_builder_get_object (builder_preferences, "radiobutton_small")); - size_radio_s = size_radio; - g_signal_connect (size_radio, "clicked", - G_CALLBACK (size_callback), GINT_TO_POINTER (1)); - if (game_size == SMALL) - gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (size_radio), TRUE); - - size_radio = GTK_WIDGET (gtk_builder_get_object (builder_preferences, "radiobutton_medium")); - size_radio_m = size_radio; - g_signal_connect (size_radio, "clicked", - G_CALLBACK (size_callback), GINT_TO_POINTER (2)); - if (game_size == MEDIUM) - gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (size_radio), TRUE); - - size_radio = GTK_WIDGET (gtk_builder_get_object (builder_preferences, "radiobutton_large")); - size_radio_l = size_radio; - g_signal_connect (size_radio, "clicked", - G_CALLBACK (size_callback), GINT_TO_POINTER (3)); - if (game_size == LARGE) - gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (size_radio), TRUE); - - fast_moves_checkbutton = GTK_WIDGET (gtk_builder_get_object (builder_preferences, "checkbutton_fast_moves")); - if (move_timeout == 10) { - gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (fast_moves_checkbutton), TRUE); - } - - g_signal_connect (fast_moves_checkbutton, "clicked", - G_CALLBACK (set_fast_moves_callback), NULL); - - g_object_unref (G_OBJECT (builder_preferences)); - - pref_dialog_done = TRUE; - gtk_window_set_transient_for (GTK_WINDOW (pref_dialog), GTK_WINDOW (app)); - } - gtk_dialog_run (GTK_DIALOG (pref_dialog)); -} - -void -game_quit_callback (GSimpleAction *action, - GVariant *parameter, - gpointer user_data) -{ - g_application_quit (G_APPLICATION (user_data)); -} - -void -game_help_callback (GSimpleAction *action, - GVariant *parameter, - gpointer user_data) -{ - GError *error = NULL; - - gtk_show_uri (gtk_widget_get_screen (GTK_WIDGET (app)), "help:five-or-more", gtk_get_current_event_time (), &error); - if (error) - g_warning ("Failed to show help: %s", error->message); - g_clear_error (&error); -} - -static int -configure_event_callback (GtkWidget * widget, GdkEventConfigure * event) -{ - if (ball_surface) - cairo_surface_destroy(ball_surface); - - if (blank_surface) - cairo_surface_destroy (blank_surface); - - boxsize = (event->width - 1) / hfieldsize; - ball_surface = gdk_window_create_similar_surface (gtk_widget_get_window (draw_area), - CAIRO_CONTENT_COLOR_ALPHA, - boxsize * 4, boxsize * 7); - blank_surface = gdk_window_create_similar_surface (gtk_widget_get_window (draw_area), - CAIRO_CONTENT_COLOR_ALPHA, - boxsize, boxsize); - refresh_pixmaps (); - - refresh_screen (); - - return TRUE; -} - -static void -load_properties (void) -{ - gchar *buf; - - ball_filename = g_settings_get_string (settings, KEY_BALL_THEME); - - move_timeout = g_settings_get_int (settings, KEY_MOVE_TIMEOUT); - if (move_timeout <= 0) - move_timeout = 100; - - buf = g_settings_get_string (settings, KEY_BACKGROUND_COLOR); /* FIXMEchpe? */ - set_backgnd_color (buf); - g_free (buf); - - load_theme (); -} - -static void -init_config (void) -{ - g_signal_connect (settings, "changed", - G_CALLBACK (conf_value_changed_cb), NULL); - - game_size = g_settings_get_int (settings, KEY_SIZE); - if (game_size == UNSET) - game_size = DEFAULT_GAME_SIZE; - - game_size = CLAMP (game_size, SMALL, MAX_SIZE - 1); - set_sizes (game_size); -} - -static gboolean -window_size_allocate_cb (GtkWidget *widget, GdkRectangle *allocation) -{ - if (!window_is_maximized && !window_is_fullscreen) - gtk_window_get_size (GTK_WINDOW (widget), &window_width, &window_height); - - return FALSE; -} - -static gboolean -window_state_event_cb (GtkWidget *widget, GdkEventWindowState *event) -{ - if ((event->changed_mask & GDK_WINDOW_STATE_MAXIMIZED) != 0) - window_is_maximized = (event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) != 0; - if ((event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN) != 0) - window_is_fullscreen = (event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN) != 0; - return FALSE; -} - -static void -startup_cb (GApplication *application) -{ - gchar *ui_path; - GtkWidget *hbox; - GtkWidget *preview_hbox; - GtkWidget *new_game_button; - guint i; - GError *error = NULL; - - GActionEntry app_actions[] = { - { "new-game", game_new_callback }, - { "scores", game_top_ten_callback }, - { "preferences", game_props_callback }, - { "help", game_help_callback }, - { "about", game_about_callback }, - { "quit", game_quit_callback } - }; - - g_action_map_add_action_entries (G_ACTION_MAP (application), - app_actions, G_N_ELEMENTS (app_actions), - application); - - gtk_application_add_accelerator (GTK_APPLICATION (application), "N", "app.new-game", NULL); - - settings = g_settings_new ("org.gnome.five-or-more"); - - highscores = games_scores_new ("five-or-more", - scorecats, G_N_ELEMENTS (scorecats), - "board size", NULL, - 0 /* default category */, - GAMES_SCORES_STYLE_PLAIN_DESCENDING); - - init_config (); - - builder = gtk_builder_new (); - - ui_path = g_build_filename (DATA_DIRECTORY, "menu.ui", NULL); - gtk_builder_add_from_file (builder, ui_path, &error); - g_free (ui_path); - - if (error) { - g_critical ("Unable to load the application menu file: %s", error->message); - g_error_free (error); - } else { - gtk_application_set_app_menu (GTK_APPLICATION (application), - G_MENU_MODEL (gtk_builder_get_object (builder, "appmenu"))); - } - - ui_path = g_build_filename (DATA_DIRECTORY, "five-or-more.ui", NULL); - gtk_builder_add_from_file (builder, ui_path, &error); - g_free (ui_path); - - if (error) { - g_critical ("Unable to load the user interface file: %s", error->message); - g_error_free (error); - g_assert_not_reached (); - } - - app = GTK_WIDGET (gtk_builder_get_object (builder, "glines_window")); - gtk_window_set_icon_name (GTK_WINDOW (app), "five-or-more"); - g_signal_connect (GTK_WINDOW (app), "size-allocate", G_CALLBACK (window_size_allocate_cb), NULL); - g_signal_connect (GTK_WINDOW (app), "window-state-event", G_CALLBACK (window_state_event_cb), NULL); - gtk_window_set_default_size (GTK_WINDOW (app), g_settings_get_int (settings, "window-width"), g_settings_get_int (settings, "window-height")); - if (g_settings_get_boolean (settings, "window-is-maximized")) - gtk_window_maximize (GTK_WINDOW (app)); - gtk_container_set_border_width (GTK_CONTAINER (app), 20); - gtk_application_add_window (GTK_APPLICATION (application), GTK_WINDOW (app)); - - headerbar = GTK_WIDGET (gtk_builder_get_object (builder, "headerbar")); - - preview_hbox = GTK_WIDGET (gtk_builder_get_object (builder, "preview_hbox")); - - for (i = 0; i < MAXNPIECES; i++) { - preview_images[i] = GTK_IMAGE (gtk_image_new ()); - gtk_box_pack_start (GTK_BOX (preview_hbox), GTK_WIDGET (preview_images[i]), FALSE, FALSE, 0); - gtk_widget_realize (GTK_WIDGET (preview_images[i])); - } - - scorelabel = GTK_WIDGET (gtk_builder_get_object (builder, "scorelabel")); - - hbox = GTK_WIDGET (gtk_builder_get_object (builder, "hbox")); - draw_area = gtk_drawing_area_new (); - gtk_widget_set_size_request (draw_area, 300, 300); - g_signal_connect (draw_area, "button-press-event", - G_CALLBACK (button_press_event), NULL); - g_signal_connect (draw_area, "key-press-event", - G_CALLBACK (key_press_event), NULL); - g_signal_connect (draw_area, "configure-event", - G_CALLBACK (configure_event_callback), NULL); - g_signal_connect (draw_area, "draw", - G_CALLBACK (field_draw_callback), NULL); - gridframe = games_grid_frame_new (hfieldsize, vfieldsize); - games_grid_frame_set_padding (GAMES_GRID_FRAME (gridframe), 1, 1); - gtk_container_add (GTK_CONTAINER (gridframe), draw_area); - gtk_box_pack_start (GTK_BOX (hbox), gridframe, TRUE, TRUE, 0); - - gtk_widget_set_events (draw_area, - gtk_widget_get_events (draw_area) | - GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_KEY_PRESS_MASK); - - gtk_widget_set_can_focus (draw_area, TRUE); - gtk_widget_grab_focus (draw_area); - - new_game_button = GTK_WIDGET (gtk_builder_get_object (builder, "new_game_button")); - - load_properties (); - - gtk_builder_connect_signals (builder, NULL); - - g_object_unref (G_OBJECT (builder)); -} - -static void -activate_cb (GApplication *application) -{ - reset_game (); - gtk_widget_show_all (app); - start_game (); -} - -static void -shutdown_cb (GApplication *application) -{ - int i = 0; - - for (i = 0; i < G_N_ELEMENTS (preview_images); i++) - if (preview_pixbufs[i]) - g_object_unref (preview_pixbufs[i]); - - g_clear_object (&ball_preimage); - g_object_unref (highscores); - - g_settings_set_int (settings, "window-width", window_width); - g_settings_set_int (settings, "window-height", window_height); - g_settings_set_boolean (settings, "window-is-maximized", window_is_maximized); -} +#include "game-area.h" +#include "five-or-more-app.h" int main (int argc, char *argv[]) @@ -1706,8 +52,9 @@ main (int argc, char *argv[]) textdomain (GETTEXT_PACKAGE); games_scores_startup (); + GRand **rgen = get_rgen(); - rgen = g_rand_new (); + *rgen = g_rand_new (); context = g_option_context_new (NULL); g_option_context_set_translation_domain (context, GETTEXT_PACKAGE); @@ -1721,14 +68,13 @@ main (int argc, char *argv[]) exit (1); } - g_set_application_name (_("Five or More")); + g_set_application_name (_("Five Or More")); gtk_window_set_default_icon_name ("five-or-more"); application = gtk_application_new ("org.gnome.five-or-more", G_APPLICATION_FLAGS_NONE); - g_signal_connect (application, "startup", G_CALLBACK (startup_cb), NULL); - g_signal_connect (application, "activate", G_CALLBACK (activate_cb), NULL); - g_signal_connect (application, "shutdown", G_CALLBACK (shutdown_cb), NULL); + //defined in header + set_application_callbacks(application); status = g_application_run (G_APPLICATION (application), argc, argv); diff --git a/src/five-or-more.h b/src/five-or-more.h deleted file mode 100644 index bfdcc1b..0000000 --- a/src/five-or-more.h +++ /dev/null @@ -1,57 +0,0 @@ -#ifndef glines_h -#define glines_h - -#define STEPSIZE 4 - -typedef struct { - int num; - int score; -} scoretable; - -typedef struct { - int color; - int pathsearch; - int phase; - int active; - int tag; -} field_props; - -void draw_box (GtkWidget * widget, int x, int y); - -void draw_ball (GtkWidget * widget, int x, int y); - -int find_route (void); - -void set_inmove (int i); - -void init_preview (void); - -void draw_preview (void); - -int init_new_balls (int num, int prev); - -gint animate (gpointer gp); - - -void game_new_callback (GSimpleAction *action, - GVariant *parameter, - gpointer user_data); -void game_top_ten_callback (GSimpleAction *action, - GVariant *parameter, - gpointer user_data); -void game_quit_callback (GSimpleAction *action, - GVariant *parameter, - gpointer user_data); -void game_props_callback (GSimpleAction *action, - GVariant *parameter, - gpointer user_data); -void game_help_callback (GSimpleAction *action, - GVariant *parameter, - gpointer user_data); -void game_about_callback (GSimpleAction *action, - GVariant *parameter, - gpointer user_data); - -void pref_dialog_response (GtkDialog * dialog, gint response, gpointer data); - -#endif diff --git a/src/game-area.c b/src/game-area.c new file mode 100644 index 0000000..7a3043b --- /dev/null +++ b/src/game-area.c @@ -0,0 +1,1057 @@ +/* -*- mode:C; indent-tabs-mode:t; tab-width:8; c-basic-offset:8; -*- */ + +/* + * Color lines for GNOME + * Copyright © 1999 Free Software Foundation + * Authors: Robert Szokovacs + * Szabolcs Ban + * Karuna Grewal + * Copyright © 2007 Christian Persch + * + * This game 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 2, 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 . + */ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "games-gridframe.h" +#include "game-area.h" +#include "five-or-more-app.h" +#include "balls-preview.h" + +#define MAXFIELDSIZE 30 +#define KEY_SIZE "size" +static GRand *rgen; +static int inmove = 0; +static int active = -1; +static int target = -1; + +static int animate_id = 0; + +static gboolean show_cursor = FALSE; +static int cursor_x = MAXFIELDSIZE / 2; +static int cursor_y = MAXFIELDSIZE / 2; +static field_props field[MAXFIELDSIZE * MAXFIELDSIZE]; +static gint hfieldsize; +static gint vfieldsize; +static int boxsize; +static gint ncolors; + +static gint npieces; +/* The tile images with balls rendered on them. */ +static cairo_surface_t *ball_surface = NULL; +/* A cairo_surface_t of a blank tile. */ +static cairo_surface_t *blank_surface = NULL; +/* Pre-rendering image data prepared from file. */ +static GamesPreimage *ball_preimage = NULL; + +enum { + UNSET = 0, + SMALL = 1, + MEDIUM, + LARGE, + MAX_SIZE, +}; + +/* Keep these in sync with the enum above. */ +static const gint field_sizes[MAX_SIZE][4] = { + {-1, -1, -1, -1}, /* This is a dummy entry. */ + {7, 7, 5, 3}, /* SMALL */ + {9, 9, 7, 3}, /* MEDIUM */ + {20, 15, 7, 7} /* LARGE */ +}; + +static scoretable sctab[] = + { {5, 10}, {6, 12}, {7, 18}, {8, 28}, {9, 42}, {10, 82}, {11, 108}, {12, + 138}, + {13, 172}, {14, 210}, {0, 0} }; + +static gchar *warning_message = NULL; +static GtkWidget *draw_area; + +void +set_sizes (gint size) +{ + hfieldsize = field_sizes[size][0]; + vfieldsize = field_sizes[size][1]; + ncolors = field_sizes[size][2]; + npieces = field_sizes[size][3]; + gint *game_size = get_game_size(); + *game_size = size; + GamesScoresCategory *scorecats = get_scorecats(); + games_scores_set_category (get_highscores(), scorecats[size - 1].key); + + g_settings_set_int (*(get_settings()), KEY_SIZE, size); + GtkWidget *gridframe = get_gridframe(); + if (gridframe) + games_grid_frame_set (GAMES_GRID_FRAME (gridframe), + hfieldsize, vfieldsize); +} + +gint +get_hfieldsize() +{ + return hfieldsize; +} + +gint +get_vfieldsize() +{ + return vfieldsize; +} + +gint * +get_active_status() +{ + return &active; +} + +gint * +get_target_status() +{ + return ⌖ +} + +gint * +get_inmove_status() +{ + return &inmove; +} + +gint +get_ncolors() +{ + return ncolors; +} + +GRand ** +get_rgen() +{ + return &rgen; +} + +GamesPreimage * +get_ball_preimage() +{ + return ball_preimage; +} + +gint +get_npieces() +{ + return npieces; +} + +int +init_new_balls (int num, int prev) +{ + int i, j = -1; + gfloat num_boxes = hfieldsize * vfieldsize; + for (i = 0; i < num;) { + j = g_rand_int_range (rgen, 0, num_boxes); + if (field[j].color == 0) { + field[j].color = (prev == -1) ? + g_rand_int_range (rgen, 1, ncolors + 1) : get_preview()[prev]; + i++; + } + } + return j; +} + +void +reset_game (void) +{ + int i; + + for (i = 0; i < hfieldsize * vfieldsize; i++) { + field[i].color = 0; + field[i].phase = 0; + field[i].active = 0; + field[i].pathsearch = -1; + } + update_score(0); + init_preview (); + init_new_balls (npieces, -1); +} + +static void +fix_route (int num, int pos) +{ + int i; + + for (i = 0; i < hfieldsize * vfieldsize; i++) + if ((i != pos) && (field[i].pathsearch == num)) + field[i].pathsearch = -1; + if (num < 2) + return; + if ((pos / hfieldsize > 0) + && (field[pos - hfieldsize].pathsearch == num - 1)) + fix_route (num - 1, pos - hfieldsize); + if ((pos % hfieldsize > 0) && (field[pos - 1].pathsearch == num - 1)) + fix_route (num - 1, pos - 1); + if ((pos / hfieldsize < vfieldsize - 1) + && (field[pos + hfieldsize].pathsearch == num - 1)) + fix_route (num - 1, pos + hfieldsize); + if ((pos % hfieldsize < hfieldsize - 1) + && (field[pos + 1].pathsearch == num - 1)) + fix_route (num - 1, pos + 1); +} + +static int +route (int num) +{ + int i; + int flag = 0; + + if (field[target].pathsearch == num) + return 1; + for (i = 0; i < hfieldsize * vfieldsize; i++) { + if (field[i].pathsearch == num) { + flag = 1; + if ((i / hfieldsize > 0) && (field[i - hfieldsize].pathsearch == -1) + && (field[i - hfieldsize].color == 0)) + field[i - hfieldsize].pathsearch = num + 1; + if ((i / hfieldsize < vfieldsize - 1) + && (field[i + hfieldsize].pathsearch == -1) + && (field[i + hfieldsize].color == 0)) + field[i + hfieldsize].pathsearch = num + 1; + if ((i % hfieldsize > 0) && (field[i - 1].pathsearch == -1) + && (field[i - 1].color == 0)) + field[i - 1].pathsearch = num + 1; + if ((i % hfieldsize < hfieldsize - 1) && (field[i + 1].pathsearch == -1) + && (field[i + 1].color == 0)) { + field[i + 1].pathsearch = num + 1; + } + } + } + if (flag == 0) + return 0; + return 2; +} + +int +find_route (void) +{ + int i = 0; + int j = 0; + + field[active].pathsearch = i; + while ((j = route (i))) { + if (j == 1) { + fix_route (i, target); + return 1; + } + i++; + } + return 0; +} + + +static void +reset_pathsearch (void) +{ + int i; + + for (i = 0; i < hfieldsize * vfieldsize; i++) + field[i].pathsearch = -1; +} + +void +draw_box (GtkWidget * widget, int x, int y) +{ + gtk_widget_queue_draw_area (widget, x * boxsize, y * boxsize, + boxsize, boxsize); +} + + +static void +deactivate (GtkWidget * widget, int x, int y) +{ + field[active].active = 0; + field[active].phase = 0; + draw_box (widget, x, y); + active = -1; +} + +static int +spaces_left (void) +{ + int i, j; + + j = 0; + + for (i = 0; i < hfieldsize * vfieldsize; i++) { + if (field[i].color == 0) + j++; + } + + return j; +} + +static int +check_gameover (void) +{ + if (spaces_left () > 0) + return 1; + game_over (); + return -1; +} + + +static int +addscore (int num) +{ + gchar string[20]; + int i = 0; + int retval; + + while ((sctab[i].num != num) && (sctab[i].num != 0)) + i++; + if (sctab[i].num == 0) { + num-=5; + retval = sctab[num%5].score + 72 * (num/5) + (num-5)*24 ; + } else + retval = sctab[i].score; + update_score(retval); + + return retval; +} + + +static void +tag_list (int *list, int len) +{ + int i; + + for (i = 0; i < len; i++) + field[list[i]].tag = 1; +} + +static int +find_lines (int num) +{ + int count = 1; + int subcount = 0; + int x = num % hfieldsize; + int y = num / hfieldsize; + int list[hfieldsize]; + + /* Horizontal */ + + x++; + while ((x <= hfieldsize - 1) + && (field[x + y * hfieldsize].color == field[num].color)) { + list[subcount] = x + y * hfieldsize; + subcount++; + x++; + } + x = num % hfieldsize - 1; + while ((x >= 0) && (field[x + y * hfieldsize].color == field[num].color)) { + list[subcount] = x + y * hfieldsize; + subcount++; + x--; + } + if (subcount >= 4) { + field[num].tag = 1; + tag_list (list, subcount); + count += subcount; + } + subcount = 0; + + /* Vertical */ + + x = num % hfieldsize; + y++; + while ((y <= vfieldsize - 1) + && (field[x + y * hfieldsize].color == field[num].color)) { + list[subcount] = x + y * hfieldsize; + subcount++; + y++; + } + y = num / hfieldsize - 1; + while ((y >= 0) && (field[x + y * hfieldsize].color == field[num].color)) { + list[subcount] = x + y * hfieldsize; + subcount++; + y--; + } + if (subcount >= 4) { + field[num].tag = 1; + tag_list (list, subcount); + count += subcount; + } + subcount = 0; + + /* Diagonal ++ */ + + x = num % hfieldsize + 1; + y = num / hfieldsize + 1; + while ((y <= vfieldsize - 1) && (x <= hfieldsize - 1) + && (field[x + y * hfieldsize].color == field[num].color)) { + list[subcount] = x + y * hfieldsize; + subcount++; + y++; + x++; + } + x = num % hfieldsize - 1; + y = num / hfieldsize - 1; + while ((y >= 0) && (x >= 0) + && (field[x + y * hfieldsize].color == field[num].color)) { + list[subcount] = x + y * hfieldsize; + subcount++; + y--; + x--; + } + if (subcount >= 4) { + field[num].tag = 1; + tag_list (list, subcount); + count += subcount; + } + subcount = 0; + + /* Diagonal +- */ + + x = num % hfieldsize + 1; + y = num / hfieldsize - 1; + while ((y >= 0) && (x <= hfieldsize - 1) + && (field[x + y * hfieldsize].color == field[num].color)) { + list[subcount] = x + y * hfieldsize; + subcount++; + y--; + x++; + } + x = num % hfieldsize - 1; + y = num / hfieldsize + 1; + while ((y <= vfieldsize - 1) && (x >= 0) + && (field[x + y * hfieldsize].color == field[num].color)) { + list[subcount] = x + y * hfieldsize; + subcount++; + y++; + x--; + } + if (subcount >= 4) { + field[num].tag = 1; + tag_list (list, subcount); + count += subcount; + } + + return count; +} + + +static void +clear_tags (void) +{ + int i; + + for (i = 0; i < hfieldsize * vfieldsize; i++) + field[i].tag = 0; +} + +static int +kill_tags (GtkWidget * widget) +{ + int i, j; + + j = 0; + for (i = 0; i < hfieldsize * vfieldsize; i++) { + if (field[i].tag == 1) { + j++; + field[i].color = 0; + field[i].phase = 0; + field[i].active = 0; + draw_box (widget, i % hfieldsize, i / hfieldsize); + } + } + + return j; +} + +static gboolean +check_goal (GtkWidget * w, int target) +{ + gboolean lines_cleared; + int count; + + clear_tags (); + + count = find_lines (target); + lines_cleared = count >= 5; + + if (lines_cleared) { + kill_tags (w); + addscore (count); + } + reset_pathsearch (); + + return lines_cleared; +} + +gint +animate (gpointer gp) +{ + GtkWidget *widget = GTK_WIDGET (gp); + int x, y; + int newactive = 0; + + x = active % hfieldsize; + y = active / hfieldsize; + + if (active == -1) { + animate_id = 0; + return FALSE; + } + + if (inmove != 0) { + if ((x > 0) + && (field[active - 1].pathsearch == field[active].pathsearch + 1)) + newactive = active - 1; + else if ((x < hfieldsize - 1) + && (field[active + 1].pathsearch == + field[active].pathsearch + 1)) + newactive = active + 1; + else if ((y > 0) + && (field[active - hfieldsize].pathsearch == + field[active].pathsearch + 1)) + newactive = active - hfieldsize; + else if ((y < vfieldsize - 1) + && (field[active + hfieldsize].pathsearch == + field[active].pathsearch + 1)) + newactive = active + hfieldsize; + else { + set_inmove (0); + } + draw_box (widget, x, y); + x = newactive % hfieldsize; + y = newactive / hfieldsize; + field[newactive].phase = field[active].phase; + field[newactive].color = field[active].color; + field[active].phase = 0; + field[active].color = 0; + field[active].active = 0; + if (newactive == target) { + target = -1; + set_inmove (0); + active = -1; + field[newactive].phase = 0; + field[newactive].active = 0; + draw_box (widget, x, y); + reset_pathsearch (); + if (!check_goal (widget, newactive)) { + gboolean fullline; + int balls[npieces]; + int spaces; + + clear_tags (); + fullline = FALSE; + spaces = spaces_left (); + if (spaces > npieces) + spaces = npieces; + for (x = 0; x < spaces; x++) { + int tmp = init_new_balls (1, x); + draw_box (widget, tmp % hfieldsize, tmp / hfieldsize); + balls[x] = tmp; + fullline = (find_lines (balls[x]) >= 5) || fullline; + } + if (fullline) + addscore (kill_tags (widget)); + if (check_gameover () == -1) + return FALSE; + init_preview (); + draw_preview (); + } + return TRUE; + } + field[newactive].active = 1; + active = newactive; + } + + field[active].phase++; + if (field[active].phase >= 4) + field[active].phase = 0; + draw_box (widget, x, y); + + return TRUE; +} + +static void +start_animation (void) +{ + int ms; + + ms = (inmove ? get_move_timeout() : 100); + if (animate_id == 0) + animate_id = g_timeout_add (ms, animate, draw_area); +} + +void +set_inmove (int i) +{ + if (inmove != i) { + inmove = i; + if (animate_id) + g_source_remove (animate_id); + animate_id = 0; + start_animation (); + } +} + +static void +cell_clicked (GtkWidget * widget, int fx, int fy) +{ + int x, y; + + set_status_message (NULL); + if (field[fx + fy * hfieldsize].color == 0) { + /* Clicked on an empty field */ + + if ((active != -1) && (inmove != 1)) { + /* We have an active ball, and it's not + * already in move. Therefore we should begin + * the moving sequence */ + + target = fx + fy * hfieldsize; + if (find_route ()) { + /* We found a route to the new position */ + + set_inmove (1); + } else { + /* Can't move there! */ + set_status_message (_("You can’t move there!")); + reset_pathsearch (); + target = -1; + } + } + } else { + /* Clicked on a ball */ + + if ((fx + fy * hfieldsize) == active) { + /* It's active, so let's deactivate it! */ + + deactivate (widget, fx, fy); + } else { + /* It's not active, so let's activate it! */ + + if (active != -1) { + /* There is an other active ball, we should deactivate it first! */ + + x = active % hfieldsize; + y = active / hfieldsize; + deactivate (widget, x, y); + } + + active = fx + fy * hfieldsize; + field[active].active = 1; + start_animation(); + } + } + +} + +static gint +button_press_event (GtkWidget * widget, GdkEvent * event) +{ + int x, y; + int fx, fy; + + if (inmove) + return TRUE; + + /* Ignore the 2BUTTON and 3BUTTON events. */ + if (event->type != GDK_BUTTON_PRESS) + return TRUE; + + if (show_cursor) { + show_cursor = FALSE; + draw_box (draw_area, cursor_x, cursor_y);//put in header + } + + fx = event->button.x / boxsize; + fy = event->button.y / boxsize; + + /* If we click on the outer border pixels, the previous calculation + * will be wrong and we must correct. */ + fx = CLAMP (fx, 0, hfieldsize - 1); + fy = CLAMP (fy, 0, vfieldsize - 1); + + cursor_x = fx; + cursor_y = fy; + + cell_clicked (widget, fx, fy); + + return TRUE; +} + +static void +move_cursor (int dx, int dy) +{ + int old_x = cursor_x; + int old_y = cursor_y; + + if (!show_cursor) { + show_cursor = TRUE; + draw_box (draw_area, cursor_x, cursor_y); + } + + cursor_x = cursor_x + dx; + if (cursor_x < 0) + cursor_x = 0; + if (cursor_x >= hfieldsize) + cursor_x = hfieldsize - 1; + cursor_y = cursor_y + dy; + if (cursor_y < 0) + cursor_y = 0; + if (cursor_y >= vfieldsize) + cursor_y = vfieldsize - 1; + + if (cursor_x == old_x && cursor_y == old_y) + return; + + draw_box (draw_area, old_x, old_y); + draw_box (draw_area, cursor_x, cursor_y); +} + +static gboolean +key_press_event (GtkWidget * widget, GdkEventKey * event, void *d) +{ + guint key; + + key = event->keyval; + + switch (key) { + case GDK_KEY_Left: + case GDK_KEY_KP_Left: + move_cursor (-1, 0); + break; + case GDK_KEY_Right: + case GDK_KEY_KP_Right: + move_cursor (1, 0); + break; + case GDK_KEY_Up: + case GDK_KEY_KP_Up: + move_cursor (0, -1); + break; + case GDK_KEY_Down: + case GDK_KEY_KP_Down: + move_cursor (0, 1); + break; + case GDK_KEY_Home: + case GDK_KEY_KP_Home: + move_cursor (-999, 0); + break; + case GDK_KEY_End: + case GDK_KEY_KP_End: + move_cursor (999, 0); + break; + case GDK_KEY_Page_Up: + case GDK_KEY_KP_Page_Up: + move_cursor (0, -999); + break; + case GDK_KEY_Page_Down: + case GDK_KEY_KP_Page_Down: + move_cursor (0, 999); + break; + + case GDK_KEY_space: + case GDK_KEY_Return: + case GDK_KEY_KP_Enter: + if (show_cursor) + cell_clicked (widget, cursor_x, cursor_y); + break; + } + + return TRUE; +} + +static void +draw_grid (cairo_t *cr) +{ + GdkRGBA color; + guint w, h; + guint i; + w = gtk_widget_get_allocated_width(draw_area); + h = gtk_widget_get_allocated_height(draw_area); + + gdk_rgba_parse (&color, "#525F6C"); + gdk_cairo_set_source_rgba (cr, &color); + cairo_set_line_width (cr, 1.0); + + for (i = boxsize; i < w; i = i + boxsize) + { + cairo_move_to (cr, i + 0.5, 0 + 0.5); + cairo_line_to (cr, i + 0.5, h + 0.5); + } + + for (i = boxsize; i < h; i = i + boxsize) + { + cairo_move_to (cr, 0 + 0.5, i + 0.5); + cairo_line_to (cr, w + 0.5, i + 0.5); + } + + cairo_rectangle (cr, 0.5, 0.5, w - 0.5, h - 0.5); + cairo_stroke (cr); +} + +static GamesPreimage * +load_image (gchar * fname) +{ + GamesPreimage *preimage; + gchar *path; + GError *error = NULL; + + path = g_build_filename (DATA_DIRECTORY, "themes", fname, NULL); + if (!g_file_test (path, G_FILE_TEST_EXISTS)) { + warning_message = g_strdup_printf (_("Unable to locate file:\n%s\n\n" + "The default theme will be loaded instead."), + fname); + + path = g_build_filename (DATA_DIRECTORY, "themes", "balls.svg", NULL); + if (!g_file_test (path, G_FILE_TEST_EXISTS)) { + g_free (warning_message); + warning_message = g_strdup_printf (_("Unable to locate file:\n%s\n\n" + "Please check that Five or More is installed correctly."), + fname); + } + } + + preimage = games_preimage_new_from_file (path, &error); + g_free (path); + + if (error) { + warning_message = g_strdup (error->message); + g_error_free (error); + } + + return preimage; +} + +static void +show_image_warning (gchar * message) +{ + GtkWidget *dialog; + GtkWidget *button; + + dialog = gtk_message_dialog_new (NULL, + GTK_DIALOG_MODAL, + GTK_MESSAGE_WARNING, + GTK_BUTTONS_CLOSE, + "%s",_("Could not load theme")); + + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), + "%s", message); + + button = gtk_dialog_add_button (GTK_DIALOG (dialog), + _("Preferences"), GTK_RESPONSE_ACCEPT); + + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT); + + g_signal_connect (button, "clicked", G_CALLBACK (game_props_callback), + NULL); + + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); +} + +void +refresh_pixmaps (void) +{ + cairo_t *cr, *cr_blank; + GdkPixbuf *ball_pixbuf = NULL; + background backgnd = get_backgnd(); + /* Since we get called both by configure and after loading an image. + * it is possible the pixmaps aren't initialised. If they aren't + * we don't do anything. */ + if (!ball_surface) + return; + + if (!boxsize) + return; + + if (ball_preimage) { + ball_pixbuf = games_preimage_render (ball_preimage, 4 * boxsize, + 7 * boxsize); + + /* Handle rendering problems. */ + if (!ball_pixbuf) { + g_object_unref (ball_preimage); + ball_preimage = NULL; + + if (!warning_message) { + warning_message = g_strdup ("The selected theme failed to render.\n\n" + "Please check that Five or More is installed correctly."); + } + } + } + + if (!ball_pixbuf) { + ball_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, + 4 * boxsize, 7 * boxsize); + gdk_pixbuf_fill (ball_pixbuf, 0x00000000); + } + + if (warning_message) + show_image_warning (warning_message); + g_free (warning_message); + warning_message = NULL; + + cr = cairo_create (ball_surface); + gdk_cairo_set_source_rgba (cr, &backgnd.color); + + cairo_rectangle (cr, 0, 0, boxsize * 4, boxsize * 7); + cairo_fill (cr); + + gdk_cairo_set_source_pixbuf (cr, ball_pixbuf, 0, 0); + cairo_mask (cr, cairo_get_source (cr)); + g_object_unref (ball_pixbuf); + + cairo_destroy (cr); + + cr_blank = cairo_create (blank_surface); + gdk_cairo_set_source_rgba (cr_blank, &backgnd.color); + + cairo_rectangle (cr_blank, 0, 0, boxsize, boxsize); + cairo_fill (cr_blank); + + cairo_destroy (cr_blank); +} + +static void +draw_all_balls (GtkWidget * widget) +{ + gdk_window_invalidate_rect (gtk_widget_get_window (widget), NULL, FALSE); +} + +void +refresh_screen (void) +{ + draw_all_balls (draw_area); + draw_preview (); +} + +static int +configure_event_callback (GtkWidget * widget, GdkEventConfigure * event) +{ + if (ball_surface) + cairo_surface_destroy(ball_surface); + + if (blank_surface) + cairo_surface_destroy (blank_surface); + + boxsize = (event->width - 1) / hfieldsize; + ball_surface = gdk_window_create_similar_surface (gtk_widget_get_window (draw_area), + CAIRO_CONTENT_COLOR_ALPHA, + boxsize * 4, boxsize * 7); + blank_surface = gdk_window_create_similar_surface (gtk_widget_get_window (draw_area), + CAIRO_CONTENT_COLOR_ALPHA, + boxsize, boxsize); + refresh_pixmaps (); + + refresh_screen (); + + return TRUE; +} + +void +load_theme (void) +{ + if (ball_preimage) + g_object_unref (ball_preimage); + char *ball_filename = get_ball_filename(); + ball_preimage = load_image (ball_filename); + + refresh_pixmaps (); + refresh_preview_surfaces (); +} + +static gboolean +field_draw_callback (GtkWidget * widget, cairo_t *cr) +{ + guint i, j, idx; + GdkRGBA cursorColor; + background backgnd = get_backgnd(); + + for (i = 0; i < vfieldsize; i++) { + for (j = 0; j < hfieldsize; j++) { + int phase, color; + + idx = j + i * hfieldsize; + + cairo_rectangle (cr, j * boxsize, i * boxsize, boxsize, boxsize); + + if (field[idx].color != 0) { + phase = field[idx].phase; + color = field[idx].color - 1; + + phase = ABS (ABS (3 - phase) - 3); + + cairo_set_source_surface (cr, ball_surface, + (1.0 * j - phase) * boxsize, + (1.0 * i - color) * boxsize); + } else { + cairo_set_source_surface (cr, blank_surface, 1.0 * j * boxsize, 1.0 * i * boxsize); + } + + cairo_fill (cr); + } + } + + /* Cursor */ + if (show_cursor) { + if (((backgnd.color.red + backgnd.color.green + backgnd.color.blue) / 3) > 0.5) + gdk_rgba_parse (&cursorColor, "#000000"); + else + gdk_rgba_parse (&cursorColor, "#FFFFFF"); + + gdk_cairo_set_source_rgba (cr, &cursorColor); + cairo_set_line_width (cr, 1.0); + cairo_rectangle (cr, + cursor_x * boxsize + 1.5, cursor_y * boxsize + 1.5, + boxsize - 2.5, boxsize - 2.5); + cairo_stroke (cr); + } + + draw_grid (cr); + + return FALSE; +} + +GtkWidget * +game_area_init (void) +{ + draw_area = gtk_drawing_area_new (); + gtk_widget_set_size_request (draw_area, 300, 300); + g_signal_connect (draw_area, "button-press-event", + G_CALLBACK (button_press_event), NULL); + g_signal_connect (draw_area, "key-press-event", + G_CALLBACK (key_press_event), NULL); + g_signal_connect (draw_area, "configure-event", + G_CALLBACK (configure_event_callback), NULL); + g_signal_connect (draw_area, "draw", + G_CALLBACK (field_draw_callback), NULL); + gtk_widget_set_events (draw_area, + gtk_widget_get_events (draw_area) | + GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_KEY_PRESS_MASK); + + gtk_widget_set_can_focus (draw_area, TRUE); + gtk_widget_grab_focus (draw_area); + return draw_area; +} \ No newline at end of file diff --git a/src/game-area.h b/src/game-area.h new file mode 100644 index 0000000..44d3303 --- /dev/null +++ b/src/game-area.h @@ -0,0 +1,55 @@ +/* + * Color lines for GNOME + * Copyright © 1999 Free Software Foundation + * Authors: Robert Szokovacs + * Szabolcs Ban + * Karuna Grewal + * Copyright © 2007 Christian Persch + * + * This game 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 2, 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 . + */ + +#ifndef GAME_AREA_H +#define GAME_AREA_H + +#include +#include "games-preimage.h" + +typedef struct { + int num; + int score; +} scoretable; + +typedef struct { + int color; + int pathsearch; + int phase; + int active; + int tag; +} field_props; + +GRand **get_rgen (); +GamesPreimage *get_ball_preimage (); +gint get_npieces (); +void set_inmove (int i); +void refresh_pixmaps (void); +void refresh_screen (void); +void load_theme (void); +GtkWidget * game_area_init (void); +gint get_ncolors (); +void set_sizes (gint size); +void reset_game (void); +gint get_hfieldsize (); +gint get_vfieldsize (); +#endif \ No newline at end of file diff --git a/src/meson.build b/src/meson.build index ab170fa..de29f83 100644 --- a/src/meson.build +++ b/src/meson.build @@ -3,6 +3,12 @@ five_or_more_c_sources = [ 'five-or-more.c', 'five-or-more.h', + 'game-area.h', + 'game-area.c', + 'five-or-more-app.c', + 'five-or-more-app.h', + 'balls-preview.c', + 'balls-preview.h', 'games-file-list.c', 'games-file-list.h', 'games-gridframe.c', -- GitLab