1/* -*- Mode: vala; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 * Gnome Nibbles: Gnome Worm Game
3 * Copyright (C) 2015 Iulian-Gabriel Radu <iulian.radu67@gmail.com>
4 * Copyright (C) 2020 Arnaud Bonatti <arnaud.bonatti@gmail.com>
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 */
19
20using Gtk;
21
22[GtkTemplate (ui = "/org/gnome/Nibbles/ui/controls.ui")]
23private class Controls : Box
24{
25    [GtkChild] private Box grids_box;
26    private Gee.LinkedList<ControlsGrid> grids = new Gee.LinkedList<ControlsGrid> ();
27
28    private Gdk.Pixbuf arrow_pixbuf;
29
30    internal void load_pixmaps (int tile_size)
31    {
32        arrow_pixbuf = NibblesView.load_pixmap_file ("arrow.svg", 5 * tile_size, 5 * tile_size);
33    }
34
35    internal void prepare (Gee.LinkedList<Worm> worms, Gee.HashMap<Worm, WormProperties> worm_props)
36    {
37        foreach (var grid in grids_box.get_children ())
38            grid.destroy ();
39
40        GenericSet<uint> duplicate_keys     = new GenericSet<uint> (direct_hash, direct_equal);
41        GenericSet<uint> encountered_keys   = new GenericSet<uint> (direct_hash, direct_equal);
42        foreach (var worm in worms)
43        {
44            if (worm.is_human)
45            {
46                WormProperties worm_prop = worm_props.@get (worm);
47
48                var grid = new ControlsGrid (worm.id, worm_prop, arrow_pixbuf);
49                grids_box.add (grid);
50                grids.add (grid);
51
52                check_for_duplicates (worm_prop.up,     ref encountered_keys, ref duplicate_keys);
53                check_for_duplicates (worm_prop.down,   ref encountered_keys, ref duplicate_keys);
54                check_for_duplicates (worm_prop.left,   ref encountered_keys, ref duplicate_keys);
55                check_for_duplicates (worm_prop.right,  ref encountered_keys, ref duplicate_keys);
56            }
57        }
58        foreach (ControlsGrid grid in grids)
59        {
60            grid.external_handler = grid.worm_props.notify.connect (() => {
61                    GenericSet<uint> _duplicate_keys    = new GenericSet<uint> (direct_hash, direct_equal);
62                    GenericSet<uint> _encountered_keys  = new GenericSet<uint> (direct_hash, direct_equal);
63                    foreach (var worm in worms)
64                    {
65                        if (worm.is_human)
66                        {
67                            WormProperties worm_prop = worm_props.@get (worm);
68
69                            check_for_duplicates (worm_prop.up,     ref _encountered_keys, ref _duplicate_keys);
70                            check_for_duplicates (worm_prop.down,   ref _encountered_keys, ref _duplicate_keys);
71                            check_for_duplicates (worm_prop.left,   ref _encountered_keys, ref _duplicate_keys);
72                            check_for_duplicates (worm_prop.right,  ref _encountered_keys, ref _duplicate_keys);
73                        }
74                    }
75                    foreach (ControlsGrid _grid in grids)
76                        _grid.mark_duplicated_keys (_duplicate_keys);
77                });
78            grid.mark_duplicated_keys (duplicate_keys);
79        }
80    }
81    private void check_for_duplicates (uint key, ref GenericSet<uint> encountered_keys, ref GenericSet<uint> duplicate_keys)
82    {
83        if (encountered_keys.contains (key))
84            duplicate_keys.add (key);
85        else
86            encountered_keys.add (key);
87    }
88
89    internal void clean ()
90    {
91        foreach (ControlsGrid grid in grids)
92        {
93            grid.worm_props.disconnect (grid.external_handler);
94            grid.disconnect_stuff ();
95        }
96        grids.clear ();
97    }
98}
99
100[GtkTemplate (ui = "/org/gnome/Nibbles/ui/controls-grid.ui")]
101private class ControlsGrid : Button
102{
103    [GtkChild] private Label name_label;
104    [GtkChild] private Image arrow_up;
105    [GtkChild] private Image arrow_down;
106    [GtkChild] private Image arrow_left;
107    [GtkChild] private Image arrow_right;
108    [GtkChild] private Label move_up_label;
109    [GtkChild] private Label move_down_label;
110    [GtkChild] private Label move_left_label;
111    [GtkChild] private Label move_right_label;
112
113    internal WormProperties worm_props;
114    internal ulong external_handler;
115    private ulong    up_handler;
116    private ulong  down_handler;
117    private ulong  left_handler;
118    private ulong right_handler;
119    private ulong color_handler;
120
121    internal ControlsGrid (int worm_id, WormProperties worm_props, Gdk.Pixbuf arrow)
122    {
123        this.worm_props = worm_props;
124
125        set_action_target ("i", worm_id + 1);
126
127        /* Translators: text displayed in a screen showing the keys used by the players; the %d is replaced by the number that identifies the player */
128        var player_id = _("Player %d").printf (worm_id + 1);
129        color_handler = worm_props.notify ["color"].connect (() => {
130                var color = Pango.Color ();
131                color.parse (NibblesView.colorval_name_untranslated (worm_props.color));
132                name_label.set_markup (@"<b><span font-family=\"Sans\" color=\"$(color.to_string ())\">$(player_id)</span></b>");
133            });
134        var color = Pango.Color ();
135        color.parse (NibblesView.colorval_name_untranslated (worm_props.color));
136        name_label.set_markup (@"<b><span font-family=\"Sans\" color=\"$(color.to_string ())\">$(player_id)</span></b>");
137
138        arrow_up.set_from_pixbuf    (arrow.rotate_simple (Gdk.PixbufRotation.NONE));
139        arrow_down.set_from_pixbuf  (arrow.rotate_simple (Gdk.PixbufRotation.UPSIDEDOWN));
140        arrow_left.set_from_pixbuf  (arrow.rotate_simple (Gdk.PixbufRotation.COUNTERCLOCKWISE));
141        arrow_right.set_from_pixbuf (arrow.rotate_simple (Gdk.PixbufRotation.CLOCKWISE));
142
143           up_handler = worm_props.notify ["up"].connect    (() => configure_label (worm_props.up,    ref move_up_label));
144         down_handler = worm_props.notify ["down"].connect  (() => configure_label (worm_props.down,  ref move_down_label));
145         left_handler = worm_props.notify ["left"].connect  (() => configure_label (worm_props.left,  ref move_left_label));
146        right_handler = worm_props.notify ["right"].connect (() => configure_label (worm_props.right, ref move_right_label));
147
148        configure_label (worm_props.up,    ref move_up_label);
149        configure_label (worm_props.down,  ref move_down_label);
150        configure_label (worm_props.left,  ref move_left_label);
151        configure_label (worm_props.right, ref move_right_label);
152    }
153
154    internal void mark_duplicated_keys (GenericSet<uint> duplicate_keys)
155    {
156        set_duplicate_class (worm_props.up    in duplicate_keys, ref move_up_label);
157        set_duplicate_class (worm_props.down  in duplicate_keys, ref move_down_label);
158        set_duplicate_class (worm_props.left  in duplicate_keys, ref move_left_label);
159        set_duplicate_class (worm_props.right in duplicate_keys, ref move_right_label);
160    }
161    private static void set_duplicate_class (bool new_value, ref Label label)
162    {
163        if (new_value)
164            label.get_style_context ().add_class ("duplicate");
165        else
166            label.get_style_context ().remove_class ("duplicate");
167    }
168
169    internal void disconnect_stuff ()
170    {
171        worm_props.disconnect (   up_handler);
172        worm_props.disconnect ( down_handler);
173        worm_props.disconnect ( left_handler);
174        worm_props.disconnect (right_handler);
175        worm_props.disconnect (color_handler);
176    }
177
178    private static void configure_label (uint key_value, ref Label label)
179    {
180        string? key_name = Gdk.keyval_name (key_value);
181        if (key_name == "Up")
182        {
183            label.get_style_context ().add_class ("arrow");
184            label.set_text ("↑");
185        }
186        else if (key_name == "Down")
187        {
188            label.get_style_context ().add_class ("arrow");
189            label.set_text ("↓");
190        }
191        else if (key_name == "Left")
192        {
193            label.get_style_context ().add_class ("arrow");
194            label.set_text ("←");
195        }
196        else if (key_name == "Right")
197        {
198            label.get_style_context ().add_class ("arrow");
199            label.set_text ("→");
200        }
201        else if (key_name == null || key_name == "")
202        {
203            label.get_style_context ().remove_class ("arrow");
204            label.set_text ("");
205        }
206        else
207        {
208            label.get_style_context ().remove_class ("arrow");
209            label.set_text (@"$(accelerator_get_label (key_value, 0))");
210        }
211    }
212}
213