taquin-main.vala 17.1 KB
Newer Older
1 2
/* -*- Mode: vala; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 *
3
 * Copyright (C) 2014-2016 Arnaud Bonatti <arnaud.bonatti@gmail.com>
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
 *
 * This file is part of Taquin.
 *
 * Taquin is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Taquin 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 Taquin. If not, see <http://www.gnu.org/licenses/>.
 */

using Gtk;

Arnaud Bonatti's avatar
Arnaud Bonatti committed
23
private class Taquin : Gtk.Application, BaseApplication
24
{
25 26 27
    /* Translators: application name, as used in the window manager, the window title, the about dialog... */
    internal const string PROGRAM_NAME = _("Taquin");

28 29 30
    /* Settings */
    private GLib.Settings settings;
    private static int tmp_size = 0;
31
    private GameType? tmp_type = null;
32 33
    private bool size_changed = false;
    private bool theme_changed = false;
Arnaud Bonatti's avatar
Arnaud Bonatti committed
34
    private static bool? sound = null;
35 36

    /* Widgets */
37
    private GameWindow window;
38
    private TaquinView view;
39
    private NewGameScreen new_game_screen;
40 41 42 43 44

    /* The game being played */
    private Game? game = null;
    List<string> theme_dirlist;

45
    private const OptionEntry [] option_entries =
46
    {
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
        /* Translators: command-line option description, see 'gnome-taquin --help' */
        { "fifteen", 0, 0,  OptionArg.NONE, null,       N_("Play the classical 1880s’ 15-puzzle"), null},

        /* Translators: command-line option description, see 'gnome-taquin --help' */
        { "sixteen", 0, 0, OptionArg.NONE, null,        N_("Try this fun alternative 16-puzzle"), null},

        /* Translators: command-line option description, see 'gnome-taquin --help' */
        { "size", 's', 0, OptionArg.INT, ref tmp_size,  N_("Sets the puzzle edges’ size (3-5, 2-9 for debug)"), null},

        /* Translators: command-line option description, see 'gnome-taquin --help' */
        { "mute", 0, 0, OptionArg.NONE, null,           N_("Turn off the sound"), null},

        /* Translators: command-line option description, see 'gnome-taquin --help' */
        { "unmute", 0, 0, OptionArg.NONE, null,         N_("Turn on the sound"), null},

        /* Translators: command-line option description, see 'gnome-taquin --help' */
        { "version", 'v', 0, OptionArg.NONE, null,      N_("Print release version and exit"), null},

        /* Translators: command-line option description, see 'gnome-taquin --help' */
     /* { "no-gtk", 0, 0, OptionArg.NONE, null,         N_("Begins a console game"), null}, TODO */
67
        {}
68 69
    };

70
    private const GLib.ActionEntry [] action_entries =
71
    {
Arnaud Bonatti's avatar
Arnaud Bonatti committed
72
        /* TODO SimpleActionChangeStateCallback is deprecated...
73 74 75 76 77 78
        {"change-size", null, "s", null, null, change_size_cb},     http://valadoc.org/#!api=gio-2.0/GLib.SimpleActionChangeStateCallback
        {"change-theme", null, "s", null, null, change_theme_cb},   see comments about window.add_action (settings.create_action (…)) */

        {"change-size", change_size_cb, "s"},
        {"change-theme", change_theme_cb, "s"},

Arnaud Bonatti's avatar
Arnaud Bonatti committed
79
        {"set-use-night-mode", set_use_night_mode, "b"},
80
        {"quit", quit}
81 82
    };

Arnaud Bonatti's avatar
Arnaud Bonatti committed
83
    private static int main (string [] args)
84 85 86 87 88 89
    {
        Intl.setlocale (LocaleCategory.ALL, "");
        Intl.bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
        Intl.bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
        Intl.textdomain (GETTEXT_PACKAGE);

90
        Environment.set_application_name (PROGRAM_NAME);
91
        Window.set_default_icon_name ("org.gnome.Taquin");
92 93 94 95

        return new Taquin ().run (args);
    }

Arnaud Bonatti's avatar
Arnaud Bonatti committed
96
    private Taquin ()
97
    {
98
        Object (application_id: "org.gnome.Taquin", flags: ApplicationFlags.FLAGS_NONE);
99 100 101 102 103 104 105 106 107

        add_main_option_entries (option_entries);
    }

    protected override int handle_local_options (GLib.VariantDict options)
    {
        if (options.contains ("version"))
        {
            /* NOTE: Is not translated so can be easily parsed */
108
            stdout.printf ("%1$s %2$s\n", "gnome-taquin", VERSION);
109 110 111 112 113 114
            return Posix.EXIT_SUCCESS;
        }

        if (tmp_size != 0 && tmp_size < 2)
            tmp_size = 2;

Arnaud Bonatti's avatar
Arnaud Bonatti committed
115 116
        if (options.contains ("mute"))
            sound = false;
117 118
        else if (options.contains ("unmute"))
            sound = true;
Arnaud Bonatti's avatar
Arnaud Bonatti committed
119

120
        if (options.contains ("fifteen"))
121
            tmp_type = GameType.FIFTEEN;
122
        else if (options.contains ("sixteen"))
123
            tmp_type = GameType.SIXTEEN;
124 125 126 127 128 129 130 131

        return -1;
    }

    protected override void startup()
    {
        base.startup ();

132
        settings = new GLib.Settings ("org.gnome.Taquin");
133 134 135 136 137 138 139 140 141 142 143
        if ((sound != null) || (tmp_size > 1) || (tmp_type != null))
        {
            settings.delay ();
            if (sound != null)
                settings.set_boolean ("sound", (!) sound);
            if (tmp_size > 1)
                settings.set_int ("size", tmp_size);
            if (tmp_type != null)
                settings.set_string ("type", ((!) tmp_type).to_string ());     // TODO better?
            settings.apply ();
        }
144

145 146
        /* UI parts */
        view = new TaquinView ();
147
        new_game_screen = new NewGameScreen ();
148 149

        /* Window */
Arnaud Bonatti's avatar
Arnaud Bonatti committed
150
        init_night_mode ();
151
        window = new GameWindow ("/org/gnome/Taquin/ui/taquin.css",
152
                                 PROGRAM_NAME,
Arnaud Bonatti's avatar
Arnaud Bonatti committed
153 154 155 156 157 158
                                 _("About Taquin"),
                                 /* start now */ true,     // TODO add an option to go to new-game screen?
                                 GameWindowFlags.SHOW_START_BUTTON
                                 | GameWindowFlags.SHORTCUTS
                                 | GameWindowFlags.SHOW_HELP
                                 | GameWindowFlags.SHOW_UNDO,
159
                                 (Box) new_game_screen,
Arnaud Bonatti's avatar
Arnaud Bonatti committed
160 161
                                 view,
                                 night_light_monitor);
162 163
        window.play.connect (start_game);
        window.undo.connect (undo_cb);
Arnaud Bonatti's avatar
Arnaud Bonatti committed
164
        window.restart.connect (restart_cb);
165

Arnaud Bonatti's avatar
Arnaud Bonatti committed
166 167
        set_accels_for_action ("base.copy",             {        "<Primary>c"       });
        set_accels_for_action ("base.copy-alt",         { "<Shift><Primary>c"       });
168
        set_accels_for_action ("ui.new-game",           {        "<Primary>n"       });
Arnaud Bonatti's avatar
Arnaud Bonatti committed
169
        set_accels_for_action ("ui.start-or-restart",   { "<Shift><Primary>n"       });
Arnaud Bonatti's avatar
Arnaud Bonatti committed
170 171 172 173
        set_accels_for_action ("app.quit",              {        "<Primary>q",
                                                          "<Shift><Primary>q"       });
        set_accels_for_action ("base.paste",            {        "<Primary>v"       });
        set_accels_for_action ("base.paste-alt",        { "<Shift><Primary>v"       });
174
        set_accels_for_action ("ui.undo",               {        "<Primary>z"       });
Arnaud Bonatti's avatar
Arnaud Bonatti committed
175
     // set_accels_for_action ("ui.restart" // TODO
Arnaud Bonatti's avatar
Arnaud Bonatti committed
176
     // set_accels_for_action ("ui.redo",               { "<Shift><Primary>z"       });
Arnaud Bonatti's avatar
Arnaud Bonatti committed
177 178 179
        set_accels_for_action ("base.escape",           {                 "Escape"  });
        set_accels_for_action ("base.toggle-hamburger", {                 "F10",
                                                                          "Menu"    });
Arnaud Bonatti's avatar
Arnaud Bonatti committed
180 181 182 183 184 185 186
//        set_accels_for_action ("app.help",              {                 "F1"      });
//        set_accels_for_action ("base.about",            {          "<Shift>F1"      });
        set_accels_for_action ("win.show-help-overlay", {                 "F1", // TODO test: if showing Yelp fails, should fallback there
                                                                 "<Primary>F1",
                                                          "<Shift><Primary>F1",
                                                                 "<Primary>question",
                                                          "<Shift><Primary>question"});
187

188
        /* New-game screen signals */
189
        settings.changed ["size"].connect (() => {
190
            if (!size_changed)
Arnaud Bonatti's avatar
Arnaud Bonatti committed
191
                new_game_screen.update_size_button_label (settings.get_int ("size") /* 2 <= size <= 9 */);
192 193
            size_changed = false;
        });
Arnaud Bonatti's avatar
Arnaud Bonatti committed
194
        new_game_screen.update_size_button_label (settings.get_int ("size") /* 2 <= size <= 9 */);
195

196
        settings.changed ["theme"].connect (() => {
197 198 199 200 201 202
            if (!theme_changed)
                update_theme (settings.get_string ("theme"));
            theme_changed = false;
        });
        update_theme (settings.get_string ("theme"));

203
        add_action_entries (action_entries, this);
204
        add_action (settings.create_action ("sound"));
Arnaud Bonatti's avatar
Arnaud Bonatti committed
205
        add_action (settings.create_action ("type"));        // TODO window action?
206
        // TODO window.add_action (settings.create_action ("size"));        // Problem: cannot use this way for an integer from a menu; works for radiobuttons in Iagno
Arnaud Bonatti's avatar
Arnaud Bonatti committed
207
        // TODO window.add_action (settings.create_action ("theme"));
208

209
        add_window (window);
Arnaud Bonatti's avatar
Arnaud Bonatti committed
210
        start_game ();
211 212 213 214 215 216 217 218 219
    }

    protected override void activate ()
    {
        window.present ();
    }

    protected override void shutdown ()
    {
220
        window.destroy ();
221 222 223
        base.shutdown ();
    }

Arnaud Bonatti's avatar
Arnaud Bonatti committed
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240
    /*\
    * * Night mode
    \*/

    NightLightMonitor night_light_monitor;  // keep it here or it is unrefed

    private void init_night_mode ()
    {
        night_light_monitor = new NightLightMonitor ("/org/gnome/taquin/");
    }

    private void set_use_night_mode (SimpleAction action, Variant? gvariant)
        requires (gvariant != null)
    {
        night_light_monitor.set_use_night_mode (((!) gvariant).get_boolean ());
    }

241 242 243 244 245 246 247
    /*\
    * * Creating and starting game
    \*/

    private void start_game ()
    {
        if (game != null)
Arnaud Bonatti's avatar
Arnaud Bonatti committed
248
        {
249
            SignalHandler.disconnect_by_func ((!) game, null, this);
Arnaud Bonatti's avatar
Arnaud Bonatti committed
250 251
            SignalHandler.disconnect_by_func ((!) game, null, window);
        }
252

253
        GameType type = (GameType) settings.get_enum ("type");
Arnaud Bonatti's avatar
Arnaud Bonatti committed
254
        int8 size = (int8) settings.get_int ("size"); /* 2 <= size <= 9 */
255
        game = new Game (type, size);
256
        view.game = (!) game;
Arnaud Bonatti's avatar
Arnaud Bonatti committed
257
        window.move_done (0);
Arnaud Bonatti's avatar
Arnaud Bonatti committed
258
        move_done = false;
259

260
        string filename = "";
Arnaud Bonatti's avatar
Arnaud Bonatti committed
261
        List<weak string> dirlist = theme_dirlist.copy ();
262 263
        do
        {
264
            int random = Random.int_range (0, (int) dirlist.length());
265
            filename = dirlist.nth_data(random);
Arnaud Bonatti's avatar
Arnaud Bonatti committed
266
            unowned List<weak string> entry = dirlist.find_custom (filename, strcmp);
267 268 269
            dirlist.remove_link (entry);
        } while (filename[0] == '0' || (filename[0] != '1' && filename[0] != size.to_string ()[0] && dirlist.length () != 0));
        view.theme = Path.build_filename (DATA_DIRECTORY, "themes", settings.get_string ("theme"), filename);
270
        view.realize ();        // TODO does that help?
271

272
        ((!) game).complete.connect (game_complete_cb);
Arnaud Bonatti's avatar
Arnaud Bonatti committed
273
        ((!) game).bad_click.connect (bad_click_cb);
274
        ((!) game).move.connect (move_cb);
275 276 277
    }

    /*\
278
    * * Signals from window
279 280
    \*/

Arnaud Bonatti's avatar
Arnaud Bonatti committed
281 282 283 284 285
    private void restart_cb ()
        requires (game != null)
    {
        ((!) game).restart ();
        play_sound ("sliding-n");
Arnaud Bonatti's avatar
Arnaud Bonatti committed
286
        move_done = false;
Arnaud Bonatti's avatar
Arnaud Bonatti committed
287 288
    }

289
    private void undo_cb ()
290
        requires (game != null)
291
    {
292
        ((!) game).undo ();
Arnaud Bonatti's avatar
Arnaud Bonatti committed
293
        play_sound ("sliding-1");
294 295
    }

296 297 298 299
    /*\
    * * Signals from game
    \*/

Arnaud Bonatti's avatar
Arnaud Bonatti committed
300
    private void move_cb (bool x_axis, int8 number, int8 x_gap, int8 y_gap, uint moves_count, bool disable_animation)
301
    {
Arnaud Bonatti's avatar
Arnaud Bonatti committed
302
        window.move_done (moves_count);
303
        play_sound ("sliding-1");       // TODO sliding-n??
Arnaud Bonatti's avatar
Arnaud Bonatti committed
304
        move_done = true;
305
    }
306

307 308
    private void game_complete_cb ()
    {
309
        window.finish_game ();
Arnaud Bonatti's avatar
Arnaud Bonatti committed
310
        play_sound ("gameover");
311 312
    }

Arnaud Bonatti's avatar
Arnaud Bonatti committed
313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343
    private bool move_done = false;
    private void bad_click_cb (Game.BadClick reason, bool keyboard_call)
    {
        switch (reason)
        {
            case Game.BadClick.NOT_MOVING:
                /* Translators: in-window notification; on the 15-Puzzle game, if the user clicks a tile that cannot move */
                window.show_notification (_("You can’t move this tile!"));
                return;

            case Game.BadClick.USE_ARROWS:
                if (move_done)  // do nothing if a move has already been done, a bad click or key press happens; reset on next game
                    return;

                if (keyboard_call)
                    /* Translators: in-window notification; on the 16-Puzzle game, help for keyboard use, displayed if the user uses an unmeaningful keyboard key */
                    window.show_notification (_("Use Shift and an arrow to move tiles!"));

                else
                    /* Translators: in-window notification; on the 16-Puzzle game, if the user clicks on a tile of the board (the game is played using mouse with arrows around the board) */
                    window.show_notification (_("Click on the arrows to move tiles!"));

                return;

            case Game.BadClick.IS_OUTSIDE:  // TODO something?
            case Game.BadClick.EMPTY_TILE:  // TODO something?
            default:
                return;
        }
    }

344 345 346 347 348
    /*\
    * * Options of the start-screen
    \*/

    private void change_size_cb (SimpleAction action, Variant? variant)
349
        requires (variant != null)
350 351
    {
        size_changed = true;
352
        int size = int.parse (((!) variant).get_string ());
Arnaud Bonatti's avatar
Arnaud Bonatti committed
353
        new_game_screen.update_size_button_label (size /* 3 <= size <= 5 */);
354 355 356 357
        settings.set_int ("size", size);
    }

    private void change_theme_cb (SimpleAction action, Variant? variant)
358
        requires (variant != null)
359 360
    {
        theme_changed = true;
361
        string name = ((!) variant).get_string ();
362 363 364 365 366
        update_theme (name);
        settings.set_string ("theme", name);
    }
    private void update_theme (string theme)
    {
367
        new_game_screen.update_theme (theme);
368 369 370 371 372 373 374 375

        Dir dir;
        theme_dirlist = new List<string> ();
        try
        {
            dir = Dir.open (Path.build_filename (DATA_DIRECTORY, "themes", theme));
            while (true)
            {
Arnaud Bonatti's avatar
Arnaud Bonatti committed
376
                string? filename = dir.read_name ();
377 378
                if (filename == null)
                    break;
379
                theme_dirlist.append ((!) filename);
380 381 382 383 384 385 386
            }
        }
        catch (FileError e)
        {
            warning ("Failed to load images: %s", e.message);
        }
    }
Arnaud Bonatti's avatar
Arnaud Bonatti committed
387 388 389 390 391 392 393 394 395 396 397 398 399 400

    /*\
    * * Sound
    \*/

    private void play_sound (string name)
    {
        if (!settings.get_boolean ("sound"))
            return;

        CanberraGtk.play_for_widget (view, 0,
                                     Canberra.PROP_MEDIA_NAME, name,
                                     Canberra.PROP_MEDIA_FILENAME, Path.build_filename (SOUND_DIRECTORY, "%s.ogg".printf (name)));
    }
401 402 403 404 405 406 407 408 409 410 411 412 413 414 415

    /*\
    * * Copy action
    \*/

    internal void copy (string text)
    {
        Gdk.Display? display = Gdk.Display.get_default ();
        if (display == null)
            return;

        Gtk.Clipboard clipboard = Gtk.Clipboard.get_default ((!) display);
        clipboard.set_text (text, text.length);
    }

Arnaud Bonatti's avatar
Arnaud Bonatti committed
416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435
    /*\
    * * about dialog infos
    \*/

    internal void get_about_dialog_infos (out string [] artists,
                                          out string [] authors,
                                          out string    comments,
                                          out string    copyright,
                                          out string [] documenters,
                                          out string    logo_icon_name,
                                          out string    program_name,
                                          out string    translator_credits,
                                          out string    version,
                                          out string    website,
                                          out string    website_label)
    {
        /* Translators: about dialog text */
        comments = _("A classic 15-puzzle game");

        artists = {
436 437 438 439
            "Abelard (Wikimedia)",
            "Alvesgaspar (Wikimedia)",
            "Mueller-rech.muenchen (Wikimedia)",
            "Ruskis (Wikimedia)",
Arnaud Bonatti's avatar
Arnaud Bonatti committed
440
            "Toyah (Wikimedia)",
441
            /* Translators: about dialog text; in the Credits, text at the end of the "Artwork by" section */
Arnaud Bonatti's avatar
Arnaud Bonatti committed
442
            _("(see COPYING.themes for informations)")
443
        };
Arnaud Bonatti's avatar
Arnaud Bonatti committed
444 445 446 447 448
        authors = { "Arnaud Bonatti" };

        /* Translators: about dialog text */
        copyright = "Copyright \xc2\xa9 2014-2019 – Arnaud Bonatti";  // TODO translation; autogen, to not change each year?
        documenters = { "Arnaud Bonatti" };
449
        logo_icon_name = "org.gnome.Taquin";
Arnaud Bonatti's avatar
Arnaud Bonatti committed
450
        program_name = PROGRAM_NAME;
Arnaud Bonatti's avatar
Arnaud Bonatti committed
451 452 453 454 455 456 457 458 459

        /* Translators: about dialog text; this string should be replaced by a text crediting yourselves and your translation team, or should be left empty. Do not translate literally! */
        translator_credits = _("translator-credits");
        version = VERSION;

        website = "https://wiki.gnome.org/Apps/Taquin";
        /* Translators: about dialog text; label of the website link */
        website_label = _("Page on GNOME wiki");
    }
460
}