[four-in-a-row/arnaudb/new-ui: 5/12] Introduce GameWindow.
- From: Arnaud B. <arnaudb src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [four-in-a-row/arnaudb/new-ui: 5/12] Introduce GameWindow.
- Date: Thu, 19 Dec 2019 19:50:19 +0000 (UTC)
commit 40c14c91d59cb89fc889dbe54476fa442d5c0b9c
Author: Arnaud Bonatti <arnaud bonatti gmail com>
Date: Mon Dec 16 04:26:35 2019 +0100
Introduce GameWindow.
Copied from Iagno 3.32.
data/org.gnome.Four-in-a-row.gschema.xml | 56 +++-
data/ui/fiar-screens.ui | 200 ++++++++++++++
data/ui/four-in-a-row.ui | 193 ++++++-------
po/POTFILES.in | 1 +
src/four-in-a-row.gresource.xml | 3 +-
src/four-in-a-row.vala | 319 +++++++++++-----------
src/game-board-view.vala | 2 +-
src/game-window.vala | 448 +++++++++++++++++++++++++++++++
src/meson.build | 1 +
src/prefs-box.vala | 109 ++------
src/prefs.vala | 14 -
src/scorebox.vala | 32 ++-
12 files changed, 1005 insertions(+), 373 deletions(-)
---
diff --git a/data/org.gnome.Four-in-a-row.gschema.xml b/data/org.gnome.Four-in-a-row.gschema.xml
index 56816ac..4d33079 100644
--- a/data/org.gnome.Four-in-a-row.gschema.xml
+++ b/data/org.gnome.Four-in-a-row.gschema.xml
@@ -1,36 +1,88 @@
<schemalist>
+ <enum id="org.gnome.Four-in-a-row.first-player">
+ <value value="0" nick="human"/>
+ <value value="1" nick="computer"/>
+ </enum>
+
<schema id="org.gnome.Four-in-a-row" path="/org/gnome/Four-in-a-row/" gettext-domain="four-in-a-row">
+ <key name="first-player" enum="org.gnome.Four-in-a-row.first-player">
+ <default>'human'</default>
+ <!-- Translators: summary of a settings key, see 'dconf-editor /org/gnome/Four-in-a-row/first-player'
-->
+ <summary>Who starts</summary>
+ <!-- Translators: description of a settings key, see 'dconf-editor
/org/gnome/Four-in-a-row/first-player' -->
+ <description>Specifies who will start the next one-player game. Ignored for two-player
games.</description>
+ </key>
+ <key name="num-players" type="i">
+ <default>1</default>
+ <range min="1" max="2" />
+ <!-- Translators: summary of a settings key, see 'dconf-editor /org/gnome/Four-in-a-row/num-players'
-->
+ <summary>Number of players</summary>
+ <!-- Translators: description of a settings key, see 'dconf-editor
/org/gnome/Four-in-a-row/num-players' -->
+ </key>
<key name="opponent" type="i">
- <range min="0" max="3"/>
+ <range min="1" max="3"/>
<default>1</default>
+ <!-- Translators: summary of a settings key, see 'dconf-editor /org/gnome/Four-in-a-row/opponent' -->
<summary>Opponent</summary>
- <description>Zero is human; one through three correspond to the level of the computer
player.</description>
+ <!-- Translators: description of a settings key, see 'dconf-editor /org/gnome/Four-in-a-row/opponent'
-->
+ <description>From 1, the easiest, to 3, the hardest. Ignored for two-player games.</description>
</key>
<key name="theme-id" type="i">
<default>4</default>
<range min="0" max="4"/>
+ <!-- Translators: summary of a settings key, see 'dconf-editor /org/gnome/Four-in-a-row/theme-id' -->
<summary>Theme ID</summary>
+ <!-- Translators: description of a settings key, see 'dconf-editor /org/gnome/Four-in-a-row/theme-id'
-->
<description>A number specifying the preferred theme.</description>
</key>
<key name="sound" type="b">
<default>true</default>
+ <!-- Translators: summary of a settings key, see 'dconf-editor /org/gnome/Four-in-a-row/sound' -->
<summary>Sound</summary>
+ <!-- Translators: description of a settings key, see 'dconf-editor /org/gnome/Four-in-a-row/sound' -->
<description>Whether or not to play event sounds.</description>
</key>
<key name="key-left" type="i">
<default>65361</default>
+ <!-- Translators: summary of a settings key, see 'dconf-editor /org/gnome/Four-in-a-row/key-left' -->
<summary>Move left</summary>
+ <!-- Translators: description of a settings key, see 'dconf-editor /org/gnome/Four-in-a-row/key-left'
-->
<description>Key press to move left.</description>
</key>
<key name="key-right" type="i">
<default>65363</default>
+ <!-- Translators: summary of a settings key, see 'dconf-editor /org/gnome/Four-in-a-row/key-right' -->
<summary>Move right</summary>
+ <!-- Translators: description of a settings key, see 'dconf-editor /org/gnome/Four-in-a-row/key-right'
-->
<description>Key press to move right.</description>
</key>
<key name="key-drop" type="i">
<default>65364</default>
+ <!-- Translators: summary of a settings key, see 'dconf-editor /org/gnome/Four-in-a-row/key-drop' -->
<summary>Drop marble</summary>
+ <!-- Translators: description of a settings key, see 'dconf-editor /org/gnome/Four-in-a-row/key-drop'
-->
<description>Key press to drop a marble.</description>
</key>
+ <key name="window-width" type="i">
+ <default>675</default>
+ <!-- Translators: summary of a settings key, see 'dconf-editor /org/gnome/Four-in-a-row/window-width'
-->
+ <summary>The width of the window</summary>
+ <!-- Translators: description of a settings key, see 'dconf-editor
/org/gnome/Four-in-a-row/window-width' -->
+ <description>The width of the main window in pixels.</description>
+ </key>
+ <key name="window-height" type="i">
+ <default>550</default>
+ <!-- Translators: summary of a settings key, see 'dconf-editor /org/gnome/Four-in-a-row/window-height'
-->
+ <summary>The height of the window</summary>
+ <!-- Translators: description of a settings key, see 'dconf-editor
/org/gnome/Four-in-a-row/window-height' -->
+ <description>The height of the main window in pixels.</description>
+ </key>
+ <key name="window-is-maximized" type="b">
+ <default>false</default>
+ <!-- Translators: summary of a settings key, see 'dconf-editor
/org/gnome/Four-in-a-row/window-is-maximized' -->
+ <summary>A flag to enable maximized mode</summary>
+ <!-- Translators: description of a settings key, see 'dconf-editor
/org/gnome/Four-in-a-row/window-is-maximized' -->
+ <description>If “true”, the main window starts in maximized mode.</description>
+ </key>
</schema>
</schemalist>
diff --git a/data/ui/fiar-screens.ui b/data/ui/fiar-screens.ui
new file mode 100644
index 0000000..6c6de7b
--- /dev/null
+++ b/data/ui/fiar-screens.ui
@@ -0,0 +1,200 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ This file is part of GNOME Four-in-a-row.
+
+ Copyright 2015, 2019 Arnaud Bonatti
+
+ GNOME Four-in-a-row is free software: you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ GNOME Four-in-a-row is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with GNOME Four-in-a-row. If not, see <https://www.gnu.org/licenses/>.
+-->
+<interface>
+ <requires lib="gtk+" version="3.12"/>
+ <object class="GtkBox" id="new-game-screen">
+ <property name="orientation">vertical</property>
+ <property name="visible">True</property>
+ <property name="homogeneous">True</property>
+ <property name="valign">fill</property>
+ <property name="spacing">18</property>
+ <property name="margin-bottom">25</property><!-- TODO better -->
+ <property name="height-request">263</property>
+ <property name="width-request">400</property>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="halign">start</property>
+ <!-- Translators: when configuring a new game, header of the row for choosing the number of
players -->
+ <property name="label" translatable="yes">Players</property>
+ <style>
+ <class name="bold-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="homogeneous">True</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkModelButton">
+ <property name="visible">True</property>
+ <property name="use_underline">True</property>
+ <!-- Translators: when configuring a new game, row "Players", label of the button to choose
a one-player game (with a mnemonic that appears pressing Alt) -->
+ <property name="text" translatable="yes">_One</property>
+ <property name="action-name">app.num-players</property>
+ <property name="action-target">1</property>
+ <property name="iconic">True</property>
+ <property name="centered">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkModelButton">
+ <property name="visible">True</property>
+ <property name="use_underline">True</property>
+ <!-- Translators: when configuring a new game, row "Players", label of the button to choose
a one-player game (with a mnemonic that appears pressing Alt) -->
+ <property name="text" translatable="yes">_Two</property>
+ <property name="action-name">app.num-players</property>
+ <property name="action-target">2</property>
+ <property name="iconic">True</property>
+ <property name="centered">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="halign">start</property>
+ <!-- Translators: when configuring a new game, header of the row for choosing the level of the
artificial intelligence -->
+ <property name="label" translatable="yes">Difficulty</property>
+ <style>
+ <class name="bold-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="difficulty-box">
+ <property name="visible">True</property>
+ <property name="homogeneous">True</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkModelButton">
+ <property name="visible">True</property>
+ <property name="use_underline">True</property>
+ <!-- Translators: when configuring a new game, row "Difficulty", label of the button to
choose an easy-level computer adversary (with a mnemonic that appears pressing Alt) -->
+ <property name="text" translatable="yes">_Easy</property>
+ <property name="action-name">app.opponent</property>
+ <property name="action-target">1</property>
+ <property name="iconic">True</property>
+ <property name="centered">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkModelButton">
+ <property name="visible">True</property>
+ <property name="use_underline">True</property>
+ <!-- Translators: when configuring a new game, row "Difficulty", label of the button to
choose a medium-level computer adversary (with a mnemonic that appears pressing Alt) -->
+ <property name="text" translatable="yes">_Medium</property>
+ <property name="action-name">app.opponent</property>
+ <property name="action-target">2</property>
+ <property name="iconic">True</property>
+ <property name="centered">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkModelButton">
+ <property name="visible">True</property>
+ <property name="use_underline">True</property>
+ <!-- Translators: when configuring a new game, row "Difficulty", label of the button to
choose a hard-level computer adversary (with a mnemonic that appears pressing Alt) -->
+ <property name="text" translatable="yes">_Hard</property>
+ <property name="action-name">app.opponent</property>
+ <property name="action-target">3</property>
+ <property name="iconic">True</property>
+ <property name="centered">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="halign">start</property>
+ <!-- Translators: when configuring a new game, header of the row for choosing whether to start
or not -->
+ <property name="label" translatable="yes">Game start</property>
+ <style>
+ <class name="bold-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="start-box">
+ <property name="visible">True</property>
+ <property name="homogeneous">True</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkModelButton">
+ <property name="visible">True</property>
+ <property name="use_underline">True</property>
+ <!-- Translators: when configuring a new game, row "Game start", label of the button to
start (with a mnemonic that appears pressing Alt) -->
+ <property name="text" translatable="yes">Play _first</property>
+ <property name="action-name">app.first-player</property>
+ <property name="action-target">'human'</property>
+ <property name="iconic">True</property>
+ <property name="centered">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkModelButton">
+ <property name="visible">True</property>
+ <property name="use_underline">True</property>
+ <!-- Translators: when configuring a new game, row "Game start", label of the button to play
after the computer (with a mnemonic that appears pressing Alt) -->
+ <property name="text" translatable="yes">Play _second</property>
+ <property name="action-name">app.first-player</property>
+ <property name="action-target">'computer'</property>
+ <property name="iconic">True</property>
+ <property name="centered">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/data/ui/four-in-a-row.ui b/data/ui/four-in-a-row.ui
index 80dfa4d..124144a 100644
--- a/data/ui/four-in-a-row.ui
+++ b/data/ui/four-in-a-row.ui
@@ -17,48 +17,49 @@
-->
<interface>
<requires lib="gtk+" version="3.12"/>
- <object class="GtkApplicationWindow" id="fiar-window">
- <property name="can-focus">False</property>
- <property name="title" translatable="yes">Four-in-a-row</property>
+ <template class="GameWindow" parent="GtkApplicationWindow">
+ <!-- <initial-focus name="view"/> -->
<child type="titlebar">
<object class="GtkHeaderBar" id="headerbar">
<property name="visible">True</property>
- <property name="can-focus">False</property>
- <property name="title" translatable="yes">Four-in-a-row</property>
<property name="show-close-button">True</property>
<child>
- <object class="GtkButton">
- <property name="visible">True</property>
- <property name="sensitive">False</property>
- <property name="can-focus">True</property>
- <property name="focus-on-click">False</property>
- <property name="receives-default">False</property>
- <property name="tooltip-text" translatable="yes">Undo your most recent move</property>
+ <object class="GtkBox" id="controls_box">
+ <property name="visible">False</property>
+ <property name="orientation">horizontal</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="back_button">
+ <property name="visible">False</property>
+ <property name="halign">center</property>
<property name="valign">center</property>
- <property name="action-name">app.undo-move</property>
+ <!-- Translators: when configuring a new game, if the user has a started game, tooltip text of
the Go back button -->
+ <property name="tooltip-text" translatable="yes">Go back to the current game</property>
+ <property name="use-underline">True</property>
+ <property name="action-name">ui.back</property>
+ <property name="focus-on-click">False</property>
+ <style>
+ <class name="image-button"/>
+ </style>
<child>
<object class="GtkImage">
+ <property name="icon-name">go-previous-symbolic</property>
<property name="visible">True</property>
- <property name="can-focus">False</property>
- <property name="icon-name">edit-undo-symbolic</property>
<property name="icon-size">1</property>
</object>
</child>
- <style>
- <class name="image-button"/>
- </style>
</object>
</child>
<child>
- <object class="GtkMenuButton" id="menu_button">
+ <object class="GtkMenuButton" id="info_button">
<property name="visible">True</property>
+ <property name="valign">center</property>
<property name="can-focus">True</property>
- <property name="receives-default">True</property>
- <accelerator key="F10" signal="activate"/>
+ <property name="focus-on-click">False</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
- <property name="can-focus">False</property>
<property name="icon-name">open-menu-symbolic</property>
<property name="icon-size">1</property>
</object>
@@ -75,91 +76,97 @@
<object class="GtkOverlay">
<property name="visible">True</property>
<child>
- <object class="GtkAspectFrame">
+ <object class="GtkStack" id="stack">
<property name="visible">True</property>
- <property name="can-focus">False</property>
- <property name="label-xalign">0</property>
- <property name="shadow-type">none</property>
- <property name="ratio">1.3999999761581421</property>
- <property name="obey-child">False</property>
- <property name="border-width">25</property>
+ <property name="homogeneous">True</property>
<child>
- <object class="GtkBox">
+ <object class="GtkBox" id="new_game_box">
+ <property name="orientation">vertical</property>
<property name="visible">True</property>
- <property name="can-focus">False</property>
- <property name="spacing">25</property>
- <child>
- <object class="GtkAspectFrame" id="frame">
- <property name="visible">True</property>
- <property name="can-focus">False</property>
- <property name="label-xalign">0</property>
- <property name="shadow-type">none</property>
- <property name="obey-child">False</property>
- <child>
- <placeholder/>
- </child>
- </object>
- <packing>
- <property name="expand">True</property>
- <property name="fill">True</property>
- <property name="position">0</property>
- </packing>
- </child>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="margin">25</property>
+ <property name="width-request">350</property>
+ <property name="height-request">350</property>
+ <property name="spacing">6</property>
+ </object>
+ <packing>
+ <property name="name">start-box</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAspectFrame">
+ <property name="visible">True</property>
+ <property name="shadow-type">GTK_SHADOW_NONE</property>
+ <property name="obey-child">false</property>
+ <property name="ratio">1.4</property>
+ <property name="margin">25</property>
<child>
- <object class="GtkButtonBox">
+ <object class="GtkBox" id="game_box">
<property name="visible">True</property>
- <property name="can-focus">False</property>
- <property name="valign">end</property>
- <property name="orientation">vertical</property>
- <property name="spacing">6</property>
+ <property name="orientation">horizontal</property>
+ <property name="spacing">25</property>
<child>
- <object class="GtkButton">
- <property name="label" translatable="yes">_Hint</property>
- <property name="width-request">120</property>
- <property name="height-request">60</property>
+ <object class="GtkBox" id="side_box">
<property name="visible">True</property>
- <property name="can-focus">False</property>
- <property name="receives-default">False</property>
- <property name="tooltip-text" translatable="yes">Receive a hint for your next
move</property>
- <property name="valign">center</property>
- <property name="action-name">app.hint</property>
- <property name="use-underline">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkButton" id="new_game_button">
+ <property name="visible">True</property>
+ <property name="use-underline">True</property>
+ <!-- Translators: during a game, label of the Start Over button (with a mnemonic
that appears pressing Alt) -->
+ <property name="label" translatable="yes">_Start Over</property>
+ <property name="halign">fill</property>
+ <property name="valign">center</property>
+ <property name="action-name">ui.new-game</property>
+ <!-- Translators: during a game, tooltip text of the Start Over button -->
+ <property name="tooltip-text" translatable="yes">Start a new game</property>
+ <property name="width-request">120</property>
+ <property name="height-request">60</property>
+ </object>
+ <packing>
+ <property name="pack-type">end</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="hint_button">
+ <property name="visible">True</property>
+ <property name="use-underline">True</property>
+ <!-- Translators: during a game, label of the Hint button (with a mnemonic that
appears pressing Alt) -->
+ <property name="label" translatable="yes">_Hint</property>
+ <property name="halign">fill</property>
+ <property name="valign">center</property>
+ <property name="action-name">ui.hint</property>
+ <!-- Translators: during a game, tooltip text of the Hint button -->
+ <property name="tooltip-text" translatable="yes">Suggest a play</property>
+ <property name="width-request">120</property>
+ <property name="height-request">60</property>
+ </object>
+ <packing>
+ <property name="pack-type">end</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
</object>
<packing>
+ <property name="pack-type">end</property>
<property name="expand">False</property>
<property name="fill">True</property>
- <property name="position">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkButton">
- <property name="label" translatable="yes">_Start Over</property>
- <property name="width-request">120</property>
- <property name="height-request">60</property>
- <property name="visible">True</property>
- <property name="can-focus">False</property>
- <property name="receives-default">False</property>
- <property name="tooltip-text" translatable="yes">Start a new game</property>
- <property name="valign">center</property>
- <property name="action-name">app.new-game</property>
- <property name="use-underline">True</property>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">False</property>
- <property name="pack-type">end</property>
- <property name="position">1</property>
+ <property name="padding">0</property>
</packing>
</child>
</object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="pack-type">end</property>
- <property name="position">1</property>
- </packing>
</child>
</object>
+ <packing>
+ <property name="name">frame</property>
+ </packing>
</child>
</object>
</child>
@@ -168,7 +175,7 @@
<property name="visible">False</property>
<property name="halign">end</property>
<property name="valign">start</property>
- <property name="action-name">app.unfullscreen</property>
+ <property name="action-name">ui.unfullscreen</property>
<style>
<class name="image-button"/>
<class name="unfullscreen-button"/>
@@ -185,5 +192,5 @@
</child>
</object>
</child>
- </object>
+ </template>
</interface>
diff --git a/po/POTFILES.in b/po/POTFILES.in
index b57d29f..e094d2e 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -1,5 +1,6 @@
# List of source files containing translatable strings.
# Please keep this file in alphabetical order.
+data/ui/fiar-screens.ui
data/ui/four-in-a-row.ui
data/org.gnome.Four-in-a-row.appdata.xml.in
data/org.gnome.Four-in-a-row.desktop.in
diff --git a/src/four-in-a-row.gresource.xml b/src/four-in-a-row.gresource.xml
index a574494..492aeed 100644
--- a/src/four-in-a-row.gresource.xml
+++ b/src/four-in-a-row.gresource.xml
@@ -10,6 +10,7 @@
</gresource>
<gresource prefix="/org/gnome/Four-in-a-row/ui">
<file alias="four-in-a-row.css">../data/four-in-a-row.css</file>
- <file preprocess="xml-stripblanks" compressed="true"
alias="four-in-a-row.ui">../data/ui/four-in-a-row.ui</file>
+ <file preprocess="xml-stripblanks" compressed="true"
alias="fiar-screens.ui">../data/ui/fiar-screens.ui</file>
+ <file preprocess="xml-stripblanks" compressed="true"
alias="game-window.ui">../data/ui/four-in-a-row.ui</file>
</gresource>
</gresources>
diff --git a/src/four-in-a-row.vala b/src/four-in-a-row.vala
index a19aaf9..6ee3745 100644
--- a/src/four-in-a-row.vala
+++ b/src/four-in-a-row.vala
@@ -3,6 +3,7 @@
This file is part of GNOME Four-in-a-row.
Copyright © 2018 Jacob Humphrey
+ Copyright © 2019 Arnaud Bonatti
GNOME Four-in-a-row is free software: you can redistribute it and/or
modify it under the terms of the GNU General Public License as published
@@ -31,6 +32,9 @@ private const string APPNAME_LONG = "Four-in-a-row";
private class FourInARow : Gtk.Application
{
+ /* Translators: application name, as used in the window manager, the window title, the about dialog... */
+ private const string PROGRAM_NAME = _("Four-in-a-row");
+
private static int main (string [] args)
{
Intl.setlocale ();
@@ -52,31 +56,28 @@ private class FourInARow : Gtk.Application
HINT
}
- // actions
- private SimpleAction hint_action;
- private SimpleAction undo_action;
- private SimpleAction new_game_action;
-
// game status
private bool gameover;
private bool player_active;
private PlayerID player;
private PlayerID winner;
- internal PlayerID who_starts;
+ private PlayerID last_first_player = PlayerID.NOBODY;
+ private bool one_player_game;
+ private int ai_level;
/**
* score:
*
* The scores for the current instance (Player 1, Player 2, Draw)
*/
private int score [3];
+ private bool reset_score = false;
// widgets
private PrefsBox? prefsbox = null;
private Scorebox scorebox;
private GameBoardView game_board_view;
private Board game_board;
- private ApplicationWindow window;
- private Button unfullscreen_button;
+ private GameWindow window;
// game state
private char vstr [53];
@@ -99,7 +100,6 @@ private class FourInARow : Gtk.Application
private const GLib.ActionEntry app_entries [] = // see also add_actions()
{
- { "unfullscreen", on_unfullscreen },
{ "scores", on_game_scores },
{ "quit", on_game_exit },
{ "preferences", on_settings_preferences },
@@ -111,11 +111,32 @@ private class FourInARow : Gtk.Application
{
stop_anim ();
- undo_action.set_enabled (false);
- hint_action.set_enabled (false);
+ window.allow_undo (false);
+ window.allow_hint (false);
- who_starts = (who_starts == PlayerID.PLAYER1) ? PlayerID.PLAYER2 : PlayerID.PLAYER1;
- player = who_starts;
+ one_player_game = Prefs.instance.settings.get_int ("num-players") == 1;
+ if (reset_score)
+ {
+ score = { 0, 0, 0 };
+ scorebox.update (score, one_player_game);
+ reset_score = false;
+ }
+ if (one_player_game)
+ {
+ player = Prefs.instance.settings.get_string ("first-player") == "computer" ? PlayerID.PLAYER2 :
PlayerID.PLAYER1;
+ Prefs.instance.settings.set_string ("first-player", player == PlayerID.PLAYER1 ? "computer" :
"human");
+ ai_level = Prefs.instance.settings.get_int ("opponent");
+ }
+ else
+ {
+ switch (last_first_player)
+ {
+ case PlayerID.PLAYER1: player = PlayerID.PLAYER2; break;
+ case PlayerID.PLAYER2:
+ case PlayerID.NOBODY : player = PlayerID.PLAYER1; break;
+ }
+ last_first_player = player;
+ }
gameover = true;
player_active = false;
@@ -134,8 +155,7 @@ private class FourInARow : Gtk.Application
prompt_player ();
if (!is_player_human ())
{
- vstr [0] = player == PLAYER1 ? vlevel [Prefs.instance.level [PlayerID.PLAYER1]]
- : vlevel [Prefs.instance.level [PlayerID.PLAYER2]];
+ vstr [0] = vlevel [ai_level];
game_process_move (playgame ((string) vstr) - 1);
}
}
@@ -165,24 +185,20 @@ private class FourInARow : Gtk.Application
{
add_action (Prefs.instance.settings.create_action ("sound"));
add_action (Prefs.instance.settings.create_action ("theme-id"));
-
- new_game_action = new SimpleAction ("new-game", null);
- new_game_action.activate.connect (on_game_new);
- add_action (new_game_action);
-
- hint_action = new SimpleAction ("hint", null);
- hint_action.activate.connect (on_game_hint);
- add_action (hint_action);
-
- undo_action = new SimpleAction ("undo-move", null);
- undo_action.activate.connect (on_game_undo);
- add_action (undo_action);
-
- set_accels_for_action ("app.new-game", { "<Primary>n" });
- set_accels_for_action ("app.hint", { "<Primary>h" });
- set_accels_for_action ("app.undo-move", { "<Primary>z" });
- set_accels_for_action ("app.quit", { "<Primary>q" });
- set_accels_for_action ("app.help", { "F1"});
+ add_action (Prefs.instance.settings.create_action ("num-players"));
+ add_action (Prefs.instance.settings.create_action ("first-player"));
+ add_action (Prefs.instance.settings.create_action ("opponent"));
+
+ set_accels_for_action ("ui.new-game", { "<Primary>n" });
+ set_accels_for_action ("ui.start-game", { "<Shift><Primary>n" });
+ set_accels_for_action ("app.quit", { "<Primary>q" });
+ set_accels_for_action ("ui.hint", { "<Primary>h" });
+ set_accels_for_action ("ui.undo", { "<Primary>z" });
+ // set_accels_for_action ("ui.redo", { "<Shift><Primary>z" });
+ set_accels_for_action ("ui.back", { "Escape" });
+ set_accels_for_action ("ui.toggle-hamburger", { "F10" });
+ set_accels_for_action ("app.help", { "F1" });
+ set_accels_for_action ("app.about", { "<Shift>F1" });
add_action_entries (app_entries, this);
}
@@ -244,11 +260,8 @@ private class FourInARow : Gtk.Application
player_active = false;
player = PlayerID.PLAYER1;
winner = PlayerID.NOBODY;
- score [PlayerID.PLAYER1] = 0;
- score [PlayerID.PLAYER2] = 0;
- score [PlayerID.NOBODY] = 0;
+ score = { 0, 0, 0 };
game_board = new Board ();
- who_starts = PlayerID.PLAYER2; /* This gets reversed immediately. */
clear_board ();
}
@@ -261,32 +274,29 @@ private class FourInARow : Gtk.Application
window.show ();
game_board_view.refresh_pixmaps ();
game_board_view.queue_draw ();
- scorebox.update (score); /* update visible player descriptions */
+ scorebox.update (score, one_player_game); /* update visible player descriptions */
prompt_player ();
game_reset ();
}
+ protected override void shutdown ()
+ {
+ window.shutdown (Prefs.instance.settings);
+ base.shutdown ();
+ }
+
private void prompt_player ()
{
- int players = Prefs.instance.get_n_human_players ();
bool human = is_player_human ();
string who;
string str;
- hint_action.set_enabled (human && !gameover);
+ window.allow_hint (human && !gameover);
- switch (players)
- {
- case 0:
- undo_action.set_enabled (false);
- break;
- case 1:
- undo_action.set_enabled ((human && moves >1) || (!human && gameover));
- break;
- case 2:
- undo_action.set_enabled (moves > 0);
- break;
- }
+ if (one_player_game)
+ window.allow_undo ((human && moves >1) || (!human && gameover));
+ else
+ window.allow_undo (moves > 0);
if (gameover && winner == PlayerID.NOBODY)
{
@@ -297,47 +307,44 @@ private class FourInARow : Gtk.Application
return;
}
- switch (players)
+ if (one_player_game)
{
- case 1:
- if (human)
- {
- if (gameover)
- set_status_message (_("You win!"));
- else
- set_status_message (_("Your Turn"));
- }
- else
- {
- if (gameover)
- set_status_message (_("I win!"));
- else
- set_status_message (_("I’m Thinking…"));
- }
- break;
-
- case 2:
- case 0:
+ if (human)
+ {
if (gameover)
- {
- who = player == PLAYER1 ? theme_get_player_win (PlayerID.PLAYER1)
- : theme_get_player_win (PlayerID.PLAYER2);
- str = _(who);
- }
- else if (player_active)
- {
+ set_status_message (_("You win!"));
+ else
set_status_message (_("Your Turn"));
- return;
- }
+ }
+ else
+ {
+ if (gameover)
+ set_status_message (_("I win!"));
else
- {
- who = player == PLAYER1 ? theme_get_player_turn (PlayerID.PLAYER1)
- : theme_get_player_turn (PlayerID.PLAYER2);
- str = _(who);
- }
-
- set_status_message (str);
- break;
+ set_status_message (_("I’m Thinking…"));
+ }
+ }
+ else
+ {
+ if (gameover)
+ {
+ who = player == PLAYER1 ? theme_get_player_win (PlayerID.PLAYER1)
+ : theme_get_player_win (PlayerID.PLAYER2);
+ str = _(who);
+ }
+ else if (player_active)
+ {
+ set_status_message (_("Your Turn"));
+ return;
+ }
+ else
+ {
+ who = player == PLAYER1 ? theme_get_player_turn (PlayerID.PLAYER1)
+ : theme_get_player_turn (PlayerID.PLAYER2);
+ str = _(who);
+ }
+
+ set_status_message (str);
}
}
@@ -365,7 +372,7 @@ private class FourInARow : Gtk.Application
if (gameover)
{
score [winner]++;
- scorebox.update (score);
+ scorebox.update (score, one_player_game);
prompt_player ();
}
else
@@ -373,8 +380,7 @@ private class FourInARow : Gtk.Application
swap_player ();
if (!is_player_human ())
{
- vstr [0] = player == PlayerID.PLAYER1 ? vlevel [Prefs.instance.level [PlayerID.PLAYER1]]
- : vlevel [Prefs.instance.level [PlayerID.PLAYER2]];
+ vstr [0] = vlevel [ai_level];
c = playgame ((string) vstr) - 1;
if (c < 0)
gameover = true;
@@ -386,8 +392,10 @@ private class FourInARow : Gtk.Application
private bool is_player_human ()
{
- return player == PLAYER1 ? Prefs.instance.level [PlayerID.PLAYER1] == Level.HUMAN
- : Prefs.instance.level [PlayerID.PLAYER2] == Level.HUMAN;
+ if (one_player_game)
+ return player == PlayerID.PLAYER1;
+ else
+ return true;
}
private void process_move2 (int c)
@@ -452,7 +460,7 @@ private class FourInARow : Gtk.Application
private void set_status_message (string? message)
{
- headerbar.set_title (message);
+ window.set_subtitle (message);
}
private class NextMove
@@ -520,8 +528,8 @@ private class FourInARow : Gtk.Application
if (gameover)
return;
- hint_action.set_enabled (false);
- undo_action.set_enabled (false);
+ window.allow_hint (false);
+ window.allow_undo (false);
set_status_message (_("I’m Thinking…"));
@@ -541,9 +549,9 @@ private class FourInARow : Gtk.Application
set_status_message (s);
if (moves <= 0 || (moves == 1 && is_player_human ()))
- undo_action.set_enabled (false);
+ window.allow_undo (false);
else
- undo_action.set_enabled (true);
+ window.allow_undo (true);
}
private inline void on_game_scores (/* SimpleAction action, Variant? parameter */)
@@ -552,24 +560,6 @@ private class FourInARow : Gtk.Application
return;
}
- private bool window_is_fullscreen = false;
- private bool window_state_event_cb (Gdk.EventWindowState event)
- {
- bool window_was_fullscreen = window_is_fullscreen;
- if ((event.changed_mask & Gdk.WindowState.FULLSCREEN) != 0)
- window_is_fullscreen = (event.new_window_state & Gdk.WindowState.FULLSCREEN) != 0;
- if (window_was_fullscreen && !window_is_fullscreen)
- unfullscreen_button.hide ();
- else if (!window_was_fullscreen && window_is_fullscreen)
- unfullscreen_button.show ();
- return false;
- }
-
- private inline void on_unfullscreen (/* SimpleAction action, Variant? parameter */)
- {
- window.unfullscreen ();
- }
-
private inline void on_game_exit (/* SimpleAction action, Variant? parameter */)
{
stop_anim ();
@@ -644,7 +634,7 @@ private class FourInARow : Gtk.Application
}
}
- private inline void on_game_undo (SimpleAction action, Variant? parameter)
+ private inline void on_game_undo ()
{
if (timeout != 0)
return;
@@ -658,7 +648,7 @@ private class FourInARow : Gtk.Application
if (gameover)
{
score [winner]--;
- scorebox.update (score);
+ scorebox.update (score, one_player_game);
gameover = false;
prompt_player ();
}
@@ -669,7 +659,7 @@ private class FourInARow : Gtk.Application
game_board [r, c] = Tile.CLEAR;
game_board_view.draw_tile (r, c);
- if (Prefs.instance.get_n_human_players () == 1
+ if (one_player_game
&& !is_player_human ()
&& moves > 0)
{
@@ -738,16 +728,11 @@ private class FourInARow : Gtk.Application
{
gameover = true;
winner = player;
- switch (Prefs.instance.get_n_human_players ())
- {
- case 1:
- play_sound (is_player_human () ? SoundID.YOU_WIN : SoundID.I_WIN);
- break;
- case 0:
- case 2:
- play_sound (SoundID.PLAYER_WIN);
- break;
- }
+ if (one_player_game)
+ play_sound (is_player_human () ? SoundID.YOU_WIN : SoundID.I_WIN);
+ else
+ play_sound (SoundID.PLAYER_WIN);
+ window.allow_hint (false);
blink_winner (6);
}
else if (moves == 42)
@@ -762,30 +747,12 @@ private class FourInARow : Gtk.Application
{
base.startup ();
- CssProvider css_provider = new CssProvider ();
- css_provider.load_from_resource ("/org/gnome/Four-in-a-row/ui/four-in-a-row.css");
- Gdk.Screen? gdk_screen = Gdk.Screen.get_default ();
- if (gdk_screen != null) // else..?
- StyleContext.add_provider_for_screen ((!) gdk_screen, css_provider,
STYLE_PROVIDER_PRIORITY_APPLICATION);
+ /* UI parts */
+ Builder builder = new Builder.from_resource ("/org/gnome/Four-in-a-row/ui/fiar-screens.ui");
game_board_view = new GameBoardView (game_board);
game_board_view.show ();
- Builder builder = new Builder.from_resource ("/org/gnome/Four-in-a-row/ui/four-in-a-row.ui");
-
- window = (ApplicationWindow) builder.get_object ("fiar-window");
- window.application = this;
- window.window_state_event.connect (window_state_event_cb);
- window.set_default_size (DEFAULT_WIDTH, DEFAULT_HEIGHT); /* TODO save size & state */
-
- unfullscreen_button = (Button) builder.get_object ("unfullscreen_button");
- headerbar = (HeaderBar) builder.get_object ("headerbar");
-
- scorebox = new Scorebox (window, this);
- add_actions ();
-
- /* hamburger button */
- MenuButton menu_button = (MenuButton) builder.get_object ("menu_button");
GLib.Menu app_menu = new GLib.Menu ();
GLib.Menu appearance_menu = new GLib.Menu ();
@@ -814,20 +781,56 @@ private class FourInARow : Gtk.Application
app_menu.append_section (null, section);
app_menu.freeze ();
- menu_button.set_menu_model (app_menu);
- /* various */
- Gtk.AspectFrame frame = builder.get_object("frame") as Gtk.AspectFrame;
+ /* Window */
+ window = new GameWindow ("/org/gnome/Four-in-a-row/ui/four-in-a-row.css",
+ PROGRAM_NAME,
+ Prefs.instance.settings.get_int ("window-width"),
+ Prefs.instance.settings.get_int ("window-height"),
+ Prefs.instance.settings.get_boolean ("window-is-maximized"),
+ /* start_now */ true,
+ GameWindowFlags.SHOW_UNDO | GameWindowFlags.SHOW_START_BUTTON,
+ (Box) builder.get_object ("new-game-screen"),
+ game_board_view,
+ app_menu);
+
+ scorebox = new Scorebox (window, this);
+
+ add_actions ();
+
+ Widget level_box = (Widget) (!) builder.get_object ("difficulty-box");
+ Widget start_box = (Widget) (!) builder.get_object ("start-box");
+ Prefs.instance.settings.changed ["num-players"].connect (() => {
+ bool solo = Prefs.instance.settings.get_int ("num-players") == 1;
+ level_box.sensitive = solo;
+ start_box.sensitive = solo;
+ reset_score = true;
+ if (solo)
+ last_first_player = PlayerID.NOBODY;
+ });
+ bool solo = Prefs.instance.settings.get_int ("num-players") == 1;
+ level_box.sensitive = solo;
+ start_box.sensitive = solo;
+
+ Prefs.instance.settings.changed ["opponent"].connect (() => {
+ if (Prefs.instance.settings.get_int ("num-players") != 1)
+ return;
+ reset_score = true;
+ });
- frame.add (game_board_view);
+ /* various */
game_board_view.column_clicked.connect (column_clicked_cb);
window.key_press_event.connect (on_key_press);
- hint_action.set_enabled (false);
- undo_action.set_enabled (false);
- }
+ window.play.connect (on_game_new);
+ window.undo.connect (on_game_undo);
+ window.hint.connect (on_game_hint);
- private HeaderBar headerbar;
+ window.allow_hint (false);
+ window.allow_undo (false);
+
+ add_window (window);
+ }
private inline bool on_key_press (Gdk.EventKey e)
{
diff --git a/src/game-board-view.vala b/src/game-board-view.vala
index 8f3835e..ecbe16f 100644
--- a/src/game-board-view.vala
+++ b/src/game-board-view.vala
@@ -46,7 +46,7 @@ private class GameBoardView : Gtk.DrawingArea {
private inline int get_column(int xpos) {
/* Derive column from pixel position */
- int c = xpos / tile_size;
+ int c = (xpos - board_x) / tile_size;
if (c > 6)
c = 6;
if (c < 0)
diff --git a/src/game-window.vala b/src/game-window.vala
new file mode 100644
index 0000000..5478652
--- /dev/null
+++ b/src/game-window.vala
@@ -0,0 +1,448 @@
+/* -*- Mode: vala; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ This file is part of GNOME Four-in-a-row.
+
+ Copyright © 2015, 2016, 2019 Arnaud Bonatti
+
+ GNOME Four-in-a-row is free software: you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ GNOME Four-in-a-row is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with GNOME Four-in-a-row. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+using Gtk;
+
+[Flags]
+private enum GameWindowFlags {
+ SHOW_UNDO,
+ // SHOW_REDO,
+ // SHOW_HINT,
+ SHOW_START_BUTTON;
+}
+
+[GtkTemplate (ui = "/org/gnome/Four-in-a-row/ui/game-window.ui")]
+private class GameWindow : ApplicationWindow
+{
+ /* settings */
+ private bool window_is_tiled;
+ private bool window_is_maximized;
+ private bool window_is_fullscreen;
+ private int window_width;
+ private int window_height;
+
+ private bool game_finished = false;
+
+ private string program_name = "";
+
+ /* private widgets */
+ [GtkChild] private HeaderBar headerbar;
+ [GtkChild] private Stack stack;
+
+ private Button? start_game_button = null;
+ [GtkChild] private Button new_game_button;
+ [GtkChild] private Button back_button;
+ [GtkChild] private Button unfullscreen_button;
+
+ [GtkChild] private Box controls_box;
+ [GtkChild] private Box game_box;
+ [GtkChild] private Box new_game_box;
+ [GtkChild] private Box side_box;
+
+ private Widget view;
+
+ /* signals */
+ internal signal void play ();
+ internal signal void wait ();
+ internal signal void back ();
+
+ internal signal void undo ();
+ // internal signal void redo ();
+ internal signal void hint ();
+
+ internal GameWindow (string? css_resource, string name, int width, int height, bool maximized, bool
start_now, GameWindowFlags flags, Box new_game_screen, Widget _view, GLib.Menu app_menu)
+ {
+ if (css_resource != null)
+ {
+ CssProvider css_provider = new CssProvider ();
+ css_provider.load_from_resource ((!) css_resource);
+ Gdk.Screen? gdk_screen = Gdk.Screen.get_default ();
+ if (gdk_screen != null) // else..?
+ StyleContext.add_provider_for_screen ((!) gdk_screen, css_provider,
STYLE_PROVIDER_PRIORITY_APPLICATION);
+ }
+
+ view = _view;
+
+ /* window config */
+ install_ui_action_entries ();
+ program_name = name;
+ set_title (name);
+ headerbar.set_title (name);
+ info_button.set_menu_model (app_menu);
+
+ set_default_size (width, height);
+ if (maximized)
+ maximize ();
+
+ size_allocate.connect (size_allocate_cb);
+ window_state_event.connect (window_state_event_cb);
+
+ /* add widgets */
+ new_game_box.pack_start (new_game_screen, true, true, 0);
+ if (GameWindowFlags.SHOW_START_BUTTON in flags)
+ {
+ /* Translators: when configuring a new game, label of the blue Start button (with a mnemonic
that appears pressing Alt) */
+ Button _start_game_button = new Button.with_mnemonic (_("_Start Game"));
+ _start_game_button.width_request = 222;
+ _start_game_button.height_request = 60;
+ _start_game_button.halign = Align.CENTER;
+ _start_game_button.set_action_name ("ui.start-game");
+ /* Translators: when configuring a new game, tooltip text of the blue Start button */
+ // _start_game_button.set_tooltip_text (_("Start a new game as configured"));
+ ((StyleContext) _start_game_button.get_style_context ()).add_class ("suggested-action");
+ _start_game_button.show ();
+ new_game_box.pack_end (_start_game_button, false, false, 0);
+ start_game_button = _start_game_button;
+ }
+
+ game_box.pack_start (view, true, true, 0);
+ game_box.set_focus_child (view); // TODO test if necessary; note: view could grab focus
from application
+ view.halign = Align.FILL;
+ view.can_focus = true;
+ view.show ();
+
+ /* add controls */
+ if (GameWindowFlags.SHOW_UNDO in flags)
+ {
+ Box history_box = new Box (Orientation.HORIZONTAL, 0);
+ history_box.get_style_context ().add_class ("linked");
+
+ Button undo_button = new Button.from_icon_name ("edit-undo-symbolic", Gtk.IconSize.BUTTON);
+ undo_button.action_name = "ui.undo";
+ /* Translators: during a game, tooltip text of the Undo button */
+ undo_button.set_tooltip_text (_("Undo your most recent move"));
+ undo_button.valign = Align.CENTER;
+ undo_button.show ();
+ history_box.pack_start (undo_button, true, true, 0);
+
+ /* if (GameWindowFlags.SHOW_REDO in flags)
+ {
+ Button redo_button = new Button.from_icon_name ("edit-redo-symbolic", Gtk.IconSize.BUTTON);
+ redo_button.action_name = "app.redo";
+ / Translators: during a game, tooltip text of the Redo button /
+ redo_button.set_tooltip_text (_("Redo your most recent undone move"));
+ redo_button.valign = Align.CENTER;
+ redo_button.show ();
+ history_box.pack_start (redo_button, true, true, 0);
+ } */
+
+ history_box.show ();
+ controls_box.pack_start (history_box, true, true, 0);
+ }
+ /* if (GameWindowFlags.SHOW_HINT in flags)
+ {
+ Button hint_button = new Button.from_icon_name ("dialog-question-symbolic", Gtk.IconSize.BUTTON);
+ hint_button.action_name = "app.hint";
+ / Translators: during a game, tooltip text of the Hint button /
+ hint_button.set_tooltip_text (_("Receive a hint for your next move"));
+ hint_button.valign = Align.CENTER;
+ hint_button.show ();
+ controls_box.pack_start (hint_button, true, true, 0);
+ } */
+
+ /* start or not */
+ if (start_now)
+ show_view ();
+ else
+ show_new_game_screen ();
+ }
+
+ /*\
+ * * actions
+ \*/
+
+ private SimpleAction back_action;
+ private SimpleAction undo_action;
+ // private SimpleAction redo_action;
+ private SimpleAction hint_action;
+
+ private void install_ui_action_entries ()
+ {
+ SimpleActionGroup action_group = new SimpleActionGroup ();
+ action_group.add_action_entries (ui_action_entries, this);
+ insert_action_group ("ui", action_group);
+
+ back_action = (SimpleAction) action_group.lookup_action ("back");
+ undo_action = (SimpleAction) action_group.lookup_action ("undo");
+ // redo_action = (SimpleAction) action_group.lookup_action ("redo");
+ hint_action = (SimpleAction) action_group.lookup_action ("hint");
+
+ back_action.set_enabled (false);
+ undo_action.set_enabled (false);
+ // redo_action.set_enabled (false);
+ hint_action.set_enabled (false);
+ }
+
+ private const GLib.ActionEntry [] ui_action_entries =
+ {
+ { "new-game", new_game_cb },
+ { "start-game", start_game_cb },
+ { "back", back_cb },
+
+ { "undo", undo_cb },
+ // { "redo", redo_cb },
+ { "hint", hint_cb },
+
+ { "toggle-hamburger", toggle_hamburger },
+ { "unfullscreen", unfullscreen }
+ };
+
+ /*\
+ * * Window events
+ \*/
+
+ private void size_allocate_cb ()
+ {
+ if (window_is_maximized || window_is_tiled || window_is_fullscreen)
+ return;
+ get_size (out window_width, out window_height);
+ }
+
+ private bool window_state_event_cb (Gdk.EventWindowState event)
+ {
+ if ((event.changed_mask & Gdk.WindowState.MAXIMIZED) != 0)
+ window_is_maximized = (event.new_window_state & Gdk.WindowState.MAXIMIZED) != 0;
+
+ /* fullscreen: saved as maximized */
+ bool window_was_fullscreen = window_is_fullscreen;
+ if ((event.changed_mask & Gdk.WindowState.FULLSCREEN) != 0)
+ window_is_fullscreen = (event.new_window_state & Gdk.WindowState.FULLSCREEN) != 0;
+ if (window_was_fullscreen && !window_is_fullscreen)
+ unfullscreen_button.hide ();
+ else if (!window_was_fullscreen && window_is_fullscreen)
+ unfullscreen_button.show ();
+
+ /* tiled: not saved, but should not change saved window size */
+ Gdk.WindowState tiled_state = Gdk.WindowState.TILED
+ | Gdk.WindowState.TOP_TILED
+ | Gdk.WindowState.BOTTOM_TILED
+ | Gdk.WindowState.LEFT_TILED
+ | Gdk.WindowState.RIGHT_TILED;
+ if ((event.changed_mask & tiled_state) != 0)
+ window_is_tiled = (event.new_window_state & tiled_state) != 0;
+
+ return false;
+ }
+
+ internal void shutdown (GLib.Settings settings)
+ {
+ settings.delay ();
+ settings.set_int ("window-width", window_width);
+ settings.set_int ("window-height", window_height);
+ settings.set_boolean ("window-is-maximized", window_is_maximized || window_is_fullscreen);
+ settings.apply ();
+ destroy ();
+ }
+
+ /*\
+ * * Some internal calls
+ \*/
+
+ internal void add_to_sidebox (Widget widget)
+ {
+ side_box.pack_start (widget, false, false, 0);
+ }
+
+ internal void cannot_undo_more ()
+ {
+ undo_action.set_enabled (false);
+ view.grab_focus ();
+ }
+
+// internal void new_turn_start (bool can_undo)
+// {
+// undo_action.set_enabled (can_undo);
+// headerbar.set_subtitle (null);
+// }
+
+ internal void set_subtitle (string? subtitle)
+ {
+ headerbar.set_title (subtitle);
+ last_subtitle = subtitle;
+ }
+
+ internal void finish_game ()
+ {
+ game_finished = true;
+ new_game_button.grab_focus ();
+ }
+
+ /* internal void about ()
+ {
+ TODO
+ } */
+
+ internal void allow_hint (bool allow)
+ {
+ string? stack_child = stack.get_visible_child_name ();
+ if (stack_child == null || (!) stack_child != "frame")
+ return;
+ hint_action.set_enabled (allow);
+ }
+
+ internal void allow_undo (bool allow)
+ {
+ string? stack_child = stack.get_visible_child_name ();
+ if (stack_child == null || (!) stack_child != "frame")
+ return;
+ undo_action.set_enabled (allow);
+ }
+
+ /*\
+ * * Showing the Stack
+ \*/
+
+ private string? last_subtitle = null;
+ private void show_new_game_screen ()
+ {
+ headerbar.set_title (program_name);
+
+ stack.set_visible_child_name ("start-box");
+ controls_box.hide ();
+
+ if (!game_finished && back_button.visible)
+ back_button.grab_focus ();
+ else if (start_game_button != null)
+ ((!) start_game_button).grab_focus ();
+ }
+
+ private void show_view ()
+ {
+ headerbar.set_title (last_subtitle);
+
+ stack.set_visible_child_name ("frame");
+ back_button.hide (); // TODO transition?
+ controls_box.show ();
+
+ if (game_finished)
+ new_game_button.grab_focus ();
+ else
+ view.grab_focus ();
+ }
+
+ /*\
+ * * Switching the Stack
+ \*/
+
+ private void new_game_cb ()
+ {
+ string? stack_child = stack.get_visible_child_name ();
+ if (stack_child == null || (!) stack_child != "frame")
+ return;
+
+ wait ();
+
+ stack.set_transition_type (StackTransitionType.SLIDE_LEFT);
+ stack.set_transition_duration (800);
+
+ back_button.show ();
+ back_action.set_enabled (true);
+
+ show_new_game_screen ();
+ }
+
+ private void start_game_cb ()
+ {
+ string? stack_child = stack.get_visible_child_name ();
+ if (stack_child == null || (!) stack_child != "start-box")
+ return;
+
+ last_subtitle = null;
+ game_finished = false;
+
+ undo_action.set_enabled (false);
+ // redo_action.set_enabled (false);
+ hint_action.set_enabled (true);
+
+ play (); // FIXME lag (see in Taquin…)
+
+ stack.set_transition_type (StackTransitionType.SLIDE_DOWN);
+ stack.set_transition_duration (1000);
+ show_view ();
+ }
+
+ private void back_cb ()
+ {
+ string? stack_child = stack.get_visible_child_name ();
+ if (stack_child == null || (!) stack_child != "start-box")
+ return;
+ // TODO change back headerbar subtitle?
+ stack.set_transition_type (StackTransitionType.SLIDE_RIGHT);
+ stack.set_transition_duration (800);
+ show_view ();
+
+ back ();
+ }
+
+ /*\
+ * * Controls_box actions
+ \*/
+
+ private void undo_cb ()
+ {
+ string? stack_child = stack.get_visible_child_name ();
+ if (stack_child == null)
+ return;
+ if ((!) stack_child != "frame")
+ {
+ if (back_action.get_enabled ())
+ back_cb ();
+ return;
+ }
+
+ game_finished = false;
+
+ if (!back_button.is_focus)
+ view.grab_focus();
+ // redo_action.set_enabled (true);
+ undo ();
+ }
+
+/* private void redo_cb ()
+ {
+ string? stack_child = stack.get_visible_child_name ();
+ if (stack_child == null || (!) stack_child != "frame")
+ return;
+
+ if (!back_button.is_focus)
+ view.grab_focus();
+ undo_action.set_enabled (true);
+ redo ();
+ } */
+
+ private void hint_cb ()
+ {
+ string? stack_child = stack.get_visible_child_name ();
+ if (stack_child == null || (!) stack_child != "frame")
+ return;
+ hint ();
+ }
+
+ /*\
+ * * hamburger menu
+ \*/
+
+ [GtkChild] private MenuButton info_button;
+
+ private void toggle_hamburger (/* SimpleAction action, Variant? variant */)
+ {
+ info_button.active = !info_button.active;
+ }
+}
diff --git a/src/meson.build b/src/meson.build
index 52166bc..bf0c9d2 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -22,6 +22,7 @@ sources = files(
'four-in-a-row.vala',
'game-board-view.vala',
'game-board.vala',
+ 'game-window.vala',
'games-controls-list.vala',
'prefs-box.vala',
'prefs.vala',
diff --git a/src/prefs-box.vala b/src/prefs-box.vala
index 921ff43..cc20f2d 100644
--- a/src/prefs-box.vala
+++ b/src/prefs-box.vala
@@ -20,102 +20,31 @@
using Gtk;
-private class PrefsBox : Dialog {
- private const uint DEFAULT_KEY_LEFT = Gdk.Key.Left;
+private class PrefsBox : Dialog
+{
+ private const uint DEFAULT_KEY_LEFT = Gdk.Key.Left;
private const uint DEFAULT_KEY_RIGHT = Gdk.Key.Right;
- private const uint DEFAULT_KEY_DROP = Gdk.Key.Down;
+ private const uint DEFAULT_KEY_DROP = Gdk.Key.Down;
- internal PrefsBox(Window parent) {
- Notebook notebook;
- ComboBox combobox;
-
- Grid grid;
- GamesControlsList controls_list;
- Label label;
- CellRendererText renderer;
- Gtk.ListStore model;
- TreeIter iter;
-
- Object(
- title: _("Preferences"),
- destroy_with_parent: true);
- set_transient_for(parent);
+ internal PrefsBox (Window parent)
+ {
+ Object (title: _("Keyboard Controls"), destroy_with_parent: true);
+ set_transient_for (parent);
this.application = parent.application;
modal = true;
- border_width = 5;
- get_content_area().spacing = 2;
- notebook = new Notebook();
- notebook.set_border_width(5);
- get_content_area().pack_start(notebook, true, true, 0);
-
- /* game tab */
- grid = new Grid();
- grid.set_row_spacing(6);
- grid.set_column_spacing(12);
- grid.set_border_width(12);
-
- label = new Label(_("Game"));
- notebook.append_page(grid, label);
-
- label = new Label(_("Opponent:")); // TODO add a mnemonic?
- label.set_xalign((float)0.0);
- label.set_yalign((float)0.5);
- label.set_hexpand(true);
- grid.attach(label,0,0 ,1, 1);
-
- combobox = new ComboBox();
- renderer = new CellRendererText();
- combobox.pack_start(renderer, true);
- combobox.add_attribute(renderer, "text", 0);
- model = new Gtk.ListStore(2, typeof(string), typeof(int));
- combobox.set_model(model);
- model.append(out iter);
- model.@set(iter, 0, _("Human"), 1, Level.HUMAN);
- if (Prefs.instance.level[PlayerID.PLAYER2] == Level.HUMAN)
- combobox.set_active_iter(iter);
- model.append(out iter);
- model.@set(iter, 0, _("Level one"), 1, Level.WEAK);
- if (Prefs.instance.level[PlayerID.PLAYER2] == Level.WEAK)
- combobox.set_active_iter(iter);
- model.append(out iter);
- model.@set(iter, 0, _("Level two"), 1, Level.MEDIUM);
- if (Prefs.instance.level[PlayerID.PLAYER2] == Level.MEDIUM)
- combobox.set_active_iter(iter);
- model.append(out iter);
- model.@set(iter, 0, _("Level three"), 1, Level.STRONG);
- if (Prefs.instance.level[PlayerID.PLAYER2] == Level.STRONG)
- combobox.set_active_iter(iter);
-
- combobox.changed.connect(on_select_opponent);
- grid.attach(combobox, 1, 0, 1, 1);
-
- /* keyboard tab */
- label = new Label.with_mnemonic(_("Keyboard Controls"));
-
- controls_list = new GamesControlsList(Prefs.instance.settings);
- controls_list.add_controls("key-left", _("Move left"), DEFAULT_KEY_LEFT,
- "key-right", _("Move right"), DEFAULT_KEY_RIGHT,
- "key-drop", _("Drop marble"), DEFAULT_KEY_DROP);
- controls_list.border_width = 12;
- notebook.append_page(controls_list, label);
+ get_content_area ().border_width = 0; // defaults on 2
+
+ GamesControlsList controls_list = new GamesControlsList (Prefs.instance.settings);
+ controls_list.shadow_type = ShadowType.NONE;
+ controls_list.add_controls ("key-left", _("Move left"), DEFAULT_KEY_LEFT,
+ "key-right", _("Move right"), DEFAULT_KEY_RIGHT,
+ "key-drop", _("Drop marble"), DEFAULT_KEY_DROP);
+ get_content_area ().pack_start (controls_list, true, true, 0);
}
- protected override bool delete_event(Gdk.EventAny event) { // TODO use hide_on_delete (Gtk3) or
hide-on-close (Gtk4) 2/2
- hide();
+ protected override bool delete_event (Gdk.EventAny event) // TODO use hide_on_delete (Gtk3) or
hide-on-close (Gtk4) 2/2
+ {
+ hide ();
return true;
}
-
- private inline void on_select_opponent(ComboBox combobox) {
- FourInARow app = (FourInARow)application;
- TreeIter iter;
- int iter_value;
-
- combobox.get_active_iter(out iter);
- combobox.get_model().@get(iter, 1, out iter_value);
-
- Prefs.instance.level[PlayerID.PLAYER2] = (Level)iter_value;
- Prefs.instance.settings.set_int("opponent", iter_value);
- app.who_starts = PlayerID.PLAYER2; /* This gets reversed in game_reset. */
- app.game_reset();
- }
}
diff --git a/src/prefs.vala b/src/prefs.vala
index 6fbfd01..656e024 100644
--- a/src/prefs.vala
+++ b/src/prefs.vala
@@ -23,8 +23,6 @@ private class Prefs : Object
internal Settings settings;
[CCode (notify = true)] internal int theme_id { internal get; internal set; }
-
- internal Level level [2];
[CCode (notify = false)] internal int keypress_drop { internal get; internal set; }
[CCode (notify = false)] internal int keypress_right { internal get; internal set; }
[CCode (notify = false)] internal int keypress_left { internal get; internal set; }
@@ -37,22 +35,10 @@ private class Prefs : Object
internal Prefs ()
{
settings = new GLib.Settings ("org.gnome.Four-in-a-row");
- level [PlayerID.PLAYER1] = Level.HUMAN; /* Human. Always human. */
- level [PlayerID.PLAYER2] = (Level) settings.get_int ("opponent");
settings.bind ("theme-id", this, "theme-id", SettingsBindFlags.DEFAULT);
settings.bind ("key-drop", this, "keypress_drop", SettingsBindFlags.DEFAULT);
settings.bind ("key-right", this, "keypress_right", SettingsBindFlags.DEFAULT);
settings.bind ("key-left", this, "keypress_left", SettingsBindFlags.DEFAULT);
}
-
- internal int get_n_human_players ()
- {
- if (level [PlayerID.PLAYER1] != Level.HUMAN && level [PlayerID.PLAYER2] != Level.HUMAN)
- assert_not_reached ();
- if (level [PlayerID.PLAYER1] != Level.HUMAN || level [PlayerID.PLAYER2] != Level.HUMAN)
- return 1;
- else
- return 2;
- }
}
diff --git a/src/scorebox.vala b/src/scorebox.vala
index 2b18934..b316f01 100644
--- a/src/scorebox.vala
+++ b/src/scorebox.vala
@@ -89,24 +89,28 @@ private class Scorebox : Dialog {
*
* updates the scorebox with the latest scores
*/
- internal void update(int[] scores) {
- if (Prefs.instance.get_n_human_players() == 1) {
- if (Prefs.instance.level[PlayerID.PLAYER1] == Level.HUMAN) { // FIXME shouldn't it be
Player1&Player2?
-
-
-
- label_name[PlayerID.PLAYER1].set_text(_("You:"));
- label_name[PlayerID.PLAYER2].label = _("Me:");
+ internal void update(int[] scores, bool one_player_game) {
+ if (one_player_game) {
+ if (scores[PlayerID.PLAYER1] >= scores[PlayerID.PLAYER2]) {
+ label_name[0].set_text(_("You:"));
+ label_name[1].set_text(_("Me:"));
+
+ label_score[0].label = scores[PlayerID.PLAYER1].to_string();
+ label_score[1].label = scores[PlayerID.PLAYER2].to_string();
} else {
- label_name[PlayerID.PLAYER2].set_text(_("You:"));
- label_name[PlayerID.PLAYER1].label = _("Me:");
+ label_name[0].set_text(_("Me:"));
+ label_name[1].set_text(_("You:"));
+
+ label_score[0].label = scores[1].to_string();
+ label_score[1].label = scores[0].to_string();
}
} else {
- label_name[PlayerID.PLAYER1].label = theme_get_player(PlayerID.PLAYER1);
- label_name[PlayerID.PLAYER2].label = theme_get_player(PlayerID.PLAYER2);
+ label_name[0].label = theme_get_player(PlayerID.PLAYER1); // FIXME missing ":" at end
+ label_name[1].label = theme_get_player(PlayerID.PLAYER2); // idem
+
+ label_score[PlayerID.PLAYER1].label = scores[PlayerID.PLAYER1].to_string();
+ label_score[PlayerID.PLAYER2].label = scores[PlayerID.PLAYER2].to_string();
}
- label_score[PlayerID.PLAYER1].label = scores[PlayerID.PLAYER1].to_string();
- label_score[PlayerID.PLAYER2].label = scores[PlayerID.PLAYER2].to_string();
label_score[PlayerID.NOBODY].label = scores[PlayerID.NOBODY].to_string();
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]