[gnome-klotski] New UI. Use GtkBuilder.



commit fdf0b06e61625bbace2a76acf594d45bf29ca89f
Author: Arnaud Bonatti <arnaud bonatti gmail com>
Date:   Mon Feb 16 20:24:28 2015 +0100

    New UI. Use GtkBuilder.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=744023

 configure.ac              |    2 +
 data/Makefile.am          |    5 +-
 data/klotski-scores.ui    |  110 +++++++
 data/klotski.css          |   37 +++
 data/klotski.ui           |  311 +++++++++++++++++++
 po/POTFILES.in            |    2 +
 src/Makefile.am           |    4 +-
 src/gnome-klotski.vala    |  758 ++++++++++++++++++++++-----------------------
 src/klotski.gresource.xml |    5 +-
 src/puzzle-view.vala      |    5 +-
 src/score-dialog.vala     |  152 ++++-----
 11 files changed, 911 insertions(+), 480 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 35d4fa8..38debfc 100644
--- a/configure.ac
+++ b/configure.ac
@@ -27,6 +27,8 @@ PKG_CHECK_MODULES(GNOME_KLOTSKI, [
   librsvg-2.0 >= $RSVG_REQUIRED
 ])
 
+AC_SUBST([GLIB_REQUIRED])
+
 AC_PATH_PROG([DESKTOP_FILE_VALIDATE], [desktop-file-validate], [/bin/true])
 
 dnl ###########################################################################
diff --git a/data/Makefile.am b/data/Makefile.am
index 828317e..76b83a7 100644
--- a/data/Makefile.am
+++ b/data/Makefile.am
@@ -6,7 +6,10 @@ gsettings_SCHEMAS = org.gnome.klotski.gschema.xml
 man_MANS = gnome-klotski.6
 
 dist_noinst_DATA = \
-       klotski-menus.ui
+       klotski-menus.ui \
+       klotski-scores.ui \
+       klotski.ui \
+       klotski.css
 
 pixmapdir = $(datadir)/gnome-klotski
 pixmap_DATA = \
diff --git a/data/klotski-scores.ui b/data/klotski-scores.ui
new file mode 100644
index 0000000..d2b93b9
--- /dev/null
+++ b/data/klotski-scores.ui
@@ -0,0 +1,110 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk+" version="3.12"/>
+  <object class="GtkListStore" id="levels_liststore">
+    <columns>
+      <column type="gchararray"/> <!-- puzzle name -->
+      <column type="gint"/>       <!-- level -->
+    </columns>
+  </object>
+  <object class="GtkListStore" id="scores_liststore">
+    <columns>
+      <column type="gchararray"/> <!-- date -->
+      <column type="gchararray"/> <!-- moves -->
+      <column type="gint"/>       <!-- weight -->
+    </columns>
+  </object>
+  <template class="ScoreDialog" parent="GtkDialog">
+    <property name="visible">False</property>
+    <property name="width-request">300</property>
+    <property name="height-request">400</property>
+    <property name="resizable">False</property>
+    <property name="title" translatable="yes">Scores</property>
+    <property name="modal">True</property>
+    <child internal-child="vbox">
+      <object class="GtkBox">
+        <property name="visible">True</property>
+        <property name="orientation">vertical</property>
+        <property name="spacing">5</property>
+        <property name="border-width">6</property>
+        <child>
+          <object class="GtkBox">
+            <property name="visible">True</property>
+            <property name="orientation">horizontal</property>
+            <property name="spacing">6</property>
+            <child>
+              <object class="GtkLabel">
+                <property name="visible">True</property>
+                <property name="label" translatable="yes">Puzzle:</property>
+              </object>
+            </child>
+            <child>
+              <object class="GtkComboBox" id="level_combo">
+                <property name="visible">True</property>
+                <property name="model">levels_liststore</property>
+                <child>
+                  <object class="GtkCellRendererText"/>
+                  <attributes>
+                    <attribute name="text">0</attribute>
+                  </attributes>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">True</property>
+                <property name="fill">True</property>
+              </packing>
+            </child>
+          </object>
+        </child>
+        <child>
+          <object class="GtkScrolledWindow">
+            <property name="visible">True</property>
+            <property name="shadow-type">etched-in</property>
+            <property name="hscrollbar-policy">never</property>
+            <property name="vscrollbar-policy">automatic</property>
+            <property name="vexpand">True</property>
+            <child>
+              <object class="GtkTreeView" id="scores_tree">
+                <property name="visible">True</property>
+                <property name="headers-visible">True</property>
+                <property name="activate-on-single-click">True</property>
+                <property name="model">scores_liststore</property>
+                <style><class name="toggle-like-pixbuf"/></style>
+                <child>
+                  <object class="GtkTreeViewColumn">
+                    <property name="title" translatable="yes">Date</property>
+                    <property name="expand">True</property>
+                    <child>
+                      <object class="GtkCellRendererText">
+                        <property name="xalign">0.0f</property>
+                      </object>
+                      <attributes>
+                        <attribute name="text">0</attribute>
+                        <attribute name="weight">2</attribute>
+                      </attributes>
+                    </child>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkTreeViewColumn">
+                    <property name="title" translatable="yes">Moves</property>
+                    <property name="expand">True</property>
+                    <child>
+                      <object class="GtkCellRendererText">
+                        <property name="xalign">1.0f</property>
+                      </object>
+                      <attributes>
+                        <attribute name="text">1</attribute>
+                        <attribute name="weight">2</attribute>
+                      </attributes>
+                    </child>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/data/klotski.css b/data/klotski.css
new file mode 100644
index 0000000..2c72f50
--- /dev/null
+++ b/data/klotski.css
@@ -0,0 +1,37 @@
+GtkPopover#puzzles-popover {
+  padding: 10px;
+}
+
+GtkStack#stack-packs > GtkLabel {
+  font-weight: bold;
+}
+
+.treeview-container GtkTreeView {
+  background-color: transparent;
+}
+.treeview-container GtkTreeView:backdrop {
+  color: @theme_unfocused_fg_color;
+}
+.treeview-container GtkTreeView:prelight {
+  background-color: @theme_bg_color;
+}
+.treeview-container GtkTreeView:prelight:backdrop {
+  background-color: @theme_unfocused_bg_color;
+}
+.treeview-container GtkTreeView:selected,
+.treeview-container GtkTreeView:selected:prelight {
+  background-color: @theme_selected_bg_color;
+  color: @theme_selected_fg_color;
+}
+.treeview-container GtkTreeView:selected:backdrop,
+.treeview-container GtkTreeView:selected:backdrop:prelight {
+  background-color: @theme_unfocused_selected_bg_color;
+  color: @theme_unfocused_selected_fg_color;
+}
+
+GtkTreeView.toggle-like-pixbuf:insensitive {
+  -gtk-icon-source: none;
+}
+GtkTreeView.toggle-like-pixbuf:insensitive:checked {
+  -gtk-icon-source: -gtk-icontheme("gtk-yes");  /* TODO maybe deprecated */
+}
diff --git a/data/klotski.ui b/data/klotski.ui
new file mode 100644
index 0000000..acc8e1d
--- /dev/null
+++ b/data/klotski.ui
@@ -0,0 +1,311 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk+" version="3.12"/>
+  <object class="GtkPopover" id="puzzles-popover">
+    <property name="visible">False</property>
+    <property name="name">puzzles-popover</property>
+    <child>
+      <object class="GtkBox">
+        <property name="visible">True</property>
+        <property name="orientation">vertical</property>
+        <property name="spacing">6</property>
+        <child>
+          <object class="GtkBox">
+            <property name="visible">True</property>
+            <property name="orientation">horizontal</property>
+            <property name="halign">fill</property>
+            <child>
+              <object class="GtkButton">
+                <property name="visible">True</property>
+                <property name="action-name">win.prev-pack</property>
+                <style><class name="flat"/><class name="image-button"/></style>
+                <child>
+                  <object class="GtkImage">
+                    <property name="icon-name">go-previous-symbolic</property>
+                    <property name="visible">True</property>
+                    <property name="icon-size">1</property>
+                  </object>
+                </child>
+              </object>
+            </child>
+            <child>
+              <object class="GtkStack" id="stack-packs">
+                <property name="name">stack-packs</property>
+                <property name="visible">True</property>
+                <child>
+                  <object class="GtkLabel">
+                    <property name="name">pack-name</property>
+                    <property name="visible">True</property>
+                    <property name="label" translatable="yes">Huarong Trail</property>
+                  </object>
+                  <packing>
+                    <property name="name">huarong</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel">
+                    <property name="name">pack-name</property>
+                    <property name="visible">True</property>
+                    <property name="label" translatable="yes">Challenge Pack</property>
+                  </object>
+                  <packing>
+                    <property name="name">challenge</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel">
+                    <property name="name">pack-name</property>
+                    <property name="visible">True</property>
+                    <property name="label" translatable="yes">Skill Pack</property>
+                  </object>
+                  <packing>
+                    <property name="name">skill</property>
+                    <property name="position">2</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">True</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton">
+                <property name="visible">True</property>
+                <property name="action-name">win.next-pack</property>
+                <style><class name="flat"/><class name="image-button"/></style>
+                <child>
+                  <object class="GtkImage">
+                    <property name="icon-name">go-next-symbolic</property>
+                    <property name="visible">True</property>
+                    <property name="icon-size">1</property>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+        <child>
+          <object class="GtkStack" id="stack-puzzles"> <!-- GtkScrolledWindow children are here for when big 
text -->
+            <property name="visible">True</property>
+            <property name="homogeneous">True</property>
+            <property name="height-request">270</property>
+            <property name="width-request">190</property>
+            <child>
+              <object class="GtkScrolledWindow">
+                <property name="visible">True</property>
+                <property name="hscrollbar-policy">never</property>
+                <style><class name="treeview-container"/></style>
+                <child>
+                  <object class="GtkTreeView" id="treeview-huarong">
+                    <property name="visible">True</property>
+                    <property name="headers-visible">False</property>
+                    <property name="activate-on-single-click">True</property>
+                    <property name="can-focus">False</property> <!-- for up/down keybinding -->
+                    <!-- <property name="model">*store</property> TODO -->
+                    <style><class name="toggle-like-pixbuf"/></style>
+                    <child>
+                      <object class="GtkTreeViewColumn">
+                        <property name="title">Puzzle</property>
+                        <child>
+                          <object class="GtkCellRendererText"/>
+                          <attributes>
+                            <attribute name="text">0</attribute>
+                          </attributes>
+                        </child>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkTreeViewColumn">
+                        <property name="title">Complete</property>
+                        <child>
+                          <object class="GtkCellRendererToggle"/>
+                          <attributes>
+                            <attribute name="active">1</attribute>
+                            <attribute name="sensitive">3</attribute>
+                          </attributes>
+                        </child>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="name">huarong</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkScrolledWindow">
+                <property name="visible">True</property>
+                <property name="hscrollbar-policy">never</property>
+                <style><class name="treeview-container"/></style>
+                <child>
+                  <object class="GtkTreeView" id="treeview-challenge">
+                    <property name="visible">True</property>
+                    <property name="headers-visible">False</property>
+                    <property name="activate-on-single-click">True</property>
+                    <property name="can-focus">False</property> <!-- for up/down keybinding -->
+                    <!-- <property name="model">*store</property> TODO -->
+                    <style><class name="toggle-like-pixbuf"/></style>
+                    <child>
+                      <object class="GtkTreeViewColumn">
+                        <property name="title">Puzzle</property>
+                        <child>
+                          <object class="GtkCellRendererText"/>
+                          <attributes>
+                            <attribute name="text">0</attribute>
+                          </attributes>
+                        </child>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkTreeViewColumn">
+                        <property name="title">Complete</property>
+                        <child>
+                          <object class="GtkCellRendererToggle"/>
+                          <attributes>
+                            <attribute name="active">1</attribute>
+                            <attribute name="sensitive">3</attribute>
+                          </attributes>
+                        </child>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="name">challenge</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkScrolledWindow">
+                <property name="visible">True</property>
+                <property name="hscrollbar-policy">never</property>
+                <style><class name="treeview-container"/></style>
+                <child>
+                  <object class="GtkTreeView" id="treeview-skill">
+                    <property name="visible">True</property>
+                    <property name="headers-visible">False</property>
+                    <property name="activate-on-single-click">True</property>
+                    <property name="can-focus">False</property> <!-- for up/down keybinding -->
+                    <!-- <property name="model">*store</property> TODO -->
+                    <style><class name="toggle-like-pixbuf"/></style>
+                    <child>
+                      <object class="GtkTreeViewColumn">
+                        <property name="title">Puzzle</property>
+                        <child>
+                          <object class="GtkCellRendererText"/>
+                          <attributes>
+                            <attribute name="text">0</attribute>
+                          </attributes>
+                        </child>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkTreeViewColumn">
+                        <property name="title">Complete</property>
+                        <child>
+                          <object class="GtkCellRendererToggle"/>
+                          <attributes>
+                            <attribute name="active">1</attribute>
+                            <attribute name="sensitive">3</attribute>
+                          </attributes>
+                        </child>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="name">skill</property>
+                <property name="position">2</property>
+              </packing>
+            </child>
+          </object>
+        </child>
+        <child>
+          <object class="GtkBox">
+            <property name="orientation">horizontal</property>
+            <property name="visible">True</property>
+            <property name="homogeneous">True</property>
+            <property name="halign">fill</property>
+            <style><class name="linked"/></style>
+            <child>
+              <object class="GtkButton">
+                <property name="visible">True</property>
+                <!-- <property name="use-underline">True</property> TODO -->
+                <property name="label" translatable="yes">Previous</property>
+                <property name="action-name">win.prev-puzzle</property>
+              </object>
+              <packing>
+                <property name="expand">True</property>
+                <property name="fill">True</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton">
+                <property name="visible">True</property>
+                <!-- <property name="use-underline">True</property> TODO -->
+                <property name="label" translatable="yes">Next</property>
+                <property name="action-name">win.next-puzzle</property>
+              </object>
+              <packing>
+                <property name="expand">True</property>
+                <property name="fill">True</property>
+              </packing>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </object>
+  <object class="GtkApplicationWindow" id="window">
+    <!-- <initial-focus name=""/> -->
+    <property name="title" translatable="yes">Klotski</property>
+    <property name="width-request">600</property>
+    <property name="height-request">400</property>
+    <property name="border-width">25</property> <!-- TODO a view margin -->
+    <child type="titlebar">
+      <object class="GtkHeaderBar" id="headerbar">
+        <property name="visible">True</property>
+        <property name="show-close-button">True</property>
+        <property name="title" translatable="yes">Klotski</property>
+        <child>
+          <object class="GtkButton">
+            <property name="visible">True</property>
+            <property name="use-underline">True</property>
+            <property name="label" translatable="yes">_Start Over</property>
+            <property name="tooltip-text" translatable="yes">Restart the current puzzle</property>
+            <property name="can-focus">True</property>
+            <property name="focus-on-click">False</property>
+            <property name="action-name">win.start-game</property>
+          </object>
+          <packing>
+            <property name="pack-type">start</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkMenuButton">
+            <property name="visible">True</property>
+            <property name="use-underline">True</property>
+            <property name="label" translatable="yes">_Change Puzzle</property>
+            <property name="tooltip-text" translatable="yes">Choose an other puzzle</property>
+            <property name="can-focus">True</property>
+            <property name="focus-on-click">False</property>
+            <property name="direction">down</property>
+            <property name="popover">puzzles-popover</property>
+          </object>
+          <packing>
+            <property name="pack-type">end</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <child>
+      <placeholder/>
+    </child>
+  </object>
+</interface>
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 275e60c..d4cde8a 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -3,7 +3,9 @@
 [encoding: UTF-8]
 data/gnome-klotski.appdata.xml.in
 data/gnome-klotski.desktop.in
+[type: gettext/glade]data/klotski.ui
 [type: gettext/glade]data/klotski-menus.ui
+[type: gettext/glade]data/klotski-scores.ui
 data/org.gnome.klotski.gschema.xml
 src/gnome-klotski.vala
 src/puzzle-view.vala
diff --git a/src/Makefile.am b/src/Makefile.am
index 920da62..bc4c005 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -22,7 +22,9 @@ gnome_klotski_CFLAGS = \
 gnome_klotski_VALAFLAGS = \
        --pkg posix \
        --pkg gtk+-3.0 \
-       --pkg librsvg-2.0
+       --pkg librsvg-2.0 \
+       --gresources $(builddir)/klotski.gresource.xml \
+       --target-glib $(GLIB_REQUIRED)
 
 gnome_klotski_LDADD = \
        $(GNOME_KLOTSKI_LIBS)
diff --git a/src/gnome-klotski.vala b/src/gnome-klotski.vala
index becb3f4..54ffc32 100644
--- a/src/gnome-klotski.vala
+++ b/src/gnome-klotski.vala
@@ -8,6 +8,8 @@
  * license.
  */
 
+using Gtk;
+
 /* Puzzle Info */
 private struct LevelInfo
 {
@@ -20,42 +22,43 @@ private struct LevelInfo
 
 public class Klotski : Gtk.Application
 {
-    private Settings settings;
-    private const int MINWIDTH = 250;
-    private const int MINHEIGHT = 250;
-    private const int SPACE_PADDING = 5;
-
-    private const string KEY_LEVEL = "level";
-
-    /* Main window */
-    private Gtk.Window window;
+    /* Settings */
+    private GLib.Settings settings;
+    private bool is_tiled;
+    private bool is_maximized;
     private int window_width;
     private int window_height;
-    private bool is_maximized;
-    private bool is_tiled;
-
-    private Gtk.Box puzzles_panel;
-
-    private Gtk.Button next_button;
-    private Gtk.Button prev_button;
-    private SimpleAction next_level_action;
-    private SimpleAction prev_level_action;
 
-    private SimpleAction new_game_action;
+    private const string KEY_LEVEL = "level";
 
+    /* Widgets */
+    private ApplicationWindow window;
+    private HeaderBar headerbar;
+    private Stack stack_packs;
+    private Stack stack_puzzles;
+    private Popover puzzles_popover;
     private PuzzleView view;
 
-    private Gtk.HeaderBar headerbar;
+    /* Actions, to disable or enable */
+    private SimpleAction prev_pack;
+    private SimpleAction next_pack;
+    private SimpleAction prev_puzzle;
+    private SimpleAction next_puzzle;
+    private SimpleAction start_game;
 
+    /* The game being played */
     private Puzzle puzzle;
 
+    private int current_pack = -1;
     private int current_level = -1;
 
     private History history;
 
     /* The "puzzle name" remarks provide context for translation. */
-    private Gtk.TreeStore puzzles;
-    private Gtk.TreeIter[] puzzles_items;
+    private Gtk.ListStore liststore_huarong;
+    private Gtk.ListStore liststore_challenge;
+    private Gtk.ListStore liststore_skill;
+    private TreeIter[] puzzles_items;
     public const LevelInfo level[] =
     {
       /* puzzle name */
@@ -436,18 +439,33 @@ public class Klotski : Gtk.Application
         { null }
     };
 
-    private const GLib.ActionEntry[] action_entries =
+    private const GLib.ActionEntry app_actions[] =
+    {
+        {"scores", scores_cb},
+        {"help", help_cb},
+        {"about", about_cb},
+        {"quit", quit}
+    };
+    private const GLib.ActionEntry win_actions[] =
     {
-        { "new-game",             restart_level_cb  },
-        { "show-puzzles",         toggle_puzzles_cb },
-        { "next-level",           next_level_cb     },
-        { "prev-level",           prev_level_cb     },
-        { "scores",               scores_cb         },
-        { "help",                 help_cb           },
-        { "about",                about_cb          },
-        { "quit",                 quit_cb           }
+        {"prev-pack", prev_pack_cb},
+        {"next-pack", next_pack_cb},
+        {"prev-puzzle", prev_puzzle_cb},
+        {"next-puzzle", next_puzzle_cb},
+        {"start-game", start_puzzle_cb}
     };
 
+    public static int main (string[] args)
+    {
+        Intl.setlocale (LocaleCategory.ALL, "");
+        Intl.bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
+        Intl.bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+        Intl.textdomain (GETTEXT_PACKAGE);
+
+        var app = new Klotski ();
+        return app.run (args);
+    }
+
     public Klotski ()
     {
         Object (application_id: "org.gnome.klotski", flags: ApplicationFlags.FLAGS_NONE);
@@ -455,199 +473,153 @@ public class Klotski : Gtk.Application
         add_main_option_entries (option_entries);
     }
 
-    protected override void startup ()
+    protected override int handle_local_options (GLib.VariantDict options)
     {
-        base.startup ();
-
-        Environment.set_application_name (_("Klotski"));
-
-        settings = new Settings ("org.gnome.klotski");
-
-        Gtk.Window.set_default_icon_name ("gnome-klotski");
-
-        var css_provider = new Gtk.CssProvider ();
-        try
-        {
-            /* Pixel-perfect compatibility with games that have a Button without ButtonBox. */
-            var data = """GtkButtonBox { -GtkButtonBox-child-internal-pad-x:0; }""";
-            css_provider.load_from_data (data, data.length);
-        }
-        catch (GLib.Error e)
+        if (options.contains ("version"))
         {
-            warning ("Error loading css styles: %s", e.message);
+            /* NOTE: Is not translated so can be easily parsed */
+            stderr.printf ("%1$s %2$s\n", "gnome-klotski", VERSION);
+            return Posix.EXIT_SUCCESS;
         }
-        Gtk.StyleContext.add_provider_for_screen (Gdk.Screen.get_default (), css_provider, 
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
 
-        add_action_entries (action_entries, this);
-        new_game_action = lookup_action ("new-game") as SimpleAction;
-        new_game_action.set_enabled (false);
-        next_level_action = lookup_action ("next-level") as SimpleAction;
-        next_level_action.set_enabled (current_level < level.length - 1);
-        prev_level_action = lookup_action ("prev-level") as SimpleAction;
-        prev_level_action.set_enabled (current_level > 0);
+        /* Activate */
+        return -1;
+    }
 
-        set_accels_for_action ("app.new-game", {"<Primary>n"});
-        set_accels_for_action ("app.quit", {"<Primary>q"});
-        set_accels_for_action ("app.help", {"F1"});
-        set_accels_for_action ("app.next-level", {"Page_Up"});
-        set_accels_for_action ("app.prev-level", {"Page_Down"});
+    protected override void startup ()
+    {
+        base.startup ();
 
-        string histfile = Path.build_filename (Environment.get_user_data_dir (), "gnome-klotski", "history");
+        Environment.set_application_name (_("Klotski"));
+        Window.set_default_icon_name ("gnome-klotski");
 
-        history = new History (histfile);
-        history.load ();
+        var css_provider = new CssProvider ();
+        css_provider.load_from_resource ("/org/gnome/klotski/ui/klotski.css");
+        StyleContext.add_provider_for_screen (Gdk.Screen.get_default (), css_provider, 
STYLE_PROVIDER_PRIORITY_APPLICATION);
 
-        headerbar = new Gtk.HeaderBar ();
-        headerbar.show_close_button = true;
-        headerbar.show ();
+        settings = new GLib.Settings ("org.gnome.klotski");
 
-        window = new Gtk.ApplicationWindow (this);
-        window.set_titlebar (headerbar);
+        var builder = new Builder.from_resource ("/org/gnome/klotski/ui/klotski.ui");
+        window = builder.get_object ("window") as ApplicationWindow;
         window.size_allocate.connect (size_allocate_cb);
         window.window_state_event.connect (window_state_event_cb);
-
-        int ww = int.max (settings.get_int ("window-width"), MINWIDTH);
-        int wh = int.max (settings.get_int ("window-height"), MINHEIGHT);
-        window.set_default_size (ww, wh);
-        window.border_width = 25;
-
+        window.set_default_size (settings.get_int ("window-width"), settings.get_int ("window-height"));
         if (settings.get_boolean ("window-is-maximized"))
             window.maximize ();
 
-        var hbox = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 25);
-        window.add (hbox);
-
-        puzzles = new Gtk.TreeStore (3, typeof (string), typeof (bool), typeof (int));
+        add_action_entries (app_actions, this);
+        window.add_action_entries (win_actions, this);
+        prev_pack = window.lookup_action ("prev-pack") as SimpleAction;
+        next_pack = window.lookup_action ("next-pack") as SimpleAction;
+        prev_puzzle = window.lookup_action ("prev-puzzle") as SimpleAction;
+        next_puzzle = window.lookup_action ("next-puzzle") as SimpleAction;
+        start_game = window.lookup_action ("start-game") as SimpleAction;
+        set_accels_for_action ("app.help", {"F1"});
+        set_accels_for_action ("app.quit", {"<Primary>q"});
+        // set_accels_for_action ("win.start-game", {"<Primary>n"}); /* or <Primary>r ? or both ? */
+        set_accels_for_action ("win.prev-puzzle", {"Up"});       // TODO
+        set_accels_for_action ("win.next-puzzle", {"Down"});     // TODO a weird behaviour exists when you 
first change puzzle pack, then go to
+        set_accels_for_action ("win.prev-pack", {"Page_Up"});    // TODO the first/last one, click on a 
puzzle, and immediatly hit Up or Down arrows.
+        set_accels_for_action ("win.next-pack", {"Page_Down"});  // TODO that makes these keybindings 
sometimes act strangely, but they’re good.
 
-        Gtk.TreeIter huarong_item;
-        puzzles.append (out huarong_item, null);
-        puzzles.set (huarong_item, 0, "HuaRong Trail", 2, -1, -1);
+        string histfile = Path.build_filename (Environment.get_user_data_dir (), "gnome-klotski", "history");
 
-        Gtk.TreeIter challenge_item;
-        puzzles.append (out challenge_item, null);
-        puzzles.set (challenge_item, 0, "Challenge Pack", 2, -1, -1);
+        history = new History (histfile);
+        history.load ();
 
-        Gtk.TreeIter skill_item;
-        puzzles.append (out skill_item, null);
-        puzzles.set (skill_item, 0, "Skill Pack", 2, -1, -1);
+        headerbar = builder.get_object ("headerbar") as HeaderBar;
+        stack_packs = builder.get_object ("stack-packs") as Stack;
+        stack_puzzles = builder.get_object ("stack-puzzles") as Stack;
+        puzzles_popover = builder.get_object ("puzzles-popover") as Popover;
 
-        puzzles_items = new Gtk.TreeIter[level.length];
+        // name, active, puzzle number (or -1), sensitive=false CSS hack
+        liststore_huarong = new Gtk.ListStore (4, typeof (string), typeof (bool), typeof (int), typeof 
(bool));
+        liststore_challenge = new Gtk.ListStore (4, typeof (string), typeof (bool), typeof (int), typeof 
(bool));
+        liststore_skill = new Gtk.ListStore (4, typeof (string), typeof (bool), typeof (int), typeof (bool));
 
+        puzzles_items = new TreeIter[level.length];
         for (var i = 0; i < level.length; i++)
         {
             switch (level[i].group)
             {
             case 0:
-                puzzles.append (out puzzles_items[i], huarong_item);
-                puzzles.set (puzzles_items[i], 0, _(level[i].name), 1, false, 2, i, -1);
+                liststore_huarong.append (out puzzles_items[i]);
+                liststore_huarong.set (puzzles_items[i],
+                                       0, _(level[i].name),
+                                       1, false,
+                                       2, i,
+                                       3, false);
                 break;
             case 1:
-                puzzles.append (out puzzles_items[i], challenge_item);
-                puzzles.set (puzzles_items[i], 0, _(level[i].name), 1, false, 2, i, -1);
+                liststore_challenge.append (out puzzles_items[i]);
+                liststore_challenge.set (puzzles_items[i],
+                                         0, _(level[i].name),
+                                         1, false,
+                                         2, i,
+                                         3, false);
                 break;
             case 2:
-                puzzles.append (out puzzles_items[i], skill_item);
-                puzzles.set (puzzles_items[i], 0, _(level[i].name), 1, false, 2, i, -1);
+                liststore_skill.append (out puzzles_items[i]);
+                liststore_skill.set (puzzles_items[i],
+                                     0, _(level[i].name),
+                                     1, false,
+                                     2, i,
+                                     3, false);
                 break;
             }
         }
 
-        puzzles_panel = new Gtk.Box (Gtk.Orientation.VERTICAL, 0);
-        puzzles_panel.visible = false;
-
-        var puzzles_view = new Gtk.TreeView.with_model (puzzles);
-        puzzles_view.set_headers_visible (false);
-
-        var cell = new Gtk.CellRendererText ();
-        var col = new Gtk.TreeViewColumn.with_attributes ("Puzzle", cell, "text", 0, null);
-        col.set_data<Klotski> ("app", this);
-        col.set_cell_data_func (cell, (Gtk.CellLayoutDataFunc) render_puzzle_name);
-        puzzles_view.append_column (col);
-
-        puzzles_view.insert_column_with_attributes (-1, "Complete", new CellRendererLevel (), "visible", 1, 
null);
-        puzzles_view.row_activated.connect (level_cb);
-        puzzles_view.show_all ();
-
-        var scroll = new Gtk.ScrolledWindow (null, null);
-        scroll.name = "puzzles";
-        scroll.set_policy (Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
-        scroll.add (puzzles_view);
-        scroll.show ();
-        puzzles_panel.pack_start (scroll, true, true, 0);
-
-        var bbox = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0);
-        var context = bbox.get_style_context ();
-        context.add_class ("linked");
-        bbox.margin_top = 12;
-        bbox.show ();
-
-        prev_button = new Gtk.Button.with_label (_("Previous Puzzle"));
-        prev_button.clicked.connect (prev_level_cb);
-        prev_button.sensitive = current_level > 0;
-        prev_button.show ();
-        bbox.add (prev_button);
-
-        next_button = new Gtk.Button.with_label (_("Next Puzzle"));
-        next_button.clicked.connect (next_level_cb);
-        next_button.sensitive = current_level < level.length - 1;
-        next_button.show ();
-        bbox.add (next_button);
-
-        puzzles_panel.pack_start (bbox, false, true, 0);
-        hbox.pack_start (puzzles_panel, false, true, 0);
+        var treeview_huarong = builder.get_object ("treeview-huarong") as TreeView;
+        var treeview_challenge = builder.get_object ("treeview-challenge") as TreeView;
+        var treeview_skill = builder.get_object ("treeview-skill") as TreeView;
+
+        treeview_huarong.set_model (liststore_huarong);
+        treeview_challenge.set_model (liststore_challenge);
+        treeview_skill.set_model (liststore_skill);
+
+        treeview_huarong.row_activated.connect (level_huarong_cb);
+        treeview_challenge.row_activated.connect (level_challenge_cb);
+        treeview_skill.row_activated.connect (level_skill_cb);
 
         view = new PuzzleView ();
-        view.set_size_request (MINWIDTH, MINHEIGHT);
+        view.halign = Align.FILL;
+        view.can_focus = true;
         view.show ();
-        hbox.pack_start (view, true, true, 0);
-
-        bbox = new Gtk.ButtonBox (Gtk.Orientation.VERTICAL);
-        bbox.halign = Gtk.Align.END;
-        bbox.valign = Gtk.Align.END;
-        bbox.spacing = 6;
-        bbox.show ();
-        hbox.pack_start (bbox, false, true, 0);
-
-        var button = new Gtk.Button ();
-        button.label = _("_Start Over");
-        button.use_underline = true;
-        button.width_request = 120;
-        button.height_request = 60;
-        button.action_name = "app.new-game";
-        button.show ();
-        bbox.pack_end (button, false, true, 0);
-
-        var togglebutton = new Gtk.ToggleButton ();
-        togglebutton.label = _("_View Puzzles");
-        togglebutton.use_underline = true;
-        togglebutton.width_request = 120;
-        togglebutton.height_request = 60;
-        togglebutton.action_name = "app.show-puzzles";
-        togglebutton.show ();
-        bbox.pack_end (togglebutton, false, true, 0);
-
-        hbox.show ();
-
-        load_solved_state ();
-
-        var startup_level = settings.get_int (KEY_LEVEL);
-        new_game (startup_level);
+        window.add (view);
+
+        load_solved_state ();       // TODO use GSettings, or the history…
+
+        current_level = settings.get_int (KEY_LEVEL).clamp (0, level.length - 1);
+        puzzles_popover.show.connect (() => { update_popover (true); });
+        update_popover (true);      // or “Start Over” logically complains
+
+        start_puzzle ();
+        add_window (window);
     }
 
-    private static void render_puzzle_name (Gtk.CellLayout cell_layout, Gtk.CellRendererText cell,
-                                            Gtk.TreeModel tree_model, Gtk.TreeIter iter)
+    protected override void activate ()
     {
-        Value val;
-        tree_model.get_value (iter, 2, out val);
-        int selected_level = (int) val;
-        Klotski app = cell_layout.get_data<Klotski> ("app");
-        if (app.current_level == selected_level)
-            cell.weight = 700;
-        else
-            cell.weight = 400;
+        window.present ();
+    }
+
+    protected override void shutdown ()
+    {
+        base.shutdown ();
+
+        /* Save game state */
+        settings.set_int (KEY_LEVEL, current_level);
+
+        /* Save window state */
+        settings.set_int ("window-width", window_width);
+        settings.set_int ("window-height", window_height);
+        settings.set_boolean ("window-is-maximized", is_maximized);
     }
 
-    private void size_allocate_cb (Gtk.Allocation allocation)
+    /*\
+    * * Window events
+    \*/
+
+    private void size_allocate_cb (Allocation allocation)
     {
         if (is_maximized || is_tiled)
             return;
@@ -665,305 +637,315 @@ public class Klotski : Gtk.Application
         return false;
     }
 
+    /*\
+    * * App-menu callbacks
+    \*/
+
     private void scores_cb ()
     {
-        show_scores (null, false);
+        show_scores (null);
     }
 
-    private void game_score ()
+    private void help_cb ()
     {
-        /* Level is complete */
-        var key = get_level_key (current_level);
-        var keyfile = new KeyFile ();
-        var filename = Path.build_filename (Environment.get_user_data_dir (), "gnome-klotski", "levels");  
// filename:~/.local/share/gnome-klotski/levels
-
         try
         {
-            keyfile.load_from_file (filename, KeyFileFlags.NONE);
+            show_uri (window.get_screen (), "help:gnome-klotski", get_current_event_time ());
         }
         catch (Error e)
         {
+            warning ("Failed to show help: %s", e.message);
         }
+    }
 
-        keyfile.set_boolean (key, "solved", true);
-
-        try
-        {
-            FileUtils.set_contents (filename, keyfile.to_data ());
-        }
-        catch (Error e)
-        {
-        }
+    private void about_cb ()
+    {
+        const string authors[] = { "Lars Rydlinge (original author)", "Robert Ancell (port to vala)", "John 
Cheetham (port to vala)", null };
+        const string documenters[] = { "Andrew Sobala", null };
 
-        puzzles.set (puzzles_items[current_level], 1, true, -1);
+        show_about_dialog (window,
+                           "program-name", _("Klotski"),
+                           "version", VERSION,
+                           "comments", _("Sliding block puzzles"),
+                           "copyright",
+                             "Copyright © 1999–2008 Lars Rydlinge\n"+
+                             "Copyright © 2014–2015 Michael Catanzaro\n"+
+                             "Copyright © 2015 Arnaud Bonatti\n",
+                           "license-type", License.GPL_2_0,     // TODO
+                           "authors", authors,
+                           "documenters", documenters,
+                           "translator-credits", _("translator-credits"),
+                           "logo-icon-name", "gnome-klotski",
+                           "website", "https://wiki.gnome.org/Apps/Klotski";,
+                           null);
+    }
 
-        var date = new DateTime.now_local ();
-        var entry = new HistoryEntry (date, current_level, puzzle.moves);
-        history.add (entry);
-        history.save ();
+    /*\
+    * * Popover’s buttons callbacks
+    \*/
 
-        if (show_scores (entry, true) == Gtk.ResponseType.OK)
-            new_game (current_level);
+    private void prev_pack_cb ()
+    {
+        if (!puzzles_popover.visible)
+            return;
+        current_pack--;
+        update_popover (false);
     }
 
-    private int show_scores (HistoryEntry? selected_entry = null, bool show_close = false)
+    private void next_pack_cb ()
     {
-        var dialog = new ScoreDialog (history, selected_entry, show_close);
-        dialog.modal = true;
-        dialog.transient_for = window;
-
-        var result = dialog.run ();
-        dialog.destroy ();
-
-        return result;
+        if (!puzzles_popover.visible)
+            return;
+        current_pack++;
+        update_popover (false);
     }
 
-    private string get_level_key (int level_number)
+    private void prev_puzzle_cb ()
     {
-        /* Calculate the CRC of the level data */
-        uint32 result = 0xFFFFFFFFu;
-        var data = level[level_number].data;
-        for (var i = 0; data[i] != '\0'; i++)
-        {
-            var octet = data[i];
-            for (var j = 0; j < 8; j++)
-            {
-                if (((octet >> 7) ^ (result >> 31)) != 0)
-                    result = (result << 1) ^ 0x04c11db7;
-                else
-                    result = (result << 1);
-                result &= 0xFFFFFFFF;
-                octet <<= 1;
-            }
-        }
+        if (!puzzles_popover.visible)
+            return;
+        current_level--;
+        update_popover (true);
+        start_puzzle ();
+    }
 
-        return "%08X".printf (~result);
+    private void next_puzzle_cb ()
+    {
+        if (!puzzles_popover.visible)
+            return;
+        current_level++;
+        update_popover (true);
+        start_puzzle ();
     }
 
-    private void load_solved_state ()
+    private void start_puzzle_cb ()
     {
-        var keyfile = new KeyFile ();
-        var filename = Path.build_filename (Environment.get_user_data_dir (), "gnome-klotski", "levels");
-        try
-        {
-            keyfile.load_from_file (filename, KeyFileFlags.NONE);
-        }
-        catch (Error e)
-        {
-        }
+        TreeView tree = ((TreeView) (((ScrolledWindow) (stack_puzzles.get_children ().nth_data 
(current_pack))).get_child ()));
+        TreeModel model = tree.get_model ();
+        TreeIter iter;
 
-        for (var i = 0; i < level.length; i++)
-        {
-            var key = get_level_key (i);
-            var value = false;
-            try
-            {
-                value = keyfile.get_boolean (key, "solved");
-            }
-            catch (Error e)
-            {
-            }
-            puzzles.set (puzzles_items[i], 1, value, -1);
-        }
+        if (tree.get_selection ().get_selected (out model, out iter))
+            start_puzzle_from_iter ((Gtk.ListStore) model, iter);
+        else
+            start_puzzle ();
+        puzzles_popover.hide ();
     }
 
-    private void update_menu_state ()
+    /*\
+    * * Update popover
+    \*/
+
+    private void update_popover (bool make_current)
     {
-        puzzles_panel.queue_draw ();
+        int current_level_pack;
+        TreeIter iter = puzzles_items[current_level];
+        if (liststore_huarong.iter_is_valid (iter))          // "slow"
+            current_level_pack = 0;
+        else if (liststore_challenge.iter_is_valid (iter))   // same here
+            current_level_pack = 1;
+        else
+            current_level_pack = 2;
+
+        if (make_current)
+            current_pack = current_level_pack;
 
-        next_button.sensitive = current_level < level.length - 1;
-        prev_button.sensitive = current_level > 0;
+        /* select or not a level */
+        TreeSelection selection = ((TreeView) (((ScrolledWindow) (stack_puzzles.get_children ().nth_data 
(current_pack))).get_child ())).get_selection ();
+        if (current_pack == current_level_pack)
+            selection.select_iter (iter);
+        else
+            selection.unselect_all ();
 
-        next_level_action.set_enabled (current_level < level.length - 1);
-        prev_level_action.set_enabled (current_level > 0);
+        update_buttons_state ();
 
-        update_moves_label ();
+        /* update stacks */
+        stack_packs.set_visible_child (stack_packs.get_children ().nth_data (current_pack));
+        stack_puzzles.set_visible_child (stack_puzzles.get_children ().nth_data (current_pack));
     }
 
-    private void new_game (int requested_level)
+    private void update_buttons_state ()
     {
-        current_level = requested_level.clamp (0, level.length - 1);
+        prev_pack.set_enabled (current_pack > 0);
+        next_pack.set_enabled (current_pack < 2);
 
-        settings.set_int (KEY_LEVEL, current_level);
-
-        headerbar.set_title (_(level[current_level].name));
-        puzzle = new Puzzle (level[current_level].width, level[current_level].height, 
level[current_level].data);
-        puzzle.moved.connect (puzzle_moved_cb);
-        view.puzzle = puzzle;
-        new_game_action.set_enabled (false);
-        update_menu_state ();
+        prev_puzzle.set_enabled (current_level > 0);
+        next_puzzle.set_enabled (current_level < level.length - 1);
     }
 
-    private void puzzle_moved_cb ()
+    /*\
+    * * Selecting puzzle by the treeview
+    \*/
+
+    private void level_huarong_cb (TreePath path, TreeViewColumn column)
     {
-        update_moves_label ();
-        new_game_action.set_enabled (true);
+        level_cb (liststore_huarong, path, column);
     }
-
-    private void update_moves_label ()
+    private void level_challenge_cb (TreePath path, TreeViewColumn column)
     {
-        headerbar.set_subtitle (_("Moves: %d").printf (puzzle.moves));
-        if (puzzle.game_over ())
-        {
-            headerbar.set_title (_("Level completed."));
-            game_score ();
-        }
+        level_cb (liststore_challenge, path, column);
     }
-
-    private void quit_cb ()
+    private void level_skill_cb (TreePath path, TreeViewColumn column)
     {
-        window.destroy ();
+        level_cb (liststore_skill, path, column);
     }
+    private void level_cb (Gtk.ListStore liststore, TreePath path, TreeViewColumn column)
+    {
+        TreeIter iter;
 
-    private void level_cb (Gtk.TreePath path, Gtk.TreeViewColumn column)
+        liststore.get_iter (out iter, path);
+        start_puzzle_from_iter (liststore, iter);
+    }
+
+    /*\
+    * * Creating and starting game
+    \*/
+
+    private void start_puzzle_from_iter (Gtk.ListStore model, TreeIter iter)
     {
-        Gtk.TreeIter iter;
         Value val;
-
-        puzzles.get_iter (out iter, path);
-        puzzles.get_value (iter, 2, out val);
+        model.get_value (iter, 2, out val);
 
         int requested_level = (int) val;
         if (requested_level < 0)
             return;
 
-        if (current_level != requested_level)
-            new_game (requested_level);
+        current_level = requested_level;
+        update_buttons_state ();
+        start_puzzle ();
     }
 
-    private void restart_level_cb ()
+    private void start_puzzle ()
     {
-        new_game (current_level);
-    }
+        headerbar.set_title (_(level[current_level].name));
+        puzzle = new Puzzle (level[current_level].width, level[current_level].height, 
level[current_level].data);
+        puzzle.moved.connect (puzzle_moved_cb);     // TODO disconnect previous puzzle?
+        view.puzzle = puzzle;
 
-    private void toggle_puzzles_cb ()
-    {
-        puzzles_panel.visible = !puzzles_panel.visible;
+        update_moves_label ();
+        start_game.set_enabled (false);
     }
 
-    private void next_level_cb ()
+    private void puzzle_moved_cb ()
     {
-        new_game (current_level + 1);
+        update_moves_label ();
     }
 
-    private void prev_level_cb ()
+    private void update_moves_label ()
     {
-        new_game (current_level - 1);
+        start_game.set_enabled (true);
+        headerbar.set_subtitle (_("Moves: %d").printf (puzzle.moves));
+        if (puzzle.game_over ())
+        {
+            headerbar.set_title (_("Level completed."));
+            game_score ();
+        }
     }
 
-    private void help_cb ()
+    /*\
+    * * Scores
+    \*/
+
+    private void game_score ()
     {
+        /* Level is complete */
+        var key = get_level_key (current_level);
+        var keyfile = new KeyFile ();
+        var filename = Path.build_filename (Environment.get_user_data_dir (), "gnome-klotski", "levels");  
// filename:~/.local/share/gnome-klotski/levels
+
         try
         {
-            Gtk.show_uri (window.get_screen (), "help:gnome-klotski", Gtk.get_current_event_time ());
+            keyfile.load_from_file (filename, KeyFileFlags.NONE);
         }
         catch (Error e)
         {
-            warning ("Failed to show help: %s", e.message);
         }
-    }
-
-    protected override void shutdown ()
-    {
-        base.shutdown ();
 
-        /* Save window state */
-        settings.set_int ("window-width", window_width);
-        settings.set_int ("window-height", window_height);
-        settings.set_boolean ("window-is-maximized", is_maximized);
-    }
+        keyfile.set_boolean (key, "solved", true);
 
-    protected override int handle_local_options (GLib.VariantDict options)
-    {
-        if (options.contains ("version"))
+        try
+        {
+            FileUtils.set_contents (filename, keyfile.to_data ());
+        }
+        catch (Error e)
         {
-            /* NOTE: Is not translated so can be easily parsed */
-            stderr.printf ("%1$s %2$s\n", "gnome-klotski", VERSION);
-            return Posix.EXIT_SUCCESS;
         }
 
-        /* Activate */
-        return -1;
-    }
-
-    protected override void activate ()
-    {
-        window.present ();
-    }
-
-    private void about_cb ()
-    {
-        const string authors[] = { "Lars Rydlinge (original author)", "Robert Ancell (port to vala)", "John 
Cheetham (port to vala)", null };
-        const string documenters[] = { "Andrew Sobala", null };
+        puzzle_solved (puzzles_items[current_level], true);
 
-        Gtk.show_about_dialog (window,
-                               "program-name", _("Klotski"),
-                               "version", VERSION,
-                               "comments", _("Sliding block puzzles"),
-                               "copyright",
-                               "Copyright © 1999–2008 Lars Rydlinge\nCopyright © 2014–2015 Michael 
Catanzaro",
-                               "license-type", Gtk.License.GPL_2_0,
-                               "authors", authors,
-                               "documenters", documenters,
-                               "translator-credits", _("translator-credits"),
-                               "logo-icon-name", "gnome-klotski",
-                               "website", "https://wiki.gnome.org/Apps/Klotski";,
-                               null);
+        var date = new DateTime.now_local ();
+        var entry = new HistoryEntry (date, current_level, puzzle.moves);
+        history.add (entry);
+        history.save ();
 
+        show_scores (entry);
     }
 
-    public static int main (string[] args)
+    private void show_scores (HistoryEntry? selected_entry = null)
     {
-        Intl.setlocale (LocaleCategory.ALL, "");
-        Intl.bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
-        Intl.bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
-        Intl.textdomain (GETTEXT_PACKAGE);
+        var dialog = new ScoreDialog (history, selected_entry);
+        dialog.set_transient_for (window);
 
-        var app = new Klotski ();
-        return app.run (args);
+        /* var result = */ dialog.run ();
+        dialog.destroy ();
     }
-}
-
-private class CellRendererLevel : Gtk.CellRenderer
-{
-    private const int icon_size = 10;
 
-    public CellRendererLevel ()
+    private string get_level_key (int level_number)
     {
-        GLib.Object ();
-    }
+        /* Calculate the CRC of the level data */
+        uint32 result = 0xFFFFFFFFu;
+        var data = level[level_number].data;
+        for (var i = 0; data[i] != '\0'; i++)
+        {
+            var octet = data[i];
+            for (var j = 0; j < 8; j++)
+            {
+                if (((octet >> 7) ^ (result >> 31)) != 0)
+                    result = (result << 1) ^ 0x04c11db7;
+                else
+                    result = (result << 1);
+                result &= 0xFFFFFFFF;
+                octet <<= 1;
+            }
+        }
 
-    public override void get_size (Gtk.Widget widget, Gdk.Rectangle? cell_area,
-                                   out int x_offset, out int y_offset,
-                                   out int width, out int height)
-    {
-        x_offset = 0;
-        y_offset = 0;
-        width = height = icon_size;
+        return "%08X".printf (~result);
     }
 
-    public override void render (Cairo.Context ctx, Gtk.Widget widget,
-                                 Gdk.Rectangle background_area,
-                                 Gdk.Rectangle cell_area,
-                                 Gtk.CellRendererState flags)
+    private void load_solved_state ()
     {
-        Gdk.cairo_rectangle (ctx, background_area);
-
+        var keyfile = new KeyFile ();
+        var filename = Path.build_filename (Environment.get_user_data_dir (), "gnome-klotski", "levels");
         try
         {
-            var icon_theme = Gtk.IconTheme.get_default ();
-            var icon = icon_theme.load_icon ("gtk-yes", icon_size, 0);
-
-            int x = background_area.x + (background_area.width - icon_size)/2;
-            int y = background_area.y + (background_area.height - icon_size)/2;
-            Gdk.cairo_set_source_pixbuf (ctx, icon, x, y);
+            keyfile.load_from_file (filename, KeyFileFlags.NONE);
         }
         catch (Error e)
         {
-            warning (e.message);
         }
 
-        ctx.fill ();
+        for (var i = 0; i < level.length; i++)
+        {
+            var key = get_level_key (i);
+            var value = false;
+            try
+            {
+                value = keyfile.get_boolean (key, "solved");
+            }
+            catch (Error e)
+            {
+            }
+
+            puzzle_solved (puzzles_items[i], value);
+        }
     }
-}
 
+    private void puzzle_solved (TreeIter iter, bool solved)
+    {
+        if (liststore_huarong.iter_is_valid (iter))          // "slow"
+            liststore_huarong.set (iter, 1, solved);
+        else if (liststore_challenge.iter_is_valid (iter))   // same here
+            liststore_challenge.set (iter, 1, solved);
+        else
+            liststore_skill.set (iter, 1, solved);
+    }
+}
diff --git a/src/klotski.gresource.xml b/src/klotski.gresource.xml
index adf4e0e..71609ef 100644
--- a/src/klotski.gresource.xml
+++ b/src/klotski.gresource.xml
@@ -1,9 +1,10 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <gresources>
-  <!-- <gresource prefix="/org/gnome/klotski/ui">
+  <gresource prefix="/org/gnome/klotski/ui">
     <file preprocess="xml-stripblanks" alias="klotski.ui">../data/klotski.ui</file>
+    <file preprocess="xml-stripblanks" alias="scores.ui">../data/klotski-scores.ui</file>
     <file alias="klotski.css">../data/klotski.css</file>
-  </gresource> -->
+  </gresource>
   <gresource prefix="/org/gnome/klotski/gtk">
     <file preprocess="xml-stripblanks" alias="menus.ui">../data/klotski-menus.ui</file>
   </gresource>
diff --git a/src/puzzle-view.vala b/src/puzzle-view.vala
index fc2a072..6a64db9 100644
--- a/src/puzzle-view.vala
+++ b/src/puzzle-view.vala
@@ -70,6 +70,7 @@ public class PuzzleView : Gtk.DrawingArea
 
     public PuzzleView ()
     {
+        set_size_request (250, 250);    // TODO enough? Taquin is in 350^2
         set_events (Gdk.EventMask.EXPOSURE_MASK | Gdk.EventMask.BUTTON_PRESS_MASK | 
Gdk.EventMask.POINTER_MOTION_MASK | Gdk.EventMask.BUTTON_RELEASE_MASK);
         load_image ();
     }
@@ -199,7 +200,7 @@ public class PuzzleView : Gtk.DrawingArea
 
     protected override bool button_press_event (Gdk.EventButton event)
     {
-        if (event.button == 1)
+        if (event.button == Gdk.BUTTON_PRIMARY)
         {
             if (puzzle.game_over ())
                 return false;
@@ -227,7 +228,7 @@ public class PuzzleView : Gtk.DrawingArea
 
     protected override bool button_release_event (Gdk.EventButton event)
     {
-        if (event.button == 1 && piece_id != '\0')
+        if (event.button == Gdk.BUTTON_PRIMARY && piece_id != '\0')
         {
             if (piece_unmoved)
                 return false;
diff --git a/src/score-dialog.vala b/src/score-dialog.vala
index 7bc34be..d20e65e 100644
--- a/src/score-dialog.vala
+++ b/src/score-dialog.vala
@@ -8,79 +8,61 @@
  * license.
  */
 
- public class ScoreDialog : Gtk.Dialog
+using Gtk;
+
+[GtkTemplate (ui = "/org/gnome/klotski/ui/scores.ui")]
+public class ScoreDialog : Dialog
 {
     private History history;
     private HistoryEntry? selected_entry = null;
-    private Gtk.ListStore level_model;
-    private Gtk.ListStore score_model;
-    private Gtk.ComboBox level_combo;
-    private Gtk.TreeView scores;
 
-    public ScoreDialog (History history, HistoryEntry? selected_entry = null, bool show_close = false)
+    [GtkChild]
+    private Gtk.ListStore levels_liststore;
+    [GtkChild]
+    private Gtk.ListStore scores_liststore;
+    [GtkChild]
+    private ComboBox level_combo;
+    [GtkChild]
+    private TreeView scores_tree;
+
+    public ScoreDialog (History history, HistoryEntry? selected_entry = null)
     {
+        bool use_header = Gtk.Settings.get_default ().gtk_dialogs_use_header;
+        Object (use_header_bar: use_header ? 1 : 0);
+        if (!use_header)
+            add_button (_("_OK"), ResponseType.DELETE_EVENT);
+
         this.history = history;
         history.entry_added.connect (entry_added_cb);
         this.selected_entry = selected_entry;
 
-        if (show_close)
-        {
-            add_button (_("_Close"), Gtk.ResponseType.CLOSE);
-            add_button (_("New Game"), Gtk.ResponseType.OK);
-        }
-        else
-            add_button (_("_OK"), Gtk.ResponseType.DELETE_EVENT);
-        set_size_request (200, 300);
-
-        var vbox = new Gtk.Box (Gtk.Orientation.VERTICAL, 5);
-        vbox.border_width = 6;
-        vbox.show ();
-        get_content_area ().pack_start (vbox, true, true, 0);
+        level_combo.changed.connect (level_changed_cb);
 
-        var hbox = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6);
-        hbox.show ();
-        vbox.pack_start (hbox, false, false, 0);
+        foreach (var entry in history.entries)
+            entry_added_cb (entry);
+    }
 
-        var label = new Gtk.Label (_("Puzzle:"));
-        label.show ();
-        hbox.pack_start (label, false, false, 0);
+    /*\
+    * * Combo reaction
+    \*/
 
-        level_model = new Gtk.ListStore (2, typeof (string), typeof (int));  // puzzle name, level
+    private void level_changed_cb (ComboBox combo)
+    {
+        TreeIter iter;
+        if (!combo.get_active_iter (out iter))
+            return;
 
-        level_combo = new Gtk.ComboBox ();
-        level_combo.changed.connect (level_changed_cb);
-        level_combo.model = level_model;
-        var renderer = new Gtk.CellRendererText ();
-        level_combo.pack_start (renderer, true);
-        level_combo.add_attribute (renderer, "text", 0);
-        level_combo.show ();
-        hbox.pack_start (level_combo, true, true, 0);
-
-        var scroll = new Gtk.ScrolledWindow (null, null);
-        scroll.shadow_type = Gtk.ShadowType.ETCHED_IN;
-        scroll.set_policy (Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
-        scroll.show ();
-        vbox.pack_start (scroll, true, true, 0);
-
-        score_model = new Gtk.ListStore (3, typeof (string), typeof (string), typeof (int));
-
-        scores = new Gtk.TreeView ();
-        renderer = new Gtk.CellRendererText ();
-        scores.insert_column_with_attributes (-1, _("Date"), renderer, "text", 0, "weight", 2);
-        renderer = new Gtk.CellRendererText ();
-        renderer.xalign = 1.0f;
-        scores.insert_column_with_attributes (-1, _("Moves"), renderer, "text", 1, "weight", 2);
-        scores.model = score_model;
-        scores.show ();
-        scroll.add (scores);
+        int level;
+        levels_liststore.get (iter, 1, out level);
 
-        foreach (var entry in history.entries)
-            entry_added_cb (entry);
+/*      set_level ((uint) level);
     }
 
-    public void set_level (uint level)
+    public void set_level (uint level)      // TODO why??
     {
-        score_model.clear ();
+        TreeIter iter;
+*/
+        scores_liststore.clear ();
 
         var entries = history.entries.copy ();
         entries.sort (compare_entries);
@@ -90,30 +72,34 @@
             if (entry.level != level)
                 continue;
 
-            var date_label = entry.date.format ("%d/%m/%Y");
-
+            var date_label = entry.date.format ("%d/%m/%Y");    // TODO
             var moves_label = "%u".printf (entry.moves);
 
-            int weight = Pango.Weight.NORMAL;
-            if (entry == selected_entry)
-                weight = Pango.Weight.BOLD;
+            scores_liststore.append (out iter);
 
-            Gtk.TreeIter iter;
-            score_model.append (out iter);
-            score_model.set (iter, 0, date_label, 1, moves_label, 2, weight);
-
-            if (entry == selected_entry)
+            if (entry != selected_entry)
+            {
+                scores_liststore.set (iter,
+                                      0, date_label,
+                                      1, moves_label,
+                                      2, Pango.Weight.NORMAL);
+            }
+            else
             {
+                scores_liststore.set (iter,
+                                      0, date_label,
+                                      1, moves_label,
+                                      2, Pango.Weight.BOLD);
                 var piter = iter;
-                if (score_model.iter_previous (ref piter))
+                if (scores_liststore.iter_previous (ref piter))
                 {
                     var ppiter = piter;
-                    if (score_model.iter_previous (ref ppiter))
+                    if (scores_liststore.iter_previous (ref ppiter))
                         piter = ppiter;
                 }
                 else
                     piter = iter;
-                scores.scroll_to_cell (score_model.get_path (piter), null, false, 0, 0);
+                scores_tree.scroll_to_cell (scores_liststore.get_path (piter), null, false, 0, 0);
             }
         }
     }
@@ -127,41 +113,36 @@
         return a.date.compare (b.date);
     }
 
-    private void level_changed_cb (Gtk.ComboBox combo)
-    {
-        Gtk.TreeIter iter;
-        if (!combo.get_active_iter (out iter))
-            return;
-
-        int level;
-        combo.model.get (iter, 1, out level);
-        set_level ((uint) level);
-    }
+    /*\
+    * * Combo and TreeView population
+    \*/
 
     private void entry_added_cb (HistoryEntry entry)
     {
         /* Ignore if already have an entry for this */
-        Gtk.TreeIter iter;
+        TreeIter iter;
         var have_level_entry = false;
-        if (level_model.get_iter_first (out iter))
+        if (levels_liststore.get_iter_first (out iter))
         {
             do
             {
                 uint level;
-                level_model.get (iter, 1, out level);
+                levels_liststore.get (iter, 1, out level);
                 if (level == entry.level)
                 {
                     have_level_entry = true;
                     break;
                 }
-            } while (level_model.iter_next (ref iter));
+            } while (levels_liststore.iter_next (ref iter));
         }
 
         if (!have_level_entry)
         {
             var label = _(Klotski.level[entry.level].name);
-            level_model.append (out iter);
-            level_model.set (iter, 0, label, 1, entry.level, -1);
+            levels_liststore.append (out iter);
+            levels_liststore.set (iter,
+                                  0, label,
+                                  1, entry.level);
 
             /* Select this entry if don't have any */
             if (level_combo.get_active () == -1)
@@ -170,7 +151,6 @@
             /* Select this entry if the same category as the selected one */
             if (selected_entry != null && entry.level == selected_entry.level)
                 level_combo.set_active_iter (iter);
-
         }
     }
 }


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