[gnome-chess/mcatanzaro/gtk4] Port to GTK 4




commit 76016f2f96a00fc64c99f953c9fc4a9fdfe4f6b6
Author: Michael Catanzaro <mcatanzaro gnome org>
Date:   Thu Dec 17 13:44:13 2020 -0600

    Port to GTK 4
    
    I probably should have removed GtkDialog.run() use first, in a separate
    commit, but I didn't and now it would be a lot of effort to split it
    out. So here it is: one big GTK 4 megacommit.

 data/gnome-chess.ui             | 208 ++--------
 data/help-overlay.ui            |  20 +-
 data/preferences.ui             | 832 ++++++++++++++++++----------------------
 data/promotion-type-selector.ui | 321 +++++++---------
 meson.build                     |   2 +-
 src/chess-scene.vala            |  37 +-
 src/chess-view.vala             |  28 +-
 src/gnome-chess.vala            | 815 ++++++++++++++++++++++-----------------
 8 files changed, 1039 insertions(+), 1224 deletions(-)
---
diff --git a/data/gnome-chess.ui b/data/gnome-chess.ui
index b190efc..3456432 100644
--- a/data/gnome-chess.ui
+++ b/data/gnome-chess.ui
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <!-- interface-requires gtk+ 3.10 -->
+  <requires lib="gtk" version="4.0"/>
   <menu id="app-menu">
     <section>
       <item>
@@ -45,29 +45,23 @@
   </menu>
   <object class="GtkListStore" id="history-model">
     <columns>
-      <!-- column-name label -->
       <column type="gchararray"/>
-      <!-- column-name move-number -->
       <column type="gint"/>
     </columns>
   </object>
   <object class="GtkApplicationWindow" id="gnome_chess_app">
-    <property name="can-focus">False</property>
+    <property name="can-focus">0</property>
     <property name="default-width">700</property>
-    <signal name="delete-event" handler="gnome_chess_app_delete_event_cb" swapped="no"/>
+    <property name="title" translatable="yes">Chess</property>
     <child type="titlebar">
       <object class="GtkHeaderBar" id="headerbar">
-        <property name="visible">True</property>
-        <property name="can-focus">False</property>
-        <property name="show-close-button">True</property>
-        <property name="title" translatable="yes">Chess</property>
+        <property name="can-focus">0</property>
         <child>
           <object class="GtkButton" id="new-game-button">
-            <property name="visible">True</property>
             <property name="halign">center</property>
             <property name="valign">center</property>
             <property name="label" translatable="yes">_New Game</property>
-            <property name="use-underline">True</property>
+            <property name="use-underline">1</property>
             <property name="action-name">win.new</property>
             <style>
               <class name="text-button"/>
@@ -76,7 +70,6 @@
         </child>
         <child>
           <object class="GtkButton" id="undo-move-button">
-            <property name="visible">True</property>
             <property name="valign">center</property>
             <property name="action-name">win.undo</property>
             <property name="tooltip-text" translatable="yes">Undo your most recent move</property>
@@ -85,8 +78,7 @@
             </style>
             <child>
               <object class="GtkImage">
-                <property name="visible">True</property>
-                <property name="can-focus">False</property>
+                <property name="can-focus">0</property>
                 <property name="icon-size">1</property>
                 <property name="icon-name">edit-undo-symbolic</property>
               </object>
@@ -95,7 +87,6 @@
         </child>
         <child>
           <object class="GtkButton" id="pause_button">
-            <property name="visible">True</property>
             <property name="valign">center</property>
             <property name="action-name">win.pause-resume</property>
             <style>
@@ -103,215 +94,136 @@
             </style>
             <child>
               <object class="GtkImage">
-                <property name="visible">True</property>
                 <property name="icon-size">1</property>
               </object>
             </child>
           </object>
         </child>
-        <child>
+        <child type="end">
           <object class="GtkMenuButton">
-            <property name="visible">True</property>
-            <property name="can-focus">True</property>
+            <property name="icon-name">open-menu-symbolic</property>
             <property name="menu-model">app-menu</property>
-            <accelerator key="F10" signal="activate"/>
-            <child>
-              <object class="GtkImage">
-                <property name="visible">True</property>
-                <property name="icon-name">open-menu-symbolic</property>
-                <property name="icon-size">1</property>
-              </object>
-            </child>
           </object>
-          <packing>
-            <property name="pack-type">end</property>
-            <property name="position">1</property>
-          </packing>
         </child>
       </object>
     </child>
     <child>
-      <object class="GtkBox">
-        <property name="visible">True</property>
-        <property name="can-focus">False</property>
+      <object class="GtkBox" id="main_box">
+        <property name="can-focus">0</property>
         <property name="orientation">vertical</property>
         <property name="spacing">3</property>
         <child>
           <object class="GtkInfoBar" id="info_bar">
-            <property name="visible">False</property>
-            <child internal-child="content_area">
+            <property name="visible">0</property>
+            <child>
               <object class="GtkBox">
-                <property name="visible">True</property>
                 <child>
                   <object class="GtkLabel" id="info_bar_label">
-                    <property name="visible">True</property>
+                    <property name="hexpand">1</property>
                     <property name="ellipsize">end</property>
                     <property name="justify">center</property>
-                    <property name="use-markup">true</property>
+                    <property name="use-markup">1</property>
                   </object>
-                  <packing>
-                    <property name="expand">True</property>
-                    <property name="fill">True</property>
-                  </packing>
                 </child>
               </object>
             </child>
           </object>
         </child>
-        <child>
-          <object class="GtkAlignment" id="view_container">
-            <property name="visible">True</property>
-            <property name="can-focus">False</property>
-            <child>
-              <placeholder/>
-            </child>
-          </object>
-          <packing>
-            <property name="expand">True</property>
-            <property name="fill">True</property>
-          </packing>
-        </child>
         <child>
           <object class="GtkBox" id="navigation_box">
-            <property name="visible">True</property>
-            <property name="can-focus">False</property>
+            <property name="can-focus">0</property>
             <property name="spacing">6</property>
-            <property name="margin-left">6</property>
-            <property name="margin-right">6</property>
+            <property name="margin-start">6</property>
+            <property name="margin-end">6</property>
+            <property name="margin-top">6</property>
             <property name="margin-bottom">6</property>
             <child>
               <object class="GtkBox">
-                <property name="visible">True</property>
-                <property name="can-focus">False</property>
+                <property name="hexpand">1</property>
+                <property name="can-focus">0</property>
                 <property name="spacing">6</property>
                 <child>
                   <object class="GtkBox">
-                    <property name="visible">True</property>
-                    <property name="can-focus">False</property>
-                    <property name="homogeneous">True</property>
+                    <property name="can-focus">0</property>
+                    <property name="homogeneous">1</property>
                     <style>
                       <class name="raised"/>
                       <class name="linked"/>
                     </style>
                     <child>
                       <object class="GtkButton" id="first_move_button">
-                        <property name="visible">True</property>
-                        <property name="sensitive">False</property>
-                        <property name="can-focus">True</property>
-                        <property name="receives-default">False</property>
+                       <property name="action-name">win.go-first</property>
+                        <property name="sensitive">0</property>
                         <property name="tooltip-text" translatable="yes" comments="Tooltip on the show first 
move (i.e. game start) navigation button">Rewind to the game start</property>
-                        <accelerator key="Left" signal="activate" modifiers="GDK_SHIFT_MASK | 
GDK_MOD1_MASK"/>
-                        <signal name="clicked" handler="history_start_clicked_cb" swapped="no"/>
                         <style>
                           <class name="image-button"/>
                         </style>
                         <child>
                           <object class="GtkImage">
-                            <property name="visible">True</property>
-                            <property name="can-focus">False</property>
+                            <property name="can-focus">0</property>
                             <property name="icon-size">1</property>
                             <property name="icon-name">go-first-symbolic</property>
                           </object>
                         </child>
                       </object>
-                      <packing>
-                        <property name="expand">True</property>
-                        <property name="fill">True</property>
-                        <property name="position">0</property>
-                      </packing>
                     </child>
                     <child>
                       <object class="GtkButton" id="prev_move_button">
-                        <property name="visible">True</property>
-                        <property name="sensitive">False</property>
-                        <property name="can-focus">True</property>
-                        <property name="receives-default">False</property>
+                       <property name="action-name">win.go-previous</property>
+                        <property name="sensitive">0</property>
                         <property name="tooltip-text" translatable="yes" comments="Tooltip on the show 
previous move navigation button">Show the previous move</property>
-                        <accelerator key="Left" signal="activate" modifiers="GDK_MOD1_MASK"/>
-                        <signal name="clicked" handler="history_previous_clicked_cb" swapped="no"/>
                         <style>
                           <class name="image-button"/>
                         </style>
                         <child>
                           <object class="GtkImage">
-                            <property name="visible">True</property>
-                            <property name="can-focus">False</property>
+                            <property name="can-focus">0</property>
                             <property name="icon-size">1</property>
                             <property name="icon-name">go-previous-symbolic</property>
                           </object>
                         </child>
                       </object>
-                      <packing>
-                        <property name="expand">True</property>
-                        <property name="fill">True</property>
-                        <property name="position">1</property>
-                      </packing>
                     </child>
                     <child>
                       <object class="GtkButton" id="next_move_button">
-                        <property name="visible">True</property>
-                        <property name="sensitive">False</property>
-                        <property name="can-focus">True</property>
-                        <property name="receives-default">False</property>
+                       <property name="action-name">win.go-next</property>
+                        <property name="sensitive">0</property>
                         <property name="tooltip-text" translatable="yes" comments="Tooltip on the show next 
move navigation button">Show the next move</property>
-                        <accelerator key="Right" signal="activate" modifiers="GDK_MOD1_MASK"/>
-                        <signal name="clicked" handler="history_next_clicked_cb" swapped="no"/>
                         <style>
                           <class name="image-button"/>
                         </style>
                         <child>
                           <object class="GtkImage">
-                            <property name="visible">True</property>
-                            <property name="can-focus">False</property>
+                            <property name="can-focus">0</property>
                             <property name="icon-size">1</property>
                             <property name="icon-name">go-next-symbolic</property>
                           </object>
                         </child>
                       </object>
-                      <packing>
-                        <property name="expand">True</property>
-                        <property name="fill">True</property>
-                        <property name="position">2</property>
-                      </packing>
                     </child>
                     <child>
                       <object class="GtkButton" id="last_move_button">
-                        <property name="visible">True</property>
-                        <property name="sensitive">False</property>
-                        <property name="can-focus">True</property>
-                        <property name="receives-default">False</property>
+                       <property name="action-name">win.go-last</property>
+                        <property name="sensitive">0</property>
                         <property name="tooltip-text" translatable="yes" comments="Tooltip on the show 
current move navigation button">Show the current move</property>
-                        <accelerator key="Right" signal="activate" modifiers="GDK_SHIFT_MASK | 
GDK_MOD1_MASK"/>
-                        <signal name="clicked" handler="history_latest_clicked_cb" swapped="no"/>
                         <style>
                           <class name="image-button"/>
                         </style>
                         <child>
                           <object class="GtkImage">
-                            <property name="visible">True</property>
-                            <property name="can-focus">False</property>
+                            <property name="can-focus">0</property>
                             <property name="icon-size">1</property>
                             <property name="icon-name">go-last-symbolic</property>
                           </object>
                         </child>
                       </object>
-                      <packing>
-                        <property name="expand">True</property>
-                        <property name="fill">True</property>
-                        <property name="position">3</property>
-                      </packing>
                     </child>
                   </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">True</property>
-                    <property name="position">0</property>
-                  </packing>
                 </child>
                 <child>
                   <object class="GtkComboBox" id="history_combo">
-                    <property name="visible">True</property>
-                    <property name="can-focus">False</property>
+                    <property name="hexpand">1</property>
+                    <property name="can-focus">0</property>
                     <property name="model">history-model</property>
                     <signal name="changed" handler="history_combo_changed_cb" swapped="no"/>
                     <child>
@@ -323,65 +235,29 @@
                       </attributes>
                     </child>
                   </object>
-                  <packing>
-                    <property name="expand">True</property>
-                    <property name="fill">True</property>
-                    <property name="position">1</property>
-                  </packing>
                 </child>
               </object>
-              <packing>
-                <property name="expand">True</property>
-                <property name="fill">True</property>
-                <property name="position">0</property>
-              </packing>
             </child>
             <child>
               <object class="GtkBox" id="clock_box">
-                <property name="visible">True</property>
-                <property name="can-focus">False</property>
+                <property name="can-focus">0</property>
                 <property name="spacing">6</property>
-                <property name="homogeneous">True</property>
+                <property name="homogeneous">1</property>
                 <child>
                   <object class="GtkDrawingArea" id="white_time_label">
-                    <!-- -1 means compute at runtime -->
-                    <property name="width-request">-1</property>
-                    <property name="visible">True</property>
-                    <property name="can-focus">False</property>
-                    <signal name="draw" handler="white_time_draw_cb" swapped="no"/>
+                    <property name="can-focus">0</property>
+                    <property name="content-width">80</property>
                   </object>
-                  <packing>
-                    <property name="expand">True</property>
-                    <property name="fill">True</property>
-                    <property name="position">0</property>
-                  </packing>
                 </child>
                 <child>
                   <object class="GtkDrawingArea" id="black_time_label">
-                    <!-- -1 means compute at runtime -->
-                    <property name="width-request">-1</property>
-                    <property name="visible">True</property>
-                    <property name="can-focus">False</property>
-                    <signal name="draw" handler="black_time_draw_cb" swapped="no"/>
+                    <property name="can-focus">0</property>
+                    <property name="content-width">80</property>
                   </object>
-                  <packing>
-                    <property name="expand">True</property>
-                    <property name="fill">True</property>
-                    <property name="position">1</property>
-                  </packing>
                 </child>
               </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">True</property>
-                <property name="position">2</property>
-              </packing>
             </child>
           </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="fill">True</property>
-          </packing>
         </child>
       </object>
     </child>
diff --git a/data/help-overlay.ui b/data/help-overlay.ui
index 51a088e..21ea074 100644
--- a/data/help-overlay.ui
+++ b/data/help-overlay.ui
@@ -1,68 +1,58 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <!-- interface-requires gtk+ 3.17 -->
+  <requires lib="gtk" version="4.0"/>
   <object class="GtkShortcutsWindow" id="help_overlay">
     <property name="modal">1</property>
     <child>
       <object class="GtkShortcutsSection">
-        <property name="visible">1</property>
         <property name="max-height">8</property>
         <child>
           <object class="GtkShortcutsGroup">
             <property name="title" translatable="yes" context="shortcut window">General</property>
-            <property name="visible">1</property>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">1</property>
                 <property name="accelerator">&lt;Ctrl&gt;N</property>
                 <property name="title" translatable="yes" context="shortcut window">Start a new 
game</property>
               </object>
             </child>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">1</property>
                 <property name="accelerator">&lt;Ctrl&gt;O</property>
                 <property name="title" translatable="yes" context="shortcut window">Open a saved 
game</property>
               </object>
             </child>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">1</property>
                 <property name="accelerator">&lt;Ctrl&gt;P Pause</property>
                 <property name="title" translatable="yes" context="shortcut window">Pause the game</property>
               </object>
             </child>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">1</property>
                 <property name="accelerator">&lt;Ctrl&gt;S</property>
                 <property name="title" translatable="yes" context="shortcut window">Save the game</property>
               </object>
             </child>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">1</property>
                 <property name="accelerator">&lt;Shift&gt;&lt;Ctrl&gt;S</property>
                 <property name="title" translatable="yes" context="shortcut window">Save the game with a 
different name</property>
               </object>
             </child>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">1</property>
                 <property name="accelerator">&lt;Ctrl&gt;Z</property>
                 <property name="title" translatable="yes" context="shortcut window">Undo move</property>
               </object>
             </child>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">1</property>
                 <property name="accelerator">&lt;Ctrl&gt;Q</property>
                 <property name="title" translatable="yes" context="shortcut window">Quit</property>
               </object>
             </child>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">1</property>
                 <property name="accelerator">F10</property>
                 <property name="title" translatable="yes" context="shortcut window">Open menu</property>
               </object>
@@ -72,31 +62,26 @@
         <child>
           <object class="GtkShortcutsGroup">
             <property name="title" translatable="yes" context="shortcut window">History</property>
-            <property name="visible">1</property>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">1</property>
                 <property name="accelerator">&lt;Shift&gt;&lt;Alt&gt;Left</property>
                 <property name="title" translatable="yes" context="shortcut window">Rewind to the game 
start</property>
               </object>
             </child>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">1</property>
                 <property name="accelerator">&lt;Alt&gt;Left</property>
                 <property name="title" translatable="yes" context="shortcut window">Show the previous 
move</property>
               </object>
             </child>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">1</property>
                 <property name="accelerator">&lt;Alt&gt;Right</property>
                 <property name="title" translatable="yes" context="shortcut window">Show the next 
move</property>
               </object>
             </child>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">1</property>
                 <property name="accelerator">&lt;Shift&gt;&lt;Alt&gt;Right</property>
                 <property name="title" translatable="yes" context="shortcut window">Show the current 
move</property>
               </object>
@@ -106,17 +91,14 @@
         <child>
           <object class="GtkShortcutsGroup">
             <property name="title" translatable="yes" context="shortcut window">Help</property>
-            <property name="visible">1</property>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">1</property>
                 <property name="accelerator">F1</property>
                 <property name="title" translatable="yes" context="shortcut window">Show Help</property>
               </object>
             </child>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">1</property>
                 <property name="accelerator">&lt;Primary&gt;question</property>
                 <property name="title" translatable="yes" context="shortcut window">Show Keyboard 
Shortcuts</property>
               </object>
diff --git a/data/preferences.ui b/data/preferences.ui
index 80f89da..3400541 100644
--- a/data/preferences.ui
+++ b/data/preferences.ui
@@ -1,11 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <!-- interface-requires gtk+ 3.12 -->
+  <requires lib="gtk" version="4.0"/>
   <object class="GtkListStore" id="custom-duration-units-model">
     <columns>
-      <!-- column-name label -->
       <column type="gchararray"/>
-      <!-- column-name multiplier -->
       <column type="gint"/>
     </columns>
     <data>
@@ -21,9 +19,7 @@
   </object>
   <object class="GtkListStore" id="timer-increment-units-model">
     <columns>
-      <!-- column-name label -->
       <column type="gchararray"/>
-      <!-- column-name multiplier -->
       <column type="gint"/>
     </columns>
     <data>
@@ -39,9 +35,7 @@
   </object>
   <object class="GtkListStore" id="clock-type-model">
     <columns>
-      <!-- column-name label -->
       <column type="gchararray"/>
-      <!-- column-name clock-type -->
       <column type="gint"/>
     </columns>
     <data>
@@ -61,9 +55,7 @@
   </object>
   <object class="GtkListStore" id="difficulty-model">
     <columns>
-      <!-- column-name label -->
       <column type="gchararray"/>
-      <!-- column-name difficulty -->
       <column type="gchararray"/>
     </columns>
     <data>
@@ -97,9 +89,7 @@
   </object>
   <object class="GtkListStore" id="duration-model">
     <columns>
-      <!-- column-name label -->
       <column type="gchararray"/>
-      <!-- column-name duration -->
       <column type="gint"/>
     </columns>
     <data>
@@ -139,9 +129,7 @@
   </object>
   <object class="GtkListStore" id="move-format-model">
     <columns>
-      <!-- column-name label -->
       <column type="gchararray"/>
-      <!-- column-name move-format -->
       <column type="gchararray"/>
     </columns>
     <data>
@@ -165,9 +153,7 @@
   </object>
   <object class="GtkListStore" id="opponent-model">
     <columns>
-      <!-- column-name label -->
       <column type="gchararray"/>
-      <!-- column-name opposing-player -->
       <column type="gchararray"/>
     </columns>
     <data>
@@ -179,9 +165,7 @@
   </object>
   <object class="GtkListStore" id="orientation-model">
     <columns>
-      <!-- column-name label -->
       <column type="gchararray"/>
-      <!-- column-name board-orientation -->
       <column type="gchararray"/>
     </columns>
     <data>
@@ -205,9 +189,7 @@
   </object>
   <object class="GtkListStore" id="piece-style-model">
     <columns>
-      <!-- column-name label -->
       <column type="gchararray"/>
-      <!-- column-name piece-style -->
       <column type="gchararray"/>
     </columns>
     <data>
@@ -222,521 +204,443 @@
     </data>
   </object>
   <object class="GtkDialog" id="preferences">
-    <property name="can-focus">False</property>
+    <property name="can-focus">0</property>
     <property name="title" translatable="yes" comments="Title for preferences dialog">Preferences</property>
-    <property name="resizable">False</property>
-    <property name="destroy-with-parent">True</property>
-    <property name="type-hint">dialog</property>
+    <property name="resizable">0</property>
+    <property name="destroy-with-parent">1</property>
     <property name="use-header-bar">1</property>
-    <signal name="delete-event" handler="preferences_delete_event_cb" swapped="no"/>
-    <signal name="response" handler="preferences_response_cb" swapped="no"/>
-    <child internal-child="vbox">
+    <child>
       <object class="GtkBox">
-        <property name="visible">True</property>
-        <property name="can-focus">False</property>
+        <property name="can-focus">0</property>
         <child>
           <object class="GtkNotebook">
-            <property name="visible">True</property>
-            <property name="can-focus">True</property>
-            <property name="border-width">5</property>
             <child>
-              <object class="GtkGrid">
-                <property name="visible">True</property>
-                <property name="can-focus">False</property>
-                <property name="border-width">10</property>
-                <property name="column-spacing">5</property>
-                <property name="row-spacing">5</property>
-                <child>
-                  <object class="GtkLabel">
-                    <property name="visible">True</property>
-                    <property name="can-focus">False</property>
-                    <property name="halign">start</property>
-                    <property name="label" translatable="yes" comments="Preferences Dialog: Label before 
clock type (Fischer/Bronstein) combo box">_Clock type:</property>
-                    <property name="use-underline">True</property>
-                    <property name="mnemonic-widget">clock_type_combo</property>
-                  </object>
-                  <packing>
-                    <property name="top-attach">5</property>
-                    <property name="left-attach">0</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkLabel" id="timer_increment_label">
-                    <property name="visible">True</property>
-                    <property name="can-focus">False</property>
-                    <property name="halign">start</property>
-                    <property name="label" translatable="yes" comments="Preferences Dialog: Label before 
timer increment combo box">Timer _increment:</property>
-                    <property name="use-underline">True</property>
-                    <property name="mnemonic-widget">timer-increment-spin</property>
-                  </object>
-                  <packing>
-                    <property name="top-attach">6</property>
-                    <property name="left-attach">0</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkComboBox" id="clock_type_combo">
-                    <property name="visible">True</property>
-                    <property name="hexpand">True</property>
-                    <property name="can-focus">False</property>
-                    <property name="model">clock-type-model</property>
-                    <signal name="changed" handler="clock_type_changed_cb" swapped="no"/>
+              <object class="GtkNotebookPage">
+                <property name="child">
+                  <object class="GtkGrid">
+                    <property name="can-focus">0</property>
+                    <property name="column-spacing">6</property>
+                    <property name="row-spacing">6</property>
+                    <property name="margin-start">18</property>
+                    <property name="margin-end">18</property>
+                    <property name="margin-top">18</property>
+                    <property name="margin-bottom">18</property>
                     <child>
-                      <object class="GtkCellRendererText"/>
-                      <attributes>
-                        <attribute name="text">0</attribute>
-                      </attributes>
+                      <object class="GtkLabel">
+                        <property name="can-focus">0</property>
+                        <property name="halign">start</property>
+                        <property name="label" translatable="yes" comments="Preferences Dialog: Label before 
clock type (Fischer/Bronstein) combo box">_Clock type:</property>
+                        <property name="use-underline">1</property>
+                        <property name="mnemonic-widget">clock_type_combo</property>
+                        <layout>
+                          <property name="row">5</property>
+                          <property name="column">0</property>
+                        </layout>
+                      </object>
                     </child>
-                  </object>
-                  <packing>
-                    <property name="top-attach">5</property>
-                    <property name="left-attach">1</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkLabel">
-                    <property name="visible">True</property>
-                    <property name="can-focus">False</property>
-                    <property name="halign">start</property>
-                    <property name="label" translatable="yes" comments="Preferences Dialog: Label before 
player side (white/black) combo box">_Play as:</property>
-                    <property name="use-underline">True</property>
-                    <property name="mnemonic-widget">side_combo</property>
-                  </object>
-                  <packing>
-                    <property name="top-attach">1</property>
-                    <property name="left-attach">0</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkLabel">
-                    <property name="visible">True</property>
-                    <property name="can-focus">False</property>
-                    <property name="halign">start</property>
-                    <property name="label" translatable="yes" comments="Preferences Dialog: Label before 
opposing player combo box">_Opposing player:</property>
-                    <property name="use-underline">True</property>
-                    <property name="mnemonic-widget">opponent_combo</property>
-                  </object>
-                  <packing>
-                    <property name="top-attach">0</property>
-                    <property name="left-attach">0</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkLabel">
-                    <property name="visible">True</property>
-                    <property name="can-focus">False</property>
-                    <property name="halign">start</property>
-                    <property name="label" translatable="yes" comments="Preferences Dialog: Label before 
difficulty level combo box">_Difficulty:</property>
-                    <property name="use-underline">True</property>
-                    <property name="mnemonic-widget">difficulty_combo</property>
-                  </object>
-                  <packing>
-                    <property name="top-attach">2</property>
-                    <property name="left-attach">0</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkBox" id="timer_increment_box">
-                    <property name="visible">True</property>
-                    <property name="hexpand">True</property>
-                    <property name="can-focus">False</property>
-                    <property name="spacing">6</property>
                     <child>
-                      <object class="GtkSpinButton" id="timer-increment-spin">
-                        <property name="visible">True</property>
-                        <property name="can-focus">True</property>
-                        <property name="invisible-char">•</property>
-                        <property name="adjustment">timer_increment_adjustment</property>
-                        <property name="climb-rate">1</property>
-                        <property name="numeric">True</property>
+                      <object class="GtkLabel" id="timer_increment_label">
+                        <property name="can-focus">0</property>
+                        <property name="halign">start</property>
+                        <property name="label" translatable="yes" comments="Preferences Dialog: Label before 
timer increment combo box">Timer _increment:</property>
+                        <property name="use-underline">1</property>
+                        <property name="mnemonic-widget">timer-increment-spin</property>
+                        <layout>
+                          <property name="row">6</property>
+                          <property name="column">0</property>
+                        </layout>
                       </object>
-                      <packing>
-                        <property name="expand">True</property>
-                        <property name="fill">True</property>
-                        <property name="position">0</property>
-                      </packing>
                     </child>
                     <child>
-                      <object class="GtkComboBox" id="timer_increment_units_combo">
-                        <property name="visible">True</property>
-                        <property name="can-focus">False</property>
-                        <property name="model">timer-increment-units-model</property>
-                        <signal name="changed" handler="timer_increment_units_changed_cb" swapped="no"/>
+                      <object class="GtkComboBox" id="clock_type_combo">
+                        <property name="hexpand">1</property>
+                        <property name="can-focus">0</property>
+                        <property name="model">clock-type-model</property>
+                        <signal name="changed" handler="clock_type_changed_cb" swapped="no"/>
                         <child>
                           <object class="GtkCellRendererText"/>
                           <attributes>
                             <attribute name="text">0</attribute>
                           </attributes>
                         </child>
+                        <layout>
+                          <property name="row">5</property>
+                          <property name="column">1</property>
+                        </layout>
                       </object>
-                      <packing>
-                        <property name="expand">True</property>
-                        <property name="fill">True</property>
-                        <property name="position">1</property>
-                      </packing>
                     </child>
-                  </object>
-                  <packing>
-                    <property name="top-attach">6</property>
-                    <property name="left-attach">1</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkComboBox" id="side_combo">
-                    <property name="visible">True</property>
-                    <property name="hexpand">True</property>
-                    <property name="can-focus">False</property>
-                    <property name="model">side-model</property>
-                    <signal name="changed" handler="side_combo_changed_cb" swapped="no"/>
                     <child>
-                      <object class="GtkCellRendererText"/>
-                      <attributes>
-                        <attribute name="text">0</attribute>
-                      </attributes>
+                      <object class="GtkLabel">
+                        <property name="can-focus">0</property>
+                        <property name="halign">start</property>
+                        <property name="label" translatable="yes" comments="Preferences Dialog: Label before 
player side (white/black) combo box">_Play as:</property>
+                        <property name="use-underline">1</property>
+                        <property name="mnemonic-widget">side_combo</property>
+                        <layout>
+                          <property name="row">1</property>
+                          <property name="column">0</property>
+                        </layout>
+                      </object>
                     </child>
-                  </object>
-                  <packing>
-                    <property name="top-attach">1</property>
-                    <property name="left-attach">1</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkComboBox" id="opponent_combo">
-                    <property name="visible">True</property>
-                    <property name="hexpand">True</property>
-                    <property name="can-focus">False</property>
-                    <property name="model">opponent-model</property>
-                    <signal name="changed" handler="opponent_combo_changed_cb" swapped="no"/>
                     <child>
-                      <object class="GtkCellRendererText"/>
-                      <attributes>
-                        <attribute name="text">0</attribute>
-                      </attributes>
+                      <object class="GtkLabel">
+                        <property name="can-focus">0</property>
+                        <property name="halign">start</property>
+                        <property name="label" translatable="yes" comments="Preferences Dialog: Label before 
opposing player combo box">_Opposing player:</property>
+                        <property name="use-underline">1</property>
+                        <property name="mnemonic-widget">opponent_combo</property>
+                        <layout>
+                          <property name="row">0</property>
+                          <property name="column">0</property>
+                        </layout>
+                      </object>
                     </child>
-                  </object>
-                  <packing>
-                    <property name="top-attach">0</property>
-                    <property name="left-attach">1</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkComboBox" id="difficulty_combo">
-                    <property name="visible">True</property>
-                    <property name="hexpand">True</property>
-                    <property name="can-focus">False</property>
-                    <property name="model">difficulty-model</property>
-                    <signal name="changed" handler="difficulty_combo_changed_cb" swapped="no"/>
                     <child>
-                      <object class="GtkCellRendererText"/>
-                      <attributes>
-                        <attribute name="text">0</attribute>
-                      </attributes>
+                      <object class="GtkLabel">
+                        <property name="can-focus">0</property>
+                        <property name="halign">start</property>
+                        <property name="label" translatable="yes" comments="Preferences Dialog: Label before 
difficulty level combo box">_Difficulty:</property>
+                        <property name="use-underline">1</property>
+                        <property name="mnemonic-widget">difficulty_combo</property>
+                        <layout>
+                          <property name="row">2</property>
+                          <property name="column">0</property>
+                        </layout>
+                      </object>
                     </child>
-                  </object>
-                  <packing>
-                    <property name="top-attach">2</property>
-                    <property name="left-attach">1</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkBox" id="custom_duration_box">
-                    <property name="visible">True</property>
-                    <property name="hexpand">True</property>
-                    <property name="can-focus">False</property>
-                    <property name="spacing">6</property>
                     <child>
-                      <object class="GtkSpinButton">
-                        <property name="visible">True</property>
-                        <property name="can-focus">True</property>
-                        <property name="invisible-char">•</property>
-                        <property name="adjustment">duration_adjustment</property>
-                        <property name="climb-rate">1</property>
-                        <property name="numeric">True</property>
+                      <object class="GtkBox" id="timer_increment_box">
+                        <property name="hexpand">1</property>
+                        <property name="can-focus">0</property>
+                        <property name="spacing">6</property>
+                        <child>
+                          <object class="GtkSpinButton" id="timer-increment-spin">
+                            <property name="hexpand">1</property>
+                            <property name="adjustment">timer_increment_adjustment</property>
+                            <property name="climb-rate">1</property>
+                            <property name="numeric">1</property>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkComboBox" id="timer_increment_units_combo">
+                            <property name="hexpand">1</property>
+                            <property name="can-focus">0</property>
+                            <property name="model">timer-increment-units-model</property>
+                            <signal name="changed" handler="timer_increment_units_changed_cb" swapped="no"/>
+                            <child>
+                              <object class="GtkCellRendererText"/>
+                              <attributes>
+                                <attribute name="text">0</attribute>
+                              </attributes>
+                            </child>
+                          </object>
+                        </child>
+                        <layout>
+                          <property name="row">6</property>
+                          <property name="column">1</property>
+                        </layout>
                       </object>
-                      <packing>
-                        <property name="expand">True</property>
-                        <property name="fill">True</property>
-                        <property name="position">0</property>
-                      </packing>
                     </child>
                     <child>
-                      <object class="GtkComboBox" id="custom_duration_units_combo">
-                        <property name="visible">True</property>
-                        <property name="can-focus">False</property>
-                        <property name="model">custom-duration-units-model</property>
-                        <signal name="changed" handler="duration_units_changed_cb" swapped="no"/>
+                      <object class="GtkComboBox" id="side_combo">
+                        <property name="hexpand">1</property>
+                        <property name="can-focus">0</property>
+                        <property name="model">side-model</property>
+                        <signal name="changed" handler="side_combo_changed_cb" swapped="no"/>
                         <child>
                           <object class="GtkCellRendererText"/>
                           <attributes>
                             <attribute name="text">0</attribute>
                           </attributes>
                         </child>
+                        <layout>
+                          <property name="row">1</property>
+                          <property name="column">1</property>
+                        </layout>
                       </object>
-                      <packing>
-                        <property name="expand">True</property>
-                        <property name="fill">True</property>
-                        <property name="position">1</property>
-                      </packing>
                     </child>
-                  </object>
-                  <packing>
-                    <property name="top-attach">4</property>
-                    <property name="left-attach">1</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkLabel">
-                    <property name="visible">True</property>
-                    <property name="can-focus">False</property>
-                    <property name="halign">start</property>
-                    <property name="label" translatable="yes" comments="Preferences Dialog: Label before 
game timer settings">_Time limit:</property>
-                    <property name="use-underline">True</property>
-                    <property name="mnemonic-widget">duration_combo</property>
-                  </object>
-                  <packing>
-                    <property name="top-attach">3</property>
-                    <property name="left-attach">0</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkComboBox" id="duration_combo">
-                    <property name="visible">True</property>
-                    <property name="hexpand">True</property>
-                    <property name="can-focus">False</property>
-                    <property name="model">duration-model</property>
-                    <signal name="changed" handler="duration_combo_changed_cb" swapped="no"/>
                     <child>
-                      <object class="GtkCellRendererText"/>
-                      <attributes>
-                        <attribute name="text">0</attribute>
-                      </attributes>
+                      <object class="GtkComboBox" id="opponent_combo">
+                        <property name="hexpand">1</property>
+                        <property name="can-focus">0</property>
+                        <property name="model">opponent-model</property>
+                        <signal name="changed" handler="opponent_combo_changed_cb" swapped="no"/>
+                        <child>
+                          <object class="GtkCellRendererText"/>
+                          <attributes>
+                            <attribute name="text">0</attribute>
+                          </attributes>
+                        </child>
+                        <layout>
+                          <property name="row">0</property>
+                          <property name="column">1</property>
+                        </layout>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkComboBox" id="difficulty_combo">
+                        <property name="hexpand">1</property>
+                        <property name="can-focus">0</property>
+                        <property name="model">difficulty-model</property>
+                        <signal name="changed" handler="difficulty_combo_changed_cb" swapped="no"/>
+                        <child>
+                          <object class="GtkCellRendererText"/>
+                          <attributes>
+                            <attribute name="text">0</attribute>
+                          </attributes>
+                        </child>
+                        <layout>
+                          <property name="row">2</property>
+                          <property name="column">1</property>
+                        </layout>
+                      </object>
                     </child>
-                  </object>
-                  <packing>
-                    <property name="top-attach">3</property>
-                    <property name="left-attach">1</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkBox">
-                    <property name="visible">True</property>
-                    <property name="hexpand">True</property>
-                    <property name="can-focus">False</property>
-                    <property name="spacing">5</property>
                     <child>
-                      <object class="GtkImage">
-                        <property name="visible">True</property>
-                        <property name="can-focus">False</property>
-                        <property name="icon-name">dialog-information-symbolic</property>
-                        <property name="icon-size">6</property>
+                      <object class="GtkBox" id="custom_duration_box">
+                        <property name="hexpand">1</property>
+                        <property name="can-focus">0</property>
+                        <property name="spacing">6</property>
+                        <child>
+                          <object class="GtkSpinButton">
+                            <property name="hexpand">1</property>
+                            <property name="adjustment">duration_adjustment</property>
+                            <property name="climb-rate">1</property>
+                            <property name="numeric">1</property>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkComboBox" id="custom_duration_units_combo">
+                            <property name="hexpand">1</property>
+                            <property name="can-focus">0</property>
+                            <property name="model">custom-duration-units-model</property>
+                            <signal name="changed" handler="duration_units_changed_cb" swapped="no"/>
+                            <child>
+                              <object class="GtkCellRendererText"/>
+                              <attributes>
+                                <attribute name="text">0</attribute>
+                              </attributes>
+                            </child>
+                          </object>
+                        </child>
+                        <layout>
+                          <property name="row">4</property>
+                          <property name="column">1</property>
+                        </layout>
                       </object>
-                      <packing>
-                        <property name="expand">False</property>
-                        <property name="fill">True</property>
-                        <property name="position">0</property>
-                      </packing>
                     </child>
                     <child>
                       <object class="GtkLabel">
-                        <property name="visible">True</property>
-                        <property name="can-focus">False</property>
+                        <property name="can-focus">0</property>
                         <property name="halign">start</property>
-                        <property name="label" translatable="yes" comments="Preferences dialog: Label to 
notify user that the settings are applied for the next game">Changes will take effect for the next 
game.</property>
+                        <property name="label" translatable="yes" comments="Preferences Dialog: Label before 
game timer settings">_Time limit:</property>
+                        <property name="use-underline">1</property>
+                        <property name="mnemonic-widget">duration_combo</property>
+                        <layout>
+                          <property name="row">3</property>
+                          <property name="column">0</property>
+                        </layout>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkComboBox" id="duration_combo">
+                        <property name="hexpand">1</property>
+                        <property name="can-focus">0</property>
+                        <property name="model">duration-model</property>
+                        <signal name="changed" handler="duration_combo_changed_cb" swapped="no"/>
+                        <child>
+                          <object class="GtkCellRendererText"/>
+                          <attributes>
+                            <attribute name="text">0</attribute>
+                          </attributes>
+                        </child>
+                        <layout>
+                          <property name="row">3</property>
+                          <property name="column">1</property>
+                        </layout>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkBox">
+                        <property name="hexpand">1</property>
+                        <property name="can-focus">0</property>
+                        <property name="spacing">5</property>
+                        <child>
+                          <object class="GtkImage">
+                            <property name="can-focus">0</property>
+                            <property name="icon-name">dialog-information-symbolic</property>
+                            <property name="icon-size">large</property>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkLabel">
+                            <property name="hexpand">1</property>
+                            <property name="can-focus">0</property>
+                            <property name="halign">start</property>
+                            <property name="label" translatable="yes" comments="Preferences dialog: Label to 
notify user that the settings are applied for the next game">Changes will take effect for the next 
game.</property>
+                          </object>
+                        </child>
+                        <layout>
+                          <property name="row">7</property>
+                          <property name="column">0</property>
+                          <property name="column-span">2</property>
+                        </layout>
                       </object>
-                      <packing>
-                        <property name="expand">True</property>
-                        <property name="fill">True</property>
-                        <property name="position">1</property>
-                      </packing>
                     </child>
                   </object>
-                  <packing>
-                    <property name="top-attach">7</property>
-                    <property name="left-attach">0</property>
-                    <property name="width">2</property>
-                  </packing>
-                </child>
-              </object>
-            </child>
-            <child type="tab">
-              <object class="GtkLabel">
-                <property name="visible">True</property>
-                <property name="can-focus">False</property>
-                <property name="label" translatable="yes" comments="Preferences Dialog: Tab title for game 
preferences">_Game</property>
-                <property name="use-underline">True</property>
+                </property>
+                <property name="tab">
+                  <object class="GtkLabel">
+                    <property name="can-focus">0</property>
+                    <property name="label" translatable="yes" comments="Preferences Dialog: Tab title for 
game preferences">_Game</property>
+                    <property name="use-underline">1</property>
+                  </object>
+                </property>
               </object>
-              <packing>
-                <property name="tab-fill">False</property>
-              </packing>
             </child>
             <child>
-              <object class="GtkGrid">
-                <property name="visible">True</property>
-                <property name="can-focus">False</property>
-                <property name="border-width">10</property>
-                <property name="column-spacing">5</property>
-                <property name="row-spacing">5</property>
-                <child>
-                  <object class="GtkLabel">
-                    <property name="visible">True</property>
-                    <property name="can-focus">False</property>
-                    <property name="halign">start</property>
-                    <property name="label" translatable="yes" comments="Preferences Dialog: Label before 
board orientation combo box">Board _orientation:</property>
-                    <property name="use-underline">True</property>
-                    <property name="mnemonic-widget">orientation_combo</property>
-                  </object>
-                  <packing>
-                    <property name="left-attach">0</property>
-                    <property name="top-attach">0</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkLabel">
-                    <property name="visible">True</property>
-                    <property name="can-focus">False</property>
-                    <property name="halign">start</property>
-                    <property name="label" translatable="yes" comments="Preferences Dialog: Label before 
move format combo box">Move _format:</property>
-                    <property name="use-underline">True</property>
-                    <property name="mnemonic-widget">move_format_combo</property>
-                  </object>
-                  <packing>
-                    <property name="left-attach">0</property>
-                    <property name="top-attach">1</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkLabel">
-                    <property name="visible">True</property>
-                    <property name="can-focus">False</property>
-                    <property name="halign">start</property>
-                    <property name="label" translatable="yes" comments="Preferences Dialog: Label before 
piece style combo box">_Piece style:</property>
-                    <property name="use-underline">True</property>
-                    <property name="mnemonic-widget">piece_style_combo</property>
-                  </object>
-                  <packing>
-                    <property name="left-attach">0</property>
-                    <property name="top-attach">2</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkComboBox" id="orientation_combo">
-                    <property name="visible">True</property>
-                    <property name="hexpand">True</property>
-                    <property name="can-focus">False</property>
-                    <property name="model">orientation-model</property>
-                    <signal name="changed" handler="orientation_combo_changed_cb" swapped="no"/>
+              <object class="GtkNotebookPage">
+                <property name="child">
+                  <object class="GtkGrid">
+                    <property name="can-focus">0</property>
+                    <property name="column-spacing">6</property>
+                    <property name="row-spacing">6</property>
+                    <property name="margin-start">18</property>
+                    <property name="margin-end">18</property>
+                    <property name="margin-top">18</property>
+                    <property name="margin-bottom">18</property>
                     <child>
-                      <object class="GtkCellRendererText"/>
-                      <attributes>
-                        <attribute name="text">0</attribute>
-                      </attributes>
+                      <object class="GtkLabel">
+                        <property name="can-focus">0</property>
+                        <property name="halign">start</property>
+                        <property name="label" translatable="yes" comments="Preferences Dialog: Label before 
board orientation combo box">Board _orientation:</property>
+                        <property name="use-underline">1</property>
+                        <property name="mnemonic-widget">orientation_combo</property>
+                        <layout>
+                          <property name="column">0</property>
+                          <property name="row">0</property>
+                        </layout>
+                      </object>
                     </child>
-                  </object>
-                  <packing>
-                    <property name="left-attach">1</property>
-                    <property name="top-attach">0</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkComboBox" id="move_format_combo">
-                    <property name="visible">True</property>
-                    <property name="hexpand">True</property>
-                    <property name="can-focus">False</property>
-                    <property name="model">move-format-model</property>
-                    <signal name="changed" handler="move_format_combo_changed_cb" swapped="no"/>
                     <child>
-                      <object class="GtkCellRendererText"/>
-                      <attributes>
-                        <attribute name="text">0</attribute>
-                      </attributes>
+                      <object class="GtkLabel">
+                        <property name="can-focus">0</property>
+                        <property name="halign">start</property>
+                        <property name="label" translatable="yes" comments="Preferences Dialog: Label before 
move format combo box">Move _format:</property>
+                        <property name="use-underline">1</property>
+                        <property name="mnemonic-widget">move_format_combo</property>
+                        <layout>
+                          <property name="column">0</property>
+                          <property name="row">1</property>
+                        </layout>
+                      </object>
                     </child>
-                  </object>
-                  <packing>
-                    <property name="left-attach">1</property>
-                    <property name="top-attach">1</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkComboBox" id="piece_style_combo">
-                    <property name="visible">True</property>
-                    <property name="hexpand">True</property>
-                    <property name="can-focus">True</property>
-                    <property name="model">piece-style-model</property>
-                    <signal name="changed" handler="piece_style_combo_changed_cb" swapped="no"/>
                     <child>
-                      <object class="GtkCellRendererText"/>
-                      <attributes>
-                        <attribute name="text">0</attribute>
-                      </attributes>
+                      <object class="GtkLabel">
+                        <property name="can-focus">0</property>
+                        <property name="halign">start</property>
+                        <property name="label" translatable="yes" comments="Preferences Dialog: Label before 
piece style combo box">_Piece style:</property>
+                        <property name="use-underline">1</property>
+                        <property name="mnemonic-widget">piece_style_combo</property>
+                        <layout>
+                          <property name="column">0</property>
+                          <property name="row">2</property>
+                        </layout>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkComboBox" id="orientation_combo">
+                        <property name="hexpand">1</property>
+                        <property name="can-focus">0</property>
+                        <property name="model">orientation-model</property>
+                        <signal name="changed" handler="orientation_combo_changed_cb" swapped="no"/>
+                        <child>
+                          <object class="GtkCellRendererText"/>
+                          <attributes>
+                            <attribute name="text">0</attribute>
+                          </attributes>
+                        </child>
+                        <layout>
+                          <property name="column">1</property>
+                          <property name="row">0</property>
+                        </layout>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkComboBox" id="move_format_combo">
+                        <property name="hexpand">1</property>
+                        <property name="can-focus">0</property>
+                        <property name="model">move-format-model</property>
+                        <signal name="changed" handler="move_format_combo_changed_cb" swapped="no"/>
+                        <child>
+                          <object class="GtkCellRendererText"/>
+                          <attributes>
+                            <attribute name="text">0</attribute>
+                          </attributes>
+                        </child>
+                        <layout>
+                          <property name="column">1</property>
+                          <property name="row">1</property>
+                        </layout>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkComboBox" id="piece_style_combo">
+                        <property name="hexpand">1</property>
+                        <property name="model">piece-style-model</property>
+                        <signal name="changed" handler="piece_style_combo_changed_cb" swapped="no"/>
+                        <child>
+                          <object class="GtkCellRendererText"/>
+                          <attributes>
+                            <attribute name="text">0</attribute>
+                          </attributes>
+                        </child>
+                        <layout>
+                          <property name="column">1</property>
+                          <property name="row">2</property>
+                        </layout>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkCheckButton" id="show_numbering_check">
+                        <property name="label" translatable="yes" comments="Preferences Dialog: Check box 
for selecting if board numbering is visible">_Board numbering</property>
+                        <property name="use-underline">1</property>
+                        <layout>
+                          <property name="column">0</property>
+                          <property name="row">3</property>
+                          <property name="column-span">2</property>
+                        </layout>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkCheckButton" id="show_move_hints_check">
+                        <property name="label" translatable="yes" comments="Preferences Dialog: Check box 
for selecting if move hints are visible">_Move hints</property>
+                        <property name="use-underline">1</property>
+                        <layout>
+                          <property name="column">0</property>
+                          <property name="row">4</property>
+                          <property name="column-span">2</property>
+                        </layout>
+                      </object>
                     </child>
                   </object>
-                  <packing>
-                    <property name="left-attach">1</property>
-                    <property name="top-attach">2</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkCheckButton" id="show_numbering_check">
-                    <property name="label" translatable="yes" comments="Preferences Dialog: Check box for 
selecting if board numbering is visible">_Board numbering</property>
-                    <property name="visible">True</property>
-                    <property name="can-focus">True</property>
-                    <property name="receives-default">False</property>
-                    <property name="use-underline">True</property>
-                    <property name="draw-indicator">True</property>
-                  </object>
-                  <packing>
-                    <property name="left-attach">0</property>
-                    <property name="top-attach">3</property>
-                    <property name="width">2</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkCheckButton" id="show_move_hints_check">
-                    <property name="label" translatable="yes" comments="Preferences Dialog: Check box for 
selecting if move hints are visible">_Move hints</property>
-                    <property name="visible">True</property>
-                    <property name="can-focus">True</property>
-                    <property name="receives-default">False</property>
-                    <property name="use-underline">True</property>
-                    <property name="draw-indicator">True</property>
+                </property>
+                <property name="tab">
+                  <object class="GtkLabel">
+                    <property name="can-focus">0</property>
+                    <property name="label" translatable="yes" comments="Preferences Dialog: Title of 
appearance options tab">_Appearance</property>
+                    <property name="use-underline">1</property>
                   </object>
-                  <packing>
-                    <property name="left-attach">0</property>
-                    <property name="top-attach">4</property>
-                    <property name="width">2</property>
-                  </packing>
-                </child>
-              </object>
-              <packing>
-                <property name="position">1</property>
-              </packing>
-            </child>
-            <child type="tab">
-              <object class="GtkLabel">
-                <property name="visible">True</property>
-                <property name="can-focus">False</property>
-                <property name="label" translatable="yes" comments="Preferences Dialog: Title of appearance 
options tab">_Appearance</property>
-                <property name="use-underline">True</property>
+                </property>
               </object>
-              <packing>
-                <property name="position">1</property>
-                <property name="tab-fill">False</property>
-              </packing>
             </child>
           </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="fill">True</property>
-            <property name="position">2</property>
-          </packing>
         </child>
       </object>
     </child>
   </object>
   <object class="GtkListStore" id="side-model">
     <columns>
-      <!-- column-name label -->
       <column type="gchararray"/>
-      <!-- column-name play-as -->
       <column type="gint"/>
     </columns>
     <data>
diff --git a/data/promotion-type-selector.ui b/data/promotion-type-selector.ui
index 6d48fca..393656c 100644
--- a/data/promotion-type-selector.ui
+++ b/data/promotion-type-selector.ui
@@ -1,227 +1,164 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <!-- interface-requires gtk+ 3.12 -->
+  <requires lib="gtk" version="4.0"/>
   <object class="GtkDialog" id="dialog_promotion_type_selector">
-    <property name="can-focus">False</property>
-    <property name="border-width">5</property>
+    <property name="can-focus">0</property>
     <property name="title" translatable="yes">Select Promotion Type</property>
-    <property name="type-hint">dialog</property>
-    <property name="has-resize-grip">False</property>
     <property name="use-header-bar">1</property>
-    <child internal-child="vbox">
-      <object class="GtkBox">
+    <child>
+      <object class="GtkBox" id="button_box">
+        <property name="can-focus">0</property>
+        <property name="margin-start">6</property>
+        <property name="margin-end">6</property>
+        <property name="margin-top">6</property>
+        <property name="margin-bottom">6</property>
         <child>
-          <object class="GtkButtonBox" id="button_box">
-            <property name="visible">true</property>
-            <property name="can-focus">False</property>
-            <property name="margin">6</property>
-            <property name="layout-style">expand</property>
+          <object class="GtkButton" id="button_queen">
+            <property name="width-request">100</property>
+            <property name="height-request">120</property>
+            <property name="receives-default">1</property>
+            <property name="use-underline">1</property>
+            <signal name="clicked" handler="queen_selected_cb"/>
             <child>
-              <object class="GtkButton" id="button_queen">
-                <property name="width-request">100</property>
-                <property name="height-request">120</property>
-                <property name="visible">True</property>
-                <property name="can-focus">True</property>
-                <property name="receives-default">True</property>
-                <property name="use-underline">True</property>
-                <property name="image-position">top</property>
-                <signal name="clicked" handler="queen_selected_cb"/>
+              <object class="GtkGrid">
+                <property name="can-focus">0</property>
+                <property name="halign">center</property>
+                <property name="valign">center</property>
                 <child>
-                  <object class="GtkGrid">
-                    <property name="visible">True</property>
-                    <property name="can-focus">False</property>
-                    <property name="halign">center</property>
-                    <property name="valign">center</property>
-                    <child>
-                      <object class="GtkLabel">
-                        <property name="visible">True</property>
-                        <property name="can-focus">False</property>
-                        <property name="label" translatable="yes">_Queen</property>
-                        <property name="use-underline">True</property>
-                      </object>
-                      <packing>
-                        <property name="left-attach">0</property>
-                        <property name="top-attach">1</property>
-                        <property name="width">1</property>
-                        <property name="height">1</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkImage" id="image_queen">
-                        <property name="visible">True</property>
-                        <property name="can-focus">False</property>
-                      </object>
-                      <packing>
-                        <property name="left-attach">0</property>
-                        <property name="top-attach">0</property>
-                        <property name="width">1</property>
-                        <property name="height">1</property>
-                      </packing>
-                    </child>
+                  <object class="GtkLabel">
+                    <property name="can-focus">0</property>
+                    <property name="label" translatable="yes">_Queen</property>
+                    <property name="use-underline">1</property>
+                    <layout>
+                      <property name="column">0</property>
+                      <property name="row">1</property>
+                    </layout>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkImage" id="image_queen">
+                    <property name="can-focus">0</property>
+                    <layout>
+                      <property name="column">0</property>
+                      <property name="row">0</property>
+                    </layout>
                   </object>
                 </child>
               </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">True</property>
-                <property name="position">0</property>
-              </packing>
             </child>
+          </object>
+        </child>
+        <child>
+          <object class="GtkButton" id="button_knight">
+            <property name="width-request">100</property>
+            <property name="height-request">120</property>
+            <property name="receives-default">1</property>
+            <property name="use-underline">1</property>
+            <signal name="clicked" handler="knight_selected_cb"/>
             <child>
-              <object class="GtkButton" id="button_knight">
-                <property name="width-request">100</property>
-                <property name="height-request">120</property>
-                <property name="visible">True</property>
-                <property name="can-focus">True</property>
-                <property name="receives-default">True</property>
-                <property name="use-underline">True</property>
-                <property name="image-position">top</property>
-                <signal name="clicked" handler="knight_selected_cb"/>
+              <object class="GtkGrid">
+                <property name="can-focus">0</property>
+                <property name="halign">center</property>
+                <property name="valign">center</property>
                 <child>
-                  <object class="GtkGrid">
-                    <property name="visible">True</property>
-                    <property name="can-focus">False</property>
-                    <property name="halign">center</property>
-                    <property name="valign">center</property>
-                    <child>
-                      <object class="GtkLabel">
-                        <property name="visible">True</property>
-                        <property name="can-focus">False</property>
-                        <property name="label" translatable="yes">_Knight</property>
-                        <property name="use-underline">True</property>
-                      </object>
-                      <packing>
-                        <property name="left-attach">0</property>
-                        <property name="top-attach">1</property>
-                        <property name="width">1</property>
-                        <property name="height">1</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkImage" id="image_knight">
-                        <property name="visible">True</property>
-                        <property name="can-focus">False</property>
-                      </object>
-                      <packing>
-                        <property name="left-attach">0</property>
-                        <property name="top-attach">0</property>
-                        <property name="width">1</property>
-                        <property name="height">1</property>
-                      </packing>
-                    </child>
+                  <object class="GtkLabel">
+                    <property name="can-focus">0</property>
+                    <property name="label" translatable="yes">_Knight</property>
+                    <property name="use-underline">1</property>
+                    <layout>
+                      <property name="column">0</property>
+                      <property name="row">1</property>
+                    </layout>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkImage" id="image_knight">
+                    <property name="can-focus">0</property>
+                    <layout>
+                      <property name="column">0</property>
+                      <property name="row">0</property>
+                    </layout>
                   </object>
                 </child>
               </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">True</property>
-                <property name="position">1</property>
-              </packing>
             </child>
+          </object>
+        </child>
+        <child>
+          <object class="GtkButton" id="button_rook">
+            <property name="width-request">100</property>
+            <property name="height-request">120</property>
+            <property name="receives-default">1</property>
+            <property name="use-underline">1</property>
+            <signal name="clicked" handler="rook_selected_cb"/>
             <child>
-              <object class="GtkButton" id="button_rook">
-                <property name="width-request">100</property>
-                <property name="height-request">120</property>
-                <property name="visible">True</property>
-                <property name="can-focus">True</property>
-                <property name="receives-default">True</property>
-                <property name="use-underline">True</property>
-                <property name="image-position">top</property>
-                <signal name="clicked" handler="rook_selected_cb"/>
+              <object class="GtkGrid">
+                <property name="can-focus">0</property>
+                <property name="halign">center</property>
+                <property name="valign">center</property>
                 <child>
-                  <object class="GtkGrid">
-                    <property name="visible">True</property>
-                    <property name="can-focus">False</property>
-                    <property name="halign">center</property>
-                    <property name="valign">center</property>
-                    <child>
-                      <object class="GtkLabel">
-                        <property name="visible">True</property>
-                        <property name="can-focus">False</property>
-                        <property name="label" translatable="yes">_Rook</property>
-                        <property name="use-underline">True</property>
-                      </object>
-                      <packing>
-                        <property name="left-attach">0</property>
-                        <property name="top-attach">1</property>
-                        <property name="width">1</property>
-                        <property name="height">1</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkImage" id="image_rook">
-                        <property name="visible">True</property>
-                        <property name="can-focus">False</property>
-                      </object>
-                      <packing>
-                        <property name="left-attach">0</property>
-                        <property name="top-attach">0</property>
-                        <property name="width">1</property>
-                        <property name="height">1</property>
-                      </packing>
-                    </child>
+                  <object class="GtkLabel">
+                    <property name="can-focus">0</property>
+                    <property name="label" translatable="yes">_Rook</property>
+                    <property name="use-underline">1</property>
+                    <layout>
+                      <property name="column">0</property>
+                      <property name="row">1</property>
+                    </layout>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkImage" id="image_rook">
+                    <property name="can-focus">0</property>
+                    <layout>
+                      <property name="column">0</property>
+                      <property name="row">0</property>
+                    </layout>
                   </object>
                 </child>
               </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">True</property>
-                <property name="position">2</property>
-              </packing>
             </child>
+          </object>
+        </child>
+        <child>
+          <object class="GtkButton" id="button_bishop">
+            <property name="width-request">100</property>
+            <property name="height-request">120</property>
+            <property name="receives-default">1</property>
+            <property name="use-underline">1</property>
+            <signal name="clicked" handler="bishop_selected_cb"/>
             <child>
-              <object class="GtkButton" id="button_bishop">
-                <property name="width-request">100</property>
-                <property name="height-request">120</property>
-                <property name="visible">True</property>
-                <property name="can-focus">True</property>
-                <property name="receives-default">True</property>
-                <property name="use-underline">True</property>
-                <property name="image-position">top</property>
-                <signal name="clicked" handler="bishop_selected_cb"/>
+              <object class="GtkGrid">
+                <property name="can-focus">0</property>
+                <property name="halign">center</property>
+                <property name="valign">center</property>
+                <child>
+                  <object class="GtkLabel">
+                    <property name="can-focus">0</property>
+                    <property name="label" translatable="yes">_Bishop</property>
+                    <property name="use-underline">1</property>
+                    <layout>
+                      <property name="column">0</property>
+                      <property name="row">1</property>
+                    </layout>
+                  </object>
+                </child>
                 <child>
-                  <object class="GtkGrid">
-                    <property name="visible">True</property>
-                    <property name="can-focus">False</property>
-                    <property name="halign">center</property>
-                    <property name="valign">center</property>
-                    <child>
-                      <object class="GtkLabel">
-                        <property name="visible">True</property>
-                        <property name="can-focus">False</property>
-                        <property name="label" translatable="yes">_Bishop</property>
-                        <property name="use-underline">True</property>
-                      </object>
-                      <packing>
-                        <property name="left-attach">0</property>
-                        <property name="top-attach">1</property>
-                        <property name="width">1</property>
-                        <property name="height">1</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkImage" id="image_bishop">
-                        <property name="visible">True</property>
-                        <property name="can-focus">False</property>
-                      </object>
-                      <packing>
-                        <property name="left-attach">0</property>
-                        <property name="top-attach">0</property>
-                        <property name="width">1</property>
-                        <property name="height">1</property>
-                      </packing>
-                    </child>
+                  <object class="GtkImage" id="image_bishop">
+                    <property name="can-focus">0</property>
+                    <layout>
+                      <property name="column">0</property>
+                      <property name="row">0</property>
+                    </layout>
                   </object>
                 </child>
               </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">True</property>
-                <property name="position">3</property>
-              </packing>
             </child>
           </object>
         </child>
+        <style>
+          <class name="linked"/>
+        </style>
       </object>
     </child>
   </object>
diff --git a/meson.build b/meson.build
index 5cb3cee..5bd07da 100644
--- a/meson.build
+++ b/meson.build
@@ -32,7 +32,7 @@ min_glib_version = '2.44.0'
 gio = dependency('gio-unix-2.0', version: '>=' + min_glib_version)
 glib = dependency('glib-2.0', version: '>=' + min_glib_version)
 gmodule = dependency('gmodule-2.0', version: '>=' + min_glib_version)
-gtk = dependency('gtk+-3.0', version: '>= 3.20.0')
+gtk = dependency('gtk4')
 librsvg = dependency('librsvg-2.0', version: '>= 2.46.0')
 
 posix = meson.get_compiler('vala').find_library('posix')
diff --git a/src/chess-scene.vala b/src/chess-scene.vala
index 35f25ec..021384c 100644
--- a/src/chess-scene.vala
+++ b/src/chess-scene.vala
@@ -78,9 +78,11 @@ public class ChessScene : Object
     private double animation_time;
 
     public signal bool is_human (ChessPlayer player);
-    public signal PieceType? choose_promotion_type ();
     public signal void changed ();
 
+    public delegate void PromotionTypeCompletionHandler (PieceType? type);
+    public signal void choose_promotion_type (owned PromotionTypeCompletionHandler handler);
+
     public int selected_rank = -1;
     public int selected_file = -1;
 
@@ -225,24 +227,29 @@ public class ChessScene : Object
         else if (selected_file != -1)
         {
             bool can_move = game.current_player.move_with_coords (selected_rank, selected_file,
-                rank, file, false);
-            if (can_move && (get_selected_piece ()).type == PieceType.PAWN &&
+                                                                  rank, file, false);
+
+            if (can_move && get_selected_piece ().type == PieceType.PAWN &&
                 (rank == 0 || rank == 7))
             {
                 // Prompt user for selecting promotion type
-                PieceType? promotion_selection = choose_promotion_type ();
-
-                // If promotion dialog is closed, do nothing
-                if (promotion_selection == null)
-                    return;
-
-                game.current_player.move_with_coords (selected_rank,
-                    selected_file, rank, file, true, promotion_selection);
-                selected_rank = selected_file = -1;
+                choose_promotion_type ((promotion_selection) => {
+                    if (promotion_selection != null)
+                    {
+                        game.current_player.move_with_coords (selected_rank,
+                                                              selected_file,
+                                                              rank, file,
+                                                              true, promotion_selection);
+                        selected_rank = selected_file = -1;
+
+                        update_board ();
+                        changed ();
+                    }
+                });
+                return;
             }
-            // Need to check selected_file here again for promotion case
-            if (selected_file != -1 &&
-                game.current_player.move_with_coords (selected_rank, selected_file, rank, file))
+
+            if (game.current_player.move_with_coords (selected_rank, selected_file, rank, file))
                 selected_rank = selected_file = -1;
         }
 
diff --git a/src/chess-view.vala b/src/chess-view.vala
index dda4866..a92f643 100644
--- a/src/chess-view.vala
+++ b/src/chess-view.vala
@@ -19,7 +19,7 @@ public class ChessView : Gtk.DrawingArea
     private Cairo.Surface? selected_model_surface;
     private string loaded_theme_name = "";
 
-    private Gtk.GestureMultiPress click_controller; // for keeping in memory
+    private Gtk.GestureClick click_controller; // for keeping in memory
 
     private ChessScene _scene;
     public ChessScene scene
@@ -40,22 +40,22 @@ public class ChessView : Gtk.DrawingArea
 
     construct
     {
-        add_events (Gdk.EventMask.BUTTON_PRESS_MASK | Gdk.EventMask.BUTTON_RELEASE_MASK);
-
         init_mouse ();
+        set_draw_func (draw);
+
+        hexpand = true;
+        vexpand = true;
     }
 
-    public override bool configure_event (Gdk.EventConfigure event)
+    public override void resize (int width, int height)
     {
-        int short_edge = int.min (get_allocated_width (), get_allocated_height ());
+        int short_edge = int.min (width, height);
 
         square_size = (int) Math.floor ((short_edge - 2 * border) / 9.0);
         var extra = square_size * 0.1;
         if (extra < 3)
             extra = 3;
         selected_square_size = square_size + 2 * (int) (extra + 0.5);
-
-        return true;
     }
 
     private void render_piece (Cairo.Context c1, Cairo.Context c2, string name, int offset)
@@ -109,12 +109,11 @@ public class ChessView : Gtk.DrawingArea
         loaded_theme_name = scene.theme_name;
     }
 
-    public override bool draw (Cairo.Context c)
+    public void draw (Gtk.DrawingArea self, Cairo.Context c, int width, int height)
     {
         load_theme (c);
 
         c.translate (get_allocated_width () / 2, get_allocated_height () / 2);
-        //c.scale (s, s);
         c.rotate (Math.PI * scene.board_angle / 180.0);
 
         int board_size = (int) Math.ceil (square_size * 4 + border_size);
@@ -220,7 +219,7 @@ public class ChessView : Gtk.DrawingArea
         {
             c.rotate (Math.PI * scene.board_angle / 180.0);
             draw_paused_overlay (c);
-            return true;
+            return;
         }
 
         /* Draw the pieces */
@@ -257,8 +256,6 @@ public class ChessView : Gtk.DrawingArea
                 }
             }
         }
-
-        return true;
     }
 
     private void draw_piece (Cairo.Context c, Cairo.Surface surface, int size, ChessPiece piece, double 
alpha)
@@ -274,13 +271,14 @@ public class ChessView : Gtk.DrawingArea
         c.paint_with_alpha (alpha);
     }
 
-    private inline void init_mouse ()
+    private void init_mouse ()
     {
-        click_controller = new Gtk.GestureMultiPress (this);    // only reacts to Gdk.BUTTON_PRIMARY
+        click_controller = new Gtk.GestureClick ();    // only reacts to Gdk.BUTTON_PRIMARY
         click_controller.pressed.connect (on_click);
+        add_controller (click_controller);
     }
 
-    private inline void on_click (Gtk.GestureMultiPress _click_controller, int n_press, double event_x, 
double event_y)
+    private void on_click (Gtk.GestureClick _click_controller, int n_press, double event_x, double event_y)
     {
         if (scene.game == null || scene.game.should_show_paused_overlay)
             return;
diff --git a/src/gnome-chess.vala b/src/gnome-chess.vala
index 751b6a9..4f282d6 100644
--- a/src/gnome-chess.vala
+++ b/src/gnome-chess.vala
@@ -21,29 +21,24 @@ public class ChessApplication : Gtk.Application
     }
     private LayoutMode layout_mode;
 
-    private bool is_tiled;
-    private bool is_maximized;
-    private int window_width;
-    private int window_height;
-
     private GLib.Settings settings;
-    private ApplicationWindow window;
+    private unowned ApplicationWindow window;
+    private Box main_box;
     private InfoBar info_bar;
     private Label info_bar_label;
-    private Container view_container;
     private ChessScene scene;
     private ChessView view;
     private Button pause_resume_button;
     private Box navigation_box;
-    private Widget first_move_button;
-    private Widget prev_move_button;
-    private Widget next_move_button;
-    private Widget last_move_button;
+    private Button first_move_button;
+    private Button prev_move_button;
+    private Button next_move_button;
+    private Button last_move_button;
     private ComboBox history_combo;
     private Box clock_box;
-    private Widget white_time_label;
-    private Widget black_time_label;
-    private Widget timer_increment_label;
+    private DrawingArea white_time_label;
+    private DrawingArea black_time_label;
+    private Label timer_increment_label;
     private HeaderBar headerbar;
 
     private Dialog? preferences_dialog = null;
@@ -58,10 +53,17 @@ public class ChessApplication : Gtk.Application
     private ComboBox timer_increment_units_combo;
     private ComboBox custom_duration_units_combo;
     private uint save_duration_timeout = 0;
-    private FileChooserNative open_dialog = null;
+    private FileChooserNative? open_dialog = null;
     private FileChooserNative? save_dialog = null;
+    private delegate void PromptSaveGameCallback (bool cancelled);
+    private PromptSaveGameCallback? prompt_save_game_cb = null;
+    private MessageDialog? prompt_save_game_dialog = null;
+    private MessageDialog? save_error_dialog = null;
+    private MessageDialog? claim_draw_dialog = null;
+    private MessageDialog? resign_dialog = null;
     private AboutDialog? about_dialog = null;
     private Dialog? promotion_type_selector_dialog = null;
+    private ChessScene.PromotionTypeCompletionHandler? promotion_type_completion_handler = null;
 
     private PGNGame pgn_game;
     private ChessGame game;
@@ -93,6 +95,10 @@ Copyright © 2015–2016 Sahil Sareen""";
     private const string UNDO_MOVE_ACTION_NAME = "undo";
     private const string RESIGN_ACTION_NAME = "resign";
     private const string PAUSE_RESUME_ACTION_NAME = "pause-resume";
+    private const string HISTORY_GO_FIRST_ACTION_NAME = "go-first";
+    private const string HISTORY_GO_PREVIOUS_ACTION_NAME = "go-previous";
+    private const string HISTORY_GO_NEXT_ACTION_NAME = "go-next";
+    private const string HISTORY_GO_LAST_ACTION_NAME = "go-last";
 
     private const GLib.ActionEntry[] window_entries =
     {
@@ -103,6 +109,10 @@ Copyright © 2015–2016 Sahil Sareen""";
         { UNDO_MOVE_ACTION_NAME, undo_move_cb },
         { RESIGN_ACTION_NAME, resign_cb },
         { PAUSE_RESUME_ACTION_NAME, pause_resume_cb },
+        { HISTORY_GO_FIRST_ACTION_NAME, history_go_first_cb },
+        { HISTORY_GO_PREVIOUS_ACTION_NAME, history_go_previous_cb },
+        { HISTORY_GO_NEXT_ACTION_NAME, history_go_next_cb },
+        { HISTORY_GO_LAST_ACTION_NAME, history_go_last_cb },
     };
 
     private const OptionEntry[] option_entries =
@@ -141,42 +151,57 @@ Copyright © 2015–2016 Sahil Sareen""";
 
         add_action_entries (app_entries, this);
         set_accels_for_action ("app.help", {"F1"});
-        set_accels_for_action ("app.quit", {"<Primary>q", "<Primary>w"});
-        Builder builder = new Builder.from_resource ("/org/gnome/Chess/ui/gnome-chess.ui");
+        set_accels_for_action ("app.quit", {"<Control>q", "<Control>w"});
+
+        Builder builder = new Builder ();
+        builder.set_current_object (this);
+        try
+        {
+            builder.add_from_resource ("/org/gnome/Chess/ui/gnome-chess.ui");
+        }
+        catch (Error e)
+        {
+            error ("Failed to load UI resource: %s", e.message);
+        }
 
         window = (ApplicationWindow) builder.get_object ("gnome_chess_app");
         window.set_default_size (settings.get_int ("width"), settings.get_int ("height"));
         if (settings.get_boolean ("maximized"))
             window.maximize ();
-        window.size_allocate.connect (size_allocate_cb);
-        window.window_state_event.connect (window_state_event_cb);
 
+        main_box = (Box) builder.get_object ("main_box");
         info_bar = (InfoBar) builder.get_object ("info_bar");
         info_bar_label = (Label) builder.get_object ("info_bar_label");
         pause_resume_button = (Button) builder.get_object ("pause_button");
         navigation_box = (Box) builder.get_object ("navigation_box");
-        first_move_button = (Widget) builder.get_object ("first_move_button");
-        prev_move_button = (Widget) builder.get_object ("prev_move_button");
-        next_move_button = (Widget) builder.get_object ("next_move_button");
-        last_move_button = (Widget) builder.get_object ("last_move_button");
+        first_move_button = (Button) builder.get_object ("first_move_button");
+        prev_move_button = (Button) builder.get_object ("prev_move_button");
+        next_move_button = (Button) builder.get_object ("next_move_button");
+        last_move_button = (Button) builder.get_object ("last_move_button");
         history_combo = (ComboBox) builder.get_object ("history_combo");
         clock_box = (Box) builder.get_object ("clock_box");
-        white_time_label = (Widget) builder.get_object ("white_time_label");
-        black_time_label = (Widget) builder.get_object ("black_time_label");
-        view_container = (Container) builder.get_object ("view_container");
+        white_time_label = (DrawingArea) builder.get_object ("white_time_label");
+        black_time_label = (DrawingArea) builder.get_object ("black_time_label");
         headerbar = (HeaderBar) builder.get_object ("headerbar");
-        builder.connect_signals (this);
 
         update_pause_resume_button ();
 
         window.add_action_entries (window_entries, this);
-        set_accels_for_action ("win." + NEW_GAME_ACTION_NAME,       {        "<Primary>n"       });
-        set_accels_for_action ("win." + OPEN_GAME_ACTION_NAME,      {        "<Primary>o"       });
-        set_accels_for_action ("win." + SAVE_GAME_ACTION_NAME,      {        "<Primary>s"       });
-        set_accels_for_action ("win." + SAVE_GAME_AS_ACTION_NAME,   { "<Shift><Primary>s"       });
-        set_accels_for_action ("win." + UNDO_MOVE_ACTION_NAME,      {        "<Primary>z"       });
-        set_accels_for_action ("win." + PAUSE_RESUME_ACTION_NAME,   {        "<Primary>p",
-                                                                                      "Pause"   });
+        set_accels_for_action ("win." + NEW_GAME_ACTION_NAME,            {        "<Control>n"     });
+        set_accels_for_action ("win." + OPEN_GAME_ACTION_NAME,           {        "<Control>o"     });
+        set_accels_for_action ("win." + SAVE_GAME_ACTION_NAME,           {        "<Control>s"     });
+        set_accels_for_action ("win." + SAVE_GAME_AS_ACTION_NAME,        { "<Shift><Control>s"     });
+        set_accels_for_action ("win." + UNDO_MOVE_ACTION_NAME,           {        "<Control>z"     });
+        set_accels_for_action ("win." + PAUSE_RESUME_ACTION_NAME,        {        "<Control>p",
+                                                                                           "Pause" });
+        set_accels_for_action ("win." + HISTORY_GO_FIRST_ACTION_NAME,    {     "<Shift><Alt>Left"  });
+        set_accels_for_action ("win." + HISTORY_GO_PREVIOUS_ACTION_NAME, {            "<Alt>Left"  });
+        set_accels_for_action ("win." + HISTORY_GO_NEXT_ACTION_NAME,     {            "<Alt>Right" });
+        set_accels_for_action ("win." + HISTORY_GO_LAST_ACTION_NAME,     {     "<Shift><Alt>Right"  });
+
+        window.notify["default-height"].connect (window_state_changed_cb);
+        window.notify["default-width"].connect (window_state_changed_cb);
+
         add_window (window);
 
         scene = new ChessScene ();
@@ -193,9 +218,12 @@ Copyright © 2015–2016 Sahil Sareen""";
         view = new ChessView ();
         view.set_size_request (100, 100);
         view.scene = scene;
-        view_container.add (view);
+        main_box.insert_child_after (view, info_bar);
         view.show ();
 
+        white_time_label.set_draw_func (draw_white_time_label);
+        black_time_label.set_draw_func (draw_black_time_label);
+
         var system_engine_cfg = Path.build_filename (SYSCONFDIR, "gnome-chess", "engines.conf", null);
         var user_engine_cfg = Path.build_filename (Environment.get_user_config_dir (), "gnome-chess", 
"engines.conf", null);
         if (FileUtils.test (user_engine_cfg, FileTest.EXISTS))
@@ -249,13 +277,6 @@ Copyright © 2015–2016 Sahil Sareen""";
             opponent_engine.stop ();
 
         base.shutdown ();
-
-        /* Save window state */
-        settings.delay ();
-        settings.set_int ("width", window_width);
-        settings.set_int ("height", window_height);
-        settings.set_boolean ("maximized", is_maximized);
-        settings.apply ();
     }
 
     private void set_layout_mode (LayoutMode new_layout_mode)
@@ -265,30 +286,26 @@ Copyright © 2015–2016 Sahil Sareen""";
 
         layout_mode = new_layout_mode;
 
-        navigation_box.set_orientation (layout_mode == LayoutMode.NORMAL ? Orientation.HORIZONTAL : 
Orientation.VERTICAL);
+        Idle.add(() => {
+            navigation_box.set_orientation (layout_mode == LayoutMode.NORMAL ? Orientation.HORIZONTAL : 
Orientation.VERTICAL);
+            return Source.REMOVE;
+        });
     }
 
-    private void size_allocate_cb (Allocation allocation)
+    private void window_state_changed_cb ()
     {
-        if (is_maximized || is_tiled)
+        if (window.fullscreened || window.maximized)
+            return;
+
+        if (window.default_width == 0 || window.default_height == 0)
             return;
-        window.get_size (out window_width, out window_height);
-        if (window_width <= 500 && layout_mode == LayoutMode.NORMAL)
+
+        if (window.default_width <= 500 && layout_mode == LayoutMode.NORMAL)
             set_layout_mode (LayoutMode.NARROW);
-        else if (window_width > 500 && layout_mode == LayoutMode.NARROW)
+        else if (window.default_width > 500 && layout_mode == LayoutMode.NARROW)
             set_layout_mode (LayoutMode.NORMAL);
     }
 
-    private bool window_state_event_cb (Gdk.EventWindowState event)
-    {
-        if ((event.changed_mask & Gdk.WindowState.MAXIMIZED) != 0)
-            is_maximized = (event.new_window_state & Gdk.WindowState.MAXIMIZED) != 0;
-        /* We don’t save this state, but track it for saving size allocation */
-        if ((event.changed_mask & Gdk.WindowState.TILED) != 0)
-            is_tiled = (event.new_window_state & Gdk.WindowState.TILED) != 0;
-        return false;
-    }
-
     [CCode (cname = "queen_selected_cb", instance_pos = -1)]
     public void queen_selected_cb (Button button)
         requires (promotion_type_selector_dialog != null)
@@ -317,81 +334,103 @@ Copyright © 2015–2016 Sahil Sareen""";
         promotion_type_selector_dialog.response (PromotionTypeSelected.BISHOP);
     }
 
-    public PieceType? show_promotion_type_selector ()
+    private void promotion_type_selector_response_cb (int response_id)
+        requires (promotion_type_completion_handler != null)
     {
-        var promotion_type_selector_builder = new Builder.from_resource 
("/org/gnome/Chess/ui/promotion-type-selector.ui");
-
-        promotion_type_selector_dialog = (Dialog) promotion_type_selector_builder.get_object 
("dialog_promotion_type_selector");
-        promotion_type_selector_dialog.transient_for = window;
-
-        var button_box = (ButtonBox) promotion_type_selector_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 filename = Path.build_filename (PKGDATADIR, "pieces", scene.theme_name, "%sQueen.svg".printf 
(color));
-        set_piece_image ((Image) promotion_type_selector_builder.get_object ("image_queen"), filename);
-
-        filename = Path.build_filename (PKGDATADIR, "pieces", scene.theme_name, "%sKnight.svg".printf 
(color));
-        set_piece_image ((Image) promotion_type_selector_builder.get_object ("image_knight"), filename);
-
-        filename = Path.build_filename (PKGDATADIR, "pieces", scene.theme_name, "%sRook.svg".printf (color));
-        set_piece_image ((Image) promotion_type_selector_builder.get_object ("image_rook"), filename);
-
-        filename = Path.build_filename (PKGDATADIR, "pieces", scene.theme_name, "%sBishop.svg".printf 
(color));
-        set_piece_image ((Image) promotion_type_selector_builder.get_object ("image_bishop"), filename);
-
-        promotion_type_selector_builder.connect_signals (this);
-
-        PieceType? selection = null;
-        int choice = promotion_type_selector_dialog.run ();
-        switch (choice)
+        switch (response_id)
         {
         case PromotionTypeSelected.QUEEN:
-            selection = PieceType.QUEEN;
+            promotion_type_completion_handler (PieceType.QUEEN);
             break;
         case PromotionTypeSelected.KNIGHT:
-            selection = PieceType.KNIGHT;
+            promotion_type_completion_handler (PieceType.KNIGHT);
             break;
         case PromotionTypeSelected.ROOK:
-            selection = PieceType.ROOK;
+            promotion_type_completion_handler (PieceType.ROOK);
             break;
         case PromotionTypeSelected.BISHOP:
-            selection = PieceType.BISHOP;
+            promotion_type_completion_handler (PieceType.BISHOP);
+            break;
+        default:
+            promotion_type_completion_handler (null);
             break;
         }
-        promotion_type_selector_dialog.destroy ();
-        promotion_type_selector_dialog = null;
 
-        return selection;
+        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 filename = Path.build_filename (PKGDATADIR, "pieces", scene.theme_name, "%sQueen.svg".printf 
(color));
+            set_piece_image ((Image) builder.get_object ("image_queen"), filename);
+
+            filename = Path.build_filename (PKGDATADIR, "pieces", scene.theme_name, "%sKnight.svg".printf 
(color));
+            set_piece_image ((Image) builder.get_object ("image_knight"), filename);
+
+            filename = Path.build_filename (PKGDATADIR, "pieces", scene.theme_name, "%sRook.svg".printf 
(color));
+            set_piece_image ((Image) builder.get_object ("image_rook"), filename);
+
+            filename = Path.build_filename (PKGDATADIR, "pieces", scene.theme_name, "%sBishop.svg".printf 
(color));
+            set_piece_image ((Image) builder.get_object ("image_bishop"), filename);
+
+            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 filename)
     {
-        int width, height;
-        if (!icon_size_lookup (IconSize.DIALOG, out width, out height))
-            return;
+        const int size = 48;
 
         try
         {
             var h = new Rsvg.Handle.from_file (filename);
 
-            var s = new Cairo.ImageSurface (Cairo.Format.ARGB32, width, height);
+            var s = new Cairo.ImageSurface (Cairo.Format.ARGB32, size, size);
             var c = new Cairo.Context (s);
-            h.render_document (c, Rsvg.Rectangle () { width = width, height = height, x = 0, y = 0 });
+            h.render_document (c, Rsvg.Rectangle () { width = size, height = size, x = 0, y = 0 });
 
-            var p = Gdk.pixbuf_get_from_surface (s, 0, 0, width, height);
+            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", filename, e.message);
-            return;
         }
     }
 
@@ -409,7 +448,16 @@ Copyright © 2015–2016 Sahil Sareen""";
             save_duration_cb ();
 
         autosave ();
+
+        /* 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.apply ();
+
         window.destroy ();
+        window = null;
     }
 
     private void autosave ()
@@ -444,10 +492,26 @@ Copyright © 2015–2016 Sahil Sareen""";
         if (move_number < 0)
             move_number += 1 + n_moves;
 
-        first_move_button.sensitive = n_moves > 0 && move_number != 0 && !game.is_paused;
-        prev_move_button.sensitive = move_number > 0 && !game.is_paused;
-        next_move_button.sensitive = move_number < n_moves && !game.is_paused;
-        last_move_button.sensitive = n_moves > 0 && move_number != n_moves && !game.is_paused;
+        if (n_moves > 0 && move_number != 0 && !game.is_paused)
+            enable_window_action (HISTORY_GO_FIRST_ACTION_NAME);
+        else
+            disable_window_action (HISTORY_GO_FIRST_ACTION_NAME);
+
+        if (move_number > 0 && !game.is_paused)
+            enable_window_action (HISTORY_GO_PREVIOUS_ACTION_NAME);
+        else
+            disable_window_action (HISTORY_GO_PREVIOUS_ACTION_NAME);
+
+        if (move_number < n_moves && !game.is_paused)
+            enable_window_action (HISTORY_GO_NEXT_ACTION_NAME);
+        else
+            disable_window_action (HISTORY_GO_NEXT_ACTION_NAME);
+
+        if (n_moves > 0 && move_number != n_moves && !game.is_paused)
+            enable_window_action (HISTORY_GO_LAST_ACTION_NAME);
+        else
+            disable_window_action (HISTORY_GO_LAST_ACTION_NAME);
+
         history_combo.sensitive = !game.is_paused;
 
         /* Set move text for all moves (it may have changed format) */
@@ -475,11 +539,6 @@ Copyright © 2015–2016 Sahil Sareen""";
     {
         starting = true;
 
-        if (game_file != null && game_file.get_path () != autosave_filename)
-            headerbar.subtitle = game_file.get_basename ();
-        else
-            headerbar.subtitle = null;
-
         var model = (Gtk.ListStore) history_combo.model;
         model.clear ();
         TreeIter iter;
@@ -1325,7 +1384,7 @@ Copyright © 2015–2016 Sahil Sareen""";
 
     private void update_game_status (string? title = null, string? info = null)
     {
-        headerbar.title = title != null ? title : compute_current_title ();
+        window.title = title != null ? title : compute_current_title ();
         info_bar_label.label = info != null ? info : compute_status_info ();
         /* Setting the label to null actually just sets it to an empty string. */
         info_bar.visible = info_bar_label.label != "";
@@ -1340,14 +1399,12 @@ Copyright © 2015–2016 Sahil Sareen""";
 
         if (game != null && game.is_paused)
         {
-            pause_resume_button.image = new Image.from_icon_name ("media-playback-start-symbolic",
-                                                                  IconSize.BUTTON);
+            pause_resume_button.icon_name = "media-playback-start-symbolic";
             pause_resume_button.tooltip_text = _("Unpause the game");
         }
         else
         {
-            pause_resume_button.image = new Image.from_icon_name ("media-playback-pause-symbolic",
-                                                                  IconSize.BUTTON);
+            pause_resume_button.icon_name = "media-playback-pause-symbolic";
             pause_resume_button.tooltip_text = _("Pause the game");
         }
     }
@@ -1476,137 +1533,173 @@ Copyright © 2015–2016 Sahil Sareen""";
         black_time_label.queue_draw ();
     }
 
-    [CCode (cname = "gnome_chess_app_delete_event_cb", instance_pos = -1)]
-    public bool gnome_chess_app_delete_event_cb (Widget widget, Gdk.Event event)
-    {
-        quit_game ();
-        return false;
-    }
-
-    private bool prompt_save_game (string prompt_text)
+    private void prompt_save_game_response_cb (int response_id)
+        requires (prompt_save_game_cb != null)
     {
-        if (!game_needs_saving)
-            return true;
-
-        var dialog = new MessageDialog (window,
-                                        DialogFlags.MODAL,
-                                        MessageType.QUESTION,
-                                        ButtonsType.NONE,
-                                        prompt_text);
-        dialog.add_button (_("_Cancel"), ResponseType.CANCEL);
-
-        if (game.result == ChessResult.IN_PROGRESS)
+        if (response_id == ResponseType.CANCEL || response_id == ResponseType.DELETE_EVENT)
         {
-            dialog.add_button (_("_Abandon game"), ResponseType.NO);
-            dialog.add_button (_("_Save game for later"), ResponseType.YES);
+            prompt_save_game_cb (true);
+            prompt_save_game_cb = null;
         }
-        else
-        {
-            dialog.add_button (_("_Discard game"), ResponseType.NO);
-            dialog.add_button (_("_Save game log"), ResponseType.YES);
-        }
-
-        var result = dialog.run ();
-        dialog.destroy ();
-
-        if (result == ResponseType.CANCEL || result == ResponseType.DELETE_EVENT)
-        {
-            return false;
-        }
-        else if (result == ResponseType.YES)
+        else if (response_id == ResponseType.YES)
         {
             present_save_dialog ();
+            prompt_save_game_cb (false);
         }
         else
         {
-            warn_if_fail (result == ResponseType.NO);
+            warn_if_fail (response_id == ResponseType.NO);
             /* Remove completed game from history */
             game_needs_saving = false;
             autosave ();
+
+            prompt_save_game_cb (false);
+            prompt_save_game_cb = null;
         }
 
-        return true;
+        prompt_save_game_dialog.hide ();
     }
 
-    private void present_claim_draw_dialog ()
-        requires (game.can_claim_draw ())
+    private void prompt_save_game (string prompt_text)
+        requires (prompt_save_game_cb != null)
     {
-        game.pause (false);
-
-        var dialog = new MessageDialog (window,
-                                        DialogFlags.MODAL,
-                                        MessageType.QUESTION,
-                                        ButtonsType.NONE,
-                                        /* Title of claim draw dialog */
-                                        _("Would you like to claim a draw?"));
-
-        string reason;
-        if (game.is_fifty_move_rule_fulfilled ())
+        if (!game_needs_saving)
         {
-            /* Message in claim draw dialog when triggered by fifty-move rule */
-            reason = _("You may claim a draw because fifty moves have passed without a capture or pawn 
advancement. (The computer player may still choose to claim a draw even if you choose to keep playing.)");
+            prompt_save_game_cb (false);
+            return;
         }
-        else if (game.is_three_fold_repeat ())
+
+        if (prompt_save_game_dialog == null)
         {
-            /* Message in claim draw dialog when triggered by three-fold repetition */
-            reason = _("You may claim a draw because the current board position has occurred three times. 
(The computer player may still choose to claim a draw even if you choose to keep playing.)");
-        }
-        else assert_not_reached ();
+            prompt_save_game_dialog = new MessageDialog (window,
+                                                         DialogFlags.MODAL,
+                                                         MessageType.QUESTION,
+                                                         ButtonsType.NONE,
+                                                         prompt_text);
+            prompt_save_game_dialog.add_button (_("_Cancel"), ResponseType.CANCEL);
 
-        dialog.secondary_text = reason;
+            if (game.result == ChessResult.IN_PROGRESS)
+            {
+                prompt_save_game_dialog.add_button (_("_Abandon game"), ResponseType.NO);
+                prompt_save_game_dialog.add_button (_("_Save game for later"), ResponseType.YES);
+            }
+            else
+            {
+                prompt_save_game_dialog.add_button (_("_Discard game"), ResponseType.NO);
+                prompt_save_game_dialog.add_button (_("_Save game log"), ResponseType.YES);
+            }
 
-        dialog.add_buttons (/* Option in claim draw dialog */
-                            _("_Keep Playing"), ResponseType.REJECT,
-                            /* Option in claim draw dialog */
-                            _("_Claim Draw"), ResponseType.ACCEPT,
-                            null);
+            prompt_save_game_dialog.response.connect (prompt_save_game_response_cb);
+        }
 
-        var response = dialog.run ();
-        dialog.destroy ();
+        prompt_save_game_dialog.show ();
+    }
+
+    private void claim_draw_response_cb (int response_id)
+    {
         game.unpause ();
 
-        if (response == ResponseType.ACCEPT)
-        {
+        if (response_id == ResponseType.ACCEPT)
             game.current_player.claim_draw ();
+
+        claim_draw_dialog.hide ();
+    }
+
+    private void present_claim_draw_dialog ()
+        requires (game.can_claim_draw ())
+    {
+        game.pause (false);
+
+        if (claim_draw_dialog == null)
+        {
+            claim_draw_dialog = new MessageDialog (window,
+                                                   DialogFlags.MODAL,
+                                                   MessageType.QUESTION,
+                                                   ButtonsType.NONE,
+                                                   /* Title of claim draw dialog */
+                                                   _("Would you like to claim a draw?"));
+
+            string reason;
+            if (game.is_fifty_move_rule_fulfilled ())
+            {
+                /* Message in claim draw dialog when triggered by fifty-move rule */
+                reason = _("You may claim a draw because fifty moves have passed without a capture or pawn 
advancement. (The computer player may still choose to claim a draw even if you choose to keep playing.)");
+            }
+            else if (game.is_three_fold_repeat ())
+            {
+                /* Message in claim draw dialog when triggered by three-fold repetition */
+                reason = _("You may claim a draw because the current board position has occurred three 
times. (The computer player may still choose to claim a draw even if you choose to keep playing.)");
+            }
+            else assert_not_reached ();
+
+            claim_draw_dialog.secondary_text = reason;
+
+            claim_draw_dialog.add_buttons (/* Option in claim draw dialog */
+                                           _("_Keep Playing"), ResponseType.REJECT,
+                                           /* Option in claim draw dialog */
+                                           _("_Claim Draw"), ResponseType.ACCEPT,
+                                           null);
+
+            claim_draw_dialog.response.connect (claim_draw_response_cb);
         }
+
+        claim_draw_dialog.show ();
     }
 
-    public void new_game_cb ()
+    private void new_game_prompt_save_game_cb (bool cancelled)
     {
-        if (prompt_save_game (_("Save this game before starting a new one?")))
+        prompt_save_game_cb = null;
+
+        if (!cancelled)
             start_new_game ();
     }
 
-    public void resign_cb ()
+    public void new_game_cb ()
+        requires (prompt_save_game_cb == null)
     {
-        game.pause (false);
+        prompt_save_game_cb = new_game_prompt_save_game_cb;
+        prompt_save_game (_("Save this game before starting a new one?"));
+    }
 
-        var dialog = new MessageDialog (window,
-                                        DialogFlags.MODAL,
-                                        MessageType.QUESTION,
-                                        ButtonsType.NONE,
-                                        /* Title of warning dialog when player clicks Resign */
-                                        _("Are you sure you want to resign?"));
-        dialog.format_secondary_text (
-            /* Text on warning dialog when player clicks Resign */
-            _("This makes sense if you plan to save the game as a record of your loss."));
-        dialog.add_buttons (/* Option on warning dialog when player clicks resign */
-                            _("_Keep Playing"), ResponseType.REJECT,
-                            /* Option on warning dialog when player clicks resign */
-                            _("_Resign"), ResponseType.ACCEPT,
-                            null);
-
-        var response = dialog.run ();
-        dialog.destroy ();
+    private void resign_response_cb (int response_id)
+    {
         game.unpause ();
 
-        if (response == ResponseType.ACCEPT)
+        if (response_id == ResponseType.ACCEPT)
         {
             if (human_player != null)
                 human_player.resign ();
             else
                 game.current_player.resign ();
         }
+
+        resign_dialog.hide ();
+    }
+
+    public void resign_cb ()
+    {
+        game.pause (false);
+
+        if (resign_dialog == null)
+        {
+            resign_dialog = new MessageDialog (window,
+                                               DialogFlags.MODAL,
+                                               MessageType.QUESTION,
+                                               ButtonsType.NONE,
+                                               /* Title of warning dialog when player clicks Resign */
+                                               _("Are you sure you want to resign?"));
+            resign_dialog.format_secondary_text (
+                /* Text on warning dialog when player clicks Resign */
+                _("This makes sense if you plan to save the game as a record of your loss."));
+            resign_dialog.add_buttons (/* Option on warning dialog when player clicks resign */
+                                       _("_Keep Playing"), ResponseType.REJECT,
+                                       /* Option on warning dialog when player clicks resign */
+                                       _("_Resign"), ResponseType.ACCEPT,
+                                       null);
+        }
+
+        resign_dialog.response.connect (resign_response_cb);
+        resign_dialog.show ();
     }
 
     public void undo_move_cb ()
@@ -1638,24 +1731,14 @@ Copyright © 2015–2016 Sahil Sareen""";
         quit_game ();
     }
 
-    [CCode (cname = "white_time_draw_cb", instance_pos = -1)]
-    public bool white_time_draw_cb (Widget widget, Cairo.Context c)
+    private void draw_white_time_label (DrawingArea drawing_area, Cairo.Context c, int width, int height)
     {
-        double fg[3] = { 0.0, 0.0, 0.0 };
-        double bg[3] = { 1.0, 1.0, 1.0 };
-
-        draw_time (widget, c, make_clock_text (game.clock, Color.WHITE), fg, bg);
-        return false;
+        draw_time (drawing_area, c, width, height, make_clock_text (game.clock, Color.WHITE), { 0.0, 0.0, 
0.0 }, { 1.0, 1.0, 1.0 });
     }
 
-    [CCode (cname = "black_time_draw_cb", instance_pos = -1)]
-    public bool black_time_draw_cb (Widget widget, Cairo.Context c)
+    private void draw_black_time_label (DrawingArea drawing_area, Cairo.Context c, int width, int height)
     {
-        double fg[3] = { 1.0, 1.0, 1.0 };
-        double bg[3] = { 0.0, 0.0, 0.0 };
-
-        draw_time (widget, c, make_clock_text (game.clock, Color.BLACK), fg, bg);
-        return false;
+        draw_time (drawing_area, c, width, height, make_clock_text (game.clock, Color.BLACK), { 1.0, 1.0, 
1.0 }, { 0.0, 0.0, 0.0 });
     }
 
     private string make_clock_text (ChessClock? clock, Color color)
@@ -1673,8 +1756,7 @@ Copyright © 2015–2016 Sahil Sareen""";
             return "∶\xE2\x80\x8E%02d".printf (time);
     }
 
-    /*
-     * Compute the largest possible size the timer label might ever want to take.
+    /* Compute the largest possible size the timer label might ever want to take.
      * The size of the characters may vary by font, but one digit will always
      * be the largest.
      */
@@ -1709,10 +1791,25 @@ Copyright © 2015–2016 Sahil Sareen""";
         return (int) Math.ceil (max) + 6;
     }
 
-    private void draw_time (Widget widget, Cairo.Context c, string text, double[] fg, double[] bg)
+    private void draw_time (Widget widget, Cairo.Context c, int width, int height, string text, double[] fg, 
double[] bg)
     {
-        double alpha = 1.0;
+        /* We need to draw text on our cairo context to properly compute our
+         * required size. But the only place we are able to access the cairo
+         * context is here, the draw function. And we are not allowed to set our
+         * size inside the draw function. So the best we can do is schedule the
+         * size computation and queue draw again when that's done.
+         */
+        if (widget.width_request == -1)
+        {
+            Idle.add(() => {
+                widget.set_size_request (compute_time_label_width_request (c), -1);
+                widget.queue_draw ();
+                return Source.REMOVE;
+            });
+            return;
+        }
 
+        double alpha = 1.0;
         if ((widget.get_state_flags () & StateFlags.INSENSITIVE) != 0)
             alpha = 0.5;
         c.set_source_rgba (bg[0], bg[1], bg[2], alpha);
@@ -1726,11 +1823,6 @@ Copyright © 2015–2016 Sahil Sareen""";
         c.move_to ((widget.get_allocated_width () - extents.width) / 2 - extents.x_bearing,
                    (widget.get_allocated_height () - extents.height) / 2 - extents.y_bearing);
         c.show_text (text);
-
-        int width;
-        widget.get_size_request (out width, null);
-        if (width == -1)
-            widget.set_size_request (compute_time_label_width_request (c), -1);
     }
 
     [CCode (cname = "history_combo_changed_cb", instance_pos = -1)]
@@ -1746,14 +1838,23 @@ Copyright © 2015–2016 Sahil Sareen""";
         scene.move_number = move_number;
     }
 
-    [CCode (cname = "history_latest_clicked_cb", instance_pos = -1)]
-    public void history_latest_clicked_cb (Widget widget)
+    private void history_go_first_cb ()
     {
-        scene.move_number = -1;
+        scene.move_number = 0;
     }
 
-    [CCode (cname = "history_next_clicked_cb", instance_pos = -1)]
-    public void history_next_clicked_cb (Widget widget)
+    private void history_go_previous_cb ()
+    {
+        if (scene.move_number == 0)
+            return;
+
+        if (scene.move_number == -1)
+            scene.move_number = (int) game.n_moves - 1;
+        else
+            scene.move_number = scene.move_number - 1;
+    }
+
+    private void history_go_next_cb ()
     {
         if (scene.move_number == -1)
             return;
@@ -1765,46 +1866,51 @@ Copyright © 2015–2016 Sahil Sareen""";
             scene.move_number = move_number;
     }
 
-    [CCode (cname = "history_previous_clicked_cb", instance_pos = -1)]
-    public void history_previous_clicked_cb (Widget widget)
+    private void history_go_last_cb ()
     {
-        if (scene.move_number == 0)
-            return;
-
-        if (scene.move_number == -1)
-            scene.move_number = (int) game.n_moves - 1;
-        else
-            scene.move_number = scene.move_number - 1;
+        scene.move_number = -1;
     }
 
-    [CCode (cname = "history_start_clicked_cb", instance_pos = -1)]
-    public void history_start_clicked_cb (Widget widget)
+    private void preferences_response_cb (int response_id)
     {
-        scene.move_number = 0;
+        preferences_dialog.hide ();
     }
 
     public void preferences_cb ()
     {
         if (preferences_dialog != null)
         {
-            preferences_dialog.run ();
+            preferences_dialog.show ();
             return;
         }
 
-        Builder preferences_builder = new Builder.from_resource ("/org/gnome/Chess/ui/preferences.ui");
+        Builder builder = new Builder ();
+        builder.set_current_object (this);
+        try
+        {
+            builder.add_from_resource ("/org/gnome/Chess/ui/preferences.ui");
+        }
+        catch (Error e)
+        {
+            error ("Failed to load UI resource: %s", e.message);
+        }
 
-        preferences_dialog = (Dialog) preferences_builder.get_object ("preferences");
+        preferences_dialog = (Dialog) builder.get_object ("preferences");
         preferences_dialog.transient_for = window;
+        preferences_dialog.modal = true;
 
-        settings.bind ("show-numbering", preferences_builder.get_object ("show_numbering_check"),
+        settings.bind ("show-numbering", builder.get_object ("show_numbering_check"),
                        "active", SettingsBindFlags.DEFAULT);
-        settings.bind ("show-move-hints", preferences_builder.get_object ("show_move_hints_check"),
+        settings.bind ("show-move-hints", builder.get_object ("show_move_hints_check"),
                        "active", SettingsBindFlags.DEFAULT);
 
-        side_combo = (ComboBox) preferences_builder.get_object ("side_combo");
+        side_combo = (ComboBox) builder.get_object ("side_combo");
         side_combo.set_active (settings.get_enum ("play-as"));
 
-        var ai_combo = (ComboBox) preferences_builder.get_object ("opponent_combo");
+        difficulty_combo = (ComboBox) builder.get_object ("difficulty_combo");
+        set_combo (difficulty_combo, 1, settings.get_string ("difficulty"));
+
+        var ai_combo = (ComboBox) builder.get_object ("opponent_combo");
         var ai_model = (Gtk.ListStore) ai_combo.model;
         var opponent_name = settings.get_string ("opponent");
         if (opponent_name == "human")
@@ -1823,19 +1929,16 @@ Copyright © 2015–2016 Sahil Sareen""";
             settings.set_string ("opponent", "human");
         }
 
-        difficulty_combo = (ComboBox) preferences_builder.get_object ("difficulty_combo");
-        set_combo (difficulty_combo, 1, settings.get_string ("difficulty"));
-
-        duration_combo = (ComboBox) preferences_builder.get_object ("duration_combo");
-        clock_type_combo = (ComboBox) preferences_builder.get_object ("clock_type_combo");
-        duration_adjustment = (Adjustment) preferences_builder.get_object ("duration_adjustment");
-        timer_increment_adjustment = (Adjustment) preferences_builder.get_object 
("timer_increment_adjustment");
-        custom_duration_box = (Box) preferences_builder.get_object ("custom_duration_box");
-        timer_increment_box = (Box) preferences_builder.get_object ("timer_increment_box");
-        custom_duration_units_combo = (ComboBox) preferences_builder.get_object 
("custom_duration_units_combo");
+        duration_adjustment = (Adjustment) builder.get_object ("duration_adjustment");
+        timer_increment_adjustment = (Adjustment) builder.get_object ("timer_increment_adjustment");
+        custom_duration_box = (Box) builder.get_object ("custom_duration_box");
+        timer_increment_box = (Box) builder.get_object ("timer_increment_box");
+        custom_duration_units_combo = (ComboBox) builder.get_object ("custom_duration_units_combo");
+        timer_increment_label = (Label) builder.get_object ("timer_increment_label");
+        timer_increment_units_combo = (ComboBox) builder.get_object ("timer_increment_units_combo");
+        clock_type_combo = (ComboBox) builder.get_object ("clock_type_combo");
+        duration_combo = (ComboBox) builder.get_object ("duration_combo");
         set_duration (settings.get_int ("duration"));
-        timer_increment_label = (Widget) preferences_builder.get_object ("timer_increment_label");
-        timer_increment_units_combo = (ComboBox) preferences_builder.get_object 
("timer_increment_units_combo");
 
         if (pgn_game.clock_type != null)
             set_clock_type (ClockType.string_to_enum (pgn_game.clock_type));
@@ -1847,17 +1950,15 @@ Copyright © 2015–2016 Sahil Sareen""";
         else
             set_timer_increment (settings.get_int ("timer-increment"));
 
-        var orientation_combo = (ComboBox) preferences_builder.get_object ("orientation_combo");
+        var orientation_combo = (ComboBox) builder.get_object ("orientation_combo");
         set_combo (orientation_combo, 1, settings.get_string ("board-side"));
 
-        var move_combo = (ComboBox) preferences_builder.get_object ("move_format_combo");
+        var move_combo = (ComboBox) builder.get_object ("move_format_combo");
         set_combo (move_combo, 1, settings.get_string ("move-format"));
 
-        var theme_combo = (ComboBox) preferences_builder.get_object ("piece_style_combo");
+        var theme_combo = (ComboBox) builder.get_object ("piece_style_combo");
         set_combo (theme_combo, 1, settings.get_string ("piece-theme"));
 
-        preferences_builder.connect_signals (this);
-
         /* Human vs. human */
         if (ai_combo.get_active () == 0)
         {
@@ -1865,7 +1966,8 @@ Copyright © 2015–2016 Sahil Sareen""";
             difficulty_combo.sensitive = false;
         }
 
-        preferences_dialog.run ();
+        preferences_dialog.response.connect (preferences_response_cb);
+        preferences_dialog.show ();
     }
 
     private void set_combo (ComboBox combo, int value_index, string value)
@@ -2218,19 +2320,6 @@ Copyright © 2015–2016 Sahil Sareen""";
         settings.set_string ("clock-type", clock_type.to_string ());
     }
 
-    [CCode (cname = "preferences_response_cb", instance_pos = -1)]
-    public void preferences_response_cb (Widget widget, int response_id)
-    {
-        preferences_dialog.hide ();
-    }
-
-    [CCode (cname = "preferences_delete_event_cb", instance_pos = -1)]
-    public bool preferences_delete_event_cb (Widget widget, Gdk.Event event)
-    {
-        preferences_response_cb (widget, ResponseType.CANCEL);
-        return true;
-    }
-
     [CCode (cname = "piece_style_combo_changed_cb", instance_pos = -1)]
     public void piece_style_combo_changed_cb (ComboBox combo)
     {
@@ -2251,14 +2340,7 @@ Copyright © 2015–2016 Sahil Sareen""";
 
     public void help_cb ()
     {
-        try
-        {
-            show_uri_on_window (window, "help:gnome-chess", get_current_event_time ());
-        }
-        catch (Error e)
-        {
-            warning ("Unable to open help: %s", e.message);
-        }
+        show_uri (window, "help:gnome-chess", Gdk.CURRENT_TIME);
     }
 
     private const string[] authors = { "Robert Ancell <robert ancell gmail com>", null };
@@ -2268,7 +2350,7 @@ Copyright © 2015–2016 Sahil Sareen""";
     {
         if (about_dialog != null)
         {
-            about_dialog.present ();
+            about_dialog.show ();
             return;
         }
 
@@ -2285,7 +2367,7 @@ Copyright © 2015–2016 Sahil Sareen""";
         about_dialog.translator_credits = _("translator-credits");
         about_dialog.website = "https://wiki.gnome.org/Apps/Chess";;
         about_dialog.logo_icon_name = "org.gnome.Chess";
-        about_dialog.response.connect (about_response_cb);
+        about_dialog.hide_on_close = true;
         about_dialog.show ();
     }
 
@@ -2298,8 +2380,8 @@ Copyright © 2015–2016 Sahil Sareen""";
                                                     _("This does not look like a valid PGN game."));
         invalid_pgn_dialog.add_button (_("_OK"), ResponseType.OK);
 
-        invalid_pgn_dialog.run ();
-        invalid_pgn_dialog.destroy ();
+        invalid_pgn_dialog.response.connect (() => invalid_pgn_dialog.destroy ());
+        invalid_pgn_dialog.show ();
     }
 
     private void run_invalid_move_dialog (string error_message)
@@ -2311,14 +2393,8 @@ Copyright © 2015–2016 Sahil Sareen""";
                                                      error_message);
         invalid_move_dialog.add_button (_("_OK"), ResponseType.OK);
 
-        invalid_move_dialog.run ();
-        invalid_move_dialog.destroy ();
-    }
-
-    private void about_response_cb (int response_id)
-    {
-        about_dialog.destroy ();
-        about_dialog = null;
+        invalid_move_dialog.response.connect (() => invalid_move_dialog.destroy ());
+        invalid_move_dialog.show ();
     }
 
     private void update_pgn_time_remaining ()
@@ -2331,6 +2407,47 @@ Copyright © 2015–2016 Sahil Sareen""";
         }
     }
 
+    private void save_dialog_response_cb (int response_id)
+    {
+        if (response_id == ResponseType.ACCEPT)
+        {
+            update_pgn_time_remaining ();
+
+            try
+            {
+                game_file = save_dialog.get_file ();
+                pgn_game.write (game_file);
+
+                disable_window_action (SAVE_GAME_ACTION_NAME);
+                game_needs_saving = false;
+            }
+            catch (Error e)
+            {
+                if (save_error_dialog == null)
+                {
+                    save_error_dialog = new MessageDialog (window,
+                                                           DialogFlags.MODAL,
+                                                           MessageType.ERROR,
+                                                           ButtonsType.NONE,
+                                                           _("Failed to save game: %s"),
+                                                           e.message);
+                    save_error_dialog.add_button (_("_OK"), ResponseType.OK);
+                    save_error_dialog.response.connect (() => save_error_dialog.hide ());
+                }
+
+                save_error_dialog.show ();
+            }
+        }
+
+        save_dialog.hide ();
+
+        if (prompt_save_game_cb != null)
+        {
+            prompt_save_game_cb (false);
+            prompt_save_game_cb = null;
+        }
+    }
+
     private void present_save_dialog ()
     {
         /* Show active dialog */
@@ -2342,7 +2459,7 @@ Copyright © 2015–2016 Sahil Sareen""";
                                                  _("_Save"),
                                                  _("_Cancel"));
 
-            var set_filename = false;
+            var set_file = false;
             if (game_file != null)
             {
                 /* If the path is under /run, we are probably sandboxed, and the
@@ -2356,12 +2473,19 @@ Copyright © 2015–2016 Sahil Sareen""";
                 var path = game_file.get_path ();
                 if (path != autosave_filename && !path.has_prefix ("/run"))
                 {
-                    save_dialog.set_filename (path);
-                    set_filename = true;
+                    try
+                    {
+                        save_dialog.set_file (game_file);
+                        set_file = true;
+                    }
+                    catch (Error e)
+                    {
+                        warning ("Failed to set file chooser default file: %s", e.message);
+                    }
                 }
             }
 
-            if (!set_filename)
+            if (!set_file)
             {
                 save_dialog.set_current_name (/* Default filename for the save game dialog */
                                               _("Untitled Chess Game") + ".pgn");
@@ -2379,40 +2503,12 @@ Copyright © 2015–2016 Sahil Sareen""";
             all_filter.set_filter_name (_("All files"));
             all_filter.add_pattern ("*");
             save_dialog.add_filter (all_filter);
-        }
-
-        var response_id = save_dialog.run ();
-        if (response_id == ResponseType.ACCEPT)
-        {
-            update_pgn_time_remaining ();
 
-            try
-            {
-                game_file = save_dialog.get_file ();
-                save_dialog.destroy ();
-                save_dialog = null;
-
-                pgn_game.write (game_file);
-
-                disable_window_action (SAVE_GAME_ACTION_NAME);
-                game_needs_saving = false;
-
-                headerbar.subtitle = game_file.get_basename ();
-            }
-            catch (Error e)
-            {
-                var error_dialog = new MessageDialog (window,
-                                                      DialogFlags.MODAL,
-                                                      MessageType.ERROR,
-                                                      ButtonsType.NONE,
-                                                      _("Failed to save game: %s"),
-                                                      e.message);
-                error_dialog.add_button (_("_OK"), ResponseType.OK);
-
-                error_dialog.run ();
-                error_dialog.destroy ();
-            }
+            save_dialog.modal = true;
+            save_dialog.response.connect (save_dialog_response_cb);
         }
+
+        save_dialog.show ();
     }
 
     public void save_game_cb ()
@@ -2442,9 +2538,22 @@ Copyright © 2015–2016 Sahil Sareen""";
         present_save_dialog ();
     }
 
-    public void open_game_cb ()
+    private void open_game_response_cb (int response_id)
+    {
+        if (response_id == ResponseType.ACCEPT)
+        {
+            game_file = open_dialog.get_file ();
+            load_game (game_file);
+        }
+
+        open_dialog.hide ();
+    }
+
+    private void open_game_prompt_save_game_cb (bool cancelled)
     {
-        if (!prompt_save_game (_("Save this game before loading another one?")))
+        prompt_save_game_cb = null;
+
+        if (cancelled)
             return;
 
         /* Show active dialog */
@@ -2468,17 +2577,19 @@ Copyright © 2015–2016 Sahil Sareen""";
             all_filter.set_filter_name (_("All files"));
             all_filter.add_pattern ("*");
             open_dialog.add_filter (all_filter);
+
+            open_dialog.modal = true;
+            open_dialog.response.connect (open_game_response_cb);
         }
 
-        var response_id = open_dialog.run ();
-        if (response_id == ResponseType.ACCEPT)
-        {
-            game_file = open_dialog.get_file ();
-            open_dialog.destroy ();
-            open_dialog = null;
+        open_dialog.show ();
+    }
 
-            load_game (game_file);
-        }
+    public void open_game_cb ()
+        requires (prompt_save_game_cb == null)
+    {
+        prompt_save_game_cb = open_game_prompt_save_game_cb;
+        prompt_save_game (_("Save this game before loading another one?"));
     }
 
     private void start_new_game ()


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