예제 #1
0
    def __init__(self):
        super().__init__()  # TODO: check whether this call is needed

        self.widget = load_uh_widget('hotkeys.xml')
        self.buttons = []
        self.secondary_buttons = []

        self.keyconf = KeyConfig()
        self.actions = self.keyconf.get_bindable_actions_by_name()
        self.keys = self.keyconf.get_keys_by_value()

        self.HELPSTRING_LAYOUT = None
        self._build_interface()

        # When `detecting` is True, the interface detects keypresses and binds them to actions
        self.detecting = False
        self.current_button = None
        self.current_index = None
        self.last_combination = []
        # Stores whether the last button pressed was for a primary or secondary binding (1 or 2)
        self.last_column = 1

        # This used to go though the widget's key events, but fifechan has different keynames
        # Using a fife keylistener ensures that the in-game keys always match
        self.listener = HotkeysListener(self._detect_keypress)

        self.widget.findChild(name=OkButton.DEFAULT_NAME).capture(
            self.save_settings)
        self.widget.mapEvents({OkButton.DEFAULT_NAME: self.save_settings})
        self.widget.findChild(name="reset_to_default").capture(
            self.reset_to_default)
예제 #2
0
	def __init__(self):
		super(HotkeyConfiguration, self).__init__()

		self.widget = load_uh_widget('hotkeys.xml')
		self.buttons = []
		self.secondary_buttons = []

		self.keyconf = KeyConfig()
		self.actions = self.keyconf.get_bindable_actions_by_name()
		self.keys = self.keyconf.get_keys_by_value()

		self.HELPSTRING_LAYOUT = None
		self._is_displayed = False
		self._build_interface()

		# When detecing is True, the interface detects keypresses and binds them to actions
		self.detecting = False
		self.current_button = None
		self.current_index = None
		self.last_combination = []
		# Stores whether the last button pressed was for a primary or secondary binding (1 or 2)
		self.last_column = 1

		# There are some keys which are not detected by the event widget/keyPressed
		# In that case, the key presses are detected by the listener, which calls _detect_keypress
		self.listener = HotkeysListener(self._detect_keypress)

		self.widget.mapEvents({self.widget.name + '/keyPressed' : self._detect_keypress})
		self.widget.findChild(name=OkButton.DEFAULT_NAME).capture(self.save_settings)
		self.widget.mapEvents({OkButton.DEFAULT_NAME : self.save_settings})
		self.widget.findChild(name="reset_to_default").capture(self.reset_to_default)
예제 #3
0
    def __init__(self, mainmenu):
        self.mainmenu = mainmenu
        self.widgets = mainmenu.widgets

        #i18n this defines how each line in our help looks like. Default: '[C] = Chat'
        self.HELPSTRING_LAYOUT = _('[{key}] = {text}')  #xgettext:python-format

        self.keyconf = KeyConfig()  # before _build_strings
        self._build_strings()
        self._is_displayed = False

        LanguageChanged.subscribe(lambda msg: self._build_strings())
예제 #4
0
	def __init__(self, windows, session=None):
		super(HelpDialog, self).__init__(windows)

		self._session = session
		self.widget = load_uh_widget('help.xml')

		self.keyconf = KeyConfig() # before _build_strings
		self.HELPSTRING_LAYOUT = None
		self._build_strings()
		self._is_displayed = False

		self.widget.findChild(name=OkButton.DEFAULT_NAME).capture(self._windows.close)

		LanguageChanged.subscribe(lambda msg: self._build_strings())
	def __init__(self):
		super(HotkeyConfiguration, self).__init__()

		self.widget = load_uh_widget('hotkeys.xml')
		self.buttons = []
		self.secondary_buttons = []

		self.keyconf = KeyConfig()
		self.actions = self.keyconf.get_bindable_actions_by_name()
		self.keys = self.keyconf.get_keys_by_value()

		self.HELPSTRING_LAYOUT = None
		self._is_displayed = False
		self._build_interface()

		# When detecing is True, the interface detects keypresses and binds them to actions
		self.detecting = False
		self.current_button = None
		self.current_index = None
		self.last_combination = []
		# Stores whether the last button pressed was for a primary or secondary binding (1 or 2)
		self.last_column = 1

		# There are some keys which are not detected by the event widget/keyPressed
		# In that case, the key presses are detected by the listener, which calls _detect_keypress
		self.listener = HotkeysListener(self._detect_keypress)

		self.widget.mapEvents({self.widget.name + '/keyPressed' : self._detect_keypress})
		self.widget.findChild(name=OkButton.DEFAULT_NAME).capture(self.save_settings)
		self.widget.mapEvents({OkButton.DEFAULT_NAME : self.save_settings})
		self.widget.findChild(name="reset_to_default").capture(self.reset_to_default)
	def __init__(self):
		super().__init__() # TODO: check whether this call is needed

		self.widget = load_uh_widget('hotkeys.xml')
		self.buttons = []
		self.secondary_buttons = []

		self.keyconf = KeyConfig()
		self.actions = self.keyconf.get_bindable_actions_by_name()
		self.keys = self.keyconf.get_keys_by_value()

		self.HELPSTRING_LAYOUT = None
		self._build_interface()

		# When `detecting` is True, the interface detects keypresses and binds them to actions
		self.detecting = False
		self.current_button = None
		self.current_index = None
		self.last_combination = []
		# Stores whether the last button pressed was for a primary or secondary binding (1 or 2)
		self.last_column = 1

		# This used to go though the widget's key events, but fifechan has different keynames
		# Using a fife keylistener ensures that the in-game keys always match
		self.listener = HotkeysListener(self._detect_keypress)

		self.widget.findChild(name=OkButton.DEFAULT_NAME).capture(self.save_settings)
		self.widget.mapEvents({OkButton.DEFAULT_NAME: self.save_settings})
		self.widget.findChild(name="reset_to_default").capture(self.reset_to_default)
예제 #7
0
	def __init__(self):
		#i18n this defines how each line in our help looks like. Default: '[C] = Chat'
		self.HELPSTRING_LAYOUT = _('[{key}] = {text}') #xgettext:python-format

		self.mainlistener = MainListener(self)
		self.current = None # currently active window
		self.widgets = LazyWidgetsDict(self.styles) # access widgets with their filenames without '.xml'
		self.keyconf = KeyConfig() # before build_help_strings
		self.build_help_strings()
		self.session = None
		self.current_dialog = None

		self.dialog_executed = False

		self.__pause_displayed = False
		self._background_image = self._get_random_background()
		self.subscribe()
예제 #8
0
class HelpDialog(object):
    def __init__(self, mainmenu):
        self.mainmenu = mainmenu
        self.widgets = mainmenu.widgets

        self.keyconf = KeyConfig()  # before _build_strings
        self.HELPSTRING_LAYOUT = None
        self._build_strings()
        self._is_displayed = False

        LanguageChanged.subscribe(lambda msg: self._build_strings())

    def _build_strings(self):
        """
		Loads the help strings from pychan object widgets (containing no key definitions)
		and adds the keys defined in the keyconfig configuration object in front of them.
		The layout is defined through HELPSTRING_LAYOUT and translated.
		"""
        # i18n this defines how each line in our help looks like. Default: '[C] = Chat'
        self.HELPSTRING_LAYOUT = _("[{key}] = {text}")  # xgettext:python-format

        widgets = self.widgets["help"]
        labels = widgets.getNamedChildren()
        # filter misc labels that do not describe key functions
        labels = dict((name[4:], lbl[0]) for (name, lbl) in labels.iteritems() if name.startswith("lbl_"))

        # now prepend the actual keys to the function strings defined in xml
        for (name, lbl) in labels.items():
            if name == "SHIFT":
                # TODO #HACK hardcoded shift key
                keyname = "SHIFT"
            else:
                # TODO Display all keys per action, not just the first
                keyname = self.keyconf.get_current_keys(name)[0]
            lbl.explanation = _(lbl.text)
            lbl.text = self.HELPSTRING_LAYOUT.format(text=lbl.explanation, key=keyname)

    def toggle(self):
        """Called on help action.
		Toggles help screen via static variable *help_is_displayed*.
		Can be called both from main menu and in-game interface.
		"""
        help_dlg = self.widgets["help"]
        if not self._is_displayed:
            self._is_displayed = True
            # make game pause if there is a game and we're not in the main menu
            if self.mainmenu.session is not None and self.mainmenu.current != self.widgets["ingamemenu"]:
                PauseCommand().execute(self.mainmenu.session)
            if self.mainmenu.session is not None:
                self.mainmenu.session.ingame_gui.on_escape()  # close dialogs that might be open
            self.mainmenu.show_dialog(help_dlg, {OkButton.DEFAULT_NAME: True})
            self.toggle()  # toggle state
        else:
            self._is_displayed = False
            if self.mainmenu.session is not None and self.mainmenu.current != self.widgets["ingamemenu"]:
                UnPauseCommand().execute(self.mainmenu.session)
            help_dlg.hide()
예제 #9
0
    def __init__(self, mainmenu):
        self.mainmenu = mainmenu
        self.widgets = mainmenu.widgets

        self.keyconf = KeyConfig()  # before _build_strings
        self.HELPSTRING_LAYOUT = None
        self._build_strings()
        self._is_displayed = False

        LanguageChanged.subscribe(lambda msg: self._build_strings())
예제 #10
0
	def __init__(self, mainmenu):
		self.mainmenu = mainmenu
		self.widgets = mainmenu.widgets

		#i18n this defines how each line in our help looks like. Default: '[C] = Chat'
		self.HELPSTRING_LAYOUT = _('[{key}] = {text}') #xgettext:python-format

		self.keyconf = KeyConfig() # before _build_strings
		self._build_strings()
		self._is_displayed = False

		LanguageChanged.subscribe(lambda msg: self._build_strings())
예제 #11
0
class HelpDialog(Window):

	def __init__(self, windows, session=None):
		super(HelpDialog, self).__init__(windows)

		self._session = session
		self.widget = load_uh_widget('help.xml')

		self.keyconf = KeyConfig() # before _build_strings
		self.HELPSTRING_LAYOUT = None
		self._build_strings()
		self._is_displayed = False

		self.widget.findChild(name=OkButton.DEFAULT_NAME).capture(self._windows.close)

		LanguageChanged.subscribe(lambda msg: self._build_strings())

	def _build_strings(self):
		"""
		Loads the help strings from pychan object widgets (containing no key definitions)
		and adds the keys defined in the keyconfig configuration object in front of them.
		The layout is defined through HELPSTRING_LAYOUT and translated.
		"""
		#i18n this defines how each line in our help looks like. Default: '[C] = Chat'
		self.HELPSTRING_LAYOUT = _('[{key}] = {text}') #xgettext:python-format

		labels = self.widget.getNamedChildren()
		# filter misc labels that do not describe key functions
		labels = dict( (name[4:], lbl[0]) for (name, lbl) in labels.iteritems()
		                                  if name.startswith('lbl_') )

		# now prepend the actual keys to the function strings defined in xml
		for (name, lbl) in labels.items():
			if name == 'SHIFT':
				#TODO #HACK hardcoded shift key
				keyname = 'SHIFT'
			else:
				#TODO Display all keys per action, not just the first
				keyname = self.keyconf.get_current_keys(name)[0]
			lbl.explanation = _(lbl.text)
			lbl.text = self.HELPSTRING_LAYOUT.format(text=lbl.explanation, key=keyname)

	def show(self):
		self.widget.show()
		if self._session:
			PauseCommand().execute(self._session)

	def hide(self):
		if self._session:
			UnPauseCommand().execute(self._session)
		self.widget.hide()
예제 #12
0
class HelpDialog(Window):

	def __init__(self, windows, session=None):
		super(HelpDialog, self).__init__(windows)

		self._session = session
		self.widget = load_uh_widget('help.xml')

		self.keyconf = KeyConfig() # before _build_strings
		self.HELPSTRING_LAYOUT = None
		self._build_strings()
		self._is_displayed = False

		self.widget.findChild(name=OkButton.DEFAULT_NAME).capture(self._windows.close)

		LanguageChanged.subscribe(lambda msg: self._build_strings())

	def _build_strings(self):
		"""
		Loads the help strings from pychan object widgets (containing no key definitions)
		and adds the keys defined in the keyconfig configuration object in front of them.
		The layout is defined through HELPSTRING_LAYOUT and translated.
		"""
		#i18n this defines how each line in our help looks like. Default: '[C] = Chat'
		self.HELPSTRING_LAYOUT = _('[{key}] = {text}') #xgettext:python-format

		labels = self.widget.getNamedChildren()
		# filter misc labels that do not describe key functions
		labels = dict( (name[4:], lbl[0]) for (name, lbl) in labels.iteritems()
		                                  if name.startswith('lbl_') )

		# now prepend the actual keys to the function strings defined in xml
		for (name, lbl) in labels.items():
			if name == 'SHIFT':
				#TODO #HACK hardcoded shift key
				keyname = 'SHIFT'
			else:
				#TODO Display all keys per action, not just the first
				keyname = self.keyconf.get_current_keys(name)[0]
			lbl.explanation = _(lbl.text)
			lbl.text = self.HELPSTRING_LAYOUT.format(text=lbl.explanation, key=keyname)

	def show(self):
		self.widget.show()
		if self._session:
			PauseCommand().execute(self._session)

	def hide(self):
		if self._session:
			UnPauseCommand().execute(self._session)
		self.widget.hide()
예제 #13
0
	def __init__(self, windows, session=None):
		super(HelpDialog, self).__init__(windows)

		self._session = session
		self.widget = load_uh_widget('help.xml')

		self.keyconf = KeyConfig() # before _build_strings
		self.HELPSTRING_LAYOUT = None
		self._build_strings()
		self._is_displayed = False

		self.widget.findChild(name=OkButton.DEFAULT_NAME).capture(self._windows.close)

		LanguageChanged.subscribe(lambda msg: self._build_strings())
예제 #14
0
	def __init__(self):
		#i18n this defines how each line in our help looks like. Default: '[C] = Chat'
		self.HELPSTRING_LAYOUT = _('[{key}] = {text}') #xgettext:python-format

		self.mainlistener = MainListener(self)
		self.current = None # currently active window
		self.widgets = LazyWidgetsDict(self.styles) # access widgets with their filenames without '.xml'
		self.keyconf = KeyConfig() # before build_help_strings
		self.build_help_strings()
		self.session = None
		self.current_dialog = None

		self.dialog_executed = False

		self.__pause_displayed = False
		self._background_image = self._get_random_background()
		self.subscribe()
예제 #15
0
class Gui(SingleplayerMenu, MultiplayerMenu):
	"""This class handles all the out of game menu, like the main and pause menu, etc.
	"""
	log = logging.getLogger("gui")

	# styles to apply to a widget
	styles = defaultdict(lambda: 'book')
	styles.update({
	  'mainmenu': 'menu',
	  'ingamemenu': 'headline',
	})

	def __init__(self):
		#i18n this defines how each line in our help looks like. Default: '[C] = Chat'
		self.HELPSTRING_LAYOUT = _('[{key}] = {text}') #xgettext:python-format

		self.mainlistener = MainListener(self)
		self.current = None # currently active window
		self.widgets = LazyWidgetsDict(self.styles) # access widgets with their filenames without '.xml'
		self.keyconf = KeyConfig() # before build_help_strings
		self.build_help_strings()
		self.session = None
		self.current_dialog = None

		self.dialog_executed = False

		self.__pause_displayed = False
		self._background_image = self._get_random_background()
		self.subscribe()

	def subscribe(self):
		"""Subscribe to the necessary messages."""
		GuiAction.subscribe(self._on_gui_action)

	def unsubscribe(self):
		GuiAction.unsubscribe(self._on_gui_action)

# basic menu widgets
	def show_main(self):
		"""Shows the main menu """
		self._switch_current_widget('mainmenu', center=True, show=True, event_map={
			'startSingle'      : self.show_single, # first is the icon in menu
			'start'            : self.show_single, # second is the label in menu
			'startMulti'       : self.show_multi,
			'start_multi'      : self.show_multi,
			'settingsLink'     : self.show_settings,
			'settings'         : self.show_settings,
			'helpLink'         : self.on_help,
			'help'             : self.on_help,
			'editor_link'      : self.editor_load_map,
			'editor'           : self.editor_load_map,
			'closeButton'      : self.show_quit,
			'quit'             : self.show_quit,
			'creditsLink'      : self.show_credits,
			'credits'          : self.show_credits,
			'loadgameButton'   : self.load_game,
			'loadgame'         : self.load_game,
			'changeBackground' : self.get_random_background_by_button,
		})

		self.on_escape = self.show_quit

	def load_game(self):
		saved_game = self.show_select_savegame(mode='load')
		if saved_game is None:
			return False # user aborted dialog

		self.show_loading_screen()
		options = StartGameOptions(saved_game)
		horizons.main.start_singleplayer(options)
		return True

	def toggle_pause(self):
		"""Shows in-game pause menu if the game is currently not paused.
		Else unpauses and hides the menu. Multiple layers of the 'paused' concept exist;
		if two widgets are opened which would both pause the game, we do not want to
		unpause after only one of them is closed. Uses PauseCommand and UnPauseCommand.
		"""
		# TODO: logically, this now belongs to the ingame_gui (it used to be different)
		#       this manifests itself by the need for the __pause_displayed hack below
		#       in the long run, this should be moved, therefore eliminating the hack, and
		#       ensuring correct setup/teardown.
		if self.__pause_displayed:
			self.__pause_displayed = False
			self.hide()
			self.current = None
			UnPauseCommand(suggestion=True).execute(self.session)
			self.on_escape = self.toggle_pause

		else:
			self.__pause_displayed = True
			# reload the menu because caching creates spacing problems
			# see http://trac.unknown-horizons.org/t/ticket/1047
			self.widgets.reload('ingamemenu')
			def do_load():
				did_load = self.load_game()
				if did_load:
					self.__pause_displayed = False
			def do_quit():
				did_quit = self.quit_session()
				if did_quit:
					self.__pause_displayed = False
			events = { # needed twice, save only once here
				'e_load' : do_load,
				'e_save' : self.save_game,
				'e_sett' : self.show_settings,
				'e_help' : self.on_help,
				'e_start': self.toggle_pause,
				'e_quit' : do_quit,
			}
			self._switch_current_widget('ingamemenu', center=True, show=False, event_map={
				  # icons
				'loadgameButton' : events['e_load'],
				'savegameButton' : events['e_save'],
				'settingsLink'   : events['e_sett'],
				'helpLink'       : events['e_help'],
				'startGame'      : events['e_start'],
				'closeButton'    : events['e_quit'],
				# labels
				'loadgame' : events['e_load'],
				'savegame' : events['e_save'],
				'settings' : events['e_sett'],
				'help'     : events['e_help'],
				'start'    : events['e_start'],
				'quit'     : events['e_quit'],
			})

			self.show_modal_background()
			self.current.show()

			PauseCommand(suggestion=True).execute(self.session)
			self.on_escape = self.toggle_pause

# what happens on button clicks

	def save_game(self):
		"""Wrapper for saving for separating gui messages from save logic
		"""
		success = self.session.save()
		if not success:
			# There was a problem during the 'save game' procedure.
			self.show_popup(_('Error'), _('Failed to save.'))

	def show_settings(self):
		"""Displays settings gui derived from the FIFE settings module."""
		horizons.globals.fife.show_settings()

	_help_is_displayed = False
	def on_help(self):
		"""Called on help action.
		Toggles help screen via static variable *help_is_displayed*.
		Can be called both from main menu and in-game interface.
		"""
		help_dlg = self.widgets['help']
		if not self._help_is_displayed:
			self._help_is_displayed = True
			# make game pause if there is a game and we're not in the main menu
			if self.session is not None and self.current != self.widgets['ingamemenu']:
				PauseCommand().execute(self.session)
			if self.session is not None:
				self.session.ingame_gui.on_escape() # close dialogs that might be open
			self.show_dialog(help_dlg, {OkButton.DEFAULT_NAME : True})
			self.on_help() # toggle state
		else:
			self._help_is_displayed = False
			if self.session is not None and self.current != self.widgets['ingamemenu']:
				UnPauseCommand().execute(self.session)
			help_dlg.hide()

	def show_quit(self):
		"""Shows the quit dialog. Closes the game unless the dialog is cancelled."""
		message = _("Are you sure you want to quit Unknown Horizons?")
		if self.show_popup(_("Quit Game"), message, show_cancel_button=True):
			horizons.main.quit()

	def quit_session(self, force=False):
		"""Quits the current session. Usually returns to main menu afterwards.
		@param force: whether to ask for confirmation"""
		message = _("Are you sure you want to abort the running session?")

		if force or self.show_popup(_("Quit Session"), message, show_cancel_button=True):
			if self.current is not None:
				# this can be None if not called from gui (e.g. scenario finished)
				self.hide()
				self.current = None
			if self.session is not None:
				self.session.end()
				self.session = None

			self.show_main()
			return True
		else:
			return False

	def show_credits(self, number=0):
		"""Shows the credits dialog. """
		if self.current_dialog is not None:
			self.current_dialog.hide()

		credits_page = self.widgets['credits{number}'.format(number=number)]
		for box in credits_page.findChildren(name='box'):
			box.margins = (30, 0) # to get some indentation
			if number in [0, 2]: # #TODO fix these hardcoded page references
				box.padding = 1
				box.parent.padding = 3 # further decrease if more entries
		labels = [credits_page.findChild(name=section+"_lbl")
		          for section in ('team', 'patchers', 'translators',
		                          'packagers', 'special_thanks')]
		for i in xrange(5): # add callbacks to each pickbelt
			labels[i].capture(Callback(self.show_credits, i), event_name="mouseClicked")

		self.show_dialog(credits_page, {OkButton.DEFAULT_NAME : True})

	def show_select_savegame(self, mode, sanity_checker=None, sanity_criteria=None):
		"""Shows menu to select a savegame.
		@param mode: Valid options are 'save', 'load', 'mp_load', 'mp_save'
		@param sanity_checker: only allow manually entered names that pass this test
		@param sanity_criteria: explain which names are allowed to the user
		@return: Path to savegamefile or None"""
		assert mode in ('save', 'load', 'mp_load', 'mp_save')
		map_files, map_file_display = None, None
		args = mode, sanity_checker, sanity_criteria # for reshow
		mp = mode.startswith('mp_')
		if mp:
			mode = mode[3:]
		# below this line, mp_load == load, mp_save == save
		if mode == 'load':
			if not mp:
				map_files, map_file_display = SavegameManager.get_saves()
			else:
				map_files, map_file_display = SavegameManager.get_multiplayersaves()
			if not map_files:
				self.show_popup(_("No saved games"), _("There are no saved games to load."))
				return
		else: # don't show autosave and quicksave on save
			if not mp:
				map_files, map_file_display = SavegameManager.get_regular_saves()
			else:
				map_files, map_file_display = SavegameManager.get_multiplayersaves()

		# Prepare widget
		old_current = self._switch_current_widget('select_savegame')
		if mode == 'save':
			helptext = _('Save game')
		elif mode == 'load':
			helptext = _('Load game')
		# else: not a valid mode, so we can as well crash on the following
		self.current.findChild(name='headline').text = helptext
		self.current.findChild(name=OkButton.DEFAULT_NAME).helptext = helptext

		name_box = self.current.findChild(name="gamename_box")
		password_box = self.current.findChild(name="gamepassword_box")
		if mp and mode == 'load': # have gamename
			name_box.parent.showChild(name_box)
			password_box.parent.showChild(password_box)
			gamename_textfield = self.current.findChild(name="gamename")
			gamepassword_textfield = self.current.findChild(name="gamepassword")
			gamepassword_textfield.text = u""
			def clear_gamedetails_textfields():
				gamename_textfield.text = u""
				gamepassword_textfield.text = u""
			gamename_textfield.capture(clear_gamedetails_textfields, 'mouseReleased', 'default')
		else:
			if name_box not in name_box.parent.hidden_children:
				name_box.parent.hideChild(name_box)
			if password_box not in name_box.parent.hidden_children:
				password_box.parent.hideChild(password_box)

		self.current.show()

		if not hasattr(self, 'filename_hbox'):
			self.filename_hbox = self.current.findChild(name='enter_filename')
			self.filename_hbox_parent = self.filename_hbox.parent

		if mode == 'save': # only show enter_filename on save
			self.filename_hbox_parent.showChild(self.filename_hbox)
		elif self.filename_hbox not in self.filename_hbox_parent.hidden_children:
			self.filename_hbox_parent.hideChild(self.filename_hbox)

		def tmp_selected_changed():
			"""Fills in the name of the savegame in the textbox when selected in the list"""
			if mode != 'save': # set textbox only if we are in save mode
				return
			if self.current.collectData('savegamelist') == -1: # set blank if nothing is selected
				self.current.findChild(name="savegamefile").text = u""
			else:
				savegamefile = map_file_display[self.current.collectData('savegamelist')]
				self.current.distributeData({'savegamefile': savegamefile})

		self.current.distributeInitialData({'savegamelist': map_file_display})
		# Select first item when loading, nothing when saving
		selected_item = -1 if mode == 'save' else 0
		self.current.distributeData({'savegamelist': selected_item})
		cb_details = Gui._create_show_savegame_details(self.current, map_files, 'savegamelist')
		cb = Callback.ChainedCallbacks(cb_details, tmp_selected_changed)
		cb() # Refresh data on start
		self.current.mapEvents({'savegamelist/action': cb})
		self.current.findChild(name="savegamelist").capture(cb, event_name="keyPressed")

		bind = {
			OkButton.DEFAULT_NAME     : True,
			CancelButton.DEFAULT_NAME : False,
			DeleteButton.DEFAULT_NAME : 'delete'
		}

		if mode == 'save':
			bind['savegamefile'] = True

		retval = self.show_dialog(self.current, bind)
		if not retval: # cancelled
			self.current = old_current
			return

		if retval == 'delete':
			# delete button was pressed. Apply delete and reshow dialog, delegating the return value
			delete_retval = self._delete_savegame(map_files)
			if delete_retval:
				self.current.distributeData({'savegamelist' : -1})
				cb()
			self.current = old_current
			return self.show_select_savegame(*args)

		selected_savegame = None
		if mode == 'save': # return from textfield
			selected_savegame = self.current.collectData('savegamefile')
			if selected_savegame == "":
				self.show_error_popup(windowtitle=_("No filename given"),
				                      description=_("Please enter a valid filename."))
				self.current = old_current
				return self.show_select_savegame(*args) # reshow dialog
			elif selected_savegame in map_file_display: # savegamename already exists
				#xgettext:python-format
				message = _("A savegame with the name '{name}' already exists.").format(
				             name=selected_savegame) + u"\n" + _('Overwrite it?')
				# keep the pop-up non-modal because otherwise it is double-modal (#1876)
				if not self.show_popup(_("Confirmation for overwriting"), message, show_cancel_button=True, modal=False):
					self.current = old_current
					return self.show_select_savegame(*args) # reshow dialog
			elif sanity_checker and sanity_criteria:
				if not sanity_checker(selected_savegame):
					self.show_error_popup(windowtitle=_("Invalid filename given"),
					                      description=sanity_criteria)
					self.current = old_current
					return self.show_select_savegame(*args) # reshow dialog
		else: # return selected item from list
			selected_savegame = self.current.collectData('savegamelist')
			assert selected_savegame != -1, "No savegame selected in savegamelist"
			selected_savegame = map_files[selected_savegame]

		if mp and mode == 'load': # also name
			gamename_textfield = self.current.findChild(name="gamename")
			ret = selected_savegame, self.current.collectData('gamename'), self.current.collectData('gamepassword')
		else:
			ret = selected_savegame
		self.current = old_current # reuse old widget
		return ret

# display

	def on_escape(self):
		pass

	def show(self):
		self.log.debug("Gui: showing current: %s", self.current)
		if self.current is not None:
			self.current.show()

	def hide(self):
		self.log.debug("Gui: hiding current: %s", self.current)
		if self.current is not None:
			self.current.hide()
			self.hide_modal_background()

	def is_visible(self):
		return self.current is not None and self.current.isVisible()

	def show_dialog(self, dlg, bind, event_map=None, modal=False):
		"""Shows any pychan dialog.
		@param dlg: dialog that is to be shown
		@param bind: events that make the dialog return + return values{ 'ok': callback, 'cancel': callback }
		@param event_map: dictionary with callbacks for buttons. See pychan docu: pychan.widget.mapEvents()
		@param modal: Whether to block user interaction while displaying the dialog
		"""
		self.current_dialog = dlg
		if event_map is not None:
			dlg.mapEvents(event_map)
		if modal:
			self.show_modal_background()

		# handle escape and enter keypresses
		def _on_keypress(event, dlg=dlg): # rebind to make sure this dlg is used
			from horizons.engine import pychan_util
			if event.getKey().getValue() == fife.Key.ESCAPE: # convention says use cancel action
				btn = dlg.findChild(name=CancelButton.DEFAULT_NAME)
				callback = pychan_util.get_button_event(btn) if btn else None
				if callback:
					pychan.tools.applyOnlySuitable(callback, event=event, widget=btn)
				else:
					# escape should hide the dialog default
					horizons.globals.fife.pychanmanager.breakFromMainLoop(returnValue=False)
					dlg.hide()
			elif event.getKey().getValue() == fife.Key.ENTER: # convention says use ok action
				btn = dlg.findChild(name=OkButton.DEFAULT_NAME)
				callback = pychan_util.get_button_event(btn) if btn else None
				if callback:
					pychan.tools.applyOnlySuitable(callback, event=event, widget=btn)
				# can't guess a default action here

		dlg.capture(_on_keypress, event_name="keyPressed")

		# show that a dialog is being executed, this can sometimes require changes in program logic elsewhere
		self.dialog_executed = True
		ret = dlg.execute(bind)
		self.dialog_executed = False
		if modal:
			self.hide_modal_background()
		return ret

	def show_popup(self, windowtitle, message, show_cancel_button=False, size=0, modal=True):
		"""Displays a popup with the specified text
		@param windowtitle: the title of the popup
		@param message: the text displayed in the popup
		@param show_cancel_button: boolean, show cancel button or not
		@param size: 0, 1 or 2. Larger means bigger.
		@param modal: Whether to block user interaction while displaying the popup
		@return: True on ok, False on cancel (if no cancel button, always True)
		"""
		popup = self.build_popup(windowtitle, message, show_cancel_button, size=size)
		# ok should be triggered on enter, therefore we need to focus the button
		# pychan will only allow it after the widgets is shown
		def focus_ok_button():
			popup.findChild(name=OkButton.DEFAULT_NAME).requestFocus()
		ExtScheduler().add_new_object(focus_ok_button, self, run_in=0)
		if show_cancel_button:
			return self.show_dialog(popup, {OkButton.DEFAULT_NAME : True,
			                                CancelButton.DEFAULT_NAME : False},
			                        modal=modal)
		else:
			return self.show_dialog(popup, {OkButton.DEFAULT_NAME : True},
			                        modal=modal)

	def show_error_popup(self, windowtitle, description, advice=None, details=None, _first=True):
		"""Displays a popup containing an error message.
		@param windowtitle: title of popup, will be auto-prefixed with "Error: "
		@param description: string to tell the user what happened
		@param advice: how the user might be able to fix the problem
		@param details: technical details, relevant for debugging but not for the user
		@param _first: Don't touch this.

		Guide for writing good error messages:
		http://www.useit.com/alertbox/20010624.html
		"""
		msg = u""
		msg += description + u"\n"
		if advice:
			msg += advice + u"\n"
		if details:
			msg += _("Details: {error_details}").format(error_details=details)
		try:
			self.show_popup( _("Error: {error_message}").format(error_message=windowtitle),
			                 msg, show_cancel_button=False)
		except SystemExit: # user really wants us to die
			raise
		except:
			# could be another game error, try to be persistent in showing the error message
			# else the game would be gone without the user being able to read the message.
			if _first:
				traceback.print_exc()
				print 'Exception while showing error, retrying once more'
				return self.show_error_popup(windowtitle, description, advice, details, _first=False)
			else:
				raise # it persists, we have to die.

	def build_popup(self, windowtitle, message, show_cancel_button=False, size=0):
		""" Creates a pychan popup widget with the specified properties.
		@param windowtitle: the title of the popup
		@param message: the text displayed in the popup
		@param show_cancel_button: boolean, include cancel button or not
		@param size: 0, 1 or 2
		@return: Container(name='popup_window') with buttons 'okButton' and optionally 'cancelButton'
		"""
		if size == 0:
			wdg_name = "popup_230"
		elif size == 1:
			wdg_name = "popup_290"
		elif size == 2:
			wdg_name = "popup_350"
		else:
			assert False, "size should be 0 <= size <= 2, but is "+str(size)

		# NOTE: reusing popup dialogs can sometimes lead to exit(0) being called.
		#       it is yet unknown why this happens, so let's be safe for now and reload the widgets.
		self.widgets.reload(wdg_name)
		popup = self.widgets[wdg_name]

		if not show_cancel_button:
			cancel_button = popup.findChild(name=CancelButton.DEFAULT_NAME)
			cancel_button.parent.removeChild(cancel_button)

		popup.headline = popup.findChild(name='headline')
		popup.headline.text = _(windowtitle)
		popup.message = popup.findChild(name='popup_message')
		popup.message.text = _(message)
		popup.adaptLayout() # recalculate widths
		return popup

	def show_modal_background(self):
		""" Loads transparent background that de facto prohibits
		access to other gui elements by eating all input events.
		Used for modal popups and our in-game menu.
		"""
		height = horizons.globals.fife.engine_settings.getScreenHeight()
		width = horizons.globals.fife.engine_settings.getScreenWidth()
		image = horizons.globals.fife.imagemanager.loadBlank(width, height)
		image = fife.GuiImage(image)
		self.additional_widget = pychan.Icon(image=image)
		self.additional_widget.position = (0, 0)
		self.additional_widget.show()

	def hide_modal_background(self):
		try:
			self.additional_widget.hide()
			del self.additional_widget
		except AttributeError:
			pass # only used for some widgets, e.g. pause

	def show_loading_screen(self):
		self._switch_current_widget('loadingscreen', center=True, show=True)
		# Add 'Quote of the Load' to loading screen:
		qotl_type_label = self.current.findChild(name='qotl_type_label')
		qotl_label = self.current.findChild(name='qotl_label')
		quote_type = int(horizons.globals.fife.get_uh_setting("QuotesType"))
		if quote_type == 2:
			quote_type = random.randint(0, 1) # choose a random type

		if quote_type == 0:
			name = GAMEPLAY_TIPS["name"]
			items = GAMEPLAY_TIPS["items"]
		elif quote_type == 1:
			name = FUN_QUOTES["name"]
			items = FUN_QUOTES["items"]

		qotl_type_label.text = unicode(name)
		qotl_label.text = unicode(random.choice(items)) # choose a random quote / gameplay tip

# helper

	def _switch_current_widget(self, new_widget, center=False, event_map=None, show=False, hide_old=False):
		"""Switches self.current to a new widget.
		@param new_widget: str, widget name
		@param center: bool, whether to center the new widget
		@param event_map: pychan event map to apply to new widget
		@param show: bool, if True old window gets hidden and new one shown
		@param hide_old: bool, if True old window gets hidden. Implied by show
		@return: instance of old widget"""
		old = self.current
		if (show or hide_old) and old is not None:
			self.log.debug("Gui: hiding %s", old)
			self.hide()
		self.log.debug("Gui: setting current to %s", new_widget)
		self.current = self.widgets[new_widget]
		bg = self.current.findChild(name='background')
		if bg:
			# Set background image
			bg.image = self._background_image
		if center:
			self.current.position_technique = "automatic" # == "center:center"
		if event_map:
			self.current.mapEvents(event_map)
		if show:
			self.current.show()

		return old

	@staticmethod
	def _create_show_savegame_details(gui, map_files, savegamelist):
		"""Creates a function that displays details of a savegame in gui"""

		def tmp_show_details():
			"""Fetches details of selected savegame and displays it"""
			gui.findChild(name="screenshot").image = None
			map_file = None
			map_file_index = gui.collectData(savegamelist)
			if map_file_index == -1:
				gui.findChild(name="savegame_details").hide()
				return
			else:
				gui.findChild(name="savegame_details").show()
			try:
				map_file = map_files[map_file_index]
			except IndexError:
				# this was a click in the savegame list, but not on an element
				# it happens when the savegame list is empty
				return
			savegame_info = SavegameManager.get_metadata(map_file)

			if savegame_info.get('screenshot'):
				# try to find a writable location, that is accessible via relative paths
				# (required by fife)
				fd, filename = tempfile.mkstemp()
				try:
					path_rel = os.path.relpath(filename)
				except ValueError: # the relative path sometimes doesn't exist on win
					os.close(fd)
					os.unlink(filename)
					# try again in the current dir, it's often writable
					fd, filename = tempfile.mkstemp(dir=os.curdir)
					try:
						path_rel = os.path.relpath(filename)
					except ValueError:
						fd, filename = None, None

				if fd:
					with os.fdopen(fd, "w") as f:
						f.write(savegame_info['screenshot'])
					# fife only supports relative paths
					gui.findChild(name="screenshot").image = path_rel
					os.unlink(filename)

			# savegamedetails
			details_label = gui.findChild(name="savegamedetails_lbl")
			details_label.text = u""
			if savegame_info['timestamp'] == -1:
				details_label.text += _("Unknown savedate")
			else:
				savetime = time.strftime("%c", time.localtime(savegame_info['timestamp']))
				#xgettext:python-format
				details_label.text += _("Saved at {time}").format(time=savetime.decode('utf-8'))
			details_label.text += u'\n'
			counter = savegame_info['savecounter']
			# N_ takes care of plural forms for different languages
			#xgettext:python-format
			details_label.text += N_("Saved {amount} time",
			                         "Saved {amount} times",
			                         counter).format(amount=counter)
			details_label.text += u'\n'
			details_label.stylize('book')

			from horizons.constants import VERSION
			try:
				#xgettext:python-format
				details_label.text += _("Savegame version {version}").format(
				                         version=savegame_info['savegamerev'])
				if savegame_info['savegamerev'] != VERSION.SAVEGAMEREVISION:
					if not SavegameUpgrader.can_upgrade(savegame_info['savegamerev']):
						details_label.text += u" " + _("(probably incompatible)")
			except KeyError:
				# this should only happen for very old savegames, so having this unfriendly
				# error is ok (savegame is quite certainly fully unusable).
				details_label.text += u" " + _("Incompatible version")

			gui.adaptLayout()
		return tmp_show_details

	def _delete_savegame(self, map_files):
		"""Deletes the selected savegame if the user confirms
		self.current has to contain the widget "savegamelist"
		@param map_files: list of files that corresponds to the entries of 'savegamelist'
		@return: True if something was deleted, else False
		"""
		selected_item = self.current.collectData("savegamelist")
		if selected_item == -1 or selected_item >= len(map_files):
			self.show_popup(_("No file selected"), _("You need to select a savegame to delete."))
			return False
		selected_file = map_files[selected_item]
		#xgettext:python-format
		message = _("Do you really want to delete the savegame '{name}'?").format(
		             name=SavegameManager.get_savegamename_from_filename(selected_file))
		if self.show_popup(_("Confirm deletion"), message, show_cancel_button=True):
			try:
				os.unlink(selected_file)
				return True
			except:
				self.show_popup(_("Error!"), _("Failed to delete savefile!"))
				return False
		else: # player cancelled deletion
			return False

	def get_random_background_by_button(self):
		"""Randomly select a background image to use. This function is triggered by
		change background button from main menu."""
		#we need to redraw screen to apply changes.
		self.hide()
		self._background_image = self._get_random_background()
		self.show_main()

	def _get_random_background(self):
		"""Randomly select a background image to use through out the game menu."""
		available_images = glob.glob('content/gui/images/background/mainmenu/bg_*.png')
		#get latest background
		latest_background = horizons.globals.fife.get_uh_setting("LatestBackground")
		#if there is a latest background then remove it from available list
		if latest_background is not None:
			available_images.remove(latest_background)
		background_choice = random.choice(available_images)
		#save current background choice
		horizons.globals.fife.set_uh_setting("LatestBackground", background_choice)
		horizons.globals.fife.save_settings()
		return background_choice

	def _on_gui_action(self, msg):
		AmbientSoundComponent.play_special('click')

	def build_help_strings(self):
		"""
		Loads the help strings from pychan object widgets (containing no key definitions)
		and adds the keys defined in the keyconfig configuration object in front of them.
		The layout is defined through HELPSTRING_LAYOUT and translated.
		"""
		widgets = self.widgets['help']
		labels = widgets.getNamedChildren()
		# filter misc labels that do not describe key functions
		labels = dict( (name[4:], lbl[0]) for (name, lbl) in labels.iteritems()
								    if name.startswith('lbl_') )

		# now prepend the actual keys to the function strings defined in xml
		actionmap = self.keyconf.get_actionname_to_keyname_map()
		for (name, lbl) in labels.items():
			keyname = actionmap.get(name, 'SHIFT') #TODO #HACK hardcoded shift key
			lbl.explanation = _(lbl.text)
			lbl.text = self.HELPSTRING_LAYOUT.format(text=lbl.explanation, key=keyname)
			lbl.capture(Callback(self.show_hotkey_change_popup, name, lbl, keyname))

	def show_hotkey_change_popup(self, action, lbl, keyname):
		def apply_new_key(newkey=None):
			if not newkey:
				newkey = free_keys[listbox.selected]
			else:
				listbox.selected = listbox.items.index(newkey)
			self.keyconf.save_new_key(action, newkey=newkey)
			update_hotkey_info(action, newkey)
			lbl.text = self.HELPSTRING_LAYOUT.format(text=lbl.explanation, key=newkey)
			lbl.capture(Callback(self.show_hotkey_change_popup, action, lbl, newkey))
			lbl.adaptLayout()

		def update_hotkey_info(action, keyname):
			default = self.keyconf.get_default_key_for_action(action)
			popup.message.text = (lbl.explanation +
			#xgettext:python-format
			                      u'\n' + _('Current key: [{key}]').format(key=keyname) +
			#xgettext:python-format
			                      u'\t' + _('Default key: [{key}]').format(key=default))
			popup.message.helptext = _('Click to reset to default key')
			reset_to_default = Callback(apply_new_key, default)
			popup.message.capture(reset_to_default)

		#xgettext:python-format
		headline = _('Change hotkey for {action}').format(action=action)
		message = ''
		if keyname in ('SHIFT', 'ESCAPE'):
			message = _('This key can not be reassigned at the moment.')
			self.show_popup(headline, message, {OkButton.DEFAULT_NAME: True})
			return

		popup = self.build_popup(headline, message, size=2, show_cancel_button=True)
		update_hotkey_info(action, keyname)
		keybox = pychan.widgets.ScrollArea()
		listbox = pychan.widgets.ListBox(is_focusable=False)
		keybox.max_size = listbox.max_size = \
		keybox.min_size = listbox.min_size = \
		keybox.size = listbox.size = (200, 200)
		keybox.position = listbox.position = (90, 110)
		prefer_short = lambda k: (len(k) > 1, len(k) > 3, k)
		is_valid, default_key = self.keyconf.is_valid_and_get_default_key(keyname, action)
		if not is_valid:
			headline = _('Invalid key')
			message = _('The default key for this action has been selected.')
			self.show_popup(headline, message, {OkButton.DEFAULT_NAME: True})
		valid_key = keyname if is_valid else default_key
		free_key_dict = self.keyconf.get_keys_by_name(only_free_keys=True,
		                                              force_include=[valid_key])
		free_keys = sorted(free_key_dict.keys(), key=prefer_short)
		listbox.items = free_keys
		listbox.selected = listbox.items.index(valid_key)
		#TODO backwards replace key names in keyconfig.get_fife_key_names in the list
		# (currently this stores PLUS and PERIOD instead of + and . in the settings)
		keybox.addChild(listbox)
		popup.addChild(keybox)
		if not is_valid:
			apply_new_key()
		listbox.capture(apply_new_key)
		button_cbs = {OkButton.DEFAULT_NAME: True, CancelButton.DEFAULT_NAME: False}
		self.show_dialog(popup, button_cbs, modal=True)

	def editor_load_map(self):
		"""Show a dialog for the user to select a map to edit."""
		old_current = self._switch_current_widget('editor_select_map')
		self.current.show()

		map_files, map_file_display = SavegameManager.get_maps()
		self.current.distributeInitialData({'maplist': map_file_display})

		bind = {
			OkButton.DEFAULT_NAME     : True,
			CancelButton.DEFAULT_NAME : False,
		}
		retval = self.show_dialog(self.current, bind)
		if not retval:
			# Dialog cancelled
			self.current = old_current
			return

		selected_map_index = self.current.collectData('maplist')
		assert selected_map_index != -1, "No map selected"

		self.current = old_current
		self.show_loading_screen()
		horizons.main.edit_map(map_file_display[selected_map_index])
예제 #16
0
class HelpDialog(object):

	def __init__(self, mainmenu):
		self.mainmenu = mainmenu
		self.widgets = mainmenu.widgets

		#i18n this defines how each line in our help looks like. Default: '[C] = Chat'
		self.HELPSTRING_LAYOUT = _('[{key}] = {text}') #xgettext:python-format

		self.keyconf = KeyConfig() # before _build_strings
		self._build_strings()
		self._is_displayed = False

		LanguageChanged.subscribe(lambda msg: self._build_strings())

	def _build_strings(self):
		"""
		Loads the help strings from pychan object widgets (containing no key definitions)
		and adds the keys defined in the keyconfig configuration object in front of them.
		The layout is defined through HELPSTRING_LAYOUT and translated.
		"""
		# retranslate the layout
		self.HELPSTRING_LAYOUT = _('[{key}] = {text}') #xgettext:python-format

		widgets = self.widgets['help']
		labels = widgets.getNamedChildren()
		# filter misc labels that do not describe key functions
		labels = dict( (name[4:], lbl[0]) for (name, lbl) in labels.iteritems()
								    if name.startswith('lbl_') )

		# now prepend the actual keys to the function strings defined in xml
		actionmap = self.keyconf.get_actionname_to_keyname_map()
		for (name, lbl) in labels.items():
			keyname = actionmap.get(name, 'SHIFT') #TODO #HACK hardcoded shift key
			lbl.explanation = _(lbl.text)
			lbl.text = self.HELPSTRING_LAYOUT.format(text=lbl.explanation, key=keyname)
			lbl.capture(Callback(self.show_hotkey_change_popup, name, lbl, keyname))

	def show_hotkey_change_popup(self, action, lbl, keyname):
		def apply_new_key(newkey=None):
			if not newkey:
				newkey = free_keys[listbox.selected]
			else:
				listbox.selected = listbox.items.index(newkey)
			self.keyconf.save_new_key(action, newkey=newkey)
			update_hotkey_info(action, newkey)
			lbl.text = self.HELPSTRING_LAYOUT.format(text=lbl.explanation, key=newkey)
			lbl.capture(Callback(self.show_hotkey_change_popup, action, lbl, newkey))
			lbl.adaptLayout()

		def update_hotkey_info(action, keyname):
			default = self.keyconf.get_default_key_for_action(action)
			popup.message.text = (lbl.explanation +
			#xgettext:python-format
			                      u'\n' + _('Current key: [{key}]').format(key=keyname) +
			#xgettext:python-format
			                      u'\t' + _('Default key: [{key}]').format(key=default))
			popup.message.helptext = _('Click to reset to default key')
			reset_to_default = Callback(apply_new_key, default)
			popup.message.capture(reset_to_default)

		#xgettext:python-format
		headline = _('Change hotkey for {action}').format(action=action)
		message = ''
		if keyname in ('SHIFT', 'ESCAPE'):
			message = _('This key can not be reassigned at the moment.')
			self.mainmenu.show_popup(headline, message, {OkButton.DEFAULT_NAME: True})
			return

		popup = self.mainmenu.build_popup(headline, message, size=2, show_cancel_button=True)
		update_hotkey_info(action, keyname)
		keybox = ScrollArea()
		listbox = ListBox(is_focusable=False, name="available_keys")
		keybox.max_size = listbox.max_size = \
		keybox.min_size = listbox.min_size = \
		keybox.size = listbox.size = (200, 200)
		keybox.position = listbox.position = (90, 110)
		prefer_short = lambda k: (len(k) > 1, len(k) > 3, k)
		is_valid, default_key = self.keyconf.is_valid_and_get_default_key(keyname, action)
		if not is_valid:
			headline = _('Invalid key')
			message = _('The default key for this action has been selected.')
			self.mainmenu.show_popup(headline, message, {OkButton.DEFAULT_NAME: True})
		valid_key = keyname if is_valid else default_key
		free_key_dict = self.keyconf.get_keys_by_name(only_free_keys=True,
		                                              force_include=[valid_key])
		free_keys = sorted(free_key_dict.keys(), key=prefer_short)
		listbox.items = free_keys
		listbox.selected = listbox.items.index(valid_key)
		#TODO backwards replace key names in keyconfig.get_fife_key_names in the list
		# (currently this stores PLUS and PERIOD instead of + and . in the settings)
		keybox.addChild(listbox)
		popup.addChild(keybox)
		if not is_valid:
			apply_new_key()
		listbox.capture(apply_new_key)
		button_cbs = {OkButton.DEFAULT_NAME: True, CancelButton.DEFAULT_NAME: False}
		self.mainmenu.show_dialog(popup, button_cbs, modal=True)

	def toggle(self):
		"""Called on help action.
		Toggles help screen via static variable *help_is_displayed*.
		Can be called both from main menu and in-game interface.
		"""
		help_dlg = self.widgets['help']
		if not self._is_displayed:
			self._is_displayed = True
			# make game pause if there is a game and we're not in the main menu
			if self.mainmenu.session is not None and self.mainmenu.current != self.widgets['ingamemenu']:
				PauseCommand().execute(self.mainmenu.session)
			if self.mainmenu.session is not None:
				self.mainmenu.session.ingame_gui.on_escape() # close dialogs that might be open
			self.mainmenu.show_dialog(help_dlg, {OkButton.DEFAULT_NAME : True})
			self.toggle() # toggle state
		else:
			self._is_displayed = False
			if self.mainmenu.session is not None and self.mainmenu.current != self.widgets['ingamemenu']:
				UnPauseCommand().execute(self.mainmenu.session)
			help_dlg.hide()
예제 #17
0
class Gui(SingleplayerMenu, MultiplayerMenu):
	"""This class handles all the out of game menu, like the main and pause menu, etc.
	"""
	log = logging.getLogger("gui")

	# styles to apply to a widget
	styles = {
	  'mainmenu': 'menu',
	  'requirerestart': 'book',
	  'ingamemenu': 'headline',
	  'help': 'book',
	  'singleplayermenu': 'book',
	  'sp_random': 'book',
	  'sp_scenario': 'book',
	  'sp_free_maps': 'book',
	  'multiplayermenu' : 'book',
	  'multiplayer_creategame' : 'book',
	  'multiplayer_gamelobby' : 'book',
	  'playerdataselection' : 'book',
	  'aidataselection' : 'book',
	  'select_savegame': 'book',
	  'ingame_pause': 'book',
	  'game_settings' : 'book',
#	  'credits': 'book',
	  'editor_select_map': 'book',
	  }

	def __init__(self):
		#i18n this defines how each line in our help looks like. Default: '[C] = Chat'
		self.HELPSTRING_LAYOUT = _('[{key}] = {text}') #xgettext:python-format

		self.mainlistener = MainListener(self)
		self.current = None # currently active window
		self.widgets = LazyWidgetsDict(self.styles) # access widgets with their filenames without '.xml'
		self.keyconf = KeyConfig() # before build_help_strings
		self.build_help_strings()
		self.session = None
		self.current_dialog = None

		self.dialog_executed = False

		self.__pause_displayed = False
		self._background_image = self._get_random_background()
		self.subscribe()

	def subscribe(self):
		"""Subscribe to the necessary messages."""
		GuiAction.subscribe(self._on_gui_action)

	def unsubscribe(self):
		GuiAction.unsubscribe(self._on_gui_action)

# basic menu widgets
	def show_main(self):
		"""Shows the main menu """
		self._switch_current_widget('mainmenu', center=True, show=True, event_map={
			'startSingle'      : self.show_single, # first is the icon in menu
			'start'            : self.show_single, # second is the label in menu
			'startMulti'       : self.show_multi,
			'start_multi'      : self.show_multi,
			'settingsLink'     : self.show_settings,
			'settings'         : self.show_settings,
			'helpLink'         : self.on_help,
			'help'             : self.on_help,
			'editor_link'      : self.editor_load_map,
			'editor'           : self.editor_load_map,
			'closeButton'      : self.show_quit,
			'quit'             : self.show_quit,
			'creditsLink'      : self.show_credits,
			'credits'          : self.show_credits,
			'loadgameButton'   : self.load_game,
			'loadgame'         : self.load_game,
			'changeBackground' : self.get_random_background_by_button,
		})

		self.on_escape = self.show_quit

	def load_game(self):
		saved_game = self.show_select_savegame(mode='load')
		if saved_game is None:
			return False # user aborted dialog

		self.show_loading_screen()
		options = StartGameOptions(saved_game)
		horizons.main.start_singleplayer(options)
		return True

	def toggle_pause(self):
		"""Shows in-game pause menu if the game is currently not paused.
		Else unpauses and hides the menu. Multiple layers of the 'paused' concept exist;
		if two widgets are opened which would both pause the game, we do not want to
		unpause after only one of them is closed. Uses PauseCommand and UnPauseCommand.
		"""
		# TODO: logically, this now belongs to the ingame_gui (it used to be different)
		#       this manifests itself by the need for the __pause_displayed hack below
		#       in the long run, this should be moved, therefore eliminating the hack, and
		#       ensuring correct setup/teardown.
		if self.__pause_displayed:
			self.__pause_displayed = False
			self.hide()
			self.current = None
			UnPauseCommand(suggestion=True).execute(self.session)
			self.on_escape = self.toggle_pause

		else:
			self.__pause_displayed = True
			# reload the menu because caching creates spacing problems
			# see http://trac.unknown-horizons.org/t/ticket/1047
			self.widgets.reload('ingamemenu')
			def do_load():
				did_load = self.load_game()
				if did_load:
					self.__pause_displayed = False
			def do_quit():
				did_quit = self.quit_session()
				if did_quit:
					self.__pause_displayed = False
			events = { # needed twice, save only once here
				'e_load' : do_load,
				'e_save' : self.save_game,
				'e_sett' : self.show_settings,
				'e_help' : self.on_help,
				'e_start': self.toggle_pause,
				'e_quit' : do_quit,
			}
			self._switch_current_widget('ingamemenu', center=True, show=False, event_map={
				  # icons
				'loadgameButton' : events['e_load'],
				'savegameButton' : events['e_save'],
				'settingsLink'   : events['e_sett'],
				'helpLink'       : events['e_help'],
				'startGame'      : events['e_start'],
				'closeButton'    : events['e_quit'],
				# labels
				'loadgame' : events['e_load'],
				'savegame' : events['e_save'],
				'settings' : events['e_sett'],
				'help'     : events['e_help'],
				'start'    : events['e_start'],
				'quit'     : events['e_quit'],
			})

			self.show_modal_background()
			self.current.show()

			PauseCommand(suggestion=True).execute(self.session)
			self.on_escape = self.toggle_pause

# what happens on button clicks

	def save_game(self):
		"""Wrapper for saving for separating gui messages from save logic
		"""
		success = self.session.save()
		if not success:
			# There was a problem during the 'save game' procedure.
			self.show_popup(_('Error'), _('Failed to save.'))

	def show_settings(self):
		"""Displays settings gui derived from the FIFE settings module."""
		horizons.globals.fife.show_settings()

	_help_is_displayed = False
	def on_help(self):
		"""Called on help action.
		Toggles help screen via static variable *help_is_displayed*.
		Can be called both from main menu and in-game interface.
		"""
		help_dlg = self.widgets['help']
		if not self._help_is_displayed:
			self._help_is_displayed = True
			# make game pause if there is a game and we're not in the main menu
			if self.session is not None and self.current != self.widgets['ingamemenu']:
				PauseCommand().execute(self.session)
			if self.session is not None:
				self.session.ingame_gui.on_escape() # close dialogs that might be open
			self.show_dialog(help_dlg, {OkButton.DEFAULT_NAME : True})
			self.on_help() # toggle state
		else:
			self._help_is_displayed = False
			if self.session is not None and self.current != self.widgets['ingamemenu']:
				UnPauseCommand().execute(self.session)
			help_dlg.hide()

	def show_quit(self):
		"""Shows the quit dialog. Closes the game unless the dialog is cancelled."""
		message = _("Are you sure you want to quit Unknown Horizons?")
		if self.show_popup(_("Quit Game"), message, show_cancel_button=True):
			horizons.main.quit()

	def quit_session(self, force=False):
		"""Quits the current session. Usually returns to main menu afterwards.
		@param force: whether to ask for confirmation"""
		message = _("Are you sure you want to abort the running session?")

		if force or self.show_popup(_("Quit Session"), message, show_cancel_button=True):
			if self.current is not None:
				# this can be None if not called from gui (e.g. scenario finished)
				self.hide()
				self.current = None
			if self.session is not None:
				self.session.end()
				self.session = None

			self.show_main()
			return True
		else:
			return False

	def show_credits(self, number=0):
		"""Shows the credits dialog. """
		if self.current_dialog is not None:
			self.current_dialog.hide()

		credits_page = self.widgets['credits{number}'.format(number=number)]
		for box in credits_page.findChildren(name='box'):
			box.margins = (30, 0) # to get some indentation
			if number in [0, 2]: # #TODO fix these hardcoded page references
				box.padding = 1
				box.parent.padding = 3 # further decrease if more entries
		labels = [credits_page.findChild(name=section+"_lbl")
		          for section in ('team', 'patchers', 'translators',
		                          'packagers', 'special_thanks')]
		for i in xrange(5): # add callbacks to each pickbelt
			labels[i].capture(Callback(self.show_credits, i), event_name="mouseClicked")

		self.show_dialog(credits_page, {OkButton.DEFAULT_NAME : True})

	def show_select_savegame(self, mode, sanity_checker=None, sanity_criteria=None):
		"""Shows menu to select a savegame.
		@param mode: Valid options are 'save', 'load', 'mp_load', 'mp_save'
		@param sanity_checker: only allow manually entered names that pass this test
		@param sanity_criteria: explain which names are allowed to the user
		@return: Path to savegamefile or None"""
		assert mode in ('save', 'load', 'mp_load', 'mp_save')
		map_files, map_file_display = None, None
		args = mode, sanity_checker, sanity_criteria # for reshow
		mp = mode.startswith('mp_')
		if mp:
			mode = mode[3:]
		# below this line, mp_load == load, mp_save == save
		if mode == 'load':
			if not mp:
				map_files, map_file_display = SavegameManager.get_saves()
			else:
				map_files, map_file_display = SavegameManager.get_multiplayersaves()
			if not map_files:
				self.show_popup(_("No saved games"), _("There are no saved games to load."))
				return
		else: # don't show autosave and quicksave on save
			if not mp:
				map_files, map_file_display = SavegameManager.get_regular_saves()
			else:
				map_files, map_file_display = SavegameManager.get_multiplayersaves()

		# Prepare widget
		old_current = self._switch_current_widget('select_savegame')
		if mode == 'save':
			helptext = _('Save game')
		elif mode == 'load':
			helptext = _('Load game')
		# else: not a valid mode, so we can as well crash on the following
		self.current.findChild(name='headline').text = helptext
		self.current.findChild(name=OkButton.DEFAULT_NAME).helptext = helptext

		name_box = self.current.findChild(name="gamename_box")
		password_box = self.current.findChild(name="gamepassword_box")
		if mp and mode == 'load': # have gamename
			name_box.parent.showChild(name_box)
			password_box.parent.showChild(password_box)
			gamename_textfield = self.current.findChild(name="gamename")
			gamepassword_textfield = self.current.findChild(name="gamepassword")
			gamepassword_textfield.text = u""
			def clear_gamedetails_textfields():
				gamename_textfield.text = u""
				gamepassword_textfield.text = u""
			gamename_textfield.capture(clear_gamedetails_textfields, 'mouseReleased', 'default')
		else:
			if name_box not in name_box.parent.hidden_children:
				name_box.parent.hideChild(name_box)
			if password_box not in name_box.parent.hidden_children:
				password_box.parent.hideChild(password_box)

		self.current.show()

		if not hasattr(self, 'filename_hbox'):
			self.filename_hbox = self.current.findChild(name='enter_filename')
			self.filename_hbox_parent = self.filename_hbox.parent

		if mode == 'save': # only show enter_filename on save
			self.filename_hbox_parent.showChild(self.filename_hbox)
		elif self.filename_hbox not in self.filename_hbox_parent.hidden_children:
			self.filename_hbox_parent.hideChild(self.filename_hbox)

		def tmp_selected_changed():
			"""Fills in the name of the savegame in the textbox when selected in the list"""
			if mode != 'save': # set textbox only if we are in save mode
				return
			if self.current.collectData('savegamelist') == -1: # set blank if nothing is selected
				self.current.findChild(name="savegamefile").text = u""
			else:
				savegamefile = map_file_display[self.current.collectData('savegamelist')]
				self.current.distributeData({'savegamefile': savegamefile})

		self.current.distributeInitialData({'savegamelist': map_file_display})
		# Select first item when loading, nothing when saving
		selected_item = -1 if mode == 'save' else 0
		self.current.distributeData({'savegamelist': selected_item})
		cb_details = Gui._create_show_savegame_details(self.current, map_files, 'savegamelist')
		cb = Callback.ChainedCallbacks(cb_details, tmp_selected_changed)
		cb() # Refresh data on start
		self.current.mapEvents({'savegamelist/action': cb})
		self.current.findChild(name="savegamelist").capture(cb, event_name="keyPressed")

		bind = {
			OkButton.DEFAULT_NAME     : True,
			CancelButton.DEFAULT_NAME : False,
			DeleteButton.DEFAULT_NAME : 'delete'
		}

		if mode == 'save':
			bind['savegamefile'] = True

		retval = self.show_dialog(self.current, bind)
		if not retval: # cancelled
			self.current = old_current
			return

		if retval == 'delete':
			# delete button was pressed. Apply delete and reshow dialog, delegating the return value
			delete_retval = self._delete_savegame(map_files)
			if delete_retval:
				self.current.distributeData({'savegamelist' : -1})
				cb()
			self.current = old_current
			return self.show_select_savegame(*args)

		selected_savegame = None
		if mode == 'save': # return from textfield
			selected_savegame = self.current.collectData('savegamefile')
			if selected_savegame == "":
				self.show_error_popup(windowtitle=_("No filename given"),
				                      description=_("Please enter a valid filename."))
				self.current = old_current
				return self.show_select_savegame(*args) # reshow dialog
			elif selected_savegame in map_file_display: # savegamename already exists
				#xgettext:python-format
				message = _("A savegame with the name '{name}' already exists.").format(
				             name=selected_savegame) + u"\n" + _('Overwrite it?')
				# keep the pop-up non-modal because otherwise it is double-modal (#1876)
				if not self.show_popup(_("Confirmation for overwriting"), message, show_cancel_button=True, modal=False):
					self.current = old_current
					return self.show_select_savegame(*args) # reshow dialog
			elif sanity_checker and sanity_criteria:
				if not sanity_checker(selected_savegame):
					self.show_error_popup(windowtitle=_("Invalid filename given"),
					                      description=sanity_criteria)
					self.current = old_current
					return self.show_select_savegame(*args) # reshow dialog
		else: # return selected item from list
			selected_savegame = self.current.collectData('savegamelist')
			assert selected_savegame != -1, "No savegame selected in savegamelist"
			selected_savegame = map_files[selected_savegame]

		if mp and mode == 'load': # also name
			gamename_textfield = self.current.findChild(name="gamename")
			ret = selected_savegame, self.current.collectData('gamename'), self.current.collectData('gamepassword')
		else:
			ret = selected_savegame
		self.current = old_current # reuse old widget
		return ret

# display

	def on_escape(self):
		pass

	def show(self):
		self.log.debug("Gui: showing current: %s", self.current)
		if self.current is not None:
			self.current.show()

	def hide(self):
		self.log.debug("Gui: hiding current: %s", self.current)
		if self.current is not None:
			self.current.hide()
			self.hide_modal_background()

	def is_visible(self):
		return self.current is not None and self.current.isVisible()

	def show_dialog(self, dlg, bind, event_map=None, modal=False):
		"""Shows any pychan dialog.
		@param dlg: dialog that is to be shown
		@param bind: events that make the dialog return + return values{ 'ok': callback, 'cancel': callback }
		@param event_map: dictionary with callbacks for buttons. See pychan docu: pychan.widget.mapEvents()
		@param modal: Whether to block user interaction while displaying the dialog
		"""
		self.current_dialog = dlg
		if event_map is not None:
			dlg.mapEvents(event_map)
		if modal:
			self.show_modal_background()

		# handle escape and enter keypresses
		def _on_keypress(event, dlg=dlg): # rebind to make sure this dlg is used
			from horizons.engine import pychan_util
			if event.getKey().getValue() == fife.Key.ESCAPE: # convention says use cancel action
				btn = dlg.findChild(name=CancelButton.DEFAULT_NAME)
				callback = pychan_util.get_button_event(btn) if btn else None
				if callback:
					pychan.tools.applyOnlySuitable(callback, event=event, widget=btn)
				else:
					# escape should hide the dialog default
					horizons.globals.fife.pychanmanager.breakFromMainLoop(returnValue=False)
					dlg.hide()
			elif event.getKey().getValue() == fife.Key.ENTER: # convention says use ok action
				btn = dlg.findChild(name=OkButton.DEFAULT_NAME)
				callback = pychan_util.get_button_event(btn) if btn else None
				if callback:
					pychan.tools.applyOnlySuitable(callback, event=event, widget=btn)
				# can't guess a default action here

		dlg.capture(_on_keypress, event_name="keyPressed")

		# show that a dialog is being executed, this can sometimes require changes in program logic elsewhere
		self.dialog_executed = True
		ret = dlg.execute(bind)
		self.dialog_executed = False
		if modal:
			self.hide_modal_background()
		return ret

	def show_popup(self, windowtitle, message, show_cancel_button=False, size=0, modal=True):
		"""Displays a popup with the specified text
		@param windowtitle: the title of the popup
		@param message: the text displayed in the popup
		@param show_cancel_button: boolean, show cancel button or not
		@param size: 0, 1 or 2. Larger means bigger.
		@param modal: Whether to block user interaction while displaying the popup
		@return: True on ok, False on cancel (if no cancel button, always True)
		"""
		popup = self.build_popup(windowtitle, message, show_cancel_button, size=size)
		# ok should be triggered on enter, therefore we need to focus the button
		# pychan will only allow it after the widgets is shown
		def focus_ok_button():
			popup.findChild(name=OkButton.DEFAULT_NAME).requestFocus()
		ExtScheduler().add_new_object(focus_ok_button, self, run_in=0)
		if show_cancel_button:
			return self.show_dialog(popup, {OkButton.DEFAULT_NAME : True,
			                                CancelButton.DEFAULT_NAME : False},
			                        modal=modal)
		else:
			return self.show_dialog(popup, {OkButton.DEFAULT_NAME : True},
			                        modal=modal)

	def show_error_popup(self, windowtitle, description, advice=None, details=None, _first=True):
		"""Displays a popup containing an error message.
		@param windowtitle: title of popup, will be auto-prefixed with "Error: "
		@param description: string to tell the user what happened
		@param advice: how the user might be able to fix the problem
		@param details: technical details, relevant for debugging but not for the user
		@param _first: Don't touch this.

		Guide for writing good error messages:
		http://www.useit.com/alertbox/20010624.html
		"""
		msg = u""
		msg += description + u"\n"
		if advice:
			msg += advice + u"\n"
		if details:
			msg += _("Details: {error_details}").format(error_details=details)
		try:
			self.show_popup( _("Error: {error_message}").format(error_message=windowtitle),
			                 msg, show_cancel_button=False)
		except SystemExit: # user really wants us to die
			raise
		except:
			# could be another game error, try to be persistent in showing the error message
			# else the game would be gone without the user being able to read the message.
			if _first:
				traceback.print_exc()
				print 'Exception while showing error, retrying once more'
				return self.show_error_popup(windowtitle, description, advice, details, _first=False)
			else:
				raise # it persists, we have to die.

	def build_popup(self, windowtitle, message, show_cancel_button=False, size=0):
		""" Creates a pychan popup widget with the specified properties.
		@param windowtitle: the title of the popup
		@param message: the text displayed in the popup
		@param show_cancel_button: boolean, include cancel button or not
		@param size: 0, 1 or 2
		@return: Container(name='popup_window') with buttons 'okButton' and optionally 'cancelButton'
		"""
		if size == 0:
			wdg_name = "popup_230"
		elif size == 1:
			wdg_name = "popup_290"
		elif size == 2:
			wdg_name = "popup_350"
		else:
			assert False, "size should be 0 <= size <= 2, but is "+str(size)

		# NOTE: reusing popup dialogs can sometimes lead to exit(0) being called.
		#       it is yet unknown why this happens, so let's be safe for now and reload the widgets.
		self.widgets.reload(wdg_name)
		popup = self.widgets[wdg_name]

		if not show_cancel_button:
			cancel_button = popup.findChild(name=CancelButton.DEFAULT_NAME)
			cancel_button.parent.removeChild(cancel_button)

		popup.headline = popup.findChild(name='headline')
		popup.headline.text = _(windowtitle)
		popup.message = popup.findChild(name='popup_message')
		popup.message.text = _(message)
		popup.adaptLayout() # recalculate widths
		return popup

	def show_modal_background(self):
		""" Loads transparent background that de facto prohibits
		access to other gui elements by eating all input events.
		Used for modal popups and our in-game menu.
		"""
		height = horizons.globals.fife.engine_settings.getScreenHeight()
		width = horizons.globals.fife.engine_settings.getScreenWidth()
		image = horizons.globals.fife.imagemanager.loadBlank(width, height)
		image = fife.GuiImage(image)
		self.additional_widget = pychan.Icon(image=image)
		self.additional_widget.position = (0, 0)
		self.additional_widget.show()

	def hide_modal_background(self):
		try:
			self.additional_widget.hide()
			del self.additional_widget
		except AttributeError:
			pass # only used for some widgets, e.g. pause

	def show_loading_screen(self):
		self._switch_current_widget('loadingscreen', center=True, show=True)
		# Add 'Quote of the Load' to loading screen:
		qotl_type_label = self.current.findChild(name='qotl_type_label')
		qotl_label = self.current.findChild(name='qotl_label')
		quote_type = int(horizons.globals.fife.get_uh_setting("QuotesType"))
		if quote_type == 2:
			quote_type = random.randint(0, 1) # choose a random type

		if quote_type == 0:
			name = GAMEPLAY_TIPS["name"]
			items = GAMEPLAY_TIPS["items"]
		elif quote_type == 1:
			name = FUN_QUOTES["name"]
			items = FUN_QUOTES["items"]

		qotl_type_label.text = unicode(name)
		qotl_label.text = unicode(random.choice(items)) # choose a random quote / gameplay tip

# helper

	def _switch_current_widget(self, new_widget, center=False, event_map=None, show=False, hide_old=False):
		"""Switches self.current to a new widget.
		@param new_widget: str, widget name
		@param center: bool, whether to center the new widget
		@param event_map: pychan event map to apply to new widget
		@param show: bool, if True old window gets hidden and new one shown
		@param hide_old: bool, if True old window gets hidden. Implied by show
		@return: instance of old widget"""
		old = self.current
		if (show or hide_old) and old is not None:
			self.log.debug("Gui: hiding %s", old)
			self.hide()
		self.log.debug("Gui: setting current to %s", new_widget)
		self.current = self.widgets[new_widget]
		bg = self.current.findChild(name='background')
		if bg:
			# Set background image
			bg.image = self._background_image
		if center:
			self.current.position_technique = "automatic" # == "center:center"
		if event_map:
			self.current.mapEvents(event_map)
		if show:
			self.current.show()

		return old

	@staticmethod
	def _create_show_savegame_details(gui, map_files, savegamelist):
		"""Creates a function that displays details of a savegame in gui"""

		def tmp_show_details():
			"""Fetches details of selected savegame and displays it"""
			gui.findChild(name="screenshot").image = None
			map_file = None
			map_file_index = gui.collectData(savegamelist)
			if map_file_index == -1:
				gui.findChild(name="savegame_details").hide()
				return
			else:
				gui.findChild(name="savegame_details").show()
			try:
				map_file = map_files[map_file_index]
			except IndexError:
				# this was a click in the savegame list, but not on an element
				# it happens when the savegame list is empty
				return
			savegame_info = SavegameManager.get_metadata(map_file)

			if savegame_info.get('screenshot'):
				# try to find a writable location, that is accessible via relative paths
				# (required by fife)
				fd, filename = tempfile.mkstemp()
				try:
					path_rel = os.path.relpath(filename)
				except ValueError: # the relative path sometimes doesn't exist on win
					os.close(fd)
					os.unlink(filename)
					# try again in the current dir, it's often writable
					fd, filename = tempfile.mkstemp(dir=os.curdir)
					try:
						path_rel = os.path.relpath(filename)
					except ValueError:
						fd, filename = None, None

				if fd:
					with os.fdopen(fd, "w") as f:
						f.write(savegame_info['screenshot'])
					# fife only supports relative paths
					gui.findChild(name="screenshot").image = path_rel
					os.unlink(filename)

			# savegamedetails
			details_label = gui.findChild(name="savegamedetails_lbl")
			details_label.text = u""
			if savegame_info['timestamp'] == -1:
				details_label.text += _("Unknown savedate")
			else:
				savetime = time.strftime("%c", time.localtime(savegame_info['timestamp']))
				#xgettext:python-format
				details_label.text += _("Saved at {time}").format(time=savetime.decode('utf-8'))
			details_label.text += u'\n'
			counter = savegame_info['savecounter']
			# N_ takes care of plural forms for different languages
			#xgettext:python-format
			details_label.text += N_("Saved {amount} time",
			                         "Saved {amount} times",
			                         counter).format(amount=counter)
			details_label.text += u'\n'
			details_label.stylize('book')

			from horizons.constants import VERSION
			try:
				#xgettext:python-format
				details_label.text += _("Savegame version {version}").format(
				                         version=savegame_info['savegamerev'])
				if savegame_info['savegamerev'] != VERSION.SAVEGAMEREVISION:
					if not SavegameUpgrader.can_upgrade(savegame_info['savegamerev']):
						details_label.text += u" " + _("(probably incompatible)")
			except KeyError:
				# this should only happen for very old savegames, so having this unfriendly
				# error is ok (savegame is quite certainly fully unusable).
				details_label.text += u" " + _("Incompatible version")

			gui.adaptLayout()
		return tmp_show_details

	def _delete_savegame(self, map_files):
		"""Deletes the selected savegame if the user confirms
		self.current has to contain the widget "savegamelist"
		@param map_files: list of files that corresponds to the entries of 'savegamelist'
		@return: True if something was deleted, else False
		"""
		selected_item = self.current.collectData("savegamelist")
		if selected_item == -1 or selected_item >= len(map_files):
			self.show_popup(_("No file selected"), _("You need to select a savegame to delete."))
			return False
		selected_file = map_files[selected_item]
		#xgettext:python-format
		message = _("Do you really want to delete the savegame '{name}'?").format(
		             name=SavegameManager.get_savegamename_from_filename(selected_file))
		if self.show_popup(_("Confirm deletion"), message, show_cancel_button=True):
			try:
				os.unlink(selected_file)
				return True
			except:
				self.show_popup(_("Error!"), _("Failed to delete savefile!"))
				return False
		else: # player cancelled deletion
			return False

	def get_random_background_by_button(self):
		"""Randomly select a background image to use. This function is triggered by
		change background button from main menu."""
		#we need to redraw screen to apply changes.
		self.hide()
		self._background_image = self._get_random_background()
		self.show_main()

	def _get_random_background(self):
		"""Randomly select a background image to use through out the game menu."""
		available_images = glob.glob('content/gui/images/background/mainmenu/bg_*.png')
		#get latest background
		latest_background = horizons.globals.fife.get_uh_setting("LatestBackground")
		#if there is a latest background then remove it from available list
		if latest_background is not None:
			available_images.remove(latest_background)
		background_choice = random.choice(available_images)
		#save current background choice
		horizons.globals.fife.set_uh_setting("LatestBackground", background_choice)
		horizons.globals.fife.save_settings()
		return background_choice

	def _on_gui_action(self, msg):
		AmbientSoundComponent.play_special('click')

	def build_help_strings(self):
		"""
		Loads the help strings from pychan object widgets (containing no key definitions)
		and adds the keys defined in the keyconfig configuration object in front of them.
		The layout is defined through HELPSTRING_LAYOUT and translated.
		"""
		widgets = self.widgets['help']
		labels = widgets.getNamedChildren()
		# filter misc labels that do not describe key functions
		labels = dict( (name[4:], lbl[0]) for (name, lbl) in labels.iteritems()
								    if name.startswith('lbl_') )

		# now prepend the actual keys to the function strings defined in xml
		actionmap = self.keyconf.get_actionname_to_keyname_map()
		for (name, lbl) in labels.items():
			keyname = actionmap.get(name, 'SHIFT') #TODO #HACK hardcoded shift key
			lbl.explanation = _(lbl.text)
			lbl.text = self.HELPSTRING_LAYOUT.format(text=lbl.explanation, key=keyname)
			lbl.capture(Callback(self.show_hotkey_change_popup, name, lbl, keyname))

	def show_hotkey_change_popup(self, action, lbl, keyname):
		def apply_new_key(newkey=None):
			if not newkey:
				newkey = free_keys[listbox.selected]
			else:
				listbox.selected = listbox.items.index(newkey)
			self.keyconf.save_new_key(action, newkey=newkey)
			update_hotkey_info(action, newkey)
			lbl.text = self.HELPSTRING_LAYOUT.format(text=lbl.explanation, key=newkey)
			lbl.capture(Callback(self.show_hotkey_change_popup, action, lbl, newkey))
			lbl.adaptLayout()

		def update_hotkey_info(action, keyname):
			default = self.keyconf.get_default_key_for_action(action)
			popup.message.text = (lbl.explanation +
			#xgettext:python-format
			                      u'\n' + _('Current key: [{key}]').format(key=keyname) +
			#xgettext:python-format
			                      u'\t' + _('Default key: [{key}]').format(key=default))
			popup.message.helptext = _('Click to reset to default key')
			reset_to_default = Callback(apply_new_key, default)
			popup.message.capture(reset_to_default)

		#xgettext:python-format
		headline = _('Change hotkey for {action}').format(action=action)
		message = ''
		if keyname in ('SHIFT', 'ESCAPE'):
			message = _('This key can not be reassigned at the moment.')
			self.show_popup(headline, message, {OkButton.DEFAULT_NAME: True})
			return

		popup = self.build_popup(headline, message, size=2, show_cancel_button=True)
		update_hotkey_info(action, keyname)
		keybox = pychan.widgets.ScrollArea()
		listbox = pychan.widgets.ListBox(is_focusable=False)
		keybox.max_size = listbox.max_size = \
		keybox.min_size = listbox.min_size = \
		keybox.size = listbox.size = (200, 200)
		keybox.position = listbox.position = (90, 110)
		prefer_short = lambda k: (len(k) > 1, len(k) > 3, k)
		is_valid, default_key = self.keyconf.is_valid_and_get_default_key(keyname, action)
		if not is_valid:
			headline = _('Invalid key')
			message = _('The default key for this action has been selected.')
			self.show_popup(headline, message, {OkButton.DEFAULT_NAME: True})
		valid_key = keyname if is_valid else default_key
		free_key_dict = self.keyconf.get_keys_by_name(only_free_keys=True,
		                                              force_include=[valid_key])
		free_keys = sorted(free_key_dict.keys(), key=prefer_short)
		listbox.items = free_keys
		listbox.selected = listbox.items.index(valid_key)
		#TODO backwards replace key names in keyconfig.get_fife_key_names in the list
		# (currently this stores PLUS and PERIOD instead of + and . in the settings)
		keybox.addChild(listbox)
		popup.addChild(keybox)
		if not is_valid:
			apply_new_key()
		listbox.capture(apply_new_key)
		button_cbs = {OkButton.DEFAULT_NAME: True, CancelButton.DEFAULT_NAME: False}
		self.show_dialog(popup, button_cbs, modal=True)

	def editor_load_map(self):
		"""Show a dialog for the user to select a map to edit."""
		old_current = self._switch_current_widget('editor_select_map')
		self.current.show()

		map_files, map_file_display = SavegameManager.get_maps()
		self.current.distributeInitialData({'maplist': map_file_display})

		bind = {
			OkButton.DEFAULT_NAME     : True,
			CancelButton.DEFAULT_NAME : False,
		}
		retval = self.show_dialog(self.current, bind)
		if not retval:
			# Dialog cancelled
			self.current = old_current
			return

		selected_map_index = self.current.collectData('maplist')
		if selected_map_index == -1:
			# No map selected yet => select first available one
			self.current.distributeData({'maplist': 0})

		self.current = old_current
		self.show_loading_screen()
		horizons.main.edit_map(map_file_display[selected_map_index])
class HotkeyConfiguration(object):

	def __init__(self):
		super(HotkeyConfiguration, self).__init__()

		self.widget = load_uh_widget('hotkeys.xml')
		self.buttons = []
		self.secondary_buttons = []

		self.keyconf = KeyConfig()
		self.actions = self.keyconf.get_bindable_actions_by_name()
		self.keys = self.keyconf.get_keys_by_value()

		self.HELPSTRING_LAYOUT = None
		self._is_displayed = False
		self._build_interface()

		# When detecing is True, the interface detects keypresses and binds them to actions
		self.detecting = False
		self.current_button = None
		self.current_index = None
		self.last_combination = []
		# Stores whether the last button pressed was for a primary or secondary binding (1 or 2)
		self.last_column = 1

		# There are some keys which are not detected by the event widget/keyPressed
		# In that case, the key presses are detected by the listener, which calls _detect_keypress
		self.listener = HotkeysListener(self._detect_keypress)

		self.widget.mapEvents({self.widget.name + '/keyPressed' : self._detect_keypress})
		self.widget.findChild(name=OkButton.DEFAULT_NAME).capture(self.save_settings)
		self.widget.mapEvents({OkButton.DEFAULT_NAME : self.save_settings})
		self.widget.findChild(name="reset_to_default").capture(self.reset_to_default)

	def _build_interface(self):
		button_container = self.widget.findChild(name='button_container')
		sec_button_container = self.widget.findChild(name='sec_button_container')
		for i, action in enumerate(self.actions):
			button = self._create_button(action, i)
			sec_button = self._create_button(action, i)
			button.mapEvents({button.name + '/mouseClicked' : Callback(self._detect_click_on_button, button, 1)})
			sec_button.mapEvents({button.name + '/mouseClicked' : Callback(self._detect_click_on_button, sec_button, 2)})
			button_container.addChild(button)
			sec_button_container.addChild(sec_button)
			self.buttons.append(button)
			self.secondary_buttons.append(sec_button)
			self.update_buttons_text()

	def _create_button(self, action, index):
		"""Important! The button name is set to index so that when a button is pressed, we know its index"""
		button = Button()
		button.name = str(index)
		button.max_size = button.min_size = (100, 18)
		return button

	def _detect_click_on_button(self, button, column):
		"""Starts the listener and remembers the position and index of the pressed button"""
		self.detecting = True
		self.current_button = button
		self.current_index = int(button.name)
		self.current_column = column
		self.listener.activate()
		self.update_buttons_text()
		button.text = _("Press desired key")

	def _detect_keypress(self, event):
		if not self.detecting:
			return
		key = event.getKey()
		# if the key is not supported, act as if it was not detected
		if not self.key_name(key):
			return
		self.last_combination.append(key)
		self.detecting = False
		self.listener.deactivate()
		self.apply_change()

	def update_buttons_text(self):
		for i, button in enumerate(self.buttons):
			action = self.actions[i]
			bindings = self.keyconf.get_current_keys(action)
			for j in range(len(bindings)):
				if bindings[j] == 'UNASSIGNED':
					bindings[j] = '-'
			secondary_button = self.secondary_buttons[i]
			button.text = unicode(bindings[0])
			if len(bindings) > 1:
				secondary_button.text = unicode(bindings[1])
			else:
				secondary_button.text = u"-"

	def apply_change(self):
		"""Binds the last keypress to the corresponding action and resets the interface to the state where it is listening for clicks on buttons"""
		key = self.last_combination[0]
		key_name = self.key_name(key)
		action = self.actions[self.current_index]
		column = self.current_column

		# Escape is used to unassign bindings
		if key_name == 'ESCAPE':
			key_name = 'UNASSIGNED'

		# If *key* is already set, replace the entry for *key* with UNASSIGNED for the last action.
		# This is done to avoid binding one key for two actions.
		elif self.key_is_set(key):
			oldaction = self.get_action_name(key)

			message = _("{key} is already set to {action}.").format(key=key_name, action=oldaction)
			message += u" " + _("Would you like to overwrite it?")
			confirmed = horizons.main._modules.gui.show_popup(_("Confirmation for overwriting"), message, show_cancel_button=True)
			if confirmed:
				horizons.globals.fife.replace_key_for_action(oldaction, key_name, "UNASSIGNED")
			else:
				self.update_buttons_text()
				self.last_combination = []
				return

		bindings = self.keyconf.get_current_keys(action)
		if column == 1:
			bindings[0] = key_name
		elif column == 2:
			if len(bindings) < 2:
				bindings.append(key_name)
			else:
				bindings[1] = key_name

		horizons.globals.fife.set_key_for_action(action, bindings)

		self.update_buttons_text()
		self.last_combination = []

	def key_name(self, key):
		value = key.getValue()
		return self.keys.get(value)

	def key_is_set(self, key):
		key_name = self.key_name(key)
		custom_key_actions = horizons.globals.fife.get_hotkey_settings()
		for k in custom_key_actions.itervalues():
			if key_name in k:
				return True
		return False

	def get_current_bindings(self):
		""" Returns a dict mapping action -> list of keys """
		bindings = {}
		for action in self.actions:
			keys = self.keyconf.get_current_keys(action)
			bindings[action] = keys
		return bindings

	def get_action_name(self, key):
		key_name = self.key_name(key)
		custom_key_actions = horizons.globals.fife.get_hotkey_settings()
		for action in custom_key_actions:
			k = custom_key_actions[action]
			if key_name in k:
				return action
		print "Action name not found. Key name must be wrong. This is not supposed to ever happen"

	def reset_to_default(self):
		"""Resets all bindings to default"""
		for action in self.actions:
			default_key = horizons.globals.fife.get_keys_for_action(action, default=True)
			horizons.globals.fife.set_key_for_action(action, default_key)

		self.update_buttons_text()

	def save_settings(self):
		"""Saves the settings and reloads the keyConfiguration so that the settings take effect without a restart"""
		horizons.globals.fife.save_settings()
		self.keyconf.loadKeyConfiguration()

	def show(self):
		self.widget.show()

	def hide(self):
		self.widget.hide()
예제 #19
0
class HelpDialog(object):
    def __init__(self, mainmenu):
        self.mainmenu = mainmenu
        self.widgets = mainmenu.widgets

        #i18n this defines how each line in our help looks like. Default: '[C] = Chat'
        self.HELPSTRING_LAYOUT = _('[{key}] = {text}')  #xgettext:python-format

        self.keyconf = KeyConfig()  # before _build_strings
        self._build_strings()
        self._is_displayed = False

        LanguageChanged.subscribe(lambda msg: self._build_strings())

    def _build_strings(self):
        """
		Loads the help strings from pychan object widgets (containing no key definitions)
		and adds the keys defined in the keyconfig configuration object in front of them.
		The layout is defined through HELPSTRING_LAYOUT and translated.
		"""
        # retranslate the layout
        self.HELPSTRING_LAYOUT = _('[{key}] = {text}')  #xgettext:python-format

        widgets = self.widgets['help']
        labels = widgets.getNamedChildren()
        # filter misc labels that do not describe key functions
        labels = dict((name[4:], lbl[0]) for (name, lbl) in labels.iteritems()
                      if name.startswith('lbl_'))

        # now prepend the actual keys to the function strings defined in xml
        actionmap = self.keyconf.get_actionname_to_keyname_map()
        for (name, lbl) in labels.items():
            keyname = actionmap.get(name,
                                    'SHIFT')  #TODO #HACK hardcoded shift key
            lbl.explanation = _(lbl.text)
            lbl.text = self.HELPSTRING_LAYOUT.format(text=lbl.explanation,
                                                     key=keyname)
            lbl.capture(
                Callback(self.show_hotkey_change_popup, name, lbl, keyname))

    def show_hotkey_change_popup(self, action, lbl, keyname):
        def apply_new_key(newkey=None):
            if not newkey:
                newkey = free_keys[listbox.selected]
            else:
                listbox.selected = listbox.items.index(newkey)
            self.keyconf.save_new_key(action, newkey=newkey)
            update_hotkey_info(action, newkey)
            lbl.text = self.HELPSTRING_LAYOUT.format(text=lbl.explanation,
                                                     key=newkey)
            lbl.capture(
                Callback(self.show_hotkey_change_popup, action, lbl, newkey))
            lbl.adaptLayout()

        def update_hotkey_info(action, keyname):
            default = self.keyconf.get_default_key_for_action(action)
            popup.message.text = (
                lbl.explanation +
                #xgettext:python-format
                u'\n' + _('Current key: [{key}]').format(key=keyname) +
                #xgettext:python-format
                u'\t' + _('Default key: [{key}]').format(key=default))
            popup.message.helptext = _('Click to reset to default key')
            reset_to_default = Callback(apply_new_key, default)
            popup.message.capture(reset_to_default)

        #xgettext:python-format
        headline = _('Change hotkey for {action}').format(action=action)
        message = ''
        if keyname in ('SHIFT', 'ESCAPE'):
            message = _('This key can not be reassigned at the moment.')
            self.mainmenu.show_popup(headline, message,
                                     {OkButton.DEFAULT_NAME: True})
            return

        popup = self.mainmenu.build_popup(headline,
                                          message,
                                          size=2,
                                          show_cancel_button=True)
        update_hotkey_info(action, keyname)
        keybox = ScrollArea()
        listbox = ListBox(is_focusable=False, name="available_keys")
        keybox.max_size = listbox.max_size = \
        keybox.min_size = listbox.min_size = \
        keybox.size = listbox.size = (200, 200)
        keybox.position = listbox.position = (90, 110)
        prefer_short = lambda k: (len(k) > 1, len(k) > 3, k)
        is_valid, default_key = self.keyconf.is_valid_and_get_default_key(
            keyname, action)
        if not is_valid:
            headline = _('Invalid key')
            message = _('The default key for this action has been selected.')
            self.mainmenu.show_popup(headline, message,
                                     {OkButton.DEFAULT_NAME: True})
        valid_key = keyname if is_valid else default_key
        free_key_dict = self.keyconf.get_keys_by_name(
            only_free_keys=True, force_include=[valid_key])
        free_keys = sorted(free_key_dict.keys(), key=prefer_short)
        listbox.items = free_keys
        listbox.selected = listbox.items.index(valid_key)
        #TODO backwards replace key names in keyconfig.get_fife_key_names in the list
        # (currently this stores PLUS and PERIOD instead of + and . in the settings)
        keybox.addChild(listbox)
        popup.addChild(keybox)
        if not is_valid:
            apply_new_key()
        listbox.capture(apply_new_key)
        button_cbs = {
            OkButton.DEFAULT_NAME: True,
            CancelButton.DEFAULT_NAME: False
        }
        self.mainmenu.show_dialog(popup, button_cbs, modal=True)

    def toggle(self):
        """Called on help action.
		Toggles help screen via static variable *help_is_displayed*.
		Can be called both from main menu and in-game interface.
		"""
        help_dlg = self.widgets['help']
        if not self._is_displayed:
            self._is_displayed = True
            # make game pause if there is a game and we're not in the main menu
            if self.mainmenu.session is not None and self.mainmenu.current != self.widgets[
                    'ingamemenu']:
                PauseCommand().execute(self.mainmenu.session)
            if self.mainmenu.session is not None:
                self.mainmenu.session.ingame_gui.on_escape(
                )  # close dialogs that might be open
            self.mainmenu.show_dialog(help_dlg, {OkButton.DEFAULT_NAME: True})
            self.toggle()  # toggle state
        else:
            self._is_displayed = False
            if self.mainmenu.session is not None and self.mainmenu.current != self.widgets[
                    'ingamemenu']:
                UnPauseCommand().execute(self.mainmenu.session)
            help_dlg.hide()
예제 #20
0
class HotkeyConfiguration(object):

	def __init__(self):
		super(HotkeyConfiguration, self).__init__()

		self.widget = load_uh_widget('hotkeys.xml')
		self.buttons = []
		self.secondary_buttons = []

		self.keyconf = KeyConfig()
		self.actions = self.keyconf.get_bindable_actions_by_name()
		self.keys = self.keyconf.get_keys_by_value()

		self.HELPSTRING_LAYOUT = None
		self._is_displayed = False
		self._build_interface()

		# When detecing is True, the interface detects keypresses and binds them to actions
		self.detecting = False
		self.current_button = None
		self.current_index = None
		self.last_combination = []
		# Stores whether the last button pressed was for a primary or secondary binding (1 or 2)
		self.last_column = 1

		# There are some keys which are not detected by the event widget/keyPressed
		# In that case, the key presses are detected by the listener, which calls _detect_keypress
		self.listener = HotkeysListener(self._detect_keypress)

		self.widget.mapEvents({self.widget.name + '/keyPressed' : self._detect_keypress})
		self.widget.findChild(name=OkButton.DEFAULT_NAME).capture(self.save_settings)
		self.widget.mapEvents({OkButton.DEFAULT_NAME : self.save_settings})
		self.widget.findChild(name="reset_to_default").capture(self.reset_to_default)

	def _build_interface(self):
		button_container = self.widget.findChild(name='button_container')
		sec_button_container = self.widget.findChild(name='sec_button_container')
		for i, action in enumerate(self.actions):
			button = self._create_button(action, i)
			sec_button = self._create_button(action, i)
			button.mapEvents({button.name + '/mouseClicked' : Callback(self._detect_click_on_button, button, 1)})
			sec_button.mapEvents({button.name + '/mouseClicked' : Callback(self._detect_click_on_button, sec_button, 2)})
			button_container.addChild(button)
			sec_button_container.addChild(sec_button)
			self.buttons.append(button)
			self.secondary_buttons.append(sec_button)
			self.update_buttons_text()

	def _create_button(self, action, index):
		"""Important! The button name is set to index so that when a button is pressed, we know its index"""
		button = Button()
		button.name = str(index)
		button.max_size = button.min_size = (100, 18)
		return button

	def _detect_click_on_button(self, button, column):
		"""Starts the listener and remembers the position and index of the pressed button"""
		self.detecting = True
		self.current_button = button
		self.current_index = int(button.name)
		self.current_column = column
		self.listener.activate()
		self.update_buttons_text()
		button.text = _("Press desired key")

	def _detect_keypress(self, event):
		if not self.detecting:
			return
		key = event.getKey()
		# if the key is not supported, act as if it was not detected
		if not self.key_name(key):
			return
		self.last_combination.append(key)
		self.detecting = False
		self.listener.deactivate()
		self.apply_change()

	def update_buttons_text(self):
		for i, button in enumerate(self.buttons):
			action = self.actions[i]
			bindings = self.keyconf.get_current_keys(action)
			for j in range(len(bindings)):
				if bindings[j] == 'UNASSIGNED':
					bindings[j] = '-'
			secondary_button = self.secondary_buttons[i]
			button.text = unicode(bindings[0])
			if len(bindings) > 1:
				secondary_button.text = unicode(bindings[1])
			else:
				secondary_button.text = u"-"

	def apply_change(self):
		"""Binds the last keypress to the corresponding action and resets the interface to the state where it is listening for clicks on buttons"""
		key = self.last_combination[0]
		key_name = self.key_name(key)
		action = self.actions[self.current_index]
		column = self.current_column

		# Escape is used to unassign bindings
		if key_name == 'ESCAPE':
			key_name = 'UNASSIGNED'

		# If *key* is already set, replace the entry for *key* with UNASSIGNED for the last action.
		# This is done to avoid binding one key for two actions.
		elif self.key_is_set(key):
			oldaction = self.get_action_name(key)

			#xgettext:python-format
			message = _("{key} is already set to {action}.").format(key=key_name, action=oldaction)
			message += u" " + _("Would you like to overwrite it?")
			confirmed = horizons.main._modules.gui.show_popup(_("Confirmation for overwriting"), message, show_cancel_button=True)
			if confirmed:
				horizons.globals.fife.replace_key_for_action(oldaction, key_name, "UNASSIGNED")
			else:
				self.update_buttons_text()
				self.last_combination = []
				return

		bindings = self.keyconf.get_current_keys(action)
		if column == 1:
			bindings[0] = key_name
		elif column == 2:
			if len(bindings) < 2:
				bindings.append(key_name)
			else:
				bindings[1] = key_name

		horizons.globals.fife.set_key_for_action(action, bindings)

		self.update_buttons_text()
		self.last_combination = []

	def key_name(self, key):
		value = key.getValue()
		return self.keys.get(value)

	def key_is_set(self, key):
		key_name = self.key_name(key)
		custom_key_actions = horizons.globals.fife.get_hotkey_settings()
		for k in custom_key_actions.itervalues():
			if key_name in k:
				return True
		return False

	def get_current_bindings(self):
		""" Returns a dict mapping action -> list of keys """
		bindings = {}
		for action in self.actions:
			keys = self.keyconf.get_current_keys(action)
			bindings[action] = keys
		return bindings

	def get_action_name(self, key):
		key_name = self.key_name(key)
		custom_key_actions = horizons.globals.fife.get_hotkey_settings()
		for action in custom_key_actions:
			k = custom_key_actions[action]
			if key_name in k:
				return action
		print "Action name not found. Key name must be wrong. This is not supposed to ever happen"

	def reset_to_default(self):
		"""Resets all bindings to default"""
		for action in self.actions:
			default_key = horizons.globals.fife.get_keys_for_action(action, default=True)
			horizons.globals.fife.set_key_for_action(action, default_key)

		self.update_buttons_text()

	def save_settings(self):
		"""Saves the settings and reloads the keyConfiguration so that the settings take effect without a restart"""
		horizons.globals.fife.save_settings()
		self.keyconf.loadKeyConfiguration()

	def show(self):
		self.widget.show()

	def hide(self):
		self.widget.hide()