[gnome-chess] Use composite template to build promotion type selector



commit 07a6086446c0f8b8678451957e564fd60df4a6be
Author: Michael Catanzaro <mcatanzaro gnome org>
Date:   Thu Dec 24 13:08:30 2020 -0600

    Use composite template to build promotion type selector
    
    This also fixes a bug that I introduced with my GTK 4 porting: the
    promotion type selector dialog would always display pieces in the color
    of the first player to promote a piece, but it should always change
    color to match the current player.
    
    Note that I declared all the GtkChild widgets as unowned due to
    vala#1121, which I discovered while working on this.

 data/chess.gresource.xml                |   1 +
 data/promotion-type-selector.ui         |  20 +-
 src/gnome-chess.vala                    | 187 ++++---------
 src/meson.build                         |   2 +
 src/preferences-dialog.vala             | 479 ++++++++++++++++++++++++++++++++
 src/promotion-type-selector-dialog.vala | 115 ++++++++
 6 files changed, 655 insertions(+), 149 deletions(-)
---
diff --git a/data/chess.gresource.xml b/data/chess.gresource.xml
index 0671d8c..42d9db1 100644
--- a/data/chess.gresource.xml
+++ b/data/chess.gresource.xml
@@ -3,6 +3,7 @@
   <gresource prefix="/org/gnome/Chess/ui">
     <file preprocess="xml-stripblanks">gnome-chess.ui</file>
     <file preprocess="xml-stripblanks">preferences.ui</file>
+    <file preprocess="xml-stripblanks">promotion-type-selector.ui</file>
   </gresource>
   <gresource prefix="/org/gnome/Chess/pieces">
     <file preprocess="xml-stripblanks" alias="fancy/blackBishop.svg">pieces/fancy/blackBishop.svg</file>
diff --git a/data/promotion-type-selector.ui b/data/promotion-type-selector.ui
index 393656c..ac95678 100644
--- a/data/promotion-type-selector.ui
+++ b/data/promotion-type-selector.ui
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <requires lib="gtk" version="4.0"/>
-  <object class="GtkDialog" id="dialog_promotion_type_selector">
+  <template class="PromotionTypeSelectorDialog" parent="GtkDialog">
     <property name="can-focus">0</property>
     <property name="title" translatable="yes">Select Promotion Type</property>
     <property name="use-header-bar">1</property>
@@ -13,7 +13,7 @@
         <property name="margin-top">6</property>
         <property name="margin-bottom">6</property>
         <child>
-          <object class="GtkButton" id="button_queen">
+          <object class="GtkButton">
             <property name="width-request">100</property>
             <property name="height-request">120</property>
             <property name="receives-default">1</property>
@@ -36,7 +36,7 @@
                   </object>
                 </child>
                 <child>
-                  <object class="GtkImage" id="image_queen">
+                  <object class="GtkImage" id="queen_image">
                     <property name="can-focus">0</property>
                     <layout>
                       <property name="column">0</property>
@@ -49,7 +49,7 @@
           </object>
         </child>
         <child>
-          <object class="GtkButton" id="button_knight">
+          <object class="GtkButton">
             <property name="width-request">100</property>
             <property name="height-request">120</property>
             <property name="receives-default">1</property>
@@ -72,7 +72,7 @@
                   </object>
                 </child>
                 <child>
-                  <object class="GtkImage" id="image_knight">
+                  <object class="GtkImage" id="knight_image">
                     <property name="can-focus">0</property>
                     <layout>
                       <property name="column">0</property>
@@ -85,7 +85,7 @@
           </object>
         </child>
         <child>
-          <object class="GtkButton" id="button_rook">
+          <object class="GtkButton">
             <property name="width-request">100</property>
             <property name="height-request">120</property>
             <property name="receives-default">1</property>
@@ -108,7 +108,7 @@
                   </object>
                 </child>
                 <child>
-                  <object class="GtkImage" id="image_rook">
+                  <object class="GtkImage" id="rook_image">
                     <property name="can-focus">0</property>
                     <layout>
                       <property name="column">0</property>
@@ -121,7 +121,7 @@
           </object>
         </child>
         <child>
-          <object class="GtkButton" id="button_bishop">
+          <object class="GtkButton">
             <property name="width-request">100</property>
             <property name="height-request">120</property>
             <property name="receives-default">1</property>
@@ -144,7 +144,7 @@
                   </object>
                 </child>
                 <child>
-                  <object class="GtkImage" id="image_bishop">
+                  <object class="GtkImage" id="bishop_image">
                     <property name="can-focus">0</property>
                     <layout>
                       <property name="column">0</property>
@@ -161,5 +161,5 @@
         </style>
       </object>
     </child>
-  </object>
+  </template>
 </interface>
diff --git a/src/gnome-chess.vala b/src/gnome-chess.vala
index 0824aa0..afdc03f 100644
--- a/src/gnome-chess.vala
+++ b/src/gnome-chess.vala
@@ -15,7 +15,7 @@ using Gtk;
 
 public class ChessApplication : Gtk.Application
 {
-    private enum LayoutMode {
+    public enum LayoutMode {
         NORMAL,
         NARROW
     }
@@ -62,7 +62,7 @@ public class ChessApplication : Gtk.Application
     private MessageDialog? claim_draw_dialog = null;
     private MessageDialog? resign_dialog = null;
     private AboutDialog? about_dialog = null;
-    private Dialog? promotion_type_selector_dialog = null;
+    private PromotionTypeSelectorDialog? promotion_type_selector_dialog = null;
     private ChessScene.PromotionTypeCompletionHandler? promotion_type_completion_handler = null;
 
     private PGNGame pgn_game;
@@ -306,143 +306,6 @@ Copyright © 2015–2016 Sahil Sareen""";
             set_layout_mode (LayoutMode.NORMAL);
     }
 
-    [CCode (cname = "queen_selected_cb", instance_pos = -1)]
-    public void queen_selected_cb (Button button)
-        requires (promotion_type_selector_dialog != null)
-    {
-        promotion_type_selector_dialog.response (PromotionTypeSelected.QUEEN);
-    }
-
-    [CCode (cname = "knight_selected_cb", instance_pos = -1)]
-    public void knight_selected_cb (Button button)
-        requires (promotion_type_selector_dialog != null)
-    {
-        promotion_type_selector_dialog.response (PromotionTypeSelected.KNIGHT);
-    }
-
-    [CCode (cname = "rook_selected_cb", instance_pos = -1)]
-    public void rook_selected_cb (Button button)
-        requires (promotion_type_selector_dialog != null)
-    {
-        promotion_type_selector_dialog.response (PromotionTypeSelected.KNIGHT);
-    }
-
-    [CCode (cname = "bishop_selected_cb", instance_pos = -1)]
-    public void bishop_selected_cb (Button button)
-        requires (promotion_type_selector_dialog != null)
-    {
-        promotion_type_selector_dialog.response (PromotionTypeSelected.BISHOP);
-    }
-
-    private void promotion_type_selector_response_cb (int response_id)
-        requires (promotion_type_completion_handler != null)
-    {
-        switch (response_id)
-        {
-        case PromotionTypeSelected.QUEEN:
-            promotion_type_completion_handler (PieceType.QUEEN);
-            break;
-        case PromotionTypeSelected.KNIGHT:
-            promotion_type_completion_handler (PieceType.KNIGHT);
-            break;
-        case PromotionTypeSelected.ROOK:
-            promotion_type_completion_handler (PieceType.ROOK);
-            break;
-        case PromotionTypeSelected.BISHOP:
-            promotion_type_completion_handler (PieceType.BISHOP);
-            break;
-        default:
-            promotion_type_completion_handler (null);
-            break;
-        }
-
-        promotion_type_selector_dialog.hide ();
-
-        promotion_type_completion_handler = null;
-    }
-
-    public void show_promotion_type_selector (owned ChessScene.PromotionTypeCompletionHandler handler)
-        requires (promotion_type_completion_handler == null)
-    {
-        if (promotion_type_selector_dialog == null)
-        {
-            Builder builder = new Builder ();
-            builder.set_current_object (this);
-            try
-            {
-                builder.add_from_resource ("/org/gnome/Chess/ui/promotion-type-selector.ui");
-            }
-            catch (Error e)
-            {
-                error ("Failed to load UI resource: %s", e.message);
-            }
-
-            promotion_type_selector_dialog = (Dialog) builder.get_object ("dialog_promotion_type_selector");
-            promotion_type_selector_dialog.transient_for = window;
-            promotion_type_selector_dialog.modal = true;
-
-            var button_box = (Box) builder.get_object ("button_box");
-            if (layout_mode == LayoutMode.NARROW)
-                button_box.orientation = Orientation.VERTICAL;
-
-            string color;
-            if (game.current_player.color == Color.WHITE)
-                color = "white";
-            else
-                color = "black";
-
-            var resource_path = Path.build_path ("/", "/org/gnome/Chess/pieces", scene.theme_name, 
"%sQueen.svg".printf (color));
-            set_piece_image ((Image) builder.get_object ("image_queen"), resource_path);
-
-            resource_path = Path.build_path ("/", "/org/gnome/Chess/pieces", scene.theme_name, 
"%sKnight.svg".printf (color));
-            set_piece_image ((Image) builder.get_object ("image_knight"), resource_path);
-
-            resource_path = Path.build_path ("/", "/org/gnome/Chess/pieces", scene.theme_name, 
"%sRook.svg".printf (color));
-            set_piece_image ((Image) builder.get_object ("image_rook"), resource_path);
-
-            resource_path = Path.build_path ("/", "/org/gnome/Chess/pieces", scene.theme_name, 
"%sBishop.svg".printf (color));
-            set_piece_image ((Image) builder.get_object ("image_bishop"), resource_path);
-
-            promotion_type_selector_dialog.response.connect (promotion_type_selector_response_cb);
-        }
-
-        promotion_type_selector_dialog.show ();
-
-        promotion_type_completion_handler = (type) => handler (type);
-    }
-
-    private void set_piece_image (Image image, string resource_path)
-    {
-        const int size = 48;
-
-        try
-        {
-            var stream = resources_open_stream (resource_path, ResourceLookupFlags.NONE);
-            var h = new Rsvg.Handle.from_stream_sync (stream, null, Rsvg.HandleFlags.FLAGS_NONE, null);
-
-            var s = new Cairo.ImageSurface (Cairo.Format.ARGB32, size, size);
-            var c = new Cairo.Context (s);
-            h.render_document (c, Rsvg.Rectangle () { width = size, height = size, x = 0, y = 0 });
-
-            var p = Gdk.pixbuf_get_from_surface (s, 0, 0, size, size);
-            image.set_from_pixbuf (p);
-
-            image.height_request = size;
-        }
-        catch (Error e)
-        {
-            warning ("Failed to load image %s: %s", resource_path, e.message);
-        }
-    }
-
-    enum PromotionTypeSelected
-    {
-        QUEEN,
-        KNIGHT,
-        ROOK,
-        BISHOP
-    }
-
     public void quit_game ()
     {
         if (save_duration_timeout != 0)
@@ -2372,6 +2235,52 @@ Copyright © 2015–2016 Sahil Sareen""";
         about_dialog.show ();
     }
 
+    private void promotion_type_selector_response_cb (int response_id)
+        requires (promotion_type_completion_handler != null)
+    {
+        switch (response_id)
+        {
+        case PromotionTypeSelectorDialog.SelectedType.QUEEN:
+            promotion_type_completion_handler (PieceType.QUEEN);
+            break;
+        case PromotionTypeSelectorDialog.SelectedType.KNIGHT:
+            promotion_type_completion_handler (PieceType.KNIGHT);
+            break;
+        case PromotionTypeSelectorDialog.SelectedType.ROOK:
+            promotion_type_completion_handler (PieceType.ROOK);
+            break;
+        case PromotionTypeSelectorDialog.SelectedType.BISHOP:
+            promotion_type_completion_handler (PieceType.BISHOP);
+            break;
+        default:
+            promotion_type_completion_handler (null);
+            break;
+        }
+
+        /* We cannot cache this dialog because it uses the piece color of the current player. */
+        promotion_type_selector_dialog.destroy ();
+        promotion_type_selector_dialog = null;
+
+        promotion_type_completion_handler = null;
+    }
+
+    private void show_promotion_type_selector (owned ChessScene.PromotionTypeCompletionHandler handler)
+        requires (promotion_type_completion_handler == null)
+    {
+        if (promotion_type_selector_dialog == null)
+        {
+            promotion_type_selector_dialog = new PromotionTypeSelectorDialog (window,
+                                                                              game.current_player.color,
+                                                                              scene.theme_name,
+                                                                              layout_mode);
+            promotion_type_selector_dialog.response.connect (promotion_type_selector_response_cb);
+        }
+
+        promotion_type_selector_dialog.show ();
+
+        promotion_type_completion_handler = (type) => handler (type);
+    }
+
     private void run_invalid_pgn_dialog ()
     {
         var invalid_pgn_dialog = new MessageDialog (window,
diff --git a/src/meson.build b/src/meson.build
index b2daee6..ab69ba9 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -4,10 +4,12 @@ chess_sources = [
   'chess-scene.vala',
   'chess-view.vala',
   'gnome-chess.vala',
+  'promotion-type-selector-dialog.vala',
 ]
 
 chess_vala_args = [
   '--target-glib=@0@'.format(min_glib_version),
+  '--gresources', resource_files,
 ]
 
 chess_c_args = [
diff --git a/src/preferences-dialog.vala b/src/preferences-dialog.vala
new file mode 100644
index 0000000..f197a42
--- /dev/null
+++ b/src/preferences-dialog.vala
@@ -0,0 +1,479 @@
+/* -*- Mode: vala; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * Copyright (C) 2010-2013 Robert Ancell
+ * Copyright (C) 2013-2020 Michael Catanzaro
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
+ * license.
+ */
+
+[GtkTemplate (ui = "/org/gnome/Chess/ui/preferences.ui")]
+public class PreferencesDialog : Gtk.Dialog
+{
+    private Settings settings;
+
+    [GtkChild]
+    private Gtk.CheckButton show_numbering_check;
+    [GtkChild]
+    private Gtk.CheckButton show_move_hints_check;
+    [GtkChild]
+    private Gtk.ComboBox side_combo;
+    [GtkChild]
+    private Gtk.ComboBox difficulty_combo;
+    [GtkChild]
+    private Gtk.ComboBox ai_combo;
+    [GtkChild]
+    private Gtk.ListStore ai_model;
+    [GtkChild]
+    private Gtk.Adjustment duration_adjustment;
+    [GtkChild]
+    private Gtk.Adjustment timer_increment_adjustment;
+    [GtkChild]
+    private Gtk.Box custom_duration_box;
+    [GtkChild]
+    private Gtk.Box timer_increment_box;
+    [GtkChild]
+    private Gtk.ComboBox custom_duration_units_combo;
+    [GtkChild]
+    private Gtk.Label timer_increment_label;
+    [GtkChild]
+    private Gtk.ComboBox timer_increment_units_combo;
+    [GtkChild]
+    private Gtk.ComboBox clock_type_combo;
+    [GtkChild]
+    private Gtk.ComboBox duration_combo;
+
+    public PreferencesDialog (Settings settings)
+    {
+        preferences_dialog.transient_for = window;
+        preferences_dialog.modal = true;
+
+        this.settings = settings;
+
+        settings.bind ("show-numbering", show_numbering_check,
+                       "active", SettingsBindFlags.DEFAULT);
+        settings.bind ("show-move-hints", show_move_hints_check,
+                       "active", SettingsBindFlags.DEFAULT);
+
+        side_combo.set_active (settings.get_enum ("play-as"));
+        set_combo (difficulty_combo, 1, settings.get_string ("difficulty"));
+
+        var opponent_name = settings.get_string ("opponent");
+        if (opponent_name == "human")
+            ai_combo.set_active (0);
+
+        foreach (var p in ai_profiles)
+        {
+            TreeIter iter;
+            ai_model.append (out iter);
+            ai_model.set (iter, 0, p.name, 1, p.name, -1);
+            if (p.name == opponent_name || (opponent_name == "" && ai_combo.active == -1))
+                ai_combo.set_active_iter (iter);
+        }
+
+        if (ai_combo.active == -1)
+        {
+            ai_combo.active = 0;
+            settings.set_string ("opponent", "human");
+        }
+
+        set_duration (settings.get_int ("duration"));
+
+        if (pgn_game.clock_type != null)
+            set_clock_type (ClockType.string_to_enum (pgn_game.clock_type));
+        else
+            set_clock_type ((int) ClockType.string_to_enum (settings.get_string ("clock-type")));
+
+        if (pgn_game.timer_increment != null)
+            set_timer_increment (int.parse (pgn_game.timer_increment));
+        else
+            set_timer_increment (settings.get_int ("timer-increment"));
+
+        var orientation_combo = (ComboBox) builder.get_object ("orientation_combo");
+        set_combo (orientation_combo, 1, settings.get_string ("board-side"));
+
+        var move_combo = (ComboBox) builder.get_object ("move_format_combo");
+        set_combo (move_combo, 1, settings.get_string ("move-format"));
+
+        var theme_combo = (ComboBox) builder.get_object ("piece_style_combo");
+        set_combo (theme_combo, 1, settings.get_string ("piece-theme"));
+
+        /* Human vs. human */
+        if (ai_combo.get_active () == 0)
+        {
+            side_combo.sensitive = false;
+            difficulty_combo.sensitive = false;
+        }
+    }
+
+    private void set_combo (ComboBox combo, int value_index, string value)
+    {
+        TreeIter iter;
+        var model = combo.model;
+        if (!model.get_iter_first (out iter))
+            return;
+        do
+        {
+            string v;
+            model.@get (iter, value_index, out v, -1);
+            if (v == value)
+            {
+                combo.set_active_iter (iter);
+                return;
+            }
+        } while (model.iter_next (ref iter));
+    }
+
+    private string? get_combo (ComboBox combo, int value_index)
+    {
+        string value;
+        TreeIter iter;
+        if (!combo.get_active_iter (out iter))
+            return null;
+        combo.model.@get (iter, value_index, out value, -1);
+        return value;
+    }
+
+    [CCode (cname = "side_combo_changed_cb", instance_pos = -1)]
+    public void side_combo_changed_cb (ComboBox combo)
+    {
+        TreeIter iter;
+        if (!combo.get_active_iter (out iter))
+            return;
+        int player;
+        combo.model.@get (iter, 1, out player, -1);
+
+        settings.set_enum ("play-as", player);
+    }
+
+    [CCode (cname = "opponent_combo_changed_cb", instance_pos = -1)]
+    public void opponent_combo_changed_cb (ComboBox combo)
+    {
+        TreeIter iter;
+        if (!combo.get_active_iter (out iter))
+            return;
+        string opponent;
+        combo.model.@get (iter, 1, out opponent, -1);
+        settings.set_string ("opponent", opponent);
+        bool vs_human = (combo.get_active () == 0);
+        side_combo.sensitive = !vs_human;
+        difficulty_combo.sensitive = !vs_human;
+    }
+
+    [CCode (cname = "difficulty_combo_changed_cb", instance_pos = -1)]
+    public void difficulty_combo_changed_cb (ComboBox combo)
+    {
+        TreeIter iter;
+        if (!combo.get_active_iter (out iter))
+            return;
+        string difficulty;
+        combo.model.@get (iter, 1, out difficulty, -1);
+        settings.set_string ("difficulty", difficulty);
+    }
+
+    private void set_clock_type (int clock_type)
+    {
+        var model = clock_type_combo.model;
+        TreeIter iter, active_iter_clock_type = {};
+
+        /* Find the largest units that can be used for this value */
+        if (model.get_iter_first (out iter))
+        {
+            do
+            {
+                int type;
+                model.@get (iter, 1, out type, -1);
+                if (type == clock_type)
+                {
+                    active_iter_clock_type = iter;
+                }
+            } while (model.iter_next (ref iter));
+        }
+
+        clock_type_combo.set_active_iter (active_iter_clock_type);
+        clock_type_changed_cb (clock_type_combo);
+    }
+
+    private void set_timer_increment (int timer_increment)
+    {
+        int timer_increment_multiplier = 1;
+
+        if (timer_increment >= 60)
+        {
+            timer_increment_adjustment.value = timer_increment / 60;
+            timer_increment_multiplier = 60;
+        } else
+            timer_increment_adjustment.value = timer_increment;
+
+        var model = timer_increment_units_combo.model;
+        TreeIter iter, reqd_iter = {};
+
+        /* Find the largest units that can be used for this value */
+        if (model.get_iter_first (out iter))
+        {
+            do
+            {
+                int multiplier;
+                model.@get (iter, 1, out multiplier, -1);
+                if (multiplier == timer_increment_multiplier)
+                {
+                    reqd_iter = iter;
+                }
+            } while (model.iter_next (ref iter));
+        }
+
+        timer_increment_units_combo.set_active_iter (reqd_iter);
+        timer_increment_units_changed_cb (timer_increment_units_combo);
+    }
+
+    private void set_duration (int duration, bool simplify = true)
+    {
+        var model = custom_duration_units_combo.model;
+        TreeIter iter, max_iter = {};
+
+        /* Find the largest units that can be used for this value */
+        int max_multiplier = 0;
+        if (model.get_iter_first (out iter))
+        {
+            do
+            {
+                int multiplier;
+                model.@get (iter, 1, out multiplier, -1);
+                if (multiplier > max_multiplier && duration % multiplier == 0)
+                {
+                    max_multiplier = multiplier;
+                    max_iter = iter;
+                }
+            } while (model.iter_next (ref iter));
+        }
+
+        /* Set the spin button to the value with the chosen units */
+        var value = 0;
+        if (max_multiplier > 0)
+        {
+            value = duration / max_multiplier;
+            duration_adjustment.value = value;
+            custom_duration_units_combo.set_active_iter (max_iter);
+        }
+
+        if (!simplify)
+            return;
+
+        model = duration_combo.model;
+        if (!model.get_iter_first (out iter))
+            return;
+        do
+        {
+            int v;
+            model.@get (iter, 1, out v, -1);
+            if (v == duration || v == -1)
+            {
+                duration_combo.set_active_iter (iter);
+                custom_duration_box.visible = v == -1;
+                return;
+            }
+        } while (model.iter_next (ref iter));
+    }
+
+    private int get_duration ()
+    {
+        TreeIter iter;
+        if (duration_combo.get_active_iter (out iter))
+        {
+            int duration;
+            duration_combo.model.@get (iter, 1, out duration, -1);
+            if (duration >= 0)
+                return duration;
+        }
+
+        var magnitude = (int) duration_adjustment.value;
+        int multiplier = 1;
+        if (custom_duration_units_combo.get_active_iter (out iter))
+            custom_duration_units_combo.model.@get (iter, 1, out multiplier, -1);
+
+        switch (multiplier)
+        {
+        case 60:
+            if (duration_adjustment.get_upper () != 600)
+                duration_adjustment.set_upper (600);
+            break;
+        case 3600:
+            if (duration_adjustment.get_upper () != 10)
+            {
+                duration_adjustment.set_upper (10);
+                if (duration_adjustment.value > 10)
+                {
+                    duration_adjustment.value = 10;
+                    magnitude = 10;
+                }
+            }
+            break;
+        default:
+            assert_not_reached ();
+        }
+
+        return magnitude * multiplier;
+    }
+
+    private bool save_duration_cb ()
+    {
+        settings.set_int ("duration", get_duration ());
+        Source.remove (save_duration_timeout);
+        save_duration_timeout = 0;
+        return Source.REMOVE;
+    }
+
+    [CCode (cname = "duration_changed_cb", instance_pos = -1)]
+    public void duration_changed_cb (Adjustment adjustment)
+    {
+        var model = (Gtk.ListStore) custom_duration_units_combo.model;
+        TreeIter iter;
+        /* Set the unit labels to the correct plural form */
+        if (model.get_iter_first (out iter))
+        {
+            do
+            {
+                int multiplier;
+                model.@get (iter, 1, out multiplier, -1);
+                switch (multiplier)
+                {
+                case 60:
+                    model.set (iter, 0, ngettext (/* Preferences Dialog: Combo box entry for a custom game 
timer set in minutes */
+                                                  "minute", "minutes", (ulong) adjustment.value), -1);
+                    break;
+                case 3600:
+                    model.set (iter, 0, ngettext (/* Preferences Dialog: Combo box entry for a custom game 
timer set in hours */
+                                                  "hour", "hours", (ulong) adjustment.value), -1);
+                    break;
+                default:
+                    assert_not_reached ();
+                }
+            } while (model.iter_next (ref iter));
+        }
+
+        save_duration ();
+    }
+
+    [CCode (cname = "duration_units_changed_cb", instance_pos = -1)]
+    public void duration_units_changed_cb (Widget widget)
+    {
+        save_duration ();
+    }
+
+    [CCode (cname = "timer_increment_units_changed_cb", instance_pos = -1)]
+    public void timer_increment_units_changed_cb (Widget widget)
+    {
+        var model = (Gtk.ListStore) timer_increment_units_combo.model;
+        TreeIter iter;
+        int multiplier = 0;
+        /* Set the unit labels to the correct plural form */
+        if (model.get_iter_first (out iter))
+        {
+            do
+            {
+                model.@get (iter, 1, out multiplier, -1);
+                switch (multiplier)
+                {
+                case 1:
+                    model.set (iter, 0, ngettext (/* Preferences Dialog: Combo box entry for a custom clock 
type set in seconds */
+                                                  "second", "seconds", (ulong) 
timer_increment_adjustment.value), -1);
+                    break;
+                case 60:
+                    model.set (iter, 0, ngettext (/* Preferences Dialog: Combo box entry for a custom clock 
type set in minutes */
+                                                  "minute", "minutes", (ulong) 
timer_increment_adjustment.value), -1);
+                    break;
+                default:
+                    assert_not_reached ();
+                }
+            } while (model.iter_next (ref iter));
+        }
+
+        if (timer_increment_units_combo.get_active_iter (out iter))
+            timer_increment_units_combo.model.@get (iter, 1, out multiplier, -1);
+
+        switch (multiplier)
+        {
+        case 1:
+            if (timer_increment_adjustment.get_upper () != 59)
+                timer_increment_adjustment.set_upper (59);
+            break;
+        case 60:
+            if (timer_increment_adjustment.get_upper () != 10)
+            {
+                timer_increment_adjustment.set_upper (10);
+                if (timer_increment_adjustment.value > 10)
+                    timer_increment_adjustment.value = 10;
+            }
+            break;
+        default:
+            assert_not_reached ();
+        }
+        settings.set_int ("timer-increment", (int) timer_increment_adjustment.value * multiplier);
+    }
+
+    private void save_duration ()
+    {
+        /* Delay writing the value as it this event will be generated a lot spinning through the value */
+        if (save_duration_timeout != 0)
+            Source.remove (save_duration_timeout);
+        save_duration_timeout = Timeout.add (100, save_duration_cb);
+    }
+
+    [CCode (cname = "duration_combo_changed_cb", instance_pos = -1)]
+    public void duration_combo_changed_cb (ComboBox combo)
+    {
+        TreeIter iter;
+        if (!combo.get_active_iter (out iter))
+            return;
+        int duration;
+        combo.model.@get (iter, 1, out duration, -1);
+        custom_duration_box.visible = duration < 0;
+        clock_type_combo.sensitive = duration != 0;
+
+        if (duration == 0)
+            set_clock_type (ClockType.SIMPLE);
+
+        if (duration >= 0)
+            set_duration (duration, false);
+        /* Default to one hour (30 minutes/player) when setting custom duration */
+        else if (get_duration () <= 0)
+            set_duration (60 * 60, false);
+
+        save_duration ();
+    }
+
+    [CCode (cname = "clock_type_changed_cb", instance_pos = -1)]
+    public void clock_type_changed_cb (ComboBox combo)
+    {
+        TreeIter iter;
+        if (!combo.get_active_iter (out iter))
+            return;
+        ClockType clock_type;
+        combo.model.@get (iter, 1, out clock_type, -1);
+
+        timer_increment_box.visible = clock_type > 0;
+        timer_increment_label.visible = clock_type > 0;
+        settings.set_string ("clock-type", clock_type.to_string ());
+    }
+
+    [CCode (cname = "piece_style_combo_changed_cb", instance_pos = -1)]
+    public void piece_style_combo_changed_cb (ComboBox combo)
+    {
+        settings.set_string ("piece-theme", get_combo (combo, 1));
+    }
+
+    [CCode (cname = "move_format_combo_changed_cb", instance_pos = -1)]
+    public void move_format_combo_changed_cb (ComboBox combo)
+    {
+        settings.set_string ("move-format", get_combo (combo, 1));
+    }
+
+    [CCode (cname = "orientation_combo_changed_cb", instance_pos = -1)]
+    public void orientation_combo_changed_cb (ComboBox combo)
+    {
+        settings.set_string ("board-side", get_combo (combo, 1));
+    }
+}
diff --git a/src/promotion-type-selector-dialog.vala b/src/promotion-type-selector-dialog.vala
new file mode 100644
index 0000000..73ea536
--- /dev/null
+++ b/src/promotion-type-selector-dialog.vala
@@ -0,0 +1,115 @@
+/* -*- Mode: vala; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * Copyright (C) 2010-2013 Robert Ancell
+ * Copyright (C) 2013-2020 Michael Catanzaro
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
+ * license.
+ */
+
+[GtkTemplate (ui = "/org/gnome/Chess/ui/promotion-type-selector.ui")]
+public class PromotionTypeSelectorDialog : Gtk.Dialog
+{
+    enum SelectedType
+    {
+        QUEEN,
+        KNIGHT,
+        ROOK,
+        BISHOP
+    }
+
+    [GtkChild]
+    private unowned Gtk.Box? button_box;
+    [GtkChild]
+    private unowned Gtk.Image queen_image;
+    [GtkChild]
+    private unowned Gtk.Image knight_image;
+    [GtkChild]
+    private unowned Gtk.Image rook_image;
+    [GtkChild]
+    private unowned Gtk.Image bishop_image;
+
+    public PromotionTypeSelectorDialog (Gtk.Window window, Color color, string theme, 
ChessApplication.LayoutMode layout_mode)
+    {
+        transient_for = window;
+        modal = true;
+
+        if (layout_mode == ChessApplication.LayoutMode.NARROW)
+            button_box.orientation = Gtk.Orientation.VERTICAL;
+
+        var color_string = color == Color.WHITE ? "white" : "black";
+        var resource_path = Path.build_path ("/", "/org/gnome/Chess/pieces", theme, "%sQueen.svg".printf 
(color_string));
+        set_piece_image (queen_image, resource_path);
+
+        resource_path = Path.build_path ("/", "/org/gnome/Chess/pieces", theme, "%sKnight.svg".printf 
(color_string));
+        set_piece_image (knight_image, resource_path);
+
+        resource_path = Path.build_path ("/", "/org/gnome/Chess/pieces", theme, "%sRook.svg".printf 
(color_string));
+        set_piece_image (rook_image, resource_path);
+
+        resource_path = Path.build_path ("/", "/org/gnome/Chess/pieces", theme, "%sBishop.svg".printf 
(color_string));
+        set_piece_image (bishop_image, resource_path);
+    }
+
+    public override void dispose ()
+    {
+        if (button_box != null)
+        {
+            button_box.unparent ();
+            button_box = null;
+        }
+
+        base.dispose ();
+    }
+
+    private void set_piece_image (Gtk.Image image, string resource_path)
+    {
+        const int size = 48;
+
+        try
+        {
+            var stream = resources_open_stream (resource_path, ResourceLookupFlags.NONE);
+            var h = new Rsvg.Handle.from_stream_sync (stream, null, Rsvg.HandleFlags.FLAGS_NONE, null);
+
+            var s = new Cairo.ImageSurface (Cairo.Format.ARGB32, size, size);
+            var c = new Cairo.Context (s);
+            h.render_document (c, Rsvg.Rectangle () { width = size, height = size, x = 0, y = 0 });
+
+            var p = Gdk.pixbuf_get_from_surface (s, 0, 0, size, size);
+            image.set_from_pixbuf (p);
+
+            image.height_request = size;
+        }
+        catch (Error e)
+        {
+            warning ("Failed to load piece image %s: %s", resource_path, e.message);
+        }
+    }
+
+    [GtkCallback]
+    private void queen_selected_cb (Gtk.Button button)
+    {
+        response (SelectedType.QUEEN);
+    }
+
+    [GtkCallback]
+    private void knight_selected_cb (Gtk.Button button)
+    {
+        response (SelectedType.KNIGHT);
+    }
+
+    [GtkCallback]
+    private void rook_selected_cb (Gtk.Button button)
+    {
+        response (SelectedType.ROOK);
+    }
+
+    [GtkCallback]
+    private void bishop_selected_cb (Gtk.Button button)
+    {
+        response (SelectedType.BISHOP);
+    }
+}


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]