changeset 568:e7a4731a278b

Add a GTK+ main menu, mimicking the original EoSD one.
author Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
date Tue, 01 Jul 2014 23:18:15 +0200
parents b2269b9c6119
children b21922a03830
files data/menu.glade pytouhou/menu.py pytouhou/options.py scripts/pytouhou
diffstat 4 files changed, 1078 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/data/menu.glade
@@ -0,0 +1,754 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.18.3 -->
+<interface>
+  <requires lib="gtk+" version="3.12"/>
+  <object class="GtkWindow" id="game_window">
+    <property name="can_focus">False</property>
+    <property name="title" translatable="yes">PyTouhou</property>
+    <property name="resizable">False</property>
+    <property name="has_resize_grip">False</property>
+    <signal name="destroy" handler="on_quit" swapped="no"/>
+    <signal name="key-press-event" handler="on_game_window_key_press_event" swapped="no"/>
+    <child>
+      <object class="GtkBox" id="game_box">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="orientation">vertical</property>
+        <child>
+          <object class="GtkBox" id="difficulty_box">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="orientation">vertical</property>
+            <child>
+              <object class="GtkLabel" id="difficulty_label">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="label" translatable="yes">Difficulty</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkComboBoxText" id="difficulty_comboboxtext">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="active">0</property>
+                <items>
+                  <item id="0" translatable="yes">Easy</item>
+                  <item id="1" translatable="yes">Normal</item>
+                  <item id="2" translatable="yes">Hard</item>
+                  <item id="3" translatable="yes">Lunatic</item>
+                </items>
+                <signal name="changed" handler="on_difficulty_comboboxtext_changed" swapped="no"/>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox" id="character_box">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="orientation">vertical</property>
+            <child>
+              <object class="GtkLabel" id="character_label">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="label" translatable="yes">Character</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkComboBoxText" id="character_comboboxtext">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="active">0</property>
+                <items>
+                  <item id="0" translatable="yes">Reimu A</item>
+                  <item id="1" translatable="yes">Reimu B</item>
+                  <item id="2" translatable="yes">Marisa A</item>
+                  <item id="3" translatable="yes">Marisa B</item>
+                </items>
+                <signal name="changed" handler="on_character_comboboxtext_changed" swapped="no"/>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox" id="stage_box">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="orientation">vertical</property>
+            <child>
+              <object class="GtkLabel" id="stage_label">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="label" translatable="yes">Stage</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkComboBoxText" id="stage_comboboxtext">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="active">0</property>
+                <items>
+                  <item id="1" translatable="yes">Stage 1</item>
+                  <item id="2" translatable="yes">Stage 2</item>
+                  <item id="3" translatable="yes">Stage 3</item>
+                  <item id="4" translatable="yes">Stage 4</item>
+                  <item id="5" translatable="yes">Stage 5</item>
+                  <item id="6" translatable="yes">Final Stage</item>
+                  <item id="7" translatable="yes">Extra Stage</item>
+                </items>
+                <signal name="changed" handler="on_stage_comboboxtext_changed" swapped="no"/>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">2</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkCheckButton" id="boss_rush_checkbutton">
+            <property name="label" translatable="yes">_Boss Rush</property>
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">False</property>
+            <property name="use_underline">True</property>
+            <property name="xalign">0</property>
+            <property name="draw_indicator">True</property>
+            <signal name="toggled" handler="on_boss_rush_checkbutton_toggled" swapped="no"/>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">3</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButtonBox" id="game_buttonbox">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="layout_style">start</property>
+            <child>
+              <object class="GtkButton" id="game_back_button">
+                <property name="label" translatable="yes">Back</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <signal name="clicked" handler="on_game_back_button_clicked" swapped="no"/>
+              </object>
+              <packing>
+                <property name="expand">True</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="play_button">
+                <property name="label" translatable="yes">Play</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <signal name="clicked" handler="on_play_button_clicked" swapped="no"/>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">4</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </object>
+  <object class="GtkWindow" id="options_window">
+    <property name="can_focus">False</property>
+    <property name="title" translatable="yes">PyTouhou</property>
+    <property name="resizable">False</property>
+    <property name="has_resize_grip">False</property>
+    <signal name="destroy" handler="on_quit" swapped="no"/>
+    <signal name="key-press-event" handler="on_options_window_key_press_event" swapped="no"/>
+    <child>
+      <object class="GtkBox" id="options_box">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="orientation">vertical</property>
+        <child>
+          <object class="GtkBox" id="path_box">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="orientation">vertical</property>
+            <child>
+              <object class="GtkLabel" id="path_label">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="label" translatable="yes">Game Directory</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkFileChooserButton" id="path_filechooserbutton">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="action">select-folder</property>
+                <property name="title" translatable="yes">Game Directory</property>
+                <signal name="file-set" handler="on_path_filechooserbutton_file_set" swapped="no"/>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox" id="backend_box">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="orientation">vertical</property>
+            <child>
+              <object class="GtkLabel" id="backend_label">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="label" translatable="yes">Backend</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkComboBoxText" id="backend_comboboxtext">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="active">0</property>
+                <items>
+                  <item id="opengl" translatable="yes">OpenGL</item>
+                  <item id="sdl" translatable="yes">SDL</item>
+                  <item id="opengl sdl" translatable="yes">OpenGL then SDL</item>
+                  <item id="sdl opengl" translatable="yes">SDL then OpenGL</item>
+                </items>
+                <signal name="changed" handler="on_backend_comboboxtext_changed" swapped="no"/>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkGrid" id="opengl_grid">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <child>
+                  <object class="GtkLabel" id="flavor_label">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="label" translatable="yes">Flavor</property>
+                  </object>
+                  <packing>
+                    <property name="left_attach">0</property>
+                    <property name="top_attach">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkComboBoxText" id="flavor_comboboxtext">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="active_id">compatibility</property>
+                    <items>
+                      <item id="legacy" translatable="yes">Legacy (1.1+)</item>
+                      <item id="compatibility" translatable="yes">Compatibility (2.1+)</item>
+                      <item id="core" translatable="yes">Core (3.0+)</item>
+                      <item id="es" translatable="yes">GLES (2.0+)</item>
+                    </items>
+                    <signal name="changed" handler="on_flavor_comboboxtext_changed" swapped="no"/>
+                  </object>
+                  <packing>
+                    <property name="left_attach">1</property>
+                    <property name="top_attach">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="version_label">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="label" translatable="yes">Version</property>
+                  </object>
+                  <packing>
+                    <property name="left_attach">0</property>
+                    <property name="top_attach">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="double_buffer_label">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="label" translatable="yes">Double Buffer</property>
+                  </object>
+                  <packing>
+                    <property name="left_attach">0</property>
+                    <property name="top_attach">2</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkCheckButton" id="double_buffer_checkbutton">
+                    <property name="label" translatable="yes">Enabled</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">False</property>
+                    <property name="xalign">0</property>
+                    <property name="inconsistent">True</property>
+                    <property name="draw_indicator">True</property>
+                    <signal name="clicked" handler="on_double_buffer_checkbutton_clicked" swapped="no"/>
+                  </object>
+                  <packing>
+                    <property name="left_attach">1</property>
+                    <property name="top_attach">2</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkEntry" id="version_entry">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="max_length">3</property>
+                    <property name="text" translatable="yes">2.1</property>
+                    <signal name="changed" handler="on_version_entry_changed" swapped="no"/>
+                  </object>
+                  <packing>
+                    <property name="left_attach">1</property>
+                    <property name="top_attach">1</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">2</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkGrid" id="sdl_grid">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <child>
+                  <placeholder/>
+                </child>
+                <child>
+                  <placeholder/>
+                </child>
+                <child>
+                  <placeholder/>
+                </child>
+                <child>
+                  <placeholder/>
+                </child>
+                <child>
+                  <placeholder/>
+                </child>
+                <child>
+                  <placeholder/>
+                </child>
+                <child>
+                  <placeholder/>
+                </child>
+                <child>
+                  <placeholder/>
+                </child>
+                <child>
+                  <placeholder/>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">3</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox" id="fps_box">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="orientation">vertical</property>
+            <child>
+              <object class="GtkLabel" id="fps_label">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="label" translatable="yes">FPS Limit</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkEntry" id="fps_entry">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="text" translatable="yes">-1</property>
+                <property name="input_purpose">number</property>
+                <signal name="changed" handler="on_fps_entry_changed" swapped="no"/>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">2</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkCheckButton" id="no_background_checkbutton">
+            <property name="label" translatable="yes">Enable _Background</property>
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">False</property>
+            <property name="use_underline">True</property>
+            <property name="xalign">0</property>
+            <property name="active">True</property>
+            <property name="draw_indicator">True</property>
+            <signal name="toggled" handler="on_no_background_checkbutton_toggled" swapped="no"/>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">3</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkCheckButton" id="no_particles_checkbutton">
+            <property name="label" translatable="yes">Enable _Particles</property>
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">False</property>
+            <property name="use_underline">True</property>
+            <property name="xalign">0</property>
+            <property name="active">True</property>
+            <property name="draw_indicator">True</property>
+            <signal name="toggled" handler="on_no_particles_checkbutton_toggled" swapped="no"/>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">4</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkCheckButton" id="no_sound_checkbutton">
+            <property name="label" translatable="yes">Enable _Sound</property>
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">False</property>
+            <property name="use_underline">True</property>
+            <property name="xalign">0</property>
+            <property name="active">True</property>
+            <property name="draw_indicator">True</property>
+            <signal name="toggled" handler="on_no_sound_checkbutton_toggled" swapped="no"/>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">5</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton" id="options_back_button">
+            <property name="label" translatable="yes">Back</property>
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+            <signal name="clicked" handler="on_options_back_button_clicked" swapped="no"/>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">6</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </object>
+  <object class="GtkFileFilter" id="replay_filefilter">
+    <patterns>
+      <pattern>*.rpy</pattern>
+    </patterns>
+  </object>
+  <object class="GtkFileChooserDialog" id="replay_filechooserdialog">
+    <property name="can_focus">False</property>
+    <property name="title" translatable="yes">PyTouhou</property>
+    <property name="type_hint">dialog</property>
+    <property name="create_folders">False</property>
+    <property name="filter">replay_filefilter</property>
+    <signal name="close" handler="on_replay_filechooserdialog_close" swapped="no"/>
+    <signal name="destroy" handler="on_quit" swapped="no"/>
+    <signal name="file-activated" handler="on_replay_filechooserdialog_file_activated" swapped="no"/>
+    <signal name="key-press-event" handler="on_replay_filechooserdialog_key_press_event" swapped="no"/>
+    <signal name="selection-changed" handler="on_replay_filechooserdialog_selection_changed" swapped="no"/>
+    <child internal-child="vbox">
+      <object class="GtkBox" id="filechooserdialog-vbox1">
+        <property name="can_focus">False</property>
+        <property name="orientation">vertical</property>
+        <property name="spacing">2</property>
+        <child internal-child="action_area">
+          <object class="GtkButtonBox" id="filechooserdialog-action_area1">
+            <property name="can_focus">False</property>
+            <property name="layout_style">end</property>
+            <child>
+              <object class="GtkButton" id="replay_cancel_button">
+                <property name="label">gtk-cancel</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="use_stock">True</property>
+                <property name="always_show_image">True</property>
+                <signal name="clicked" handler="on_replay_cancel_button_clicked" swapped="no"/>
+              </object>
+              <packing>
+                <property name="expand">True</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="replay_open_button">
+                <property name="label">gtk-open</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="use_stock">True</property>
+                <property name="always_show_image">True</property>
+                <signal name="clicked" handler="on_replay_open_button_clicked" swapped="no"/>
+              </object>
+              <packing>
+                <property name="expand">True</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="pack_type">end</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </object>
+  <object class="GtkWindow" id="start_window">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="title" translatable="yes">PyTouhou</property>
+    <property name="resizable">False</property>
+    <property name="has_resize_grip">False</property>
+    <signal name="destroy" handler="on_quit" swapped="no"/>
+    <signal name="key-press-event" handler="on_start_window_key_press_event" swapped="no"/>
+    <child>
+      <object class="GtkButtonBox" id="start_buttonbox">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="orientation">vertical</property>
+        <property name="layout_style">start</property>
+        <child>
+          <object class="GtkButton" id="start_button">
+            <property name="label" translatable="yes">Start</property>
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+            <signal name="clicked" handler="on_start_button_clicked" swapped="no"/>
+          </object>
+          <packing>
+            <property name="expand">True</property>
+            <property name="fill">True</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton" id="extra_start_button">
+            <property name="label" translatable="yes">Extra Start</property>
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+            <signal name="clicked" handler="on_extra_start_button_clicked" swapped="no"/>
+          </object>
+          <packing>
+            <property name="expand">True</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton" id="practice_start_button">
+            <property name="label" translatable="yes">Practice Start</property>
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+            <signal name="clicked" handler="on_practice_start_button_clicked" swapped="no"/>
+          </object>
+          <packing>
+            <property name="expand">True</property>
+            <property name="fill">True</property>
+            <property name="position">2</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton" id="netplay_start_button">
+            <property name="label" translatable="yes">Netplay Start</property>
+            <property name="visible">True</property>
+            <property name="sensitive">False</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+            <signal name="clicked" handler="on_netplay_start_button_clicked" swapped="no"/>
+          </object>
+          <packing>
+            <property name="expand">True</property>
+            <property name="fill">True</property>
+            <property name="position">3</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton" id="replay_button">
+            <property name="label" translatable="yes">Replay</property>
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+            <signal name="clicked" handler="on_replay_button_clicked" swapped="no"/>
+          </object>
+          <packing>
+            <property name="expand">True</property>
+            <property name="fill">True</property>
+            <property name="position">4</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton" id="score_button">
+            <property name="label" translatable="yes">Score</property>
+            <property name="visible">True</property>
+            <property name="sensitive">False</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+            <signal name="clicked" handler="on_score_button_clicked" swapped="no"/>
+          </object>
+          <packing>
+            <property name="expand">True</property>
+            <property name="fill">True</property>
+            <property name="position">5</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton" id="music_room_button">
+            <property name="label" translatable="yes">Music Room</property>
+            <property name="visible">True</property>
+            <property name="sensitive">False</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+            <signal name="clicked" handler="on_music_room_button_clicked" swapped="no"/>
+          </object>
+          <packing>
+            <property name="expand">True</property>
+            <property name="fill">True</property>
+            <property name="position">6</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton" id="options_button">
+            <property name="label" translatable="yes">Options</property>
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+            <signal name="clicked" handler="on_options_button_clicked" swapped="no"/>
+          </object>
+          <packing>
+            <property name="expand">True</property>
+            <property name="fill">True</property>
+            <property name="position">7</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton" id="quit_button">
+            <property name="label" translatable="yes">Quit</property>
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+            <signal name="clicked" handler="on_quit" swapped="no"/>
+          </object>
+          <packing>
+            <property name="expand">True</property>
+            <property name="fill">True</property>
+            <property name="position">8</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </object>
+</interface>
new file mode 100644
--- /dev/null
+++ b/pytouhou/menu.py
@@ -0,0 +1,305 @@
+# -*- encoding: utf-8 -*-
+##
+## Copyright (C) 2014 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published
+## by the Free Software Foundation; version 3 only.
+##
+## This program 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.
+##
+
+from pytouhou.utils.helpers import get_logger
+logger = get_logger(__name__)
+
+try:
+    from gi.repository import Gtk, Gdk
+except ImportError:
+    logger.error('GTK+ unavailable, disabling the GUI menu.')
+    raise
+
+import sys
+import re
+
+GL_VERSION_REGEX = re.compile(r'^\d\.\d$')
+
+
+class Handler(object):
+    def __init__(self, config, args):
+        self.config = config
+        self.args = args
+
+    def init_gtk(self, builder):
+        self.start_window = builder.get_object('start_window')
+        self.game_window = builder.get_object('game_window')
+        self.options_window = builder.get_object('options_window')
+
+        self.replay_filechooserdialog = builder.get_object('replay_filechooserdialog')
+
+        # Game widgets
+        self.difficulty_box = builder.get_object('difficulty_box')
+        self.character_box = builder.get_object('character_box')
+        self.stage_box = builder.get_object('stage_box')
+        self.boss_rush_checkbutton = builder.get_object('boss_rush_checkbutton')
+
+        self.difficulty_comboboxtext = builder.get_object('difficulty_comboboxtext')
+        self.character_comboboxtext = builder.get_object('character_comboboxtext')
+        self.stage_comboboxtext = builder.get_object('stage_comboboxtext')
+
+        # Options widgets
+        self.path_filechooserbutton = builder.get_object('path_filechooserbutton')
+        self.backend_comboboxtext = builder.get_object('backend_comboboxtext')
+
+        # OpenGL backend
+        self.opengl_grid = builder.get_object('opengl_grid')
+        self.flavor_comboboxtext = builder.get_object('flavor_comboboxtext')
+        self.version_entry = builder.get_object('version_entry')
+        self.double_buffer_checkbutton = builder.get_object('double_buffer_checkbutton')
+
+        # SDL backend
+        self.sdl_grid = builder.get_object('sdl_grid')
+
+        self.fps_entry = builder.get_object('fps_entry')
+        self.no_background_checkbutton = builder.get_object('no_background_checkbutton')
+        self.no_particles_checkbutton = builder.get_object('no_particles_checkbutton')
+        self.no_sound_checkbutton = builder.get_object('no_sound_checkbutton')
+
+        self.difficulty_comboboxtext.set_active_id(str(self.args.rank))
+        self.character_comboboxtext.set_active_id(str(self.args.character))
+        self.stage_comboboxtext.set_active_id(str(self.args.stage))
+        self.boss_rush_checkbutton.set_active(self.args.boss_rush)
+
+        self.path_filechooserbutton.set_filename(self.args.path)
+        self.backend_comboboxtext.set_active_id(' '.join(self.args.backend))
+
+        self.flavor_comboboxtext.set_active_id(self.args.gl_flavor)
+        self.version_entry.set_text(str(self.args.gl_version))
+        if self.args.double_buffer is None:
+            self.double_buffer_checkbutton.set_inconsistent(True)
+        else:
+            self.double_buffer_checkbutton.set_inconsistent(False)
+            self.double_buffer_checkbutton.set_active(self.args.double_buffer)
+
+        self.fps_entry.set_text(str(self.args.fps_limit))
+        self.no_background_checkbutton.set_active(self.args.no_background)
+        self.no_particles_checkbutton.set_active(self.args.no_particles)
+        self.no_sound_checkbutton.set_active(self.args.no_sound)
+
+    def hide_and_play(self, window):
+        window.hide()
+        Gtk.main_quit()
+        print('Play!')
+
+    def on_quit(self, *args):
+        Gtk.main_quit(*args)
+        sys.exit(0)
+
+
+    # Main menu
+
+    def on_start_window_key_press_event(self, window, event_key):
+        if (event_key.keyval == Gdk.KEY_Escape or
+                event_key.state == Gdk.ModifierType.CONTROL_MASK and event_key.keyval == Gdk.KEY_q):
+            self.on_quit()
+
+    def on_start_button_clicked(self, _):
+        self.difficulty_box.show()
+        self.character_box.show()
+        self.stage_box.hide()
+
+        self.stage_comboboxtext.set_active_id(None)
+        self.args.stage = None
+
+        self.start_window.hide()
+        self.game_window.show()
+
+    def on_extra_start_button_clicked(self, _):
+        self.difficulty_box.hide()
+        self.character_box.show()
+        self.stage_box.hide()
+
+        self.difficulty_comboboxtext.set_active_id('4')
+        self.stage_comboboxtext.set_active_id('7')
+
+        self.start_window.hide()
+        self.game_window.show()
+
+    def on_practice_start_button_clicked(self, _):
+        self.difficulty_box.show()
+        self.character_box.show()
+        self.stage_box.show()
+
+        self.start_window.hide()
+        self.game_window.show()
+
+    def on_options_button_clicked(self, _):
+        self.start_window.hide()
+        self.options_window.show()
+
+    def on_replay_button_clicked(self, _):
+        self.start_window.hide()
+        self.replay_filechooserdialog.show()
+
+    def on_inactive_button_clicked(self, _):
+        raise NotImplementedError('Menu not implemented')
+
+    on_netplay_start_button_clicked = on_inactive_button_clicked
+    on_score_button_clicked = on_inactive_button_clicked
+    on_music_room_button_clicked = on_inactive_button_clicked
+
+
+    # Game menu
+
+    def on_game_back_button_clicked(self, _):
+        self.game_window.hide()
+        self.start_window.show()
+
+    def on_play_button_clicked(self, _):
+        self.hide_and_play(self.game_window)
+
+    def on_game_window_key_press_event(self, window, event_key):
+        if event_key.keyval == Gdk.KEY_Escape:
+            self.game_window.hide()
+            self.start_window.show()
+        elif event_key.state == Gdk.ModifierType.CONTROL_MASK and event_key.keyval == Gdk.KEY_q:
+            self.on_quit()
+
+    def on_difficulty_comboboxtext_changed(self, item):
+        active = item.get_active_id()
+        difficulty = int(active)
+        self.config.set('rank', active if difficulty < 4 else None)
+        self.args.rank = difficulty
+
+    def on_character_comboboxtext_changed(self, item):
+        character = int(item.get_active_id())
+        self.config.set('character', character)
+        self.args.character = character
+
+    def on_stage_comboboxtext_changed(self, item):
+        stage = item.get_active_id()
+        self.args.stage = int(stage) if stage is not None else None
+
+    def on_boss_rush_checkbutton_toggled(self, boss_rush_checkbutton):
+        active = boss_rush_checkbutton.get_active()
+        self.config.set('boss-rush', active)
+        self.args.boss_rush = active
+
+
+    # Replay dialog
+
+    def on_replay_filechooserdialog_close(self, _):
+        self.replay_filechooserdialog.hide()
+        self.start_window.show()
+
+    on_replay_cancel_button_clicked = on_replay_filechooserdialog_close
+
+    def on_replay_open_button_clicked(self, button):
+        try:
+            open(self.args.replay, 'rb').close()
+        except (IOError, TypeError):
+            return
+        self.hide_and_play(self.replay_filechooserdialog)
+
+    def on_replay_filechooserdialog_selection_changed(self, dialog):
+        self.args.replay = dialog.get_filename()
+
+    def on_replay_filechooserdialog_file_activated(self, window):
+        self.args.replay = window.get_filename()
+        self.hide_and_play(self.replay_filechooserdialog)
+
+    def on_replay_filechooserdialog_key_press_event(self, window, event_key):
+        if event_key.keyval == Gdk.KEY_Escape:
+            self.replay_filechooserdialog.hide()
+            self.start_window.show()
+        elif event_key.state == Gdk.ModifierType.CONTROL_MASK and event_key.keyval == Gdk.KEY_q:
+            self.on_quit()
+
+
+    # Options menu
+
+    def on_options_back_button_clicked(self, _):
+        self.options_window.hide()
+        self.start_window.show()
+
+    def on_options_window_key_press_event(self, window, event_key):
+        if event_key.keyval == Gdk.KEY_Escape:
+            self.options_window.hide()
+            self.start_window.show()
+        elif event_key.state == Gdk.ModifierType.CONTROL_MASK and event_key.keyval == Gdk.KEY_q:
+            self.on_quit()
+
+    def on_path_filechooserbutton_file_set(self, path_filechooserbutton):
+        path = path_filechooserbutton.get_filename()
+        self.config.set('path', path)
+
+    def on_backend_comboboxtext_changed(self, backend_comboboxtext):
+        active = backend_comboboxtext.get_active_id()
+        self.config.set('backend', active)
+        backends = active.split()
+        new_grids = [getattr(self, backend + '_grid') for backend in backends]
+        for grid in [self.opengl_grid, self.sdl_grid]:
+            if grid not in new_grids:
+                grid.hide()
+            else:
+                grid.show()
+
+    def on_flavor_comboboxtext_changed(self, flavor_comboboxtext):
+        active = flavor_comboboxtext.get_active_id()
+        self.config.set('gl-flavor', active)
+
+    def on_version_entry_changed(self, version_entry):
+        text = version_entry.get_text()
+        if not GL_VERSION_REGEX.match(text):
+            raise ValueError('ā€œ%sā€ is not <digit>.<digit>' % text)
+        self.config.set('gl-version', text)
+
+    def on_double_buffer_checkbutton_clicked(self, double_buffer_checkbutton):
+        inconsistent = double_buffer_checkbutton.get_inconsistent()
+        active = double_buffer_checkbutton.get_active()
+        if inconsistent:
+            active = False
+            inconsistent = False
+        elif active:
+            active = True
+        else:
+            inconsistent = True
+        double_buffer_checkbutton.set_active(active)
+        double_buffer_checkbutton.set_inconsistent(inconsistent)
+        self.config.set('double-buffer', None if inconsistent else active)
+
+    def on_fps_entry_changed(self, fps_entry):
+        text = fps_entry.get_text()
+        try:
+            int(text)
+        except ValueError:
+            raise ValueError('ā€œ%sā€ is not integer' % text)
+        else:
+            self.config.set('fps-limit', text)
+
+    def on_no_background_checkbutton_toggled(self, checkbutton):
+        active = checkbutton.get_active()
+        self.config.set('no-background', not active)
+
+    def on_no_particles_checkbutton_toggled(self, checkbutton):
+        active = checkbutton.get_active()
+        self.config.set('no-particles', active)
+
+    def on_no_sound_checkbutton_toggled(self, checkbutton):
+        active = checkbutton.get_active()
+        self.config.set('no-sound', active)
+
+
+def menu(config, args):
+    assert Gtk
+    handler = Handler(config, args)
+
+    builder = Gtk.Builder()
+    builder.add_from_file('data/menu.glade')
+    builder.connect_signals(handler)
+
+    handler.init_gtk(builder)
+
+    Gtk.main()
--- a/pytouhou/options.py
+++ b/pytouhou/options.py
@@ -15,7 +15,7 @@
 import os
 from ConfigParser import RawConfigParser, NoOptionError
 
-from pytouhou.utils.xdg import load_config_paths
+from pytouhou.utils.xdg import load_config_paths, save_config_path
 
 
 class Options(object):
@@ -23,6 +23,7 @@ class Options(object):
         load_paths = list(reversed([os.path.join(directory, '%s.cfg' % name)
                                     for directory
                                     in load_config_paths(name)]))
+        self.save_path = os.path.join(save_config_path(name), '%s.cfg' % name)
 
         self.config = RawConfigParser(defaults)
         self.paths = self.config.read(load_paths)
@@ -34,6 +35,18 @@ class Options(object):
         except NoOptionError:
             return None
 
+    def set(self, option, value):
+        if value is not None:
+            self.config.set(self.section, option, value)
+        else:
+            self.config.remove_option(self.section, option)
+
+        defaults = self.config._defaults
+        self.config._defaults = None
+        with open(self.save_path, 'w') as save_file:
+            self.config.write(save_file)
+        self.config._defaults = defaults
+
 
 def patch_argument_parser():
     from argparse import ArgumentParser, _ActionsContainer
@@ -105,6 +118,7 @@ def parse_arguments(defaults):
     parser.add_argument('-p', '--path', metavar='DIRECTORY', help='Game directory path.')
     parser.add_argument('--debug', action='store_true', help='Set unlimited continues, and perhaps other debug features.')
     parser.add_argument('--verbosity', metavar='VERBOSITY', choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'], help='Select the wanted logging level.')
+    parser.add_argument('--no-menu', action='store_true', help='Disable the menu.')
 
     game_group = parser.add_argument_group('Game options')
     game_group.add_argument('-s', '--stage', metavar='STAGE', type=int, help='Stage, 1 to 7 (Extra), nothing means story mode.')
--- a/scripts/pytouhou
+++ b/scripts/pytouhou
@@ -48,6 +48,10 @@ logger.root.name = 'pytouhou'
 
 logger.info('Configuration loaded from: %s', ', '.join(options.paths))
 
+if not args.no_menu:
+    from pytouhou.menu import menu
+    menu(options, args)
+
 if args.game == 'EoSD':
     from pytouhou.games.eosd import EoSDCommon as Common, EoSDGame as Game