コード例 #1
0
ファイル: gui.py プロジェクト: mitfik/unknown-horizons
	def __init__(self):
		self.mainlistener = MainListener(self)
		self.current = None # currently active window
		self.widgets = LazyWidgetsDict(self.styles) # access widgets with their filenames without '.xml'
		self.session = None
		self.current_dialog = None

		self.__pause_displayed = False
コード例 #2
0
ファイル: gui.py プロジェクト: aviler/unknown-horizons
	def __init__(self):
		self.mainlistener = MainListener(self)
		self.current = None # currently active window
		self.widgets = LazyWidgetsDict(self.styles) # access widgets with their filenames without '.xml'
		build_help_strings(self.widgets['help'])
		self.session = None
		self.current_dialog = None

		self.dialog_executed = False

		self.__pause_displayed = False
		self._background_image = self._get_random_background()
コード例 #3
0
ファイル: gui.py プロジェクト: slbucur/unknown-horizons
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",
        "multiplayermenu": "book",
        "multiplayer_creategame": "book",
        "multiplayer_gamelobby": "book",
        "playerdataselection": "book",
        "aidataselection": "book",
        "select_savegame": "book",
        "ingame_pause": "book",
        # 	  'credits': 'book',
    }

    def __init__(self):
        self.mainlistener = MainListener(self)
        self.current = None  # currently active window
        self.widgets = LazyWidgetsDict(self.styles)  # access widgets with their filenames without '.xml'
        self.session = None
        self.current_dialog = None

        self.dialog_executed = False

        self.__pause_displayed = False

    # 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,
                "startMulti": self.show_multi,
                "settingsLink": self.show_settings,
                "helpLink": self.on_help,
                "closeButton": self.show_quit,
                "dead_link": self.on_chime,  # call for help; SoC information
                "creditsLink": self.show_credits,
                "loadgameButton": horizons.main.load_game,
            },
        )

        self.on_escape = self.show_quit

    def toggle_pause(self):
        """
		Show Pause menu
		"""
        # import here because we get a weird cycle otherwise
        if self.__pause_displayed:
            self.__pause_displayed = False
            self.hide()
            self.current = None
            self.session.speed_unpause(True)
            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")
            self._switch_current_widget(
                "ingamemenu",
                center=True,
                show=False,
                event_map={
                    # icons
                    "loadgameButton": horizons.main.load_game,
                    "savegameButton": self.save_game,
                    "settingsLink": self.show_settings,
                    "helpLink": self.on_help,
                    "startGame": self.toggle_pause,
                    "closeButton": self.quit_session,
                    # labels
                    "loadgame": horizons.main.load_game,
                    "savegame": self.save_game,
                    "settings": self.show_settings,
                    "help": self.on_help,
                    "start": self.toggle_pause,
                    "quit": self.quit_session,
                },
            )
            self.current.additional_widget = pychan.Icon(image="content/gui/images/background/transparent.png")
            self.current.additional_widget.position = (0, 0)
            self.current.additional_widget.show()
            self.current.show()

            self.session.speed_pause(True)
            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:
            self.show_popup(_("Error"), _("Failed to save."))

    def show_settings(self):
        horizons.main.fife._setting.onOptionsPress()

    _help_is_displayed = False

    def on_help(self):
        """Called on help action
		Toggles help screen via static variable help_is_displayed"""
        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"]:
                self.session.speed_pause()
            self.show_dialog(help_dlg, {"okButton": True}, onPressEscape=True)
            self.on_help()  # toggle state
        else:
            self._help_is_displayed = False
            if self.session is not None and self.current != self.widgets["ingamemenu"]:
                self.session.speed_unpause()
            help_dlg.hide()

    def show_quit(self):
        """Shows the quit dialog """
        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.
		@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()

    def on_chime(self):
        """
		Called chime action. Displaying call for help on artists and game design,
		introduces information for SoC applicants (if valid).
		"""
        AmbientSound.play_special("message")
        self.show_dialog(self.widgets["call_for_support"], {"okButton": True}, onPressEscape=True)

    def show_credits(self, number=0):
        """Shows the credits dialog. """
        for box in self.widgets["credits" + str(number)].findChildren(name="box"):
            box.margins = (30, 0)  # to get some indentation
            if number == 2:  # #TODO fix this hardcoded translators page ref
                box.padding = 1  # further decrease if more entries
                box.parent.padding = 3  # see above
        label = [
            self.widgets["credits" + str(number)].findChild(name=section + "_lbl")
            for section in ("team", "patchers", "translators", "special_thanks")
        ]
        for i in xrange(0, 4):
            if label[i]:  # add callbacks to each pickbelt that is displayed
                label[i].capture(Callback(self.show_credits, i), event_name="mouseClicked")

        if self.current_dialog is not None:
            self.current_dialog.hide()
        self.show_dialog(self.widgets["credits" + str(number)], {"okButton": True}, onPressEscape=True)

    def show_select_savegame(self, mode):
        """Shows menu to select a savegame.
		@param mode: 'save' or 'load'
		@return: Path to savegamefile or None"""
        assert mode in ("save", "load")
        map_files, map_file_display = None, None
        if mode == "load":
            map_files, map_file_display = SavegameManager.get_saves()
            if len(map_files) == 0:
                self.show_popup(_("No saved games"), _("There are no saved games to load"))
                return
        else:  # don't show autosave and quicksave on save
            map_files, map_file_display = SavegameManager.get_regular_saves()

            # Prepare widget
        old_current = self._switch_current_widget("select_savegame")
        self.current.findChild(name="headline").text = _("Save game") if mode == "save" else _("Load game")

        """ this doesn't work (yet), see http://fife.trac.cvsdude.com/engine/ticket/375
		if mode == 'save': # only show enter_filename on save
			self.current.findChild(name='enter_filename').show()
		else:
			self.current.findChild(name='enter_filename').hide()
		"""

        def tmp_selected_changed():
            """Fills in the name of the savegame in the textbox when selected in the list"""
            if self.current.collectData("savegamelist") != -1:  # check if we actually collect valid data
                self.current.distributeData(
                    {"savegamefile": map_file_display[self.current.collectData("savegamelist")]}
                )

        self.current.distributeInitialData({"savegamelist": map_file_display})
        cb = Callback.ChainedCallbacks(
            Gui._create_show_savegame_details(self.current, map_files, "savegamelist"), tmp_selected_changed
        )
        self.current.findChild(name="savegamelist").mapEvents(
            {"savegamelist/action": cb, "savegamelist/mouseWheelMovedUp": cb, "savegamelist/mouseWheelMovedDown": cb}
        )
        self.current.findChild(name="savegamelist").capture(cb, event_name="keyPressed")

        retval = self.show_dialog(
            self.current,
            {"okButton": True, "cancelButton": False, "deleteButton": "delete", "savegamefile": True},
            onPressEscape=False,
        )
        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
            self._delete_savegame(map_files)
            self.current = old_current
            return self.show_select_savegame(mode=mode)

        selected_savegame = None
        if mode == "save":  # return from textfield
            selected_savegame = self.current.collectData("savegamefile")
            if selected_savegame in map_file_display:  # savegamename already exists
                message = (
                    _('A savegame with the name "%s" already exists. \nShould i overwrite it?') % selected_savegame
                )
                if not self.show_popup(_("Confirmation for overwriting"), message, show_cancel_button=True):
                    self.current = old_current
                    return self.show_select_savegame(mode=mode)  # reshow dialog
        else:  # return selected item from list
            selected_savegame = self.current.collectData("savegamelist")
            selected_savegame = None if selected_savegame == -1 else map_files[selected_savegame]
            if selected_savegame is None:
                # ok button has been pressed, but no savegame was selected
                self.show_popup(_("Select a savegame"), _("Please select a savegame or click on cancel."))
                self.current = old_current
                return self.show_select_savegame(mode=mode)  # reshow dialog
        self.current = old_current  # reuse old widget
        return selected_savegame

    # 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()
            try:
                self.current.additional_widget.hide()
                del self.current.additional_widget
            except AttributeError, e:
                pass  # only used for some widgets, e.g. pause
コード例 #4
0
ファイル: ingamegui.py プロジェクト: aviler/unknown-horizons
	def __init__(self, session, gui):
		super(IngameGui, self).__init__()
		self.session = session
		assert isinstance(self.session, horizons.session.Session)
		self.main_gui = gui
		self.main_widget = None
		self.tabwidgets = {}
		self.settlement = None
		self.resource_source = None
		self.resources_needed, self.resources_usable = {}, {}
		self._old_menu = None

		self.widgets = LazyWidgetsDict(self.styles, center_widgets=False)

		cityinfo = self.widgets['city_info']
		cityinfo.child_finder = PychanChildFinder(cityinfo)

		# special settings for really small resolutions
		#TODO explain what actually happens
		width = horizons.main.fife.engine_settings.getScreenWidth()
		x = 'center'
		y = 'top'
		x_offset = -10
		y_offset = +4
		if width < 800:
			x = 'left'
			x_offset = 10
			y_offset = +66
		elif width < 1020:
			x_offset = (1000 - width) / 2
		cityinfo.position_technique = "%s%+d:%s%+d" % (x, x_offset, y, y_offset) # usually "center-10:top+4"

		self.logbook = LogBook(self.session)
		self.message_widget = MessageWidget(self.session)
		self.players_overview = PlayersOverview(self.session)
		self.players_settlements = PlayersSettlements(self.session)
		self.players_ships = PlayersShips(self.session)
		self.scenario_chooser = ScenarioChooser(self.session)

		# self.widgets['minimap'] is the guichan gui around the actual minimap,
		# which is saved in self.minimap
		minimap = self.widgets['minimap']
		minimap.position_technique = "right+0:top+0"

		icon = minimap.findChild(name="minimap")
		self.minimap = Minimap(icon,
		                       targetrenderer=horizons.main.fife.targetrenderer,
		                       imagemanager=horizons.main.fife.imagemanager,
		                       session=self.session,
		                       view=self.session.view)

		def speed_up():
			SpeedUpCommand().execute(self.session)

		def speed_down():
			SpeedDownCommand().execute(self.session)

		minimap.mapEvents({
			'zoomIn' : self.session.view.zoom_in,
			'zoomOut' : self.session.view.zoom_out,
			'rotateRight' : Callback.ChainedCallbacks(self.session.view.rotate_right, self.minimap.rotate_right),
			'rotateLeft' : Callback.ChainedCallbacks(self.session.view.rotate_left, self.minimap.rotate_left),
			'speedUp' : speed_up,
			'speedDown' : speed_down,
			'destroy_tool' : self.session.toggle_destroy_tool,
			'build' : self.show_build_menu,
			'diplomacyButton' : self.show_diplomacy_menu,
			'gameMenuButton' : self.main_gui.toggle_pause,
			'logbook' : self.logbook.toggle_visibility
		})
		minimap.show()
		#minimap.position_technique = "right+15:top+153"

		self.widgets['tooltip'].hide()

		self.resource_overview = ResourceOverviewBar(self.session)
		self.session.message_bus.subscribe_globally(ResourceBarResize, self._on_resourcebar_resize)

		# map buildings to build functions calls with their building id.
		# This is necessary because BuildTabs have no session.
		self.callbacks_build = dict()
		for building_id in Entities.buildings.iterkeys():
			self.callbacks_build[building_id] = Callback(self._build, building_id)

		# Register for messages
		self.session.message_bus.subscribe_globally(SettlerUpdate, self._on_settler_level_change)
		self.session.message_bus.subscribe_globally(SettlerInhabitantsChanged, self._on_settler_inhabitant_change)
		self.session.message_bus.subscribe_globally(HoverSettlementChanged, self._cityinfo_set)
コード例 #5
0
ファイル: ingamegui.py プロジェクト: aviler/unknown-horizons
class IngameGui(LivingObject):
	"""Class handling all the ingame gui events.
	Assumes that only 1 instance is used (class variables)"""

	gui = livingProperty()
	tabwidgets = livingProperty()
	message_widget = livingProperty()
	minimap = livingProperty()

	styles = {
		'city_info' : 'city_info',
		'change_name' : 'book',
		'save_map' : 'book',
		'chat' : 'book',
	}

	def __init__(self, session, gui):
		super(IngameGui, self).__init__()
		self.session = session
		assert isinstance(self.session, horizons.session.Session)
		self.main_gui = gui
		self.main_widget = None
		self.tabwidgets = {}
		self.settlement = None
		self.resource_source = None
		self.resources_needed, self.resources_usable = {}, {}
		self._old_menu = None

		self.widgets = LazyWidgetsDict(self.styles, center_widgets=False)

		cityinfo = self.widgets['city_info']
		cityinfo.child_finder = PychanChildFinder(cityinfo)

		# special settings for really small resolutions
		#TODO explain what actually happens
		width = horizons.main.fife.engine_settings.getScreenWidth()
		x = 'center'
		y = 'top'
		x_offset = -10
		y_offset = +4
		if width < 800:
			x = 'left'
			x_offset = 10
			y_offset = +66
		elif width < 1020:
			x_offset = (1000 - width) / 2
		cityinfo.position_technique = "%s%+d:%s%+d" % (x, x_offset, y, y_offset) # usually "center-10:top+4"

		self.logbook = LogBook(self.session)
		self.message_widget = MessageWidget(self.session)
		self.players_overview = PlayersOverview(self.session)
		self.players_settlements = PlayersSettlements(self.session)
		self.players_ships = PlayersShips(self.session)
		self.scenario_chooser = ScenarioChooser(self.session)

		# self.widgets['minimap'] is the guichan gui around the actual minimap,
		# which is saved in self.minimap
		minimap = self.widgets['minimap']
		minimap.position_technique = "right+0:top+0"

		icon = minimap.findChild(name="minimap")
		self.minimap = Minimap(icon,
		                       targetrenderer=horizons.main.fife.targetrenderer,
		                       imagemanager=horizons.main.fife.imagemanager,
		                       session=self.session,
		                       view=self.session.view)

		def speed_up():
			SpeedUpCommand().execute(self.session)

		def speed_down():
			SpeedDownCommand().execute(self.session)

		minimap.mapEvents({
			'zoomIn' : self.session.view.zoom_in,
			'zoomOut' : self.session.view.zoom_out,
			'rotateRight' : Callback.ChainedCallbacks(self.session.view.rotate_right, self.minimap.rotate_right),
			'rotateLeft' : Callback.ChainedCallbacks(self.session.view.rotate_left, self.minimap.rotate_left),
			'speedUp' : speed_up,
			'speedDown' : speed_down,
			'destroy_tool' : self.session.toggle_destroy_tool,
			'build' : self.show_build_menu,
			'diplomacyButton' : self.show_diplomacy_menu,
			'gameMenuButton' : self.main_gui.toggle_pause,
			'logbook' : self.logbook.toggle_visibility
		})
		minimap.show()
		#minimap.position_technique = "right+15:top+153"

		self.widgets['tooltip'].hide()

		self.resource_overview = ResourceOverviewBar(self.session)
		self.session.message_bus.subscribe_globally(ResourceBarResize, self._on_resourcebar_resize)

		# map buildings to build functions calls with their building id.
		# This is necessary because BuildTabs have no session.
		self.callbacks_build = dict()
		for building_id in Entities.buildings.iterkeys():
			self.callbacks_build[building_id] = Callback(self._build, building_id)

		# Register for messages
		self.session.message_bus.subscribe_globally(SettlerUpdate, self._on_settler_level_change)
		self.session.message_bus.subscribe_globally(SettlerInhabitantsChanged, self._on_settler_inhabitant_change)
		self.session.message_bus.subscribe_globally(HoverSettlementChanged, self._cityinfo_set)

	def _on_resourcebar_resize(self, message):
		###
		# TODO implement
		###
		pass

	def end(self):
		self.widgets['minimap'].mapEvents({
			'zoomIn' : None,
			'zoomOut' : None,
			'rotateRight' : None,
			'rotateLeft': None,

			'destroy_tool' : None,
			'build' : None,
			'diplomacyButton' : None,
			'gameMenuButton' : None
		})

		for w in self.widgets.itervalues():
			if w.parent is None:
				w.hide()
		self.message_widget = None
		self.tabwidgets = None
		self.minimap = None
		self.resource_overview.end()
		self.resource_overview = None
		self.hide_menu()
		self.session.message_bus.unsubscribe_globally(SettlerUpdate, self._on_settler_level_change)
		self.session.message_bus.unsubscribe_globally(ResourceBarResize, self._on_resourcebar_resize)
		self.session.message_bus.unsubscribe_globally(HoverSettlementChanged, self._cityinfo_set)
		self.session.message_bus.unsubscribe_globally(SettlerInhabitantsChanged, self._on_settler_inhabitant_change)

		super(IngameGui, self).end()

	def _cityinfo_set(self, message):
		"""Sets the city name at top center of screen.

		Show/Hide is handled automatically
		To hide cityname, set name to ''
		@param message: HoverSettlementChanged message
		"""
		settlement = message.settlement
		old_was_player_settlement = False
		if self.settlement is not None:
			self.settlement.remove_change_listener(self.update_settlement)
			old_was_player_settlement = (self.settlement.owner == self.session.world.player)

		# save reference to new "current" settlement in self.settlement
		self.settlement = settlement

		if settlement is None: # we want to hide the widget now (but perhaps delayed).
			if old_was_player_settlement:
				# Interface feature: Since players might need to scroll to an area not
				# occupied by the current settlement, leave name on screen in case they
				# want to e.g. rename the settlement which requires a click on cityinfo
				ExtScheduler().add_new_object(self.widgets['city_info'].hide, self,
				      run_in=GUI.CITYINFO_UPDATE_DELAY)
				#TODO 'click to rename' tooltip of cityinfo can stay visible in
				# certain cases if cityinfo gets hidden in tooltip delay buffer.
			else:
				# this happens if you have not hovered an own settlement,
				# but others like AI settlements. Simply hide the widget.
				self.widgets['city_info'].hide()

		else:# we want to show the widget.
			# do not hide cityinfo if we again hover the settlement
			# before the delayed hide of the old info kicks in
			ExtScheduler().rem_call(self, self.widgets['city_info'].hide)

			self.widgets['city_info'].show()
			self.update_settlement()
			settlement.add_change_listener(self.update_settlement)

	def _on_settler_inhabitant_change(self, message):
		assert isinstance(message, SettlerInhabitantsChanged)
		cityinfo = self.widgets['city_info']
		foundlabel = cityinfo.child_finder('city_inhabitants')
		foundlabel.text = unicode(' %s' % ((int(foundlabel.text) if foundlabel.text else 0) + message.change))
		foundlabel.resizeToContent()

	def update_settlement(self):
		cityinfo = self.widgets['city_info']
		if self.settlement.owner.is_local_player: # allow name changes
			cb = Callback(self.show_change_name_dialog, self.settlement)
			helptext = _("Click to change the name of your settlement")
		else: # no name changes
			cb = lambda : 42
			helptext = u""
		cityinfo.mapEvents({
			'city_name': cb
		})
		cityinfo.findChild(name="city_name").helptext = helptext

		foundlabel = cityinfo.child_finder('owner_emblem')
		foundlabel.image = 'content/gui/images/tabwidget/emblems/emblem_%s.png' % (self.settlement.owner.color.name)
		foundlabel.helptext = unicode(self.settlement.owner.name)

		foundlabel = cityinfo.child_finder('city_name')
		foundlabel.text = unicode(self.settlement.get_component(SettlementNameComponent).name)
		foundlabel.resizeToContent()

		foundlabel = cityinfo.child_finder('city_inhabitants')
		foundlabel.text = unicode(' %s' % (self.settlement.inhabitants))
		foundlabel.resizeToContent()

		cityinfo.adaptLayout()

	def minimap_to_front(self):
		"""Make sure the full right top gui is visible and not covered by some dialog"""
		self.widgets['minimap'].hide()
		self.widgets['minimap'].show()

	def show_diplomacy_menu(self):
		# check if the menu is already shown
		if hasattr(self.get_cur_menu(), 'name') and self.get_cur_menu().name == "diplomacy_widget":
			self.hide_menu()
			return
		players = set(self.session.world.players)
		players.add(self.session.world.pirate)
		players.discard(self.session.world.player)
		players.discard(None) # e.g. when the pirate is disabled
		if len(players) == 0: # this dialog is pretty useless in this case
			self.main_gui.show_popup(_("No diplomacy possible"), \
			                         _("Cannot do diplomacy as there are no other players."))
			return

		dtabs = []
		for player in players:
			dtabs.append(DiplomacyTab(player))
		tab = TabWidget(self, tabs=dtabs, name="diplomacy_widget")
		self.show_menu(tab)

	def show_multi_select_tab(self):
		tab = TabWidget(self, tabs = [SelectMultiTab(self.session)], name = 'select_multi')
		self.show_menu(tab)

	def show_build_menu(self, update=False):
		"""
		@param update: set when build possiblities change (e.g. after settler upgrade)
		"""
		# check if build menu is already shown
		if hasattr(self.get_cur_menu(), 'name') and self.get_cur_menu().name == "build_menu_tab_widget":
			self.hide_menu()

			if not update: # this was only a toggle call, don't reshow
				return

		self.session.set_cursor() # set default cursor for build menu
		self.deselect_all()

		if not any( settlement.owner.is_local_player for settlement in self.session.world.settlements):
			# player has not built any settlements yet. Accessing the build menu at such a point
			# indicates a mistake in the mental model of the user. Display a hint.
			tab = TabWidget(self, tabs=[ TabInterface(widget="buildtab_no_settlement.xml") ])
		else:
			btabs = [BuildTab(index+1, self.callbacks_build, self.session) for index in \
							 xrange(self.session.world.player.settler_level+1)]
			tab = TabWidget(self, tabs=btabs, name="build_menu_tab_widget", \
											active_tab=BuildTab.last_active_build_tab)
		self.show_menu(tab)

	def deselect_all(self):
		for instance in self.session.selected_instances:
			instance.get_component(SelectableComponent).deselect()
		self.session.selected_instances.clear()

	def _build(self, building_id, unit = None):
		"""Calls the games buildingtool class for the building_id.
		@param building_id: int with the building id that is to be built.
		@param unit: weakref to the unit, that builds (e.g. ship for warehouse)"""
		self.hide_menu()
		self.deselect_all()
		cls = Entities.buildings[building_id]
		if hasattr(cls, 'show_build_menu'):
			cls.show_build_menu()
		self.session.set_cursor('building', cls, None if unit is None else unit())

	def toggle_road_tool(self):
		if not isinstance(self.session.cursor, BuildingTool) or self.session.cursor._class.id != BUILDINGS.TRAIL_CLASS:
			if isinstance(self.session.cursor, BuildingTool):
				print self.session.cursor._class.id, BUILDINGS.TRAIL_CLASS
			self._build(BUILDINGS.TRAIL_CLASS)
		else:
			self.session.set_cursor()

	def _get_menu_object(self, menu):
		"""Returns pychan object if menu is a string, else returns menu
		@param menu: str with the guiname or pychan object.
		"""
		if isinstance(menu, str):
			menu = self.widgets[menu]
		return menu

	def get_cur_menu(self):
		"""Returns menu that is currently displayed"""
		return self._old_menu

	def show_menu(self, menu):
		"""Shows a menu
		@param menu: str with the guiname or pychan object.
		"""
		if self._old_menu is not None:
			if hasattr(self._old_menu, "remove_remove_listener"):
				self._old_menu.remove_remove_listener( Callback(self.show_menu, None) )
			self._old_menu.hide()

		self._old_menu = self._get_menu_object(menu)
		if self._old_menu is not None:
			if hasattr(self._old_menu, "add_remove_listener"):
				self._old_menu.add_remove_listener( Callback(self.show_menu, None) )
			self._old_menu.show()
			self.minimap_to_front()

	def hide_menu(self):
		self.show_menu(None)

	def toggle_menu(self, menu):
		"""Shows a menu or hides it if it is already displayed.
		@param menu: parameter supported by show_menu().
		"""
		if self.get_cur_menu() == self._get_menu_object(menu):
			self.hide_menu()
		else:
			self.show_menu(menu)


	def save(self, db):
		self.message_widget.save(db)
		self.logbook.save(db)
		self.resource_overview.save(db)

	def load(self, db):
		self.message_widget.load(db)
		self.logbook.load(db)
		self.resource_overview.load(db)

		self.minimap.draw() # update minimap to new world

	def show_change_name_dialog(self, instance):
		"""Shows a dialog where the user can change the name of a NamedComponant.
		The game gets paused while the dialog is executed."""
		events = {
			'okButton': Callback(self.change_name, instance),
			'cancelButton': self._hide_change_name_dialog
		}
		self.main_gui.on_escape = self._hide_change_name_dialog
		changename = self.widgets['change_name']
		oldname = changename.findChild(name='old_name')
		oldname.text =  unicode(instance.get_component(SettlementNameComponent).name)
		newname = changename.findChild(name='new_name')
		changename.mapEvents(events)
		newname.capture(Callback(self.change_name, instance))

		def forward_escape(event):
			# the textfield will eat everything, even control events
			if event.getKey().getValue() == fife.Key.ESCAPE:
				self.main_gui.on_escape()
		newname.capture( forward_escape, "keyPressed" )

		changename.show()
		newname.requestFocus()

	def _hide_change_name_dialog(self):
		"""Escapes the change_name dialog"""
		self.main_gui.on_escape = self.main_gui.toggle_pause
		self.widgets['change_name'].hide()

	def change_name(self, instance):
		"""Applies the change_name dialogs input and hides it.
		If the new name has length 0 or only contains blanks, the old name is kept.
		"""
		new_name = self.widgets['change_name'].collectData('new_name')
		self.widgets['change_name'].findChild(name='new_name').text = u''
		if not (len(new_name) == 0 or new_name.isspace()):
			# different namedcomponent classes share the name
			RenameObject(instance.get_component_by_name(NamedComponent.NAME), new_name).execute(self.session)
		self._hide_change_name_dialog()

	def show_save_map_dialog(self):
		"""Shows a dialog where the user can set the name of the saved map."""
		events = {
			'okButton': self.save_map,
			'cancelButton': self._hide_save_map_dialog
		}
		self.main_gui.on_escape = self._hide_save_map_dialog
		dialog = self.widgets['save_map']
		name = dialog.findChild(name = 'map_name')
		name.text = u''
		dialog.mapEvents(events)
		name.capture(Callback(self.save_map))
		dialog.show()
		name.requestFocus()

	def _hide_save_map_dialog(self):
		"""Closes the map saving dialog."""
		self.main_gui.on_escape = self.main_gui.toggle_pause
		self.widgets['save_map'].hide()

	def save_map(self):
		"""Saves the map and hides the dialog."""
		name = self.widgets['save_map'].collectData('map_name')
		if re.match('^[a-zA-Z0-9_-]+$', name):
			self.session.save_map(name)
			self._hide_save_map_dialog()
		else:
			#xgettext:python-format
			message = _('Valid map names are in the following form: {expression}').format(expression='[a-zA-Z0-9_-]+')
			#xgettext:python-format
			advice = _('Try a name that only contains letters and numbers.')
			self.session.gui.show_error_popup(_('Error'), message, advice)

	def on_escape(self):
		if self.main_widget:
			self.main_widget.hide()
		else:
			return False
		return True

	def on_switch_main_widget(self, widget):
		"""The main widget has been switched to the given one (possibly None)."""
		if self.main_widget and self.main_widget != widget: # close the old one if it exists
			old_main_widget = self.main_widget
			self.main_widget = None
			old_main_widget.hide()
		self.main_widget = widget

	def display_game_speed(self, text):
		"""
		@param text: unicode string to display as speed value
		"""
		wdg = self.widgets['minimap'].findChild(name="speed_text")
		wdg.text = text
		wdg.resizeToContent()
		self.widgets['minimap'].show()

	def _on_settler_level_change(self, message):
		"""Gets called when the player changes"""
		if message.sender.owner.is_local_player:
			menu = self.get_cur_menu()
			if hasattr(menu, "name") and menu.name == "build_menu_tab_widget":
				# player changed and build menu is currently displayed
				self.show_build_menu(update=True)

	def show_chat_dialog(self):
		"""Show a dialog where the user can enter a chat message"""
		events = {
			'okButton': self._do_chat,
			'cancelButton': self._hide_chat_dialog
		}
		self.main_gui.on_escape = self._hide_chat_dialog

		self.widgets['chat'].mapEvents(events)
		def forward_escape(event):
			# the textfield will eat everything, even control events
			if event.getKey().getValue() == fife.Key.ESCAPE:
				self.main_gui.on_escape()

		self.widgets['chat'].findChild(name='msg').capture( forward_escape, "keyPressed" )
		self.widgets['chat'].findChild(name='msg').capture( self._do_chat )
		self.widgets['chat'].show()
		self.widgets['chat'].findChild(name="msg").requestFocus()

	def _hide_chat_dialog(self):
		"""Escapes the chat dialog"""
		self.main_gui.on_escape = self.main_gui.toggle_pause
		self.widgets['chat'].hide()

	def _do_chat(self):
		"""Actually initiates chatting and hides the dialog"""
		msg = self.widgets['chat'].findChild(name='msg').text
		Chat(msg).execute(self.session)
		self.widgets['chat'].findChild(name='msg').text = u''
		self._hide_chat_dialog()
コード例 #6
0
ファイル: gui.py プロジェクト: aviler/unknown-horizons
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_campaign': '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',
	  }

	def __init__(self):
		self.mainlistener = MainListener(self)
		self.current = None # currently active window
		self.widgets = LazyWidgetsDict(self.styles) # access widgets with their filenames without '.xml'
		build_help_strings(self.widgets['help'])
		self.session = None
		self.current_dialog = None

		self.dialog_executed = False

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

# 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,
			'startMulti'     : self.show_multi,
			'settingsLink'   : self.show_settings,
			'helpLink'       : self.on_help,
			'closeButton'    : self.show_quit,
			'dead_link'      : self.on_chime, # call for help; SoC information
			'creditsLink'    : self.show_credits,
			'loadgameButton' : horizons.main.load_game
		})

		self.on_escape = self.show_quit

	def toggle_pause(self):
		"""
		Show Pause menu
		"""
		# 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 = horizons.main.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'],
			})

			# load transparent background, that de facto prohibits access to other
			# gui elements by eating all events
			height = horizons.main.fife.engine_settings.getScreenHeight()
			width = horizons.main.fife.engine_settings.getScreenWidth()
			image = horizons.main.fife.imagemanager.loadBlank(width,  height)
			image = fife.GuiImage(image)
			self.current.additional_widget = pychan.Icon(image=image)
			self.current.additional_widget.position = (0, 0)
			self.current.additional_widget.show()
			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):
		self.on_escape = lambda : horizons.main.fife._setting.OptionsDlg.hide()
		horizons.main.fife.show_settings()

	_help_is_displayed = False
	def on_help(self):
		"""Called on help action
		Toggles help screen via static variable help_is_displayed"""
		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' : True}, onPressEscape = 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 """
		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.
		@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 on_chime(self):
		"""
		Called chime action. Displaying call for help on artists and game design,
		introduces information for SoC applicants (if valid).
		"""
		AmbientSoundComponent.play_special("message")
		self.show_dialog(self.widgets['call_for_support'], {'okButton' : True}, onPressEscape = True)

	def show_credits(self, number=0):
		"""Shows the credits dialog. """
		for box in self.widgets['credits'+str(number)].findChildren(name='box'):
			box.margins = (30, 0) # to get some indentation
			if number == 2: # #TODO fix this hardcoded translators page ref
				box.padding = 1 # further decrease if more entries
				box.parent.padding = 3 # see above
		label = [self.widgets['credits'+str(number)].findChild(name=section+"_lbl") \
		              for section in ('team','patchers','translators','packagers','special_thanks')]
		for i in xrange(5):
			if label[i]: # add callbacks to each pickbelt that is displayed
				label[i].capture(Callback(self.show_credits, i),
				                 event_name="mouseClicked")

		if self.current_dialog is not None:
			self.current_dialog.hide()
		self.show_dialog(self.widgets['credits'+str(number)], {'okButton' : True}, onPressEscape = True)

	def show_select_savegame(self, mode, sanity_checker=None, sanity_criteria=None):
		"""Shows menu to select a savegame.
		@param mode: 'save', 'load' or 'mp_load'
		@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
		mp = False
		args = mode, sanity_checker, sanity_criteria # for reshow
		if mode.startswith('mp'):
			mode = mode[3:]
			mp = True
			# 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 len(map_files) == 0:
				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')
		self.current.findChild(name='headline').text = _('Save game') if mode == 'save' else _('Load game')
		self.current.findChild(name='okButton').helptext = _('Save game') if mode == 'save' else _('Load game')

		name_box = self.current.findChild(name="gamename_box")
		if mp and mode == 'load': # have gamename
			name_box.parent.showChild(name_box)
			gamename_textfield = self.current.findChild(name="gamename")
			def clear_gamename_textfield():
				gamename_textfield.text = u""
			gamename_textfield.capture(clear_gamename_textfield, 'mouseReleased', 'default')
		else:
			if name_box not in name_box.parent.hidden_children:
				name_box.parent.hideChild(name_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._getParent()

		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
				if self.current.collectData('savegamelist') == -1: # set blank if nothing is selected
					self.current.findChild(name="savegamefile").text = u""
				else:
					self.current.distributeData({'savegamefile' : \
				                             map_file_display[self.current.collectData('savegamelist')]})

		self.current.distributeInitialData({'savegamelist' : map_file_display})
		self.current.distributeData({'savegamelist' : -1}) # Don't select anything by default
		cb = Callback.ChainedCallbacks(Gui._create_show_savegame_details(self.current, map_files, 'savegamelist'), \
		                               tmp_selected_changed)
		cb() # Refresh data on start
		self.current.findChild(name="savegamelist").mapEvents({
		    'savegamelist/action'              : cb,
		    'savegamelist/mouseWheelMovedUp'   : cb,
		    'savegamelist/mouseWheelMovedDown' : cb
		})
		self.current.findChild(name="savegamelist").capture(cb, event_name="keyPressed")

		eventMap = {
			'okButton'     : True,
			'cancelButton' : False,
			'deleteButton' : 'delete'
		}

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

		retval = self.show_dialog(self.current, eventMap, onPressEscape = False)
		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?')
				if not self.show_popup(_("Confirmation for overwriting"), message, show_cancel_button = True):
					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')
			selected_savegame = None if selected_savegame == -1 else map_files[selected_savegame]
			if selected_savegame is None:
				# ok button has been pressed, but no savegame was selected
				self.show_popup(_("Select a savegame"), _("Please select a savegame or click on cancel."))
				self.current = old_current
				return self.show_select_savegame(*args) # reshow dialog

		if mp and mode == 'load': # also name
			gamename_textfield = self.current.findChild(name="gamename")
			ret = selected_savegame, self.current.collectData('gamename')
		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()
			try:
				self.current.additional_widget.hide()
				del self.current.additional_widget
			except AttributeError:
				pass # only used for some widgets, e.g. pause

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


	def show_dialog(self, dlg, bind, onPressEscape = None, event_map = None):
		"""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 onPressEscape: callback that is to be called if the escape button is pressed
		@param event_map: dictionary with callbacks for buttons. See pychan docu: pychan.widget.mapEvents()
		"""
		self.current_dialog = dlg
		if event_map is not None:
			dlg.mapEvents(event_map)
		if onPressEscape is not None:
			def _escape(event):
				if event.getKey().getValue() == fife.Key.ESCAPE:
					pychan.internal.get_manager().breakFromMainLoop(onPressEscape)
					dlg.hide()
			dlg.capture(_escape, event_name="keyPressed")
		self.dialog_executed = True
		ret = dlg.execute(bind)
		self.dialog_executed = False
		return ret

	def show_popup(self, windowtitle, message, show_cancel_button=False, size=0):
		"""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.
		@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
		ExtScheduler().add_new_object(lambda : popup.findChild(name='okButton').requestFocus(), self, run_in=0)
		if show_cancel_button:
			return self.show_dialog(popup, {'okButton' : True, 'cancelButton' : False}, onPressEscape = False)
		else:
			return self.show_dialog(popup, {'okButton' : True}, onPressEscape = True)

	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 += _(u"Details:") + u" " + details
		try:
			self.show_popup( _(u"Error:") + u" " + 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")
			cancel_button.parent.removeChild(cancel_button)

		headline = popup.findChild(name='headline')
		# just to be safe, the gettext-function is used twice,
		# once on the original, once on the unicode string.
		headline.text = _(_(windowtitle))
		popup.findChild(name='popup_message').text = _(_(message))
		popup.adaptLayout() # recalculate widths
		return popup

	def show_loading_screen(self):
		self._switch_current_widget('loadingscreen', center=True, show=True)

# 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]
		# Set background image
		bg = self.current.findChild(name='background')
		if bg:
			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
			box = gui.findChild(name="savegamedetails_box")
			old_label = box.findChild(name="savegamedetails_lbl")
			if old_label is not None:
				box.removeChild(old_label)
			map_file = None
			map_file_index = gui.collectData(savegamelist)
			if map_file_index == -1:
				return
			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)

			# screenshot (len can be 0 if save failed in a weird way)
			if 'screenshot' in savegame_info and \
			   savegame_info['screenshot'] is not None and \
			   len(savegame_info['screenshot']) > 0:
				# try to find a writeable 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 = pychan.widgets.Label(min_size=(290, 0), max_size=(290, 290), wrap_text=True)
			details_label.name = "savegamedetails_lbl"
			details_label.text = u""
			if savegame_info['timestamp'] == -1:
				details_label.text += _("Unknown savedate")
			else:
				#xgettext:python-format
				details_label.text += _("Saved at {time}").format(
				                         time=time.strftime("%c",
				                         time.localtime(savegame_info['timestamp'])).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_t')

			from horizons.constants import VERSION
			try:
				if savegame_info['savegamerev'] == VERSION.SAVEGAMEREVISION:
					#xgettext:python-format
					details_label.text += _("Savegame version {version}").format(
					                         version=savegame_info['savegamerev'])
				else:
					#xgettext:python-format
					details_label.text += _("WARNING: Incompatible version {version}!").format(
					                         version=savegame_info['savegamerev']) + u"\n"
					#xgettext:python-format
					details_label.text += _("Required version: {required}!").format(
					                         required=VERSION.SAVEGAMEREVISION)
			except KeyError:
				details_label.text += _("Incompatible version")


			box.addChild( details_label )


			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(self):
		"""Randomly select a background image to use through out the game menu."""
		available_images = glob.glob('content/gui/images/background/mainmenu/bg_*.png')
		return random.choice(available_images)
コード例 #7
0
ファイル: gui.py プロジェクト: mitfik/unknown-horizons
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',
	  'multiplayermenu' : 'book',
	  'multiplayer_creategame' : 'book',
	  'multiplayer_gamelobby' : 'book',
	  'playerdataselection' : 'book',
	  'aidataselection' : 'book',
	  'select_savegame': 'book',
	  'ingame_pause': 'book',
#	  'credits': 'book',
	  }

	def __init__(self):
		self.mainlistener = MainListener(self)
		self.current = None # currently active window
		self.widgets = LazyWidgetsDict(self.styles) # access widgets with their filenames without '.xml'
		self.session = None
		self.current_dialog = None

		self.__pause_displayed = False

# 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,
			'startMulti'     : self.show_multi,
			'settingsLink'   : self.show_settings,
			'helpLink'       : self.on_help,
			'closeButton'    : self.show_quit,
			'dead_link'      : self.on_chime, # call for help; SoC information
			'creditsLink'    : self.show_credits,
			'loadgameButton' : horizons.main.load_game
		})

		self.on_escape = self.show_quit

	def toggle_pause(self):
		"""
		Show Pause menu
		"""
		if self.__pause_displayed:
			self.__pause_displayed = False
			self.return_to_game()
		else:
			self.__pause_displayed = True
			self._switch_current_widget('ingamemenu', center=True, show=True, event_map={
				  # icons
				'loadgameButton' : horizons.main.load_game,
				'savegameButton' : self.save_game,
				'settingsLink'   : self.show_settings,
				'helpLink'       : self.on_help,
				'startGame'      : self.return_to_game,
				'closeButton'    : self.quit_session,
				# labels
				'loadgame' : horizons.main.load_game,
				'savegame' : self.save_game,
				'settings' : self.show_settings,
				'help'     : self.on_help,
				'start'    : self.return_to_game,
				'quit'     : self.quit_session,
			})

			self.session.speed_pause()
			self.on_escape = self.toggle_pause

# what happens on button clicks

	def return_to_game(self):
		"""Return to the horizons."""
		self.hide() # Hide old gui
		self.current = None
		self.session.speed_unpause()
		self.on_escape = self.toggle_pause

	def save_game(self):
		"""Wrapper for saving for separating gui messages from save logic
		"""
		success = self.session.save()
		if not success:
			self.show_popup(_('Error'), _('Failed to save.'))

	def show_settings(self):
		horizons.main.fife._setting.onOptionsPress()

	_help_is_displayed = False
	def on_help(self):
		"""Called on help action
		Toggles help screen via static variable help_is_displayed"""
		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']:
				self.session.speed_pause()
			self.show_dialog(help_dlg, {'okButton' : True}, onPressEscape = True)
			self.on_help() # toggle state
		else:
			self._help_is_displayed = False
			if self.session is not None and self.current != self.widgets['ingamemenu']:
				self.session.speed_unpause()
			help_dlg.hide()

	def show_quit(self):
		"""Shows the quit dialog """
		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.
		@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.current.hide()
				self.current = None
			if self.session is not None:
				self.session.end()
				self.session = None

			self.show_main()

	def on_chime(self):
		"""
		Called chime action. Displaying call for help on artists and game design,
		introduces information for SoC applicants (if valid).
		"""
		AmbientSound.play_special("message")
		self.show_dialog(self.widgets['call_for_support'], {'okButton' : True}, onPressEscape = True)

	def show_credits(self, number=0):
		"""Shows the credits dialog. """
		for box in self.widgets['credits'+str(number)].findChildren(name='box'):
			box.margins = (30,0) # to get some indentation
			if number == 2: # #TODO fix this hardcoded translators page ref
				box.padding = 1 # further decrease if more entries
				box.parent.padding = 3 # see above
		label = [self.widgets['credits'+str(number)].findChild(name=section+"_lbl") \
		              for section in ('team','patchers','translators','special_thanks')]
		for i in xrange (0,4):
			if label[i]: # add callbacks to each pickbelt that is displayed
				label[i].capture(Callback(self.show_credits, i),
				                 event_name="mouseClicked")

		if self.current_dialog is not None:
			self.current_dialog.hide()
		self.show_dialog(self.widgets['credits'+str(number)], {'okButton' : True}, onPressEscape = True)

	def show_select_savegame(self, mode):
		"""Shows menu to select a savegame.
		@param mode: 'save' or 'load'
		@return: Path to savegamefile or None"""
		assert mode in ('save', 'load')
		map_files, map_file_display = None, None
		if mode == 'load':
			map_files, map_file_display = SavegameManager.get_saves()
			if len(map_files) == 0:
				self.show_popup(_("No saved games"), _("There are no saved games to load"))
				return
		else: # don't show autosave and quicksave on save
			map_files, map_file_display = SavegameManager.get_regular_saves()

		# Prepare widget
		old_current = self._switch_current_widget('select_savegame')
		self.current.findChild(name='headline').text = _('Save game') if mode == 'save' else _('Load game')

		""" this doesn't work (yet), see http://fife.trac.cvsdude.com/engine/ticket/375
		if mode == 'save': # only show enter_filename on save
			self.current.findChild(name='enter_filename').show()
		else:
			self.current.findChild(name='enter_filename').hide()
		"""

		def tmp_selected_changed():
			"""Fills in the name of the savegame in the textbox when selected in the list"""
			if self.current.collectData('savegamelist') != -1: # check if we actually collect valid data
				self.current.distributeData({'savegamefile' : \
				                             map_file_display[self.current.collectData('savegamelist')]})

		self.current.distributeInitialData({'savegamelist' : map_file_display})
		cb = Callback.ChainedCallbacks(Gui._create_show_savegame_details(self.current, map_files, 'savegamelist'), \
		                               tmp_selected_changed)
		self.current.findChild(name="savegamelist").mapEvents({
		    'savegamelist/action'              : cb,
		    'savegamelist/mouseWheelMovedUp'   : cb,
		    'savegamelist/mouseWheelMovedDown' : cb
		})
		self.current.findChild(name="savegamelist").capture(cb, event_name="keyPressed")

		retval = self.show_dialog(self.current, {
		                                          'okButton'     : True,
		                                          'cancelButton' : False,
		                                          'deleteButton' : 'delete',
		                                          'savegamefile' : True
		                                        },
		                                        onPressEscape = False)
		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
			self._delete_savegame(map_files)
			self.current = old_current
			return self.show_select_savegame(mode=mode)

		selected_savegame = None
		if mode == 'save': # return from textfield
			selected_savegame = self.current.collectData('savegamefile')
			if selected_savegame in map_file_display: # savegamename already exists
				message = _("A savegame with the name \"%s\" already exists. \nShould i overwrite it?") % selected_savegame
				if not self.show_popup(_("Confirmation for overwriting"),message,show_cancel_button = True):
					self.current = old_current
					return self.show_select_savegame(mode=mode) # reshow dialog
		else: # return selected item from list
			selected_savegame = self.current.collectData('savegamelist')
			selected_savegame = None if selected_savegame == -1 else map_files[selected_savegame]
			if selected_savegame is None:
				# ok button has been pressed, but no savegame was selected
				self.show_popup(_("Select a savegame"), _("Please select a savegame or click on cancel."));
				self.current = old_current
				return self.show_select_savegame(mode=mode) # reshow dialog
		self.current = old_current # reuse old widget
		return selected_savegame

# 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()

	def show_dialog(self, dlg, actions, onPressEscape = None, event_map = None):
		"""Shows any pychan dialog.
		@param dlg: dialog that is to be shown
		@param actions: actions that are executed by the dialog { 'ok': callback, 'cancel': callback }
		@param onPressEscape: callback that is to be called if the escape button is pressed
		@param event_map: dictionary with callbacks for buttons. See pychan docu: pychan.widget.mapEvents()
		"""
		self.current_dialog = dlg
		if event_map is not None:
			dlg.mapEvents(event_map)
		if onPressEscape is not None:
			def _escape(event):
				if event.getKey().getValue() == fife.Key.ESCAPE:
					pychan.internal.get_manager().breakFromMainLoop(onPressEscape)
					dlg.hide()
			dlg.capture(_escape, event_name="keyPressed")
		ret = dlg.execute(actions)
		return ret

	def show_popup(self, windowtitle, message, show_cancel_button = False):
		"""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
		@return: True on ok, False on cancel (if no cancel button, always True)
		"""
		popup = self.build_popup(windowtitle, message, show_cancel_button)
		if show_cancel_button:
			return self.show_dialog(popup, {'okButton' : True, 'cancelButton' : False}, onPressEscape = False)
		else:
			return self.show_dialog(popup, {'okButton' : True}, onPressEscape = True)

	def show_error_popup(self, windowtitle, description, advice=None, details=None):
		"""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

		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 += _(u"Details:") + u" " + details
		self.show_popup( _(u"Error:") + u" " + windowtitle, msg, show_cancel_button=False)

	def build_popup(self, windowtitle, message, show_cancel_button = False):
		""" 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
		@return: Container(name='popup_window') with buttons 'okButton' and optionally 'cancelButton'
		"""
		# 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.
		if show_cancel_button:
			self.widgets.reload('popup_with_cancel')
			popup = self.widgets['popup_with_cancel']
		else:
			self.widgets.reload('popup')
			popup = self.widgets['popup']
		headline = popup.findChild(name='headline')
		# just to be safe, the gettext-function is used twice,
		# once on the original, once on the unicode string.
		headline.text = _(_(windowtitle))
		popup.findChild(name='popup_message').text = _(_(message))
		popup.adaptLayout() # recalculate widths
		return popup

	def show_loading_screen(self):
		self._switch_current_widget('loadingscreen', center=True, show=True)

# 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)
			old.hide()
		self.log.debug("Gui: setting current to %s", new_widget)
		self.current = self.widgets[new_widget]
		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"""
			box = gui.findChild(name="savegamedetails_box")
			old_label = box.findChild(name="savegamedetails_lbl")
			if old_label is not None:
				box.removeChild(old_label)
			map_file = None
			try:
				map_file = map_files[gui.collectData(savegamelist)]
			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)
			details_label = pychan.widgets.Label(min_size=(140, 0), max_size=(140, 290), wrap_text=True)
			details_label.name = "savegamedetails_lbl"
			details_label.text = u""
			if savegame_info['timestamp'] == -1:
				details_label.text += _("Unknown savedate\n")
			else:
				details_label.text += _("Saved at %s\n") % \
										time.strftime("%H:%M, %A, %B %d", time.localtime(savegame_info['timestamp']))
			counter = savegame_info['savecounter']
			# N_ takes care of plural forms for different languages
			details_label.text += N_("Saved %(counter)d time\n", \
			                         "Saved %(counter)d times\n", \
			                         counter) % {'counter':counter}
			details_label.stylize('book_t')

			from horizons.constants import VERSION
			try:
				if savegame_info['savegamerev'] == VERSION.SAVEGAMEREVISION:
					details_label.text += _("Savegame ver. %d") % ( savegame_info['savegamerev'] )
				else:
					details_label.text += _("WARNING: Incompatible ver. %(ver)d!\nNeed ver. %(need)d!") \
					             % {'ver' : savegame_info['savegamerev'], 'need' : VERSION.SAVEGAMEREVISION}
			except KeyError:
				details_label.text += _("INCOMPATIBLE VERSION\n")


			box.addChild( details_label )

			"""
			if savegame_info['screenshot']:
				fd, filename = tempfile.mkstemp()
				os.fdopen(fd, "w").write(savegame_info['screenshot'])
				box.addChild( pychan.widgets.Icon(image=filename) )
			"""

			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]
		message = _('Do you really want to delete the savegame "%s"?') % \
		             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
コード例 #8
0
ファイル: ingamegui.py プロジェクト: mitfik/unknown-horizons
	def __init__(self, session, gui):
		super(IngameGui, self).__init__()
		self.session = session
		self.main_gui = gui
		self.widgets = {}
		self.tabwidgets = {}
		self.settlement = None
		self.resource_source = None
		self.resources_needed, self.resources_usable = {}, {}
		self._old_menu = None

		self.widgets = LazyWidgetsDict(self.styles, center_widgets=False)

		cityinfo = self.widgets['city_info']
		cityinfo.child_finder = PychanChildFinder(cityinfo)
		cityinfo.position_technique = "center-10:top+5"

		self.logbook = LogBook()
		self.logbook.add_pause_request_listener(Callback(self.session.speed_pause))
		self.logbook.add_unpause_request_listener(Callback(self.session.speed_unpause))
		self.players_overview = PlayersOverview(self.session)
		self.scenario_chooser = ScenarioChooser(self.session)

		# self.widgets['minimap'] is the guichan gui around the actual minimap,
		# which is saved in self.minimap

		minimap = self.widgets['minimap']
		minimap.position_technique = "right-20:top+4"
		minimap.show()

		minimap_rect = Rect.init_from_topleft_and_size(minimap.position[0]+77, 52, 120, 117)

		self.minimap = Minimap(minimap_rect, self.session, \
		                       self.session.view.renderer['GenericRenderer'])
		minimap.mapEvents({
			'zoomIn' : self.session.view.zoom_in,
			'zoomOut' : self.session.view.zoom_out,
			'rotateRight' : Callback.ChainedCallbacks(self.session.view.rotate_right, self.minimap.rotate_right),
			'rotateLeft' : Callback.ChainedCallbacks(self.session.view.rotate_left, self.minimap.rotate_left),
			'speedUp' : self.session.speed_up,
			'speedDown' : self.session.speed_down
		})

		minimap_overlay = minimap.findChild(name='minimap_overlay_image')

		self.minimap.use_overlay_icon(minimap_overlay)

		self.widgets['menu_panel'].position_technique = "right+15:top+153"
		self.widgets['menu_panel'].show()
		self.widgets['menu_panel'].mapEvents({
			'destroy_tool' : self.session.destroy_tool,
			'build' : self.show_build_menu,
			'diplomacyButton' : self.show_diplomacy_menu,
			'gameMenuButton' : self.main_gui.toggle_pause,
			'logbook' : self.logbook.toggle_visibility
		})

		self.widgets['tooltip'].hide()

		self.widgets['status'].child_finder = PychanChildFinder(self.widgets['status'])
		self.widgets['status_extra'].child_finder = PychanChildFinder(self.widgets['status_extra'])

		self.message_widget = MessageWidget(self.session, \
		                                    cityinfo.position[0] + cityinfo.size[0], 5)
		self.widgets['status_gold'].show()
		self.widgets['status_gold'].child_finder = PychanChildFinder(self.widgets['status_gold'])
		self.widgets['status_extra_gold'].child_finder = PychanChildFinder(self.widgets['status_extra_gold'])

		# map button names to build functions calls with the building id
		self.callbacks_build = {}
		for id,button_name,settler_level in horizons.main.db.get_building_id_buttonname_settlerlvl():
			if not settler_level in self.callbacks_build:
				self.callbacks_build[settler_level] = {}
			self.callbacks_build[settler_level][button_name] = Callback(self._build, id)
コード例 #9
0
ファイル: ingamegui.py プロジェクト: mitfik/unknown-horizons
class IngameGui(LivingObject):
	"""Class handling all the ingame gui events.
	Assumes that only 1 instance is used (class variables)"""

	gui = livingProperty()
	tabwidgets = livingProperty()
	message_widget = livingProperty()
	minimap = livingProperty()

	styles = {
		'city_info' : 'city_info',
		'change_name' : 'book',
		'chat' : 'book',
		'status'            : 'resource_bar',
		'status_gold'       : 'resource_bar',
		'status_extra'      : 'resource_bar',
		'status_extra_gold' : 'resource_bar',
	  }

	def __init__(self, session, gui):
		super(IngameGui, self).__init__()
		self.session = session
		self.main_gui = gui
		self.widgets = {}
		self.tabwidgets = {}
		self.settlement = None
		self.resource_source = None
		self.resources_needed, self.resources_usable = {}, {}
		self._old_menu = None

		self.widgets = LazyWidgetsDict(self.styles, center_widgets=False)

		cityinfo = self.widgets['city_info']
		cityinfo.child_finder = PychanChildFinder(cityinfo)
		cityinfo.position_technique = "center-10:top+5"

		self.logbook = LogBook()
		self.logbook.add_pause_request_listener(Callback(self.session.speed_pause))
		self.logbook.add_unpause_request_listener(Callback(self.session.speed_unpause))
		self.players_overview = PlayersOverview(self.session)
		self.scenario_chooser = ScenarioChooser(self.session)

		# self.widgets['minimap'] is the guichan gui around the actual minimap,
		# which is saved in self.minimap

		minimap = self.widgets['minimap']
		minimap.position_technique = "right-20:top+4"
		minimap.show()

		minimap_rect = Rect.init_from_topleft_and_size(minimap.position[0]+77, 52, 120, 117)

		self.minimap = Minimap(minimap_rect, self.session, \
		                       self.session.view.renderer['GenericRenderer'])
		minimap.mapEvents({
			'zoomIn' : self.session.view.zoom_in,
			'zoomOut' : self.session.view.zoom_out,
			'rotateRight' : Callback.ChainedCallbacks(self.session.view.rotate_right, self.minimap.rotate_right),
			'rotateLeft' : Callback.ChainedCallbacks(self.session.view.rotate_left, self.minimap.rotate_left),
			'speedUp' : self.session.speed_up,
			'speedDown' : self.session.speed_down
		})

		minimap_overlay = minimap.findChild(name='minimap_overlay_image')

		self.minimap.use_overlay_icon(minimap_overlay)

		self.widgets['menu_panel'].position_technique = "right+15:top+153"
		self.widgets['menu_panel'].show()
		self.widgets['menu_panel'].mapEvents({
			'destroy_tool' : self.session.destroy_tool,
			'build' : self.show_build_menu,
			'diplomacyButton' : self.show_diplomacy_menu,
			'gameMenuButton' : self.main_gui.toggle_pause,
			'logbook' : self.logbook.toggle_visibility
		})

		self.widgets['tooltip'].hide()

		self.widgets['status'].child_finder = PychanChildFinder(self.widgets['status'])
		self.widgets['status_extra'].child_finder = PychanChildFinder(self.widgets['status_extra'])

		self.message_widget = MessageWidget(self.session, \
		                                    cityinfo.position[0] + cityinfo.size[0], 5)
		self.widgets['status_gold'].show()
		self.widgets['status_gold'].child_finder = PychanChildFinder(self.widgets['status_gold'])
		self.widgets['status_extra_gold'].child_finder = PychanChildFinder(self.widgets['status_extra_gold'])

		# map button names to build functions calls with the building id
		self.callbacks_build = {}
		for id,button_name,settler_level in horizons.main.db.get_building_id_buttonname_settlerlvl():
			if not settler_level in self.callbacks_build:
				self.callbacks_build[settler_level] = {}
			self.callbacks_build[settler_level][button_name] = Callback(self._build, id)

	def end(self):
		self.widgets['menu_panel'].mapEvents({
			'destroy_tool' : None,
			'build' : None,
			'diplomacyButton' : None,
			'gameMenuButton' : None
		})

		self.widgets['minimap'].mapEvents({
			'zoomIn' : None,
			'zoomOut' : None,
			'rotateRight' : None,
			'rotateLeft' : None
		})

		for w in self.widgets.itervalues():
			if w.parent is None:
				w.hide()
		self.message_widget = None
		self.tabwidgets = None
		self.minimap = None
		self.hide_menu()
		super(IngameGui, self).end()

	def update_gold(self):
		first = str(self.session.world.player.inventory[RES.GOLD_ID])
		lines = []
		show = False
		if self.resource_source is not None and self.resources_needed.get(RES.GOLD_ID, 0) != 0:
			show = True
			lines.append('- ' + str(self.resources_needed[RES.GOLD_ID]))
		self.status_set('gold', first)
		self.status_set_extra('gold',lines)
		self.set_status_position('gold')
		if show:
			self.widgets['status_extra_gold'].show()
		else:
			self.widgets['status_extra_gold'].hide()

	def status_set(self, label, value):
		"""Sets a value on the status bar (available res of the settlement).
		@param label: str containing the name of the label to be set.
		@param value: value the Label is to be set to.
		"""
		if isinstance(value,list):
			value = value[0]
		gui = self.widgets['status_gold'] if label == 'gold' else self.widgets['status']
		foundlabel = gui.child_finder(label + '_1')
		foundlabel._setText(unicode(value))
		foundlabel.resizeToContent()
		gui.resizeToContent()

	def status_set_extra(self,label,value):
		"""Sets a value on the extra status bar. (below normal status bar, needed res for build)
		@param label: str containing the name of the label to be set.
		@param value: value the Label is to be set to.
		"""
		bg_icon_gold = "content/gui/images/background/widgets/res_mon_extra_bg.png"
		bg_icon_res = "content/gui/images/background/widgets/res_extra_bg.png"
		if not hasattr(self, "bg_icon_pos"):
			self.bg_icon_pos = {'gold':(14,83), 'food':(0,6), 'tools':(52,6), 'boards':(104,6), 'bricks':(156,6), 'textiles':(207,6)}
			self.bgs_shown = {}
		bg_icon = pychan.widgets.Icon(image=bg_icon_gold if label == 'gold' else bg_icon_res, \
		                              position=self.bg_icon_pos[label], name='bg_icon_' + label)

		if not value:
			foundlabel = (self.widgets['status_extra_gold'] if label == 'gold' else self.widgets['status_extra']).child_finder(label + '_' + str(2))
			foundlabel.text = u''
			foundlabel.resizeToContent()
			if label in self.bgs_shown:
				(self.widgets['status_extra_gold'] if label == 'gold' else self.widgets['status_extra']).removeChild(self.bgs_shown[label])
				del self.bgs_shown[label]
			self.widgets['status_extra_gold'].resizeToContent() if label == 'gold' else self.widgets['status_extra'].resizeToContent()
			return
		if isinstance(value, str):
			value = [value]
		#for i in xrange(len(value), 3):
		#	value.append("")

		if (self.widgets['status_extra_gold'] if label == 'gold' else self.widgets['status_extra']).findChild(name='bg_icon_' + label) is None:
			(self.widgets['status_extra_gold'] if label == 'gold' else self.widgets['status_extra']).insertChild(bg_icon, 0)
			self.bgs_shown[label] = bg_icon

		for i in xrange(0,len(value)):
			foundlabel = (self.widgets['status_extra_gold'] if label == 'gold' else self.widgets['status_extra']).child_finder(name=label + '_' + str(i+2))
			foundlabel._setText(unicode(value[i]))
			foundlabel.resizeToContent()
		if label == 'gold':
			self.widgets['status_extra_gold'].resizeToContent()
		else:
			self.widgets['status_extra'].resizeToContent()

	def cityinfo_set(self, settlement):
		"""Sets the city name at top center

		Show/Hide is handled automatically
		To hide cityname, set name to ''
		@param settlement: Settlement class providing the information needed
		"""
		if settlement is self.settlement:
			return
		if self.settlement is not None:
			self.settlement.remove_change_listener(self.update_settlement)
		self.settlement = settlement
		if settlement is None:
			self.widgets['city_info'].hide()
		else:
			self.widgets['city_info'].show()
			self.update_settlement()
			settlement.add_change_listener(self.update_settlement)

	def resourceinfo_set(self, source, res_needed = None, res_usable = None, res_from_ship = False):
		city = source if not res_from_ship else None
		self.cityinfo_set(city)
		if source is not self.resource_source:
			if self.resource_source is not None:
				self.resource_source.remove_change_listener(self.update_resource_source)
			if source is None or self.session.world.player != source.owner:
				self.widgets['status'].hide()
				self.widgets['status_extra'].hide()
				self.resource_source = None
				self.update_gold()
		if source is not None and self.session.world.player == source.owner:
			if source is not self.resource_source:
				source.add_change_listener(self.update_resource_source)
			self.resource_source = source
			self.resources_needed = {} if not res_needed else res_needed
			self.resources_usable = {} if not res_usable else res_usable
			self.update_resource_source()
			self.widgets['status'].show()

	def update_settlement(self):
		cityinfo = self.widgets['city_info']
		cityinfo.mapEvents({
			'city_name': Callback(self.show_change_name_dialog, self.settlement)
			})
		foundlabel = cityinfo.child_finder('city_name')
		foundlabel._setText(unicode(self.settlement.name))
		foundlabel.resizeToContent()
		foundlabel = self.widgets['city_info'].child_finder('city_inhabitants')
		foundlabel.text = unicode(' '+str(self.settlement.inhabitants))
		foundlabel.resizeToContent()
		self.widgets['city_info'].resizeToContent()

	def update_resource_source(self):
		"""Sets the values for resource status bar as well as the building costs"""
		self.update_gold()
		for res_id, res_name in {3 : 'textiles', 4 : 'boards', 5 : 'food', 6 : 'tools', 7 : 'bricks'}.iteritems():
			first = str(self.resource_source.inventory[res_id])
			lines = []
			show = False
			if self.resources_needed.get(res_id, 0) != 0:
				show = True
				lines.append('- ' + str(self.resources_needed[res_id]))
			self.status_set(res_name, first)
			self.status_set_extra(res_name,lines)
			self.set_status_position(res_name)
			if show:
				self.widgets['status_extra'].show()

	def ship_build(self, ship):
		"""Calls the Games build_object class."""
		self._build(1, ship)

	def minimap_to_front(self):
		self.widgets['minimap'].hide()
		self.widgets['minimap'].show()
		self.widgets['menu_panel'].hide()
		self.widgets['menu_panel'].show()

	def show_diplomacy_menu(self):
		# check if the menu is already shown
		if hasattr(self.get_cur_menu(), 'name') and self.get_cur_menu().name == "diplomacy_widget":
			self.hide_menu()
			return
		players = self.session.world.players
		local_player = self.session.world.player
		dtabs = []
		for player in players + [self.session.world.pirate]:
			if player is not local_player:
				dtabs.append(DiplomacyTab(player))
		tab = TabWidget(self, tabs=dtabs, name="diplomacy_widget")
		self.show_menu(tab)

	def show_multi_select_tab(self):
		tab = TabWidget(self, tabs = [SelectMultiTab(self.session)], name = 'select_multi')
		self.show_menu(tab)

	def show_build_menu(self, update=False):
		"""
		@param update: set when build possiblities change (e.g. after settler upgrade)
		"""
		# check if build menu is already shown
		if hasattr(self.get_cur_menu(), 'name') and self.get_cur_menu().name == "build_menu_tab_widget":
			self.hide_menu()

			if not update: # this was only a toggle call, don't reshow
				return

		self.session.cursor = SelectionTool(self.session) # set cursor for build menu
		self.deselect_all()
		btabs = [BuildTab(index, self.callbacks_build[index]) for index in \
		         range(0, self.session.world.player.settler_level+1)]
		tab = TabWidget(self, tabs=btabs, name="build_menu_tab_widget", \
								    active_tab=BuildTab.last_active_build_tab)
		self.show_menu(tab)

	def deselect_all(self):
		for instance in self.session.selected_instances:
			instance.deselect()
		self.session.selected_instances.clear()

	def _build(self, building_id, unit = None):
		"""Calls the games buildingtool class for the building_id.
		@param building_id: int with the building id that is to be built.
		@param unit: weakref to the unit, that builds (e.g. ship for branch office)"""
		self.hide_menu()
		self.deselect_all()
		cls = Entities.buildings[building_id]
		if hasattr(cls, 'show_build_menu'):
			cls.show_build_menu()
		self.session.cursor = BuildingTool(self.session, cls, None if unit is None else unit())

	def _get_menu_object(self, menu):
		"""Returns pychan object if menu is a string, else returns menu
		@param menu: str with the guiname or pychan object.
		"""
		if isinstance(menu, str):
			menu = self.widgets[menu]
		return menu

	def get_cur_menu(self):
		"""Returns menu that is currently displayed"""
		return self._old_menu

	def show_menu(self, menu):
		"""Shows a menu
		@param menu: str with the guiname or pychan object.
		"""
		if self._old_menu is not None:
			self._old_menu.hide()

		self._old_menu = self._get_menu_object(menu)
		if self._old_menu is not None:
			self._old_menu.show()
			self.minimap_to_front()

	def hide_menu(self):
		self.show_menu(None)

	def toggle_menu(self, menu):
		"""Shows a menu or hides it if it is already displayed.
		@param menu: parameter supported by show_menu().
		"""
		if self.get_cur_menu() == self._get_menu_object(menu):
			self.hide_menu()
		else:
			self.show_menu(menu)

	def build_load_tab(self, num):
		"""Loads a subcontainer into the build menu and changes the tabs background.
		@param num: number representing the tab to load.
		"""
		tab1 = self.widgets['build'].findChild(name=('tab'+str(self.active_build)))
		tab2 = self.widgets['build'].findChild(name=('tab'+str(num)))
		activetabimg, nonactiveimg= tab1._getImage(), tab2._getImage()
		tab1._setImage(nonactiveimg)
		tab2._setImage(activetabimg)
		contentarea = self.widgets['build'].findChild(name='content')
		contentarea.removeChild(self.widgets['build_tab'+str(self.active_build)])
		contentarea.addChild(self.widgets['build_tab'+str(num)])
		contentarea.adaptLayout()
		self.active_build = num

	def set_status_position(self, resource_name):
		icon_name = resource_name + '_icon'
		for i in xrange(1, 3):
			lbl_name = resource_name + '_' + str(i)
			# tools_1 = inventory amount, tools_2 = cost of to-be-built building
			if resource_name == 'gold':
				self._set_label_position('status_gold', lbl_name, icon_name, 33, 31 + i*20)
			else:
				self._set_label_position('status', lbl_name, icon_name, 24, 31 + i*20)

	def _set_label_position(self, widget, lbl_name, icon_name, xoffset, yoffset):
		icon  = self.widgets[widget].child_finder(icon_name)
		label = self.widgets[widget].child_finder(lbl_name)
		label.position = (icon.position[0] - label.size[0]/2 + xoffset, yoffset)

	def save(self, db):
		self.message_widget.save(db)
		self.logbook.save(db)

	def load(self, db):
		self.message_widget.load(db)
		self.logbook.load(db)

		self.minimap.draw() # update minimap to new world

	def show_change_name_dialog(self, instance):
		"""Shows a dialog where the user can change the name of a NamedObject.
		The game gets paused while the dialog is executed."""
		self.session.speed_pause()
		events = {
			'okButton': Callback(self.change_name, instance),
			'cancelButton': self._hide_change_name_dialog
		}
		self.main_gui.on_escape = self._hide_change_name_dialog
		changename = self.widgets['change_name']
		newname = changename.findChild(name='new_name')
		changename.mapEvents(events)
		newname.capture(Callback(self.change_name, instance))
		changename.show()
		newname.requestFocus()

	def _hide_change_name_dialog(self):
		"""Escapes the change_name dialog"""
		self.session.speed_unpause()
		self.main_gui.on_escape = self.main_gui.toggle_pause
		self.widgets['change_name'].hide()

	def change_name(self, instance):
		"""Applies the change_name dialogs input and hides it"""
		new_name = self.widgets['change_name'].collectData('new_name')
		self.widgets['change_name'].findChild(name='new_name').text = u''
		if not (len(new_name) == 0 or new_name.isspace()):
			RenameObject(instance, new_name).execute(self.session)
		self._hide_change_name_dialog()

	def on_escape(self):
		if self.logbook.is_visible():
			self.logbook.hide()
		else:
			return False
		return True

	def display_game_speed(self, text):
		"""
		@param text: unicode string to display as speed value
		"""
		wdg = self.widgets['minimap'].findChild(name="speed_text")
		wdg.text = text
		wdg.resizeToContent()
		self.widgets['minimap'].show()

	def _player_settler_level_change_listener(self):
		"""Gets called when the player changes"""
		menu = self.get_cur_menu()
		if hasattr(menu, "name"):
			if menu.name == "build_menu_tab_widget":
				# player changed and build menu is currently displayed
				self.show_build_menu(update=True)

	def show_chat_dialog(self):
		"""Show a dialog where the user can enter a chat message"""
		events = {
			'okButton': self._do_chat,
			'cancelButton': self._hide_chat_dialog
		}
		self.main_gui.on_escape = self._hide_chat_dialog

		self.widgets['chat'].mapEvents(events)
		self.widgets['chat'].findChild(name='msg').capture( self._do_chat )
		self.widgets['chat'].show()
		self.widgets['chat'].findChild(name="msg").requestFocus()

	def _hide_chat_dialog(self):
		"""Escapes the chat dialog"""
		self.main_gui.on_escape = self.main_gui.toggle_pause
		self.widgets['chat'].hide()

	def _do_chat(self):
		"""Actually initiates chatting and hides the dialog"""
		msg = self.widgets['chat'].findChild(name='msg').text
		Chat(msg).execute(self.session)
		self.widgets['chat'].findChild(name='msg').text = u''
		self._hide_chat_dialog()