[gnome-chess] Modernized preferences



commit f8aa4eab3d15fb1ed4a0af14c0b77cbc9cc542ae
Author: Nils Lück <nils luck outlook com>
Date:   Fri Aug 19 21:18:48 2022 +0000

    Modernized preferences

 data/chess-window.ui        |   2 +-
 data/chess.gresource.xml    |   3 +-
 data/new-game-window.ui     | 158 +++++++++++
 data/preferences-window.ui  |  84 ++++++
 data/preferences.ui         | 661 --------------------------------------------
 lib/chess-clock.vala        |  10 +-
 lib/chess-pgn.vala          |   2 +-
 src/gnome-chess.vala        |  80 +++---
 src/meson.build             |   4 +-
 src/new-game-window.vala    | 198 +++++++++++++
 src/preferences-dialog.vala | 485 --------------------------------
 src/preferences-window.vala |  60 ++++
 src/preferences.vala        | 607 ++++++++++++++++++++++++++++++++++++++++
 13 files changed, 1162 insertions(+), 1192 deletions(-)
---
diff --git a/data/chess-window.ui b/data/chess-window.ui
index 9ea3418..72b9522 100644
--- a/data/chess-window.ui
+++ b/data/chess-window.ui
@@ -4,7 +4,7 @@
   <menu id="app_menu">
     <section>
       <item>
-        <attribute name="label" translatable="yes">_New Game</attribute>
+        <attribute name="label" translatable="yes">_New Game…</attribute>
         <attribute name="action">app.new</attribute>
       </item>
       <item>
diff --git a/data/chess.gresource.xml b/data/chess.gresource.xml
index d027685..01c536b 100644
--- a/data/chess.gresource.xml
+++ b/data/chess.gresource.xml
@@ -2,7 +2,8 @@
 <gresources>
   <gresource prefix="/org/gnome/Chess/ui">
     <file preprocess="xml-stripblanks">chess-window.ui</file>
-    <file preprocess="xml-stripblanks">preferences.ui</file>
+    <file preprocess="xml-stripblanks">new-game-window.ui</file>
+    <file preprocess="xml-stripblanks">preferences-window.ui</file>
     <file preprocess="xml-stripblanks">promotion-type-selector.ui</file>
   </gresource>
   <gresource prefix="/org/gnome/Chess/pieces">
diff --git a/data/new-game-window.ui b/data/new-game-window.ui
new file mode 100644
index 0000000..eea93ae
--- /dev/null
+++ b/data/new-game-window.ui
@@ -0,0 +1,158 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk" version="4.0"/>
+  <template class="NewGameWindow" parent="AdwPreferencesWindow">
+    <property name="title" translatable="yes">New Game</property>
+    <property name="hide-on-close">true</property>
+    <property name="modal">true</property>
+    <property name="search-enabled">false</property>
+    <property name="default_width">400</property>
+    <property name="default_height">550</property>
+    <child>
+      <object class="GtkBox">
+        <property name="orientation">vertical</property>
+        <child>
+          <object class="AdwHeaderBar">
+            <property name="show-start-title-buttons">false</property>
+            <property name="show-end-title-buttons">false</property>
+            <child type="start">
+              <object class="GtkButton">
+                <property name="label" translatable="yes">_Cancel</property>
+                <property name="valign">center</property>
+                <property name="use-underline">true</property>
+                <signal name="clicked" handler="gtk_window_close" object="NewGameWindow" swapped="yes"/>
+              </object>
+            </child>
+            <child type="end">
+              <object class="GtkButton">
+                <property name="label" translatable="yes">_Start</property>
+                <property name="valign">center</property>
+                <property name="use-underline">true</property>
+                <signal name="clicked" handler="start_game_cb" />
+                <style>
+                  <class name="suggested-action"/>
+                </style>
+              </object>
+            </child>
+          </object>
+        </child>
+        <child>
+          <object class="AdwPreferencesPage">
+            <child>
+              <object class="AdwPreferencesGroup">
+                <child>
+                  <object class="AdwComboRow" id="opponent_combo">
+                    <property name="title" translatable="yes">_Opposing Player</property>
+                    <property name="use-underline">true</property>
+                  </object>
+                </child>
+                <child>
+                  <object class="AdwComboRow" id="play_as_combo">
+                    <property name="title" translatable="yes">_Play As</property>
+                    <property name="use-underline">true</property>
+                    <property name="model">
+                      <object class="AdwEnumListModel">
+                        <property name="enum-type">PlayAs</property>
+                      </object>
+                    </property>
+                    <property name="expression">
+                      <closure type="gchararray" function="play_as_display_name_cb" swapped="true" />
+                    </property>
+                  </object>
+                </child>
+                <child>
+                  <object class="AdwComboRow" id="difficulty_combo">
+                    <property name="title" translatable="yes">_Difficulty</property>
+                    <property name="use-underline">true</property>
+                    <property name="model">
+                      <object class="AdwEnumListModel">
+                        <property name="enum-type">Difficulty</property>
+                      </object>
+                    </property>
+                    <property name="expression">
+                      <closure type="gchararray" function="difficulty_display_name_cb" swapped="true" />
+                    </property>
+                  </object>
+                </child>
+              </object>
+            </child>
+            <child>
+              <object class="AdwPreferencesGroup">
+                <property name="title">Time Limit</property>
+                <child>
+                  <object class="AdwActionRow">
+                    <property name="title" translatable="yes">_Use Time Limit</property>
+                    <property name="use-underline">true</property>
+                    <property name="activatable_widget">time_limit_switch</property>
+                    <child>
+                      <object class="GtkSwitch" id="time_limit_switch">
+                        <property name="valign">center</property>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+                <child>
+                  <object class="AdwActionRow">
+                    <property name="title" translatable="yes">_Minutes Per Side</property>
+                    <property name="use-underline">true</property>
+                    <property name="sensitive" bind-source="time_limit_switch" bind-property="active" 
bind-flags="default|sync-create" />
+                    <property name="activatable-widget">duration_spin</property>
+                    <child>
+                      <object class="GtkSpinButton" id="duration_spin">
+                        <property name="valign">center</property>
+                        <property name="adjustment">
+                          <object class="GtkAdjustment">
+                            <property name="lower">1</property>
+                            <property name="step-increment">1</property>
+                            <property name="upper">600</property>
+                          </object>
+                        </property>
+                        <property name="value">5.0</property>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+                <child>
+                  <object class="AdwActionRow">
+                    <property name="title" translatable="yes">_Increment In Seconds</property>
+                    <property name="use-underline">true</property>
+                    <property name="sensitive" bind-source="time_limit_switch" bind-property="active" 
bind-flags="default|sync-create" />
+                    <property name="activatable-widget">increment_spin</property>
+                    <child>
+                      <object class="GtkSpinButton" id="increment_spin">
+                        <property name="valign">center</property>
+                        <property name="adjustment">
+                          <object class="GtkAdjustment">
+                            <property name="lower">0</property>
+                            <property name="step-increment">1</property>
+                            <property name="upper">600</property>
+                          </object>
+                        </property>
+                        <property name="value">0</property>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+                <child>
+                  <object class="AdwComboRow" id="clock_type_combo" >
+                    <property name="title" translatable="yes">Clock _Type</property>
+                    <property name="use-underline">true</property>
+                    <property name="sensitive" bind-source="time_limit_switch" bind-property="active" 
bind-flags="default|sync-create" />
+                    <property name="model">
+                      <object class="AdwEnumListModel">
+                        <property name="enum-type">ClockType</property>
+                      </object>
+                    </property>
+                    <property name="expression">
+                      <closure type="gchararray" function="clock_type_display_name_cb" swapped="true" />
+                    </property>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/data/preferences-window.ui b/data/preferences-window.ui
new file mode 100644
index 0000000..e4b6401
--- /dev/null
+++ b/data/preferences-window.ui
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk" version="4.0"/>
+  <template class="PreferencesWindow" parent="AdwPreferencesWindow">
+    <property name="search-enabled">false</property>
+    <property name="hide-on-close">true</property>
+    <property name="default_width">400</property>
+    <property name="default_height">380</property>
+    <child>
+      <object class="AdwPreferencesPage">
+        <child>
+          <object class="AdwPreferencesGroup">
+            <child>
+              <object class="AdwComboRow" id="board_orientation_combo">
+                <property name="title" translatable="yes">_Board Orientation</property>
+                <property name="use-underline">true</property>
+                <property name="model">
+                  <object class="AdwEnumListModel">
+                    <property name="enum-type">BoardOrientation</property>
+                  </object>
+                </property>
+                <property name="expression">
+                  <closure type="gchararray" function="board_orientation_display_name_cb" swapped="true" />
+                </property>
+              </object>
+            </child>
+            <child>
+              <object class="AdwComboRow" id="move_format_combo">
+                <property name="title" translatable="yes">_Move Format</property>
+                <property name="use-underline">true</property>
+                <property name="model">
+                  <object class="AdwEnumListModel">
+                    <property name="enum-type">MoveFormat</property>
+                  </object>
+                </property>
+                <property name="expression">
+                  <closure type="gchararray" function="move_format_display_name_cb" swapped="true" />
+                </property>
+              </object>
+            </child>
+            <child>
+              <object class="AdwComboRow" id="piece_style_combo">
+                <property name="title" translatable="yes">_Piece Style</property>
+                <property name="use-underline">true</property>
+                <property name="model">
+                  <object class="AdwEnumListModel">
+                    <property name="enum-type">PieceStyle</property>
+                  </object>
+                </property>
+                <property name="expression">
+                  <closure type="gchararray" function="piece_style_display_name_cb" swapped="true" />
+                </property>
+              </object>
+            </child>
+            <child>
+              <object class="AdwActionRow">
+                <property name="title" translatable="yes">Board _Numbering</property>
+                <property name="use-underline">true</property>
+                <property name="activatable_widget">board_numbering_switch</property>
+                <child>
+                  <object class="GtkSwitch" id="board_numbering_switch">
+                    <property name="valign">center</property>
+                  </object>
+                </child>
+              </object>
+            </child>
+            <child>
+              <object class="AdwActionRow">
+                <property name="title" translatable="yes">Move _Hints</property>
+                <property name="use-underline">true</property>
+                <property name="activatable_widget">move_hints_switch</property>
+                <child>
+                  <object class="GtkSwitch" id="move_hints_switch">
+                    <property name="valign">center</property>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/lib/chess-clock.vala b/lib/chess-clock.vala
index 9c0770f..fb63b86 100644
--- a/lib/chess-clock.vala
+++ b/lib/chess-clock.vala
@@ -11,7 +11,7 @@
  * license.
  */
 
-public enum ClockType
+public enum ChessClockType
 {
     SIMPLE,
     FISCHER,
@@ -33,7 +33,7 @@ public enum ClockType
         }
     }
 
-    public static ClockType string_to_enum (string s)
+    public static ChessClockType string_to_enum (string s)
     {
         switch (s)
         {
@@ -75,7 +75,7 @@ public class ChessClock : Object
         get { return black_initial_seconds + black_extra_seconds - black_seconds_used; }
     }
 
-    public ClockType clock_type { get; set; default = ClockType.SIMPLE; }
+    public ChessClockType clock_type { get; set; default = ChessClockType.SIMPLE; }
 
     private Color _active_color = Color.WHITE;
     public Color active_color
@@ -206,13 +206,13 @@ public class ChessClock : Object
         int white_move_used = 0, black_move_used = 0;
         switch (clock_type)
         {
-        case ClockType.FISCHER:
+        case ChessClockType.FISCHER:
             if (active_color == Color.WHITE)
                 white_extra_seconds += extra_seconds;
             else
                 black_extra_seconds += extra_seconds;
             break;
-        case ClockType.BRONSTEIN:
+        case ChessClockType.BRONSTEIN:
             white_move_used = white_seconds_used - white_prev_move_seconds;
             black_move_used = black_seconds_used - black_prev_move_seconds;
             if (active_color != Color.WHITE)
diff --git a/lib/chess-pgn.vala b/lib/chess-pgn.vala
index 2cade4b..a4ff25b 100644
--- a/lib/chess-pgn.vala
+++ b/lib/chess-pgn.vala
@@ -262,7 +262,7 @@ public class PGN : Object
                 game.tags.insert ("X-GNOME-BlackTimeLeft", tag_value);
             break;
         case "X-GNOME-ClockType":
-            if (ClockType.string_to_enum (tag_value) == ClockType.INVALID)
+            if (ChessClockType.string_to_enum (tag_value) == ChessClockType.INVALID)
             {
                 warning (_("Invalid clock type in PGN: %s, using a simple clock."), tag_value);
                 game.tags.insert ("X-GNOME-ClockType", "simple");
diff --git a/src/gnome-chess.vala b/src/gnome-chess.vala
index 3e8e68b..e66bfc3 100644
--- a/src/gnome-chess.vala
+++ b/src/gnome-chess.vala
@@ -30,6 +30,7 @@ public const string QUIT_ACTION_NAME = "quit";
 public class ChessApplication : Adw.Application
 {
     private GLib.Settings settings;
+    private Preferences preferences;
 
     public unowned ChessWindow window
     {
@@ -46,7 +47,8 @@ public class ChessApplication : Adw.Application
         get { return view.scene; }
     }
 
-    private PreferencesDialog? preferences_dialog = null;
+    private NewGameWindow? new_game_window = null;
+    private PreferencesWindow? preferences_window = null;
     private Gtk.AboutDialog? about_dialog = null;
     private Gtk.FileChooserNative? open_dialog = null;
     private Gtk.FileChooserNative? save_dialog = null;
@@ -119,6 +121,7 @@ Copyright © 2015–2016 Sahil Sareen""";
         base.startup ();
 
         settings = new Settings ("org.gnome.Chess");
+        preferences = new Preferences (settings);
 
         add_action_entries (action_entries, this);
         set_accels_for_action ("app." + NEW_GAME_ACTION_NAME,            {        "<Control>n"     });
@@ -137,16 +140,16 @@ Copyright © 2015–2016 Sahil Sareen""";
                                                                                   "<Control>w"     });
 
         window = new ChessWindow (this);
-        window.set_default_size (settings.get_int ("width"), settings.get_int ("height"));
-        if (settings.get_boolean ("maximized"))
+        window.set_default_size (settings.get_int (WIDTH_SETTINGS_KEY), settings.get_int 
(HEIGHT_SETTINGS_KEY));
+        if (settings.get_boolean (MAXIMIZED_SETTINGS_KEY))
             window.maximize ();
         add_window (window);
 
-        settings.bind ("show-move-hints", scene, "show-move-hints", SettingsBindFlags.GET);
-        settings.bind ("show-numbering", scene, "show-numbering", SettingsBindFlags.GET);
-        settings.bind ("piece-theme", scene, "theme-name", SettingsBindFlags.GET);
-        settings.bind ("move-format", scene, "move-format", SettingsBindFlags.GET);
-        settings.bind ("board-side", scene, "board-side", SettingsBindFlags.GET);
+        settings.bind (SHOW_MOVE_HINTS_SETTINGS_KEY, scene, "show-move-hints", SettingsBindFlags.GET);
+        settings.bind (SHOW_BOARD_NUMBERING_SETTINGS_KEY, scene, "show-numbering", SettingsBindFlags.GET);
+        settings.bind (PIECE_STYLE_SETTINGS_KEY, scene, "theme-name", SettingsBindFlags.GET);
+        settings.bind (MOVE_FORMAT_SETTINGS_KEY, scene, "move-format", SettingsBindFlags.GET);
+        settings.bind (BOARD_ORIENTATION_SETTINGS_KEY, scene, "board-side", SettingsBindFlags.GET);
 
         scene.is_human.connect ((p) => { return p == human_player; });
         scene.choose_promotion_type.connect (show_promotion_type_selector);
@@ -207,9 +210,9 @@ Copyright © 2015–2016 Sahil Sareen""";
 
         /* Save window state */
         settings.delay ();
-        settings.set_int ("width", window.default_width);
-        settings.set_int ("height", window.default_height);
-        settings.set_boolean ("maximized", window.maximized);
+        settings.set_int (WIDTH_SETTINGS_KEY, window.default_width);
+        settings.set_int (HEIGHT_SETTINGS_KEY, window.default_height);
+        settings.set_boolean (MAXIMIZED_SETTINGS_KEY, window.maximized);
         settings.apply ();
 
         base.shutdown ();
@@ -275,7 +278,7 @@ Copyright © 2015–2016 Sahil Sareen""";
         catch (Error e)
         {
             show_invalid_move_dialog (e.message);
-            start_new_game ();
+            configure_new_game ();
             return;
         }
 
@@ -396,16 +399,16 @@ Copyright © 2015–2016 Sahil Sareen""";
             timer_increment_adj_value = int.parse (pgn_game.timer_increment);
         else
         {
-            timer_increment_adj_value = settings.get_int ("timer-increment");
+            timer_increment_adj_value = settings.get_int (INCREMENT_SETTINGS_KEY);
             pgn_game.timer_increment = timer_increment_adj_value.to_string ();
         }
 
-        ClockType clock_type = ClockType.SIMPLE;
+        ChessClockType clock_type = ChessClockType.SIMPLE;
         if (pgn_game.clock_type != null)
-            clock_type = ClockType.string_to_enum (pgn_game.clock_type);
+            clock_type = ChessClockType.string_to_enum (pgn_game.clock_type);
         else
         {
-            clock_type = ClockType.string_to_enum (settings.get_string ("clock-type"));
+            clock_type = ChessClockType.string_to_enum (settings.get_string (CLOCK_TYPE_SETTINGS_KEY));
             pgn_game.clock_type = clock_type.to_string ();
         }
 
@@ -689,8 +692,6 @@ Copyright © 2015–2016 Sahil Sareen""";
     {
         /* Warning: this callback is invoked several times when loading a game. */
 
-        enable_action (NEW_GAME_ACTION_NAME);
-
         /* Need to save after each move */
         game_needs_saving = true;
 
@@ -755,7 +756,6 @@ Copyright © 2015–2016 Sahil Sareen""";
         else
         {
             game_needs_saving = false;
-            disable_action (NEW_GAME_ACTION_NAME);
             disable_action (SAVE_GAME_ACTION_NAME);
             disable_action (SAVE_GAME_AS_ACTION_NAME);
         }
@@ -838,9 +838,6 @@ Copyright © 2015–2016 Sahil Sareen""";
         disable_action (RESIGN_ACTION_NAME);
         disable_action (PAUSE_RESUME_ACTION_NAME);
 
-        /* In case of engine desync before the first move, or after undo */
-        enable_action (NEW_GAME_ACTION_NAME);
-
         game_needs_saving = false;
 
         string what = "";
@@ -1060,7 +1057,7 @@ Copyright © 2015–2016 Sahil Sareen""";
     {
         prompt_save_game (_("Save this game before starting a new one?"), (cancelled) => {
             if (!cancelled)
-                start_new_game ();
+                configure_new_game ();
         });
     }
 
@@ -1162,17 +1159,29 @@ Copyright © 2015–2016 Sahil Sareen""";
         scene.move_number = -1;
     }
 
+    private void configure_new_game ()
+    {
+        if (new_game_window != null)
+        {
+            new_game_window.show ();
+            return;
+        }
+
+        new_game_window = new NewGameWindow (window, preferences, ai_profiles);
+        new_game_window.new_game_requested.connect (() => start_new_game ());
+        new_game_window.show ();
+    }
+
     private void preferences_cb ()
     {
-        if (preferences_dialog != null)
+        if (preferences_window != null)
         {
-            preferences_dialog.show ();
+            preferences_window.show ();
             return;
         }
 
-        preferences_dialog = new PreferencesDialog (window, settings, ai_profiles);
-        preferences_dialog.response.connect (() => preferences_dialog.hide ());
-        preferences_dialog.show ();
+        preferences_window = new PreferencesWindow (window, preferences);
+        preferences_window.show ();
     }
 
     public void help_cb ()
@@ -1463,21 +1472,20 @@ Copyright © 2015–2016 Sahil Sareen""";
     {
         game_file = null;
 
-        disable_action (NEW_GAME_ACTION_NAME);
         disable_action (SAVE_GAME_AS_ACTION_NAME);
 
         pgn_game = new PGNGame ();
         var now = new DateTime.now_local ();
         pgn_game.date = now.format ("%Y.%m.%d");
         pgn_game.time = now.format ("%H:%M:%S");
-        var duration = settings.get_int ("duration");
+        var duration = settings.get_int (DURATION_SETTINGS_KEY);
         if (duration > 0)
         {
             pgn_game.time_control = duration.to_string ();
             pgn_game.white_time_left = duration.to_string ();
             pgn_game.black_time_left = duration.to_string ();
         }
-        var engine_name = settings.get_string ("opponent");
+        var engine_name = settings.get_string (OPPONENT_SETTINGS_KEY);
         if (engine_name == "")
         {
             if (ai_profiles != null)
@@ -1485,14 +1493,14 @@ Copyright © 2015–2016 Sahil Sareen""";
             else
                 engine_name = "human";
         }
-        var engine_level = settings.get_string ("difficulty");
+        var engine_level = settings.get_string (DIFFICULTY_SETTINGS_KEY);
         if (engine_name != null && engine_name != "human")
         {
-            var play_as = settings.get_string ("play-as");
+            var play_as = settings.get_string (PLAY_AS_SETTINGS_KEY);
 
             if (play_as == "alternate")
             {
-                var last_side = settings.get_string ("last-played-as");
+                var last_side = settings.get_string (LAST_PLAYED_AS_SETTINGS_KEY);
                 play_as = (last_side == "white" ? "black" : "white");
             }
 
@@ -1511,7 +1519,7 @@ Copyright © 2015–2016 Sahil Sareen""";
                 assert_not_reached ();
             }
 
-            settings.set_string ("last-played-as", play_as);
+            settings.set_string (LAST_PLAYED_AS_SETTINGS_KEY, play_as);
         }
 
         start_game ();
@@ -1519,8 +1527,6 @@ Copyright © 2015–2016 Sahil Sareen""";
 
     private void load_game (File file)
     {
-        enable_action (NEW_GAME_ACTION_NAME);
-
         try
         {
             var pgn = new PGN.from_file (file);
diff --git a/src/meson.build b/src/meson.build
index dc751ff..c9bcf8c 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -5,7 +5,9 @@ chess_sources = [
   'chess-view.vala',
   'chess-window.vala',
   'gnome-chess.vala',
-  'preferences-dialog.vala',
+  'new-game-window.vala',
+  'preferences-window.vala',
+  'preferences.vala',
   'promotion-type-selector-dialog.vala',
 ]
 
diff --git a/src/new-game-window.vala b/src/new-game-window.vala
new file mode 100644
index 0000000..fbaec3c
--- /dev/null
+++ b/src/new-game-window.vala
@@ -0,0 +1,198 @@
+/* -*- Mode: vala; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * Copyright (C) 2022 Nils Lück
+ *
+ * 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/new-game-window.ui")]
+public class NewGameWindow : Adw.PreferencesWindow
+{
+    private bool syncing_time_limit = false;
+    private Preferences preferences;
+    private unowned List<AIProfile> ai_profiles;
+    private List<Opponent> opponents = new List<Opponent> ();
+
+    public signal void new_game_requested ();
+
+    [GtkChild]
+    private unowned Adw.ComboRow play_as_combo;
+    [GtkChild]
+    private unowned Adw.ComboRow difficulty_combo;
+    [GtkChild]
+    private unowned Adw.ComboRow opponent_combo;
+    [GtkChild]
+    private unowned Adw.ComboRow clock_type_combo;
+    [GtkChild]
+    private unowned Gtk.SpinButton duration_spin;
+    [GtkChild]
+    private unowned Gtk.SpinButton increment_spin;
+    [GtkChild]
+    private unowned Gtk.Switch time_limit_switch;
+
+    public NewGameWindow (Gtk.Window window, Preferences preferences, List<AIProfile> ai_profiles)
+    {
+        transient_for = window;
+
+        this.preferences = preferences;
+        this.ai_profiles = ai_profiles;
+        initialize_opponents (ai_profiles);
+
+        preferences.bind_property ("play-as", play_as_combo, "selected", BindingFlags.BIDIRECTIONAL | 
BindingFlags.SYNC_CREATE, null, null);
+        preferences.bind_property ("difficulty", difficulty_combo, "selected", BindingFlags.BIDIRECTIONAL | 
BindingFlags.SYNC_CREATE, null, null);
+
+        preferences.bind_property (
+            "opponent", 
+            opponent_combo, 
+            "selected",
+            BindingFlags.BIDIRECTIONAL | BindingFlags.SYNC_CREATE, 
+            (binding, from_value, ref to_value) =>
+            {
+                var opponent = (Opponent) from_value.get_object ();
+                var position = get_opponent_index (opponent.display_name);
+                to_value.set_uint (position);
+                return true;
+            },
+            (binding, from_value, ref to_value) =>
+            {
+                var position = from_value.get_uint ();
+                to_value.set_object (opponents.nth_data (position));
+                return true;
+            });
+
+        time_limit_settings_changed_cb ();
+        time_limit_switch.notify["active"].connect (time_limit_controls_changed_cb);
+        clock_type_combo.notify["selected"].connect (time_limit_controls_changed_cb);
+        duration_spin.value_changed.connect (time_limit_controls_changed_cb);
+        increment_spin.value_changed.connect (time_limit_controls_changed_cb);
+        preferences.notify["time-limit"].connect (time_limit_settings_changed_cb);
+
+        opponent_combo.bind_property (
+            "selected",
+            difficulty_combo,
+            "sensitive",
+            BindingFlags.DEFAULT | BindingFlags.SYNC_CREATE,
+            (binding, from_value, ref to_value) =>
+            {
+                var opponent = opponents.nth_data (from_value.get_uint ());
+                to_value.set_boolean (!opponent.is_human);
+                return true;
+            },
+            null);
+
+        opponent_combo.bind_property (
+            "selected",
+            play_as_combo,
+            "sensitive",
+            BindingFlags.DEFAULT | BindingFlags.SYNC_CREATE,
+            (binding, from_value, ref to_value) =>
+            {
+                var opponent = opponents.nth_data (from_value.get_uint ());
+                to_value.set_boolean (!opponent.is_human);
+                return true;
+            },
+            null);
+    }
+
+    [GtkCallback]
+    private void start_game_cb ()
+    {
+        close ();
+        new_game_requested ();
+    }
+    
+    [GtkCallback]
+    private string play_as_display_name_cb (Adw.EnumListItem item)
+    {
+        var value = (PlayAs) item.value;
+        return value.display_name ();
+    }
+    
+    [GtkCallback]
+    private string difficulty_display_name_cb (Adw.EnumListItem item)
+    {
+        var value = (Difficulty) item.value;
+        return value.display_name ();
+    }
+    
+    [GtkCallback]
+    private string clock_type_display_name_cb (Adw.EnumListItem item)
+    {
+        var value = (ClockType) item.value;
+        return value.display_name ();
+    }
+
+    private void time_limit_settings_changed_cb ()
+    {
+        if (syncing_time_limit)
+            return;
+        syncing_time_limit = true;
+
+        if (preferences.time_limit == null)
+        {
+            time_limit_switch.active = false;
+            duration_spin.value = 5;
+            increment_spin.value = 0;
+            clock_type_combo.selected = (int) ClockType.FISCHER;
+        }
+        else
+        {
+            time_limit_switch.active = true;
+            duration_spin.value = preferences.time_limit.duration_in_seconds / 60.0;
+            increment_spin.value = preferences.time_limit.increment_in_seconds;
+            clock_type_combo.selected = (int) preferences.time_limit.clock_type;
+        }
+
+        syncing_time_limit = false;
+    }
+
+    private void time_limit_controls_changed_cb ()
+    {
+        if (syncing_time_limit)
+            return;
+        syncing_time_limit = true;
+
+        if (time_limit_switch.active)
+        {
+            var duration_in_seconds = (int) duration_spin.value * 60;
+            var increment_in_seconds = (int) increment_spin.value;
+            var clock_type = (ClockType) clock_type_combo.selected;
+            preferences.time_limit = new TimeLimit (duration_in_seconds, increment_in_seconds, clock_type);
+        }
+        else
+            preferences.time_limit = null;
+
+        syncing_time_limit = false;
+    }
+
+    private void initialize_opponents (List<AIProfile> ai_profiles)
+    {
+        opponents.append (Opponent.human);
+        foreach (var ai_profile in ai_profiles)
+        {
+            opponents.append (Opponent.from_ai_profile (ai_profile));
+        }
+        var opponents_model = new Gtk.StringList (null);
+        foreach (var opponent in opponents)
+        {
+            opponents_model.append(opponent.display_name);
+        }
+        opponent_combo.model = opponents_model;
+    }
+
+    private uint get_opponent_index (string display_name)
+    {
+        var i = 0;
+        foreach (var opponent in opponents)
+        {
+            if (opponent.display_name == display_name)
+                return i;
+            i++; 
+        }
+        return 0;
+    }
+}
\ No newline at end of file
diff --git a/src/preferences-window.vala b/src/preferences-window.vala
new file mode 100644
index 0000000..f2f7b2b
--- /dev/null
+++ b/src/preferences-window.vala
@@ -0,0 +1,60 @@
+/* -*- Mode: vala; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * Copyright (C) 2022 Nils Lück
+ *
+ * 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-window.ui")]
+public class PreferencesWindow : Adw.PreferencesWindow
+{
+    private Preferences preferences;
+
+    [GtkChild]
+    private unowned Adw.ComboRow board_orientation_combo;
+    [GtkChild]
+    private unowned Adw.ComboRow move_format_combo;
+    [GtkChild]
+    private unowned Adw.ComboRow piece_style_combo;
+    [GtkChild]
+    private unowned Gtk.Switch board_numbering_switch;
+    [GtkChild]
+    private unowned Gtk.Switch move_hints_switch;
+
+    public PreferencesWindow (Gtk.Window window, Preferences preferences)
+    {
+        transient_for = window;
+        this.preferences = preferences;
+
+        preferences.bind_property ("show-board-numbering", board_numbering_switch, "active", 
BindingFlags.BIDIRECTIONAL | BindingFlags.SYNC_CREATE, null, null);
+        preferences.bind_property ("show-move-hints", move_hints_switch, "active", 
BindingFlags.BIDIRECTIONAL | BindingFlags.SYNC_CREATE, null, null);
+        preferences.bind_property ("piece-style", piece_style_combo, "selected", BindingFlags.BIDIRECTIONAL 
| BindingFlags.SYNC_CREATE, null, null);
+        preferences.bind_property ("move-format", move_format_combo, "selected", BindingFlags.BIDIRECTIONAL 
| BindingFlags.SYNC_CREATE, null, null);
+        preferences.bind_property ("board-orientation", board_orientation_combo, "selected", 
BindingFlags.BIDIRECTIONAL | BindingFlags.SYNC_CREATE, null, null);
+    }
+    
+    [GtkCallback]
+    private string board_orientation_display_name_cb (Adw.EnumListItem item)
+    {
+        var value = (BoardOrientation) item.value;
+        return value.display_name ();
+    }
+    
+    [GtkCallback]
+    private string move_format_display_name_cb (Adw.EnumListItem item)
+    {
+        var value = (MoveFormat) item.value;
+        return value.display_name ();
+    }
+
+    [GtkCallback]
+    private string piece_style_display_name_cb (Adw.EnumListItem item)
+    {
+        var value = (PieceStyle) item.value;
+        return value.display_name ();
+    }
+}
diff --git a/src/preferences.vala b/src/preferences.vala
new file mode 100644
index 0000000..eebfcfe
--- /dev/null
+++ b/src/preferences.vala
@@ -0,0 +1,607 @@
+/* -*- Mode: vala; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * Copyright (C) 2022 Nils Lück
+ *
+ * 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.
+ */
+
+public const string WIDTH_SETTINGS_KEY = "width";
+public const string HEIGHT_SETTINGS_KEY = "height";
+public const string MAXIMIZED_SETTINGS_KEY = "maximized";
+public const string PIECE_STYLE_SETTINGS_KEY = "piece-theme";
+public const string SHOW_MOVE_HINTS_SETTINGS_KEY = "show-move-hints";
+public const string SHOW_BOARD_NUMBERING_SETTINGS_KEY = "show-numbering";
+public const string MOVE_FORMAT_SETTINGS_KEY = "move-format";
+public const string BOARD_ORIENTATION_SETTINGS_KEY = "board-side";
+public const string DURATION_SETTINGS_KEY = "duration";
+public const string CLOCK_TYPE_SETTINGS_KEY = "clock-type";
+public const string INCREMENT_SETTINGS_KEY = "timer-increment";
+public const string PLAY_AS_SETTINGS_KEY = "play-as";
+public const string LAST_PLAYED_AS_SETTINGS_KEY = "last-played-as";
+public const string OPPONENT_SETTINGS_KEY = "opponent";
+public const string DIFFICULTY_SETTINGS_KEY = "difficulty";
+
+public class Preferences : Object
+{
+    private Settings settings;
+
+    public BoardOrientation board_orientation { get; set; }
+    public MoveFormat move_format { get; set; }
+    public PieceStyle piece_style { get; set; }
+    public bool show_board_numbering { get; set; }
+    public bool show_move_hints { get; set; }
+
+    public Opponent opponent { get; set; }
+    public PlayAs play_as { get; set; }
+    public Difficulty difficulty { get; set; }
+
+    private bool syncing_time_limit = false;
+    public TimeLimit? time_limit { get; set; }
+
+    public Preferences(Settings settings)
+    {
+        this.settings = settings;
+
+        settings.bind (SHOW_BOARD_NUMBERING_SETTINGS_KEY, this, "show-board-numbering", 
SettingsBindFlags.DEFAULT);
+        settings.bind (SHOW_MOVE_HINTS_SETTINGS_KEY, this, "show-move-hints", SettingsBindFlags.DEFAULT);
+
+        settings.bind_with_mapping (
+            BOARD_ORIENTATION_SETTINGS_KEY, 
+            this, 
+            "board-orientation", 
+            SettingsBindFlags.DEFAULT, 
+            (to_value, from_value, user_data) =>
+            {
+                var value = BoardOrientation.from_setting (from_value.get_string ()) ?? 
BoardOrientation.HUMAN_SIDE;
+                to_value.set_enum (value);
+                return true;
+            },
+            (from_value, expected_type, user_data) =>
+            {
+                var value = (BoardOrientation) from_value.get_enum ();
+                return new Variant.string (value.to_setting ());
+            },
+            null, 
+            null);
+
+        settings.bind_with_mapping (
+            MOVE_FORMAT_SETTINGS_KEY, 
+            this, 
+            "move-format", 
+            SettingsBindFlags.DEFAULT, 
+            (to_value, from_value, user_data) =>
+            {
+                var value = MoveFormat.from_setting (from_value.get_string ()) ?? MoveFormat.HUMAN;
+                to_value.set_enum (value);
+                return true;
+            },
+            (from_value, expected_type, user_data) =>
+            {
+                var value = (MoveFormat) from_value.get_enum ();
+                return new Variant.string (value.to_setting ());
+            },
+            null, 
+            null);
+
+        settings.bind_with_mapping (
+            PIECE_STYLE_SETTINGS_KEY, 
+            this, 
+            "piece-style", 
+            SettingsBindFlags.DEFAULT, 
+            (to_value, from_value, user_data) =>
+            {
+                var value = PieceStyle.from_setting (from_value.get_string ()) ?? PieceStyle.SIMPLE;
+                to_value.set_enum (value);
+                return true;
+            },
+            (from_value, expected_type, user_data) =>
+            {
+                var value = (PieceStyle) from_value.get_enum ();
+                return new Variant.string (value.to_setting ());
+            },
+            null, 
+            null);
+
+        settings.bind_with_mapping (
+            PLAY_AS_SETTINGS_KEY, 
+            this, 
+            "play-as", 
+            SettingsBindFlags.DEFAULT, 
+            (to_value, from_value, user_data) =>
+            {
+                var value = PlayAs.from_setting (from_value.get_string ()) ?? PlayAs.WHITE;
+                to_value.set_enum (value);
+                return true;
+            },
+            (from_value, expected_type, user_data) =>
+            {
+                var value = (PlayAs) from_value.get_enum ();
+                return new Variant.string (value.to_setting ());
+            },
+            null, 
+            null);
+
+        settings.bind_with_mapping (
+            DIFFICULTY_SETTINGS_KEY, 
+            this, 
+            "difficulty", 
+            SettingsBindFlags.DEFAULT, 
+            (to_value, from_value, user_data) =>
+            {
+                var value = Difficulty.from_setting (from_value.get_string ()) ?? Difficulty.EASY;
+                to_value.set_enum (value);
+                return true;
+            },
+            (from_value, expected_type, user_data) =>
+            {
+                var value = (Difficulty) from_value.get_enum ();
+                return new Variant.string (value.to_setting ());
+            },
+            null, 
+            null);
+
+        settings.bind_with_mapping (
+            OPPONENT_SETTINGS_KEY, 
+            this, 
+            "opponent", 
+            SettingsBindFlags.DEFAULT, 
+            (to_value, from_value, user_data) =>
+            {
+                var value = Opponent.from_setting (from_value.get_string ()) ?? Opponent.human;
+                to_value.set_object (value);
+                return true;
+            },
+            (from_value, expected_type, user_data) =>
+            {
+                var value = (Opponent) from_value.get_object ();
+                return new Variant.string (value.to_setting ());
+            },
+            null, 
+            null);
+        
+        time_limit_settings_changed_cb ();
+        settings.changed[DURATION_SETTINGS_KEY].connect(time_limit_settings_changed_cb);
+        settings.changed[INCREMENT_SETTINGS_KEY].connect(time_limit_settings_changed_cb);
+        settings.changed[CLOCK_TYPE_SETTINGS_KEY].connect(time_limit_settings_changed_cb);
+        notify["time-limit"].connect(time_limit_preferences_changed_cb);
+    }
+
+    private void time_limit_settings_changed_cb ()
+    {
+        if (syncing_time_limit)
+            return;
+        syncing_time_limit = true;
+
+        var duration = settings.get_int (DURATION_SETTINGS_KEY);
+        var increment = settings.get_int (INCREMENT_SETTINGS_KEY);
+        var clock_type = settings.get_string (CLOCK_TYPE_SETTINGS_KEY);
+        time_limit = TimeLimit.from_settings (duration, increment, clock_type);
+
+        syncing_time_limit = false;
+    }
+
+    private void time_limit_preferences_changed_cb ()
+    {
+        if (syncing_time_limit)
+            return;
+        syncing_time_limit = true;
+
+        int duration;
+        int increment;
+        string clock_type;
+        TimeLimit.to_settings (time_limit, out duration, out increment, out clock_type);
+        settings.set_int (DURATION_SETTINGS_KEY, duration);
+        settings.set_int (INCREMENT_SETTINGS_KEY, increment);
+        settings.set_string (CLOCK_TYPE_SETTINGS_KEY, clock_type);
+
+        syncing_time_limit = false;
+    }
+}
+
+public enum BoardOrientation
+{
+    HUMAN_SIDE,
+    WHITE_SIDE,
+    BLACK_SIDE,
+    CURRENT_PLAYER;
+
+    public string display_name ()
+    {
+        switch (this)
+        {
+        case WHITE_SIDE:
+            return C_("chess-side", "White Side");
+        case BLACK_SIDE:
+            return C_("chess-side", "Black Side");
+        case HUMAN_SIDE:
+            return C_("chess-side", "Human Side");
+        case CURRENT_PLAYER:
+            return C_("chess-side", "Current Player");
+        default:
+            assert_not_reached ();
+        }
+    }
+
+    public string to_setting ()
+    {
+        switch (this)
+        {
+        case WHITE_SIDE:
+            return "white";
+        case BLACK_SIDE:
+            return "black";
+        case HUMAN_SIDE:
+            return "human";
+        case CURRENT_PLAYER:
+            return "current";
+        default:
+            assert_not_reached ();
+        }
+    }
+
+    public static BoardOrientation? from_setting (string s)
+    {
+        switch (s)
+        {
+        case "white":
+            return WHITE_SIDE;
+        case "black":
+            return BLACK_SIDE;
+        case "human":
+            return HUMAN_SIDE;
+        case "current":
+            return CURRENT_PLAYER;
+        default:
+            return null;
+        }
+    }
+}
+
+public enum MoveFormat
+{
+    HUMAN,
+    STANDARD_ALGEBRAIC,
+    LONG_ALGEBRAIC,
+    FIGURINE;
+
+    public string display_name ()
+    {
+        switch (this)
+        {
+        case HUMAN:
+            return C_("chess-move-format", "Human");
+        case STANDARD_ALGEBRAIC:
+            return C_("chess-move-format", "Standard Algebraic");
+        case LONG_ALGEBRAIC:
+            return C_("chess-move-format", "Long Algebraic");
+        case FIGURINE:
+            return C_("chess-move-format", "Figurine");
+        default:
+            assert_not_reached ();
+        }
+    }
+
+    public string to_setting ()
+    {
+        switch (this)
+        {
+        case HUMAN:
+            return "human";
+        case STANDARD_ALGEBRAIC:
+            return "san";
+        case LONG_ALGEBRAIC:
+            return "lan";
+        case FIGURINE:
+            return "fan";
+        default:
+            assert_not_reached ();
+        }
+    }
+
+    public static MoveFormat? from_setting (string s)
+    {
+        switch (s)
+        {
+        case "human":
+            return HUMAN;
+        case "san":
+            return STANDARD_ALGEBRAIC;
+        case "lan":
+            return LONG_ALGEBRAIC;
+        case "fan":
+            return FIGURINE;
+        default:
+            return null;
+        }
+    }
+}
+
+public enum PieceStyle
+{
+    SIMPLE,
+    FANCY;
+
+    public string display_name ()
+    {
+        switch (this)
+        {
+        case SIMPLE:
+            return C_("chess-piece-style", "Simple");
+        case FANCY:
+            return C_("chess-piece-style", "Fancy");
+        default:
+            assert_not_reached ();
+        }
+    }
+
+    public string to_setting ()
+    {
+        switch (this)
+        {
+        case SIMPLE:
+            return "simple";
+        case FANCY:
+            return "fancy";
+        default:
+            assert_not_reached ();
+        }
+    }
+
+    public static PieceStyle? from_setting (string s)
+    {
+        switch (s)
+        {
+        case "simple":
+            return SIMPLE;
+        case "fancy":
+            return FANCY;
+        default:
+            return null;
+        }
+    }
+}
+
+public enum PlayAs
+{
+    WHITE,
+    BLACK,
+    ALTERNATE;
+
+    public string display_name ()
+    {
+        switch (this)
+        {
+        case WHITE:
+            return C_("chess-player", "White");
+        case BLACK:
+            return C_("chess-player", "Black");
+        case ALTERNATE:
+            return C_("chess-player", "Alternate");
+        default:
+            assert_not_reached ();
+        }
+    }
+
+    public string to_setting ()
+    {
+        switch (this)
+        {
+        case WHITE:
+            return "white";
+        case BLACK:
+            return "black";
+        case ALTERNATE:
+            return "alternate";
+        default:
+            assert_not_reached ();
+        }
+    }
+
+    public static PlayAs? from_setting (string s)
+    {
+        switch (s)
+        {
+        case "white":
+            return WHITE;
+        case "black":
+            return BLACK;
+        case "alternate":
+            return ALTERNATE;
+        default:
+            return null;
+        }
+    }
+}
+
+public enum Difficulty
+{
+    EASY,
+    NORMAL,
+    HARD;
+
+    public string display_name ()
+    {
+        switch (this)
+        {
+        case EASY:
+            return C_("difficulty", "Easy");
+        case NORMAL:
+            return C_("difficulty", "Normal");
+        case HARD:
+            return C_("difficulty", "Hard");
+        default:
+            assert_not_reached ();
+        }
+    }
+
+    public string to_setting ()
+    {
+        switch (this)
+        {
+        case EASY:
+            return "easy";
+        case NORMAL:
+            return "normal";
+        case HARD:
+            return "hard";
+        default:
+            assert_not_reached ();
+        }
+    }
+
+    public static Difficulty? from_setting (string s)
+    {
+        switch (s)
+        {
+        case "easy":
+            return EASY;
+        case "normal":
+            return NORMAL;
+        case "hard":
+            return HARD;
+        default:
+            return null;
+        }
+    }
+}
+
+public enum ClockType
+{
+    FISCHER,
+    BRONSTEIN;
+
+    public string display_name ()
+    {
+        switch (this)
+        {
+        case FISCHER:
+            return C_("clock-type", "Fischer");
+        case BRONSTEIN:
+            return C_("clock-type", "Bronstein");
+        default:
+            assert_not_reached ();
+        }
+    }
+
+    public string to_setting ()
+    {
+        switch (this)
+        {
+        case FISCHER:
+            return "fischer";
+        case BRONSTEIN:
+            return "bronstein";
+        default:
+            assert_not_reached ();
+        }
+    }
+
+    public static ClockType? from_setting (string s)
+    {
+        switch (s)
+        {
+        case "fischer":
+            return FISCHER;
+        case "bronstein":
+            return BRONSTEIN;
+        default:
+            return null;
+        }
+    }
+}
+
+public class Opponent : Object
+{
+    private const string HUMAN_NAME = "human";
+
+    private static Opponent _human;
+    public static Opponent human
+    {
+        get
+        {
+            if (_human == null)
+                _human = new Opponent (HUMAN_NAME, C_("chess-opponent", "Human"));
+            return _human;
+        }
+    }
+
+    public string name { get; private set; }
+    public string display_name { get; private set; }
+    public bool is_human { get { return name == HUMAN_NAME; } }
+
+    public Opponent (string name, string display_name)
+    {
+        this.name = name;
+        this.display_name = display_name;
+    }
+
+    public string to_setting ()
+    {
+        return name;
+    }
+
+    public static Opponent? from_setting (string s)
+    {
+        if (s == HUMAN_NAME)
+            return human;
+        else if (s == null || s.length == 0)
+            return null;
+        return new Opponent (s, s);
+    }
+
+    public static Opponent from_ai_profile (AIProfile ai_profile)
+    {
+        return new Opponent (ai_profile.name, ai_profile.name);
+    }
+}
+
+public class TimeLimit
+{
+    public int duration_in_seconds { get; private set; }
+    public int increment_in_seconds { get; private set; }
+    public ClockType clock_type { get; private set; }
+
+    public TimeLimit (int duration_in_seconds, int increment_in_seconds, ClockType clock_type)
+    {
+        assert_cmpint (duration_in_seconds, CompareOperator.GT, 0);
+        assert_cmpint (increment_in_seconds, CompareOperator.GE, 0);
+        this.duration_in_seconds = duration_in_seconds;
+        this.increment_in_seconds = increment_in_seconds;
+        this.clock_type = clock_type;
+    }
+
+    public static void to_settings (TimeLimit? time_limit, out int duration, out int increment, out string 
clock_type)
+    {
+        if (time_limit == null)
+        {
+            duration = 0;
+            increment = 0;
+            clock_type = "simple";
+            return;
+        }
+
+        duration = time_limit.duration_in_seconds;
+        increment = time_limit.increment_in_seconds;
+
+        if (increment == 0)
+            clock_type = "simple";
+        else
+            clock_type = time_limit.clock_type.to_setting ();
+    }
+
+    public static TimeLimit? from_settings (int duration_setting, int increment_setting, string 
clock_type_setting)
+    {
+        if (duration_setting <= 0)
+            return null;
+
+        var clock_type = ClockType.from_setting (clock_type_setting);
+        if (clock_type == null || increment_setting <= 0)
+            return new TimeLimit (duration_setting, 0, ClockType.FISCHER);
+
+        return new TimeLimit (duration_setting, increment_setting, clock_type);
+    }
+}
\ No newline at end of file


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