diff pytouhou/menu.py @ 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
children f0252cf111d8
line wrap: on
line diff
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()