Example #1
0
    def generate_minimap(cls, size, parameters):
        """Called as subprocess, calculates minimap data and passes it via string via stdout"""
        # called as standalone basically, so init everything we need
        from horizons.main import _create_main_db
        from horizons.entities import Entities
        from horizons.ext.dummy import Dummy

        db = _create_main_db()
        Entities.load_grounds(db, load_now=False)  # create all references
        map_file = SingleplayerMenu._generate_random_map(parameters)
        world = cls._load_raw_world(map_file)
        location = Rect.init_from_topleft_and_size_tuples((0, 0), size)
        minimap = Minimap(
            location,
            session=None,
            view=None,
            world=world,
            targetrenderer=Dummy(),
            imagemanager=Dummy(),
            cam_border=False,
            use_rotation=False,
            preview=True,
        )
        # communicate via stdout
        print minimap.dump_data()
Example #2
0
    def remove_settlement(self, building):
        """Removes the settlement property from tiles within the radius of the given building"""
        settlement = building.settlement
        buildings_to_abandon, settlement_coords_to_change = Tear.additional_removals_after_tear(
            building)
        assert building not in buildings_to_abandon
        self.abandon_buildings(buildings_to_abandon)

        flat_land_set = self.terrain_cache.cache[TerrainRequirement.LAND][(1,
                                                                           1)]
        land_or_coast = self.terrain_cache.land_or_coast
        settlement_tiles_changed = []
        clean_coords = set()
        for coords in settlement_coords_to_change:
            tile = self.ground_map[coords]
            tile.settlement = None
            building = tile.object
            if building is not None:
                settlement.remove_building(building)
                building.owner = None
                building.settlement = None
            if coords in land_or_coast:
                clean_coords.add(coords)
            settlement_tiles_changed.append(self.ground_map[coords])
            del settlement.ground_map[coords]
            Minimap.update(coords)
            if coords in flat_land_set:
                self.available_flat_land += 1
        self.available_land_cache.add_area(clean_coords)

        self._register_change()
        if self.terrain_cache:
            settlement.buildability_cache.modify_area(clean_coords)

        SettlementRangeChanged.broadcast(settlement, settlement_tiles_changed)
Example #3
0
	def assign_settlement(self, position, radius, settlement):
		"""Assigns the settlement property to tiles within the circle defined by \
		position and radius.
		@param position: Rect
		@param radius:
		@param settlement:
		"""
		for coord in position.get_radius_coordinates(radius, include_self=True):
			tile = self.get_tile_tuple(coord)
			if tile is not None:
				if tile.settlement == settlement:
					continue
				if tile.settlement is None:
					tile.settlement = settlement
					settlement.ground_map[coord] = tile
					Minimap.update(coord)
					self._register_change(coord[0], coord[1])

					# notify all AI players when land ownership changes
					for player in self.session.world.players:
						if hasattr(player, 'on_settlement_expansion'):
							player.on_settlement_expansion(settlement, coord)

				building = tile.object
				# found a new building, that is now in settlement radius
				# assign buildings on tiles to settlement
				if building is not None and building.settlement is None and \
				   building.island == self: # don't steal from other islands
					building.settlement = settlement
					building.owner = settlement.owner
					settlement.add_building(building)
Example #4
0
    def set_tile(self, coords, tile_details):
        if coords not in self.world.full_map:
            return

        old_tile = self.world.full_map[coords]
        if old_tile and old_tile.id != -1 and old_tile._instance and old_tile not in self._tile_delete_set:
            if (old_tile.id, old_tile.shape,
                    old_tile.rotation) == tile_details:
                return
            self._tile_delete_set.add(old_tile)
            Scheduler().add_new_object(Callback(self._delete_tile_instance,
                                                old_tile),
                                       self,
                                       run_in=0)

        (ground_id, shape, rotation) = tile_details
        if ground_id != 0:
            ground = Entities.grounds['{:d}-{}'.format(ground_id,
                                                       shape)](self.session,
                                                               *coords)
            ground.act(rotation)
            self.world.full_map[coords] = ground
        else:
            self.world.full_map[coords] = self.world.fake_tile_map[coords]
        Minimap.update(coords)

        # update cam, that's necessary because of the static layer WATER
        self.session.view.cam.refresh()
Example #5
0
	def remove_settlement(self, building):
		"""Removes the settlement property from tiles within the radius of the given building"""
		settlement = building.settlement
		buildings_to_abandon, settlement_coords_to_change = Tear.additional_removals_after_tear(building)
		assert building not in buildings_to_abandon
		self.abandon_buildings(buildings_to_abandon)
		
		flat_land_set = self.terrain_cache.cache[TerrainRequirement.LAND][(1, 1)]
		land_or_coast = self.terrain_cache.land_or_coast
		settlement_tiles_changed = []
		clean_coords = set()
		for coords in settlement_coords_to_change:
			tile = self.ground_map[coords]
			tile.settlement = None
			building = tile.object
			if building is not None:
				settlement.remove_building(building)
				building.owner = None
				building.settlement = None
			if coords in land_or_coast:
				clean_coords.add(coords)
			settlement_tiles_changed.append(self.ground_map[coords])
			del settlement.ground_map[coords]
			Minimap.update(coords)
			if coords in flat_land_set:
				self.available_flat_land += 1
		self.available_land_cache.add_area(clean_coords)

		self._register_change()
		if self.terrain_cache:
			settlement.buildability_cache.modify_area(clean_coords)

		SettlementRangeChanged.broadcast(settlement, settlement_tiles_changed)
Example #6
0
	def assign_settlement(self, position, radius, settlement):
		"""Assigns the settlement property to tiles within the circle defined by \
		position and radius.
		@param position: Rect
		@param radius:
		@param settlement:
		"""
		settlement_coords_changed = []
		for coords in position.get_radius_coordinates(radius, include_self=True):
			if coords not in self.ground_map:
				continue

			tile = self.ground_map[coords]
			if tile.settlement is not None:
				continue

			tile.settlement = settlement
			settlement.ground_map[coords] = tile
			settlement_coords_changed.append(coords)

			building = tile.object
			# In theory fish deposits should never be on the island but this has been
			# possible since they were turned into a 2x2 building. Since they are never
			# entirely on the island then it is easiest to just make it impossible to own
			# fish deposits.
			if building is None or building.id == BUILDINGS.FISH_DEPOSIT:
				continue

			# Assign the entire building to the first settlement that covers some of it.
			assert building.settlement is None or building.settlement is settlement
			for building_coords in building.position.tuple_iter():
				building_tile = self.ground_map[building_coords]
				if building_tile.settlement is not settlement:
					assert building_tile.settlement is None
					building_tile.settlement = settlement
					settlement.ground_map[building_coords] = building_tile
					settlement_coords_changed.append(building_coords)

			building.settlement = settlement
			building.owner = settlement.owner
			settlement.add_building(building)

		if not settlement_coords_changed:
			return

		flat_land_set = self.terrain_cache.cache[TerrainRequirement.LAND][(1, 1)]
		settlement_tiles_changed = []
		for coords in settlement_coords_changed:
			settlement_tiles_changed.append(self.ground_map[coords])
			Minimap.update(coords)
			if coords in flat_land_set:
				self.available_flat_land -= 1
		self.available_land_cache.remove_area(settlement_coords_changed)

		self._register_change()
		if self.terrain_cache:
			settlement.buildability_cache.modify_area(settlement_coords_changed)

		SettlementRangeChanged.broadcast(settlement, settlement_tiles_changed)
Example #7
0
	def assign_settlement(self, position, radius, settlement):
		"""Assigns the settlement property to tiles within the circle defined by \
		position and radius.
		@param position: Rect
		@param radius:
		@param settlement:
		"""
		settlement_coords_changed = []
		for coords in position.get_radius_coordinates(radius, include_self=True):
			if coords not in self.ground_map:
				continue

			tile = self.ground_map[coords]
			if tile.settlement is not None:
				continue

			tile.settlement = settlement
			settlement.ground_map[coords] = tile
			settlement_coords_changed.append(coords)

			building = tile.object
			# In theory fish deposits should never be on the island but this has been
			# possible since they were turned into a 2x2 building. Since they are never
			# entirely on the island then it is easiest to just make it impossible to own
			# fish deposits.
			if building is None or building.id == BUILDINGS.FISH_DEPOSIT:
				continue

			# Assign the entire building to the first settlement that covers some of it.
			assert building.settlement is None or building.settlement is settlement
			for building_coords in building.position.tuple_iter():
				building_tile = self.ground_map[building_coords]
				if building_tile.settlement is not settlement:
					assert building_tile.settlement is None
					building_tile.settlement = settlement
					settlement.ground_map[building_coords] = building_tile
					settlement_coords_changed.append(building_coords)

			building.settlement = settlement
			building.owner = settlement.owner
			settlement.add_building(building)

		if not settlement_coords_changed:
			return

		flat_land_set = self.terrain_cache.cache[TerrainRequirement.LAND][(1, 1)]
		settlement_tiles_changed = []
		for coords in settlement_coords_changed:
			settlement_tiles_changed.append(self.ground_map[coords])
			Minimap.update(coords)
			if coords in flat_land_set:
				self.available_flat_land -= 1
		self.available_land_cache.remove_area(settlement_coords_changed)

		self._register_change()
		if self.terrain_cache:
			settlement.buildability_cache.modify_area(settlement_coords_changed)

		SettlementRangeChanged.broadcast(settlement, settlement_tiles_changed)
Example #8
0
    def __init__(self, session):
        self.session = session

        self.which = None
        self.cursor = None
        self.coordinates_tooltip = None
        self.keylistener = IngameKeyListener(self.session)
        # used by NavigationTool
        LastActivePlayerSettlementManager.create_instance(self.session)

        # Mocks needed to act like the real IngameGui
        self.show_menu = mock.Mock()
        self.hide_menu = mock.Mock()
        # this is necessary for message_widget to work
        self.logbook = mock.Mock()

        self.mainhud = load_uh_widget('minimap.xml')
        self.mainhud.position_technique = "right+0:top+0"

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

        self.mainhud.mapEvents({
            'zoomIn':
            self.session.view.zoom_in,
            'zoomOut':
            self.session.view.zoom_out,
            'rotateRight':
            Callback.ChainedCallbacks(self.session.view.rotate_right,
                                      self.minimap.update_rotation),
            'rotateLeft':
            Callback.ChainedCallbacks(self.session.view.rotate_left,
                                      self.minimap.update_rotation),
            'gameMenuButton':
            self.toggle_pause,
        })

        self.mainhud.show()
        ZoomChanged.subscribe(self._update_zoom)

        # Hide unnecessary buttons in hud
        for widget in ("build", "speedUp", "speedDown", "destroy_tool",
                       "diplomacyButton", "logbook"):
            self.mainhud.findChild(name=widget).hide()

        self.windows = WindowManager()
        self.message_widget = MessageWidget(self.session)
        self.pausemenu = PauseMenu(self.session,
                                   self,
                                   self.windows,
                                   in_editor_mode=True)
        self.help_dialog = HelpDialog(self.windows)
    def _poll_preview_process(self):
        """This will be called regularly to see if the process ended.

		If the process has not yet finished, schedule a new callback to this function.
		Otherwise use the data to update the minimap.
		"""
        if not self._preview_process:
            return

        self._preview_process.poll()

        if self._preview_process.returncode is None:  # not finished
            ExtScheduler().add_new_object(self._poll_preview_process, self,
                                          0.1)
            return
        elif self._preview_process.returncode != 0:
            self._preview_process = None
            self._set_map_preview_status(
                "An unknown error occurred while generating the map preview")
            return

        with open(self._preview_output, 'r') as f:
            data = f.read()
            # Sometimes the subprocess outputs more then the minimap data, e.g. debug
            # information. Since we just read from its stdout, parse out the data that
            # is relevant to us.
            data = re.findall(r'^DATA (\[\[.*\]\]) ENDDATA$', data,
                              re.MULTILINE)[0]
            data = json.loads(data)

        os.unlink(self._preview_output)
        self._preview_process = None

        if self._map_preview:
            self._map_preview.end()

        self._map_preview = Minimap(
            self._gui.findChild(name='map_preview_minimap'),
            session=None,
            view=None,
            world=None,
            targetrenderer=horizons.globals.fife.targetrenderer,
            imagemanager=horizons.globals.fife.imagemanager,
            cam_border=False,
            use_rotation=False,
            tooltip=T("Click to generate a different random map"),
            on_click=self._on_preview_click,
            preview=True)

        self._map_preview.draw_data(data)
        self._set_map_preview_status("")
Example #10
0
    def _poll_preview_process(self):
        """This will be called regularly to see if the process ended.

		If the process has not yet finished, schedule a new callback to this function.
		Otherwise use the data to update the minimap.
		"""
        if not self._preview_process:
            return

        self._preview_process.poll()

        if self._preview_process.returncode is None:  # not finished
            ExtScheduler().add_new_object(self._poll_preview_process, self,
                                          0.1)
            return
        elif self._preview_process.returncode != 0:
            self._preview_process = None
            self._set_map_preview_status(
                u"An unknown error occurred while generating the map preview")
            return

        with open(self._preview_output, 'r') as f:
            data = f.read()

        os.unlink(self._preview_output)
        self._preview_process = None

        if self._map_preview:
            self._map_preview.end()

        self._map_preview = Minimap(
            self._gui.findChild(name='map_preview_minimap'),
            session=None,
            view=None,
            world=None,
            targetrenderer=horizons.globals.fife.targetrenderer,
            imagemanager=horizons.globals.fife.imagemanager,
            cam_border=False,
            use_rotation=False,
            tooltip=_("Click to generate a different random map"),
            on_click=self._on_preview_click,
            preview=True)

        self._map_preview.draw_data(data)
        self._set_map_preview_status(u"")
Example #11
0
	def update_map(self, map_file):
		"""Direct map preview update.
		Only use for existing maps, it's too slow for random maps"""
		if self.minimap is not None:
			self.minimap.end()
		world = self._load_raw_world(map_file)
		self.minimap = Minimap(self._get_map_preview_icon(),
		                       session=None,
		                       view=None,
		                       world=world,
		                       targetrenderer=horizons.globals.fife.targetrenderer,
		                       imagemanager=horizons.globals.fife.imagemanager,
		                       cam_border=False,
		                       use_rotation=False,
		                       tooltip=None,
		                       on_click=None,
		                       preview=True)
		self.minimap.draw()
    def _update_map_preview(self, map_file):
        if self._map_preview:
            self._map_preview.end()

        world = load_raw_world(map_file)
        self._map_preview = Minimap(
            self._gui.findChild(name='map_preview_minimap'),
            session=None,
            view=None,
            world=world,
            targetrenderer=horizons.globals.fife.targetrenderer,
            imagemanager=horizons.globals.fife.imagemanager,
            cam_border=False,
            use_rotation=False,
            tooltip=None,
            on_click=None,
            preview=True)

        self._map_preview.draw()
	def set_tile(self, coords, tile_details):
		if coords not in self.world.full_map:
			return

		old_tile = self.world.full_map[coords]
		if old_tile and old_tile.id != -1 and old_tile._instance and old_tile not in self._tile_delete_set:
			if (old_tile.id, old_tile.shape, old_tile.rotation + 45) == tile_details:
				return
			self._tile_delete_set.add(old_tile)
			Scheduler().add_new_object(Callback(self._delete_tile_instance, old_tile), self, run_in=0)

		(ground_id, shape, rotation) = tile_details
		if ground_id != 0:
			ground = Entities.grounds['%d-%s' % (ground_id, shape)](self.session, *coords)
			ground.act(rotation)
			self.world.full_map[coords] = ground
		else:
			self.world.full_map[coords] = self.world.fake_tile_map[coords]
		Minimap.update(coords)
Example #14
0
	def _init_gui(self):
		"""
		Initial init of gui.
		widgets : list of route entry widgets
		slots : dict with slots for each entry
		"""
		self._gui = load_uh_widget("configure_route.xml", center_widget=True)

		self.widgets = []
		self.slots = {}

		icon = self._gui.findChild(name="minimap")

		self.minimap = Minimap(icon, session=self.session,
		                       world=self.session.world,
		                       view=self.session.view,
		                       targetrenderer=horizons.globals.fife.targetrenderer,
		                       imagemanager=horizons.globals.fife.imagemanager,
		                       cam_border=False,
		                       use_rotation=False,
		                       on_click=self.on_map_click)

		resources = self.session.db.get_res(only_tradeable=True)
		# map an icon for a resource
		# map a resource for an icon
		self.resource_for_icon = {self.dummy_icon_path: 0}
		self.icon_for_resource = {0: self.dummy_icon_path}
		for res_id in resources:
			icon = get_res_icon_path(res_id)
			self.resource_for_icon[icon] = res_id
			self.icon_for_resource[res_id] = icon

		# don't do any actions if the resource menu is shown
		self.resource_menu_shown = False
		for entry in self.instance.route.waypoints:
			self.add_gui_entry(entry['warehouse'], entry['resource_list'])

		self._check_no_entries_label()

		wait_at_unload_box = self._gui.findChild(name="wait_at_unload")
		wait_at_unload_box.marked = self.instance.route.wait_at_unload
		def toggle_wait_at_unload():
			self._route_cmd("set_wait_at_unload", not self.instance.route.wait_at_unload)
		wait_at_unload_box.capture(toggle_wait_at_unload)

		wait_at_load_box = self._gui.findChild(name="wait_at_load")
		wait_at_load_box.marked = self.instance.route.wait_at_load
		def toggle_wait_at_load():
			self._route_cmd("set_wait_at_load", not self.instance.route.wait_at_load)
		wait_at_load_box.capture(toggle_wait_at_load)

		self._gui.mapEvents({
			OkButton.DEFAULT_NAME: self._windows.close,
			'start_route/mouseClicked': self.toggle_route,
		})
        def check_calc_process():
            # checks up on calc process (see below)
            if self.calc_proc is not None:
                state = self.calc_proc.poll()
                if state is None:  # not finished
                    ExtScheduler().add_new_object(check_calc_process, self,
                                                  0.1)
                elif state != 0:
                    self._set_map_preview_status(
                        u"An unknown error occured while generating the map preview"
                    )
                else:  # done

                    data = open(self.calc_proc.output_filename, "r").read()
                    os.unlink(self.calc_proc.output_filename)
                    self.calc_proc = None

                    icon = self._get_map_preview_icon()
                    if icon is None:
                        return  # dialog already gone

                    tooltip = _("Click to generate a different random map")

                    if self.minimap is not None:
                        self.minimap.end()
                    self.minimap = Minimap(
                        icon,
                        session=None,
                        view=None,
                        world=None,
                        targetrenderer=horizons.globals.fife.targetrenderer,
                        imagemanager=horizons.globals.fife.imagemanager,
                        cam_border=False,
                        use_rotation=False,
                        tooltip=tooltip,
                        on_click=on_click,
                        preview=True)
                    self.minimap.draw_data(data)
                    icon.show()
                    self._set_map_preview_status(u"")
	def set_tile(self, coords, tile_details):
		if coords not in self.world.full_map:
			return

		old_tile = self.world.full_map[coords]
		if old_tile and old_tile.id != -1 and old_tile._instance and old_tile not in self._tile_delete_set:
			if (old_tile.id, old_tile.shape, old_tile.rotation) == tile_details:
				return
			self._tile_delete_set.add(old_tile)
			Scheduler().add_new_object(Callback(self._delete_tile_instance, old_tile), self, run_in=0)

		(ground_id, shape, rotation) = tile_details
		if ground_id != 0:
			ground = Entities.grounds['{:d}-{}'.format(ground_id, shape)](self.session, *coords)
			ground.act(rotation)
			self.world.full_map[coords] = ground
		else:
			self.world.full_map[coords] = self.world.fake_tile_map[coords]
		Minimap.update(coords)

		# update cam, that's necessary because of the static layer WATER
		self.session.view.cam.refresh()
def generate_random_minimap(size, parameters):
	"""Called as subprocess, calculates minimap data and passes it via string via stdout"""
	# called as standalone basically, so init everything we need
	from horizons.entities import Entities
	from horizons.ext.dummy import Dummy
	from horizons.main import _create_main_db

	if not VERSION.IS_DEV_VERSION:
		# Hack enable atlases.
		# Usually the minimap generator uses single tile files, but in release
		# mode these are not available. Therefor we have to hackenable atlases
		# for the minimap generation in this case. This forces the game to use
		# the correct imageloader
		# In normal dev mode + enabled atlases we ignore this and just continue
		# to use single tile files instead of atlases for the minimap generation.
		# These are always available in dev checkouts
		PATHS.DB_FILES = PATHS.DB_FILES + (PATHS.ATLAS_DB_PATH, )

	db = _create_main_db()
	horizons.globals.db = db
	horizons.globals.fife.init_animation_loader(not VERSION.IS_DEV_VERSION)
	Entities.load_grounds(db, load_now=False) # create all references

	map_file = generate_random_map(*parameters)
	world = load_raw_world(map_file)
	location = Rect.init_from_topleft_and_size_tuples((0, 0), size)
	minimap = Minimap(
		location,
		session=None,
		view=None,
		world=world,
		targetrenderer=Dummy(),
		imagemanager=Dummy(),
		cam_border=False,
		use_rotation=False,
		preview=True)

	# communicate via stdout
	print minimap.dump_data()
def generate_random_minimap(size, parameters):
	"""Called as subprocess, calculates minimap data and passes it via string via stdout"""
	# called as standalone basically, so init everything we need
	from horizons.entities import Entities
	from horizons.ext.dummy import Dummy
	from horizons.main import _create_main_db

	if not VERSION.IS_DEV_VERSION:
		# Hack enable atlases.
		# Usually the minimap generator uses single tile files, but in release
		# mode these are not available. Therefor we have to hackenable atlases
		# for the minimap generation in this case. This forces the game to use
		# the correct imageloader
		# In normal dev mode + enabled atlases we ignore this and just continue
		# to use single tile files instead of atlases for the minimap generation.
		# These are always available in dev checkouts
		PATHS.DB_FILES = PATHS.DB_FILES + (PATHS.ATLAS_DB_PATH, )

	db = _create_main_db()
	horizons.globals.db = db
	horizons.globals.fife.init_animation_loader(not VERSION.IS_DEV_VERSION)
	Entities.load_grounds(db, load_now=False) # create all references

	map_file = generate_random_map(*parameters)
	world = load_raw_world(map_file)
	location = Rect.init_from_topleft_and_size_tuples((0, 0), size)
	minimap = Minimap(
		location,
		session=None,
		view=None,
		world=world,
		targetrenderer=Dummy(),
		imagemanager=Dummy(),
		cam_border=False,
		use_rotation=False,
		preview=True)

	# communicate via stdout
	print minimap.dump_data()
 def generate_minimap(cls, size, parameters):
     """Called as subprocess, calculates minimap data and passes it via string via stdout"""
     # called as standalone basically, so init everything we need
     from horizons.main import _create_main_db
     from horizons.entities import Entities
     from horizons.ext.dummy import Dummy
     db = _create_main_db()
     Entities.load_grounds(db, load_now=False)  # create all references
     map_file = SingleplayerMenu._generate_random_map(parameters)
     world = cls._load_raw_world(map_file)
     location = Rect.init_from_topleft_and_size_tuples((0, 0), size)
     minimap = Minimap(location,
                       session=None,
                       view=None,
                       world=world,
                       targetrenderer=Dummy(),
                       imagemanager=Dummy(),
                       cam_border=False,
                       use_rotation=False,
                       preview=True)
     # communicate via stdout
     print minimap.dump_data()
Example #20
0
    def assign_settlement(self, position, radius, settlement):
        """Assigns the settlement property to tiles within the circle defined by \
		position and radius.
		@param position: Rect
		@param radius:
		@param settlement:
		"""
        settlement_tiles_changed = []
        for coord in position.get_radius_coordinates(radius,
                                                     include_self=True):
            tile = self.get_tile_tuple(coord)
            if tile is not None:
                if tile.settlement == settlement:
                    continue
                if tile.settlement is None:
                    tile.settlement = settlement
                    settlement.ground_map[coord] = tile
                    Minimap.update(coord)
                    self._register_change(coord[0], coord[1])
                    settlement_tiles_changed.append(tile)

                    # notify all AI players when land ownership changes
                    for player in self.session.world.players:
                        if hasattr(player, 'on_settlement_expansion'):
                            player.on_settlement_expansion(settlement, coord)

                building = tile.object
                # found a new building, that is now in settlement radius
                # assign buildings on tiles to settlement
                if building is not None and building.settlement is None and \
                   building.island == self: # don't steal from other islands
                    building.settlement = settlement
                    building.owner = settlement.owner
                    settlement.add_building(building)

        if settlement_tiles_changed:
            SettlementRangeChanged.broadcast(settlement,
                                             settlement_tiles_changed)
Example #21
0
    def set_tile(self, coords, tile_details):
        if coords not in self.world.full_map:
            return

        old_tile = self.world.full_map[coords]
        if old_tile and old_tile.id != -1 and old_tile._instance and old_tile not in self._tile_delete_set:
            if (old_tile.id, old_tile.shape,
                    old_tile.rotation + 45) == tile_details:
                return
            self._tile_delete_set.add(old_tile)
            Scheduler().add_new_object(Callback(self._delete_tile_instance,
                                                old_tile),
                                       self,
                                       run_in=0)

        (ground_id, shape, rotation) = tile_details
        if ground_id != 0:
            ground = Entities.grounds['%d-%s' % (ground_id, shape)](
                self.session, *coords)
            ground.act(rotation)
            self.world.full_map[coords] = ground
        else:
            self.world.full_map[coords] = self.world.fake_tile_map[coords]
        Minimap.update(coords)
Example #22
0
	def remove_settlement(self, position, radius, settlement):
		"""Removes the settlement property from tiles within the circle defined by \
		position and radius.
		@param position: Rect
		@param radius:
		@param settlement:
		"""
		buildings_to_abandon, settlement_coords_to_change = Tear.destroyable_buildings(position, settlement)
		self.abandon_buildings(buildings_to_abandon)
		
		flat_land_set = self.terrain_cache.cache[TerrainRequirement.LAND][(1, 1)]
		land_or_coast = self.terrain_cache.land_or_coast
		settlement_tiles_changed = []
		clean_coords = set()
		for coords in settlement_coords_to_change:
			tile = self.ground_map[coords]
			tile.settlement = None
			building = tile.object
			if building is not None:
				settlement.remove_building(building)
				building.owner = None
				building.settlement = None
			if coords in land_or_coast:
				clean_coords.add(coords)
			settlement_tiles_changed.append(self.ground_map[coords])
			del settlement.ground_map[coords]
			Minimap.update(coords)
			if coords in flat_land_set:
				self.available_flat_land += 1
		self.available_land_cache.add_area(clean_coords)

		self._register_change()
		if self.terrain_cache:
			settlement.buildability_cache.modify_area(clean_coords)

		SettlementRangeChanged.broadcast(settlement, settlement_tiles_changed)
	def _poll_preview_process(self):
		"""This will be called regularly to see if the process ended.

		If the process has not yet finished, schedule a new callback to this function.
		Otherwise use the data to update the minimap.
		"""
		if not self._preview_process:
			return

		self._preview_process.poll()

		if self._preview_process.returncode is None: # not finished
			ExtScheduler().add_new_object(self._poll_preview_process, self, 0.1)
			return
		elif self._preview_process.returncode != 0:
			self._preview_process = None
			self._set_map_preview_status("An unknown error occurred while generating the map preview")
			return

		with open(self._preview_output, 'r') as f:
			data = f.read()
			# Sometimes the subprocess outputs more then the minimap data, e.g. debug
			# information. Since we just read from its stdout, parse out the data that
			# is relevant to us.
			data = re.findall(r'^DATA (\[\[.*\]\]) ENDDATA$', data, re.MULTILINE)[0]
			data = json.loads(data)

		os.unlink(self._preview_output)
		self._preview_process = None

		if self._map_preview:
			self._map_preview.end()

		self._map_preview = Minimap(
			self._gui.findChild(name='map_preview_minimap'),
			session=None,
			view=None,
			world=None,
			targetrenderer=horizons.globals.fife.targetrenderer,
			imagemanager=horizons.globals.fife.imagemanager,
			cam_border=False,
			use_rotation=False,
			tooltip=T("Click to generate a different random map"),
			on_click=self._on_preview_click,
			preview=True)

		self._map_preview.draw_data(data)
		self._set_map_preview_status("")
Example #24
0
    def __init__(self, session):
        self.session = session

        self.cursor = None
        self.coordinates_tooltip = None
        self.keylistener = IngameKeyListener(self.session)
        # used by NavigationTool
        LastActivePlayerSettlementManager.create_instance(self.session)

        # Mocks needed to act like the real IngameGui
        self.show_menu = Dummy
        self.hide_menu = Dummy
        # a logbook Dummy is necessary for message_widget to work
        self.logbook = Dummy

        self.mainhud = load_uh_widget("minimap.xml")
        self.mainhud.position_technique = "right+0:top+0"

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

        self.mainhud.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),
                "gameMenuButton": self.toggle_pause,
            }
        )

        self.mainhud.show()
        ZoomChanged.subscribe(self._update_zoom)

        # Hide unnecessary buttons in hud
        for widget in ("build", "speedUp", "speedDown", "destroy_tool", "diplomacyButton", "logbook"):
            self.mainhud.findChild(name=widget).hide()

        self.windows = WindowManager()
        self.message_widget = MessageWidget(self.session)
        self.pausemenu = PauseMenu(self.session, self, self.windows, in_editor_mode=True)
        self.help_dialog = HelpDialog(self.windows)
	def update_map(self, map_file):
		"""Direct map preview update.
		Only use for existing maps, it's too slow for random maps"""
		if self.minimap is not None:
			self.minimap.end()
		world = self._load_raw_world(map_file)
		self.minimap = Minimap(self._get_map_preview_icon(),
			                     session=None,
			                     view=None,
			                     world=world,
			                     targetrenderer=horizons.main.fife.targetrenderer,
			                     imagemanager=horizons.main.fife.imagemanager,
			                     cam_border=False,
			                     use_rotation=False,
			                     tooltip=None,
			                     on_click=None,
			                     preview=True)
		self.minimap.draw()
	def _poll_preview_process(self):
		"""This will be called regularly to see if the process ended.

		If the process has not yet finished, schedule a new callback to this function.
		Otherwise use the data to update the minimap.
		"""
		if not self._preview_process:
			return

		self._preview_process.poll()

		if self._preview_process.returncode is None: # not finished
			ExtScheduler().add_new_object(self._poll_preview_process, self, 0.1)
			return
		elif self._preview_process.returncode != 0:
			self._preview_process = None
			self._set_map_preview_status(u"An unknown error occurred while generating the map preview")
			return

		with open(self._preview_output, 'r') as f:
			data = f.read()

		os.unlink(self._preview_output)
		self._preview_process = None

		if self._map_preview:
			self._map_preview.end()

		self._map_preview = Minimap(
			self._gui.findChild(name='map_preview_minimap'),
			session=None,
			view=None,
			world=None,
			targetrenderer=horizons.globals.fife.targetrenderer,
			imagemanager=horizons.globals.fife.imagemanager,
			cam_border=False,
			use_rotation=False,
			tooltip=_("Click to generate a different random map"),
			on_click=self._on_preview_click,
			preview=True)

		self._map_preview.draw_data(data)
		self._set_map_preview_status(u"")
	def _update_map_preview(self, map_file):
		if self._map_preview:
			self._map_preview.end()

		world = load_raw_world(map_file)
		self._map_preview = Minimap(
			self._gui.findChild(name='map_preview_minimap'),
			session=None,
			view=None,
			world=world,
			targetrenderer=horizons.globals.fife.targetrenderer,
			imagemanager=horizons.globals.fife.imagemanager,
			cam_border=False,
			use_rotation=False,
			tooltip=None,
			on_click=None,
			preview=True)

		self._map_preview.draw()
Example #28
0
	def __init__(self, session, main_gui):
		self.session = session
		self.main_gui = main_gui

		self.cursor = None
		self.coordinates_tooltip = None
		self.keylistener = IngameKeyListener(self.session)
		# used by NavigationTool
		LastActivePlayerSettlementManager.create_instance(self.session)

		# Mocks needed to act like the real IngameGui
		self.show_menu = Dummy
		self.hide_menu = Dummy

		self.mainhud = load_uh_widget('minimap.xml')
		self.mainhud.position_technique = "right+0:top+0"

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

		self.mainhud.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),
			'gameMenuButton': self.toggle_pause,
		})

		self.mainhud.show()
		self.session.view.add_change_listener(self._update_zoom)

		# Hide unnecessary buttons in hud
		for widget in ("build", "speedUp", "speedDown", "destroy_tool", "diplomacyButton", "logbook"):
			self.mainhud.findChild(name=widget).hide()

		self.save_map_dialog = SaveMapDialog(self.main_gui, self, self.session)
		self.pausemenu = PauseMenu(self.session, self.main_gui, self, in_editor_mode=True)
Example #29
0
        def check_calc_process():
            # checks up on calc process (see below)
            if self.calc_proc is not None:
                state = self.calc_proc.poll()
                if state is None:  # not finished
                    ExtScheduler().add_new_object(check_calc_process, self, 0.1)
                elif state != 0:
                    self._set_map_preview_status(u"An unknown error occured while generating the map preview")
                else:  # done

                    data = open(self.calc_proc.output_filename, "r").read()
                    os.unlink(self.calc_proc.output_filename)
                    self.calc_proc = None

                    icon = self._get_map_preview_icon()
                    if icon is None:
                        return  # dialog already gone

                    tooltip = _("Click to generate a different random map")

                    if self.minimap is not None:
                        self.minimap.end()
                    self.minimap = Minimap(
                        icon,
                        session=None,
                        view=None,
                        world=None,
                        targetrenderer=horizons.main.fife.targetrenderer,
                        imagemanager=horizons.main.fife.imagemanager,
                        cam_border=False,
                        use_rotation=False,
                        tooltip=tooltip,
                        on_click=on_click,
                        preview=True,
                    )
                    self.minimap.draw_data(data)
                    icon.show()
                    self._set_map_preview_status(u"")
class CreateGame(Window):
	"""Interface for creating a multiplayer game"""

	def __init__(self, windows):
		super(CreateGame, self).__init__(windows)

		self._gui = load_uh_widget('multiplayer_creategame.xml')
		self._gui.mapEvents({
			'cancel': self._windows.close,
			'create': self.act,
		})

		self._files = []
		self._maps_display = []
		self._map_preview = None

	def hide(self):
		self._gui.hide()

	def show(self):
		self._files, self._maps_display = SavegameManager.get_maps()

		self._gui.distributeInitialData({
			'maplist': self._maps_display,
			'playerlimit': range(2, MULTIPLAYER.MAX_PLAYER_COUNT)
		})

		if self._maps_display: # select first entry
			self._gui.distributeData({
				'maplist': 0,
				'playerlimit': 0
			})
			self._update_infos()

		self._gui.findChild(name="maplist").mapEvents({
			'maplist/action': self._update_infos
		})
		self._gui.show()

	def act(self):
		mapindex = self._gui.collectData('maplist')
		mapname = self._maps_display[mapindex]
		maxplayers = self._gui.collectData('playerlimit') + 2 # 1 is the first entry
		gamename = self._gui.collectData('gamename')
		password = self._gui.collectData('password')
		maphash = ""

		password = hashlib.sha1(password).hexdigest() if password != "" else ""
		game = NetworkInterface().creategame(mapname, maxplayers, gamename, maphash, password)
		if game:
			# FIXME When canceling the lobby, I'd like the player to return to the main mp
			# menu, and not see the 'create game' again. We need to close this window, however,
			# this will trigger the display of the main gui, which will part the game in
			# `MultiplayerMenu._check_connection`
			#self._windows.close()
			window = GameLobby(self._windows)
			self._windows.show(window)

	def _update_infos(self):
		index = self._gui.collectData('maplist')
		mapfile = self._files[index]
		number_of_players = SavegameManager.get_recommended_number_of_players(mapfile)

		lbl = self._gui.findChild(name="recommended_number_of_players_lbl")
		lbl.text = _("Recommended number of players: {number}").format(number=number_of_players)

		self._update_map_preview(mapfile)

	def _update_map_preview(self, map_file):
		if self._map_preview:
			self._map_preview.end()

		world = load_raw_world(map_file)
		self._map_preview = Minimap(
			self._gui.findChild(name='map_preview_minimap'),
			session=None,
			view=None,
			world=world,
			targetrenderer=horizons.globals.fife.targetrenderer,
			imagemanager=horizons.globals.fife.imagemanager,
			cam_border=False,
			use_rotation=False,
			tooltip=None,
			on_click=None,
			preview=True)

		self._map_preview.draw()
class IngameGui(LivingObject):
	"""Class handling all the ingame gui events.
	Assumes that only 1 instance is used (class variables)"""

	message_widget = livingProperty()
	minimap = livingProperty()
	keylistener = livingProperty()

	def __init__(self, session):
		super(IngameGui, self).__init__()
		self.session = session
		assert isinstance(self.session, horizons.session.Session)
		self.settlement = None
		self._old_menu = None

		self.cursor = None
		self.coordinates_tooltip = None

		self.keylistener = IngameKeyListener(self.session)

		self.cityinfo = CityInfo(self)
		LastActivePlayerSettlementManager.create_instance(self.session)

		self.message_widget = MessageWidget(self.session)

		# Windows
		self.windows = WindowManager()
		self.show_popup = self.windows.show_popup
		self.show_error_popup = self.windows.show_error_popup

		self.logbook = LogBook(self.session, self.windows)
		self.players_overview = PlayersOverview(self.session)
		self.players_settlements = PlayersSettlements(self.session)
		self.players_ships = PlayersShips(self.session)

		self.chat_dialog = ChatDialog(self.windows, self.session)
		self.change_name_dialog = ChangeNameDialog(self.windows, self.session)
		self.pausemenu = PauseMenu(self.session, self, self.windows, in_editor_mode=False)
		self.help_dialog = HelpDialog(self.windows, session=self.session)

		# Icon manager
		self.status_icon_manager = StatusIconManager(
			renderer=self.session.view.renderer['GenericRenderer'],
			layer=self.session.view.layers[LAYERS.OBJECTS]
		)
		self.production_finished_icon_manager = ProductionFinishedIconManager(
			renderer=self.session.view.renderer['GenericRenderer'],
			layer=self.session.view.layers[LAYERS.OBJECTS]
		)

		# 'minimap' is the guichan gui around the actual minimap, which is saved
		# in self.minimap
		self.mainhud = load_uh_widget('minimap.xml')
		self.mainhud.position_technique = "right:top"

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

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

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

		self.mainhud.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.toggle_destroy_tool,
			'build' : self.show_build_menu,
			'diplomacyButton' : self.show_diplomacy_menu,
			'gameMenuButton' : self.toggle_pause,
			'logbook' : lambda: self.windows.toggle(self.logbook)
		})
		self.mainhud.show()

		hotkey_replacements = {
			'rotateRight': 'ROTATE_RIGHT',
			'rotateLeft': 'ROTATE_LEFT',
			'speedUp': 'SPEED_UP',
			'speedDown': 'SPEED_DOWN',
			'destroy_tool': 'DESTROY_TOOL',
			'build': 'BUILD_TOOL',
			'gameMenuButton': 'ESCAPE',
			'logbook': 'LOGBOOK',
		}
		for (widgetname, action) in hotkey_replacements.iteritems():
			widget = self.mainhud.findChild(name=widgetname)
			keys = horizons.globals.fife.get_keys_for_action(action)
			# No `.upper()` here: "Pause" looks better than "PAUSE".
			keyname = HOTKEYS.DISPLAY_KEY.get(keys[0], keys[0].capitalize())
			widget.helptext = widget.helptext.format(key=keyname)

		self.resource_overview = ResourceOverviewBar(self.session)

		# Register for messages
		SpeedChanged.subscribe(self._on_speed_changed)
		NewDisaster.subscribe(self._on_new_disaster)
		NewSettlement.subscribe(self._on_new_settlement)
		PlayerLevelUpgrade.subscribe(self._on_player_level_upgrade)
		MineEmpty.subscribe(self._on_mine_empty)
		ZoomChanged.subscribe(self._update_zoom)

		self._display_speed(self.session.timer.ticks_per_second)

	def end(self):
		# unsubscribe early, to avoid messages coming in while we're shutting down
		SpeedChanged.unsubscribe(self._on_speed_changed)
		NewDisaster.unsubscribe(self._on_new_disaster)
		NewSettlement.unsubscribe(self._on_new_settlement)
		PlayerLevelUpgrade.unsubscribe(self._on_player_level_upgrade)
		MineEmpty.unsubscribe(self._on_mine_empty)
		ZoomChanged.unsubscribe(self._update_zoom)

		self.mainhud.mapEvents({
			'zoomIn' : None,
			'zoomOut' : None,
			'rotateRight' : None,
			'rotateLeft': None,

			'destroy_tool' : None,
			'build' : None,
			'diplomacyButton' : None,
			'gameMenuButton' : None
		})
		self.mainhud.hide()
		self.mainhud = None

		self.windows.close_all()
		self.message_widget = None
		self.minimap = None
		self.resource_overview.end()
		self.resource_overview = None
		self.keylistener = None
		self.cityinfo.end()
		self.cityinfo = None
		self.hide_menu()

		if self.cursor:
			self.cursor.remove()
			self.cursor.end()
			self.cursor = None

		LastActivePlayerSettlementManager().remove()
		LastActivePlayerSettlementManager.destroy_instance()

		self.production_finished_icon_manager.end()
		self.production_finished_icon_manager = None
		self.status_icon_manager.end()
		self.status_icon_manager = None

		super(IngameGui, self).end()

	def show_select_savegame(self, mode):
		window = SelectSavegameDialog(mode, self.windows)
		return self.windows.show(window)

	def toggle_pause(self):
		self.windows.toggle(self.pausemenu)

	def toggle_help(self):
		self.windows.toggle(self.help_dialog)

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

	def show_diplomacy_menu(self):
		# check if the menu is already shown
		if getattr(self.get_cur_menu(), 'name', None) == "diplomacy_widget":
			self.hide_menu()
			return

		if not DiplomacyTab.is_useable(self.session.world):
			self.windows.show_popup(_("No diplomacy possible"),
			                        _("Cannot do diplomacy as there are no other players."))
			return

		tab = DiplomacyTab(self, self.session.world)
		self.show_menu(tab)

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

	def show_build_menu(self, update=False):
		"""
		@param update: set when build possibilities change (e.g. after inhabitant tier 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.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.create_tabs(self.session, self._build)
			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.set_cursor('building', cls, None if unit is None else unit())

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

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

		TabWidgetChanged.broadcast(self)

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

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

	def save_selection(self, db):
		# Store instances that are selected right now.
		for instance in self.session.selected_instances:
			db("INSERT INTO selected (`group`, id) VALUES (NULL, ?)", instance.worldid)

		# If a single instance is selected, also store the currently displayed tab.
		# (Else, upon restoring, we display a multi-selection tab.)
		tabname = None
		if len(self.session.selected_instances) == 1:
			tabclass = self.get_cur_menu().current_tab
			tabname = tabclass.__class__.__name__
		db("INSERT INTO metadata (name, value) VALUES (?, ?)", 'selected_tab', tabname)

		# Store user defined unit selection groups (Ctrl+number)
		for (number, group) in enumerate(self.session.selection_groups):
			for instance in group:
				db("INSERT INTO selected (`group`, id) VALUES (?, ?)", number, instance.worldid)

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

		if self.session.is_game_loaded():
			LastActivePlayerSettlementManager().load(db)
			cur_settlement = LastActivePlayerSettlementManager().get_current_settlement()
			self.cityinfo.set_settlement(cur_settlement)

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

		self.current_cursor = 'default'
		self.cursor = mousetools.SelectionTool(self.session)
		# Set cursor correctly, menus might need to be opened.
		# Open menus later; they may need unit data not yet inited
		self.cursor.apply_select()

		self.load_selection(db)

		if not self.session.is_game_loaded():
			# Fire a message for new world creation
			self.message_widget.add('NEW_WORLD')

		# Show message when the relationship between players changed
		def notify_change(caller, old_state, new_state, a, b):
			player1 = u"%s" % a.name
			player2 = u"%s" % b.name

			data = {'player1' : player1, 'player2' : player2}

			string_id = 'DIPLOMACY_STATUS_{old}_{new}'.format(old=old_state.upper(),
			                                                  new=new_state.upper())
			self.message_widget.add(string_id=string_id, message_dict=data)

		self.session.world.diplomacy.add_diplomacy_status_changed_listener(notify_change)

	def load_selection(self, db):
		# Re-select old selected instance
		for (instance_id, ) in db("SELECT id FROM selected WHERE `group` IS NULL"):
			obj = WorldObject.get_object_by_id(instance_id)
			self.session.selected_instances.add(obj)
			obj.get_component(SelectableComponent).select()

		# Re-show old tab (if there was one) or multiselection
		if len(self.session.selected_instances) == 1:
			tabname = db("SELECT value FROM metadata WHERE name = ?",
			             'selected_tab')[0][0]
			# This can still be None due to old savegames not storing the information
			tabclass = None if tabname is None else resolve_tab(tabname)
			obj.get_component(SelectableComponent).show_menu(jump_to_tabclass=tabclass)
		elif self.session.selected_instances:
			self.show_multi_select_tab(self.session.selected_instances)

		# Load user defined unit selection groups (Ctrl+number)
		for (num, group) in enumerate(self.session.selection_groups):
			for (instance_id, ) in db("SELECT id FROM selected WHERE `group` = ?", num):
				obj = WorldObject.get_object_by_id(instance_id)
				group.add(obj)

	def show_change_name_dialog(self, instance):
		"""Shows a dialog where the user can change the name of an object."""
		self.windows.show(self.change_name_dialog, instance=instance)

	def on_escape(self):
		if self.windows.visible:
			self.windows.on_escape()
		elif hasattr(self.cursor, 'on_escape'):
			self.cursor.on_escape()
		else:
			self.toggle_pause()

		return True

	def on_return(self):
		if self.windows.visible:
			self.windows.on_return()

		return True

	def _on_speed_changed(self, message):
		self._display_speed(message.new)

	def _display_speed(self, tps):
		text = u''
		up_icon = self.mainhud.findChild(name='speedUp')
		down_icon = self.mainhud.findChild(name='speedDown')
		if tps == 0: # pause
			text = u'0x'
			up_icon.set_inactive()
			down_icon.set_inactive()
		else:
			if tps != GAME_SPEED.TICKS_PER_SECOND:
				text = u"%1gx" % (tps * 1.0/GAME_SPEED.TICKS_PER_SECOND)
				#%1g: displays 0.5x, but 2x instead of 2.0x
			index = GAME_SPEED.TICK_RATES.index(tps)
			if index + 1 >= len(GAME_SPEED.TICK_RATES):
				up_icon.set_inactive()
			else:
				up_icon.set_active()
			if index > 0:
				down_icon.set_active()
			else:
				down_icon.set_inactive()

		wdg = self.mainhud.findChild(name="speed_text")
		wdg.text = text
		wdg.resizeToContent()
		self.mainhud.show()

	def on_key_press(self, action, evt):
		"""Handle a key press in-game.

		Returns True if the key was acted upon.
		"""
		_Actions = KeyConfig._Actions
		keyval = evt.getKey().getValue()

		if action == _Actions.ESCAPE:
			return self.on_escape()		
		elif keyval == fife.Key.ENTER:
			return self.on_return()

		if action == _Actions.GRID:
			gridrenderer = self.session.view.renderer['GridRenderer']
			gridrenderer.setEnabled( not gridrenderer.isEnabled() )
		elif action == _Actions.COORD_TOOLTIP:
			self.coordinates_tooltip.toggle()
		elif action == _Actions.DESTROY_TOOL:
			self.toggle_destroy_tool()
		elif action == _Actions.REMOVE_SELECTED:
			message = _(u"Are you sure you want to delete these objects?")
			if self.windows.show_popup(_(u"Delete"), message, show_cancel_button=True):
				self.session.remove_selected()
			else:
				self.deselect_all()
		elif action == _Actions.ROAD_TOOL:
			self.toggle_road_tool()
		elif action == _Actions.SPEED_UP:
			SpeedUpCommand().execute(self.session)
		elif action == _Actions.SPEED_DOWN:
			SpeedDownCommand().execute(self.session)
		elif action == _Actions.PAUSE:
			TogglePauseCommand().execute(self.session)
		elif action == _Actions.PLAYERS_OVERVIEW:
			self.logbook.toggle_stats_visibility(widget='players')
		elif action == _Actions.SETTLEMENTS_OVERVIEW:
			self.logbook.toggle_stats_visibility(widget='settlements')
		elif action == _Actions.SHIPS_OVERVIEW:
			self.logbook.toggle_stats_visibility(widget='ships')
		elif action == _Actions.LOGBOOK:
			self.windows.toggle(self.logbook)
		elif action == _Actions.DEBUG and VERSION.IS_DEV_VERSION:
			import pdb; pdb.set_trace()
		elif action == _Actions.BUILD_TOOL:
			self.show_build_menu()
		elif action == _Actions.ROTATE_RIGHT:
			if hasattr(self.cursor, "rotate_right"):
				# used in e.g. build preview to rotate building instead of map
				self.cursor.rotate_right()
			else:
				self.session.view.rotate_right()
				self.minimap.rotate_right()
		elif action == _Actions.ROTATE_LEFT:
			if hasattr(self.cursor, "rotate_left"):
				self.cursor.rotate_left()
			else:
				self.session.view.rotate_left()
				self.minimap.rotate_left()
		elif action == _Actions.CHAT:
			self.windows.show(self.chat_dialog)
		elif action == _Actions.TRANSLUCENCY:
			self.session.world.toggle_translucency()
		elif action == _Actions.TILE_OWNER_HIGHLIGHT:
			self.session.world.toggle_owner_highlight()
		elif fife.Key.NUM_0 <= keyval <= fife.Key.NUM_9:
			num = int(keyval - fife.Key.NUM_0)
			self.handle_selection_group(num, evt.isControlPressed())
		elif action == _Actions.QUICKSAVE:
			self.session.quicksave()
		# Quickload is only handled by the MainListener.
		elif action == _Actions.PIPETTE:
			# Mode that allows copying buildings.
			self.toggle_cursor('pipette')
		elif action == _Actions.HEALTH_BAR:
			# Show health bar of every instance with a health component.
			self.session.world.toggle_health_for_all_health_instances()
		elif action == _Actions.SHOW_SELECTED:
			if self.session.selected_instances:
				# Scroll to first one, we can never guarantee to display all selected units.
				instance = iter(self.session.selected_instances).next()
				self.session.view.center( * instance.position.center.to_tuple())
				for instance in self.session.selected_instances:
					if hasattr(instance, "path") and instance.owner.is_local_player:
						self.minimap.show_unit_path(instance)
		elif action == _Actions.HELP:
			self.toggle_help()
		else:
			return False

		return True

	def handle_selection_group(self, num, ctrl_pressed):
		"""Select existing or assign new unit selection group.

		Ctrl+number creates or overwrites the group of number `num`
		with the currently selected units.
		Pressing the associated key selects a group and centers the
		camera around these units.
		"""
		if ctrl_pressed:
			# Only consider units owned by the player.
			units = set(u for u in self.session.selected_instances
			            if u.owner.is_local_player)
			self.session.selection_groups[num] = units
			# Drop units of the new group from all other groups.
			for group in self.session.selection_groups:
				current_group = self.session.selection_groups[num]
				if group != current_group:
					group -= current_group
		else:
			# We need to make sure to have a cursor capable of selection
			# for apply_select() to work.
			# This handles deselection implicitly in the destructor.
			self.set_cursor('selection')
			# Apply new selection.
			for instance in self.session.selection_groups[num]:
				instance.get_component(SelectableComponent).select(reset_cam=True)
			# Assign copy since it will be randomly changed in selection code.
			# The unit group itself should only be changed on Ctrl events.
			self.session.selected_instances = self.session.selection_groups[num].copy()
			# Show correct tabs depending on what's selected.
			if self.session.selected_instances:
				self.cursor.apply_select()
			else:
				# Nothing is selected here. Hide the menu since apply_select
				# doesn't handle that case.
				self.show_menu(None)

	def toggle_cursor(self, which):
		"""Alternate between the cursor *which* and the default cursor."""
		if which == self.current_cursor:
			self.set_cursor()
		else:
			self.set_cursor(which)

	def set_cursor(self, which='default', *args, **kwargs):
		"""Sets the mousetool (i.e. cursor).
		This is done here for encapsulation and control over destructors.
		Further arguments are passed to the mouse tool constructor."""
		self.cursor.remove()
		self.current_cursor = which
		klass = {
			'default'        : mousetools.SelectionTool,
			'selection'      : mousetools.SelectionTool,
			'tearing'        : mousetools.TearingTool,
			'pipette'        : mousetools.PipetteTool,
			'attacking'      : mousetools.AttackingTool,
			'building'       : mousetools.BuildingTool,
		}[which]
		self.cursor = klass(self.session, *args, **kwargs)

	def toggle_destroy_tool(self):
		"""Initiate the destroy tool"""
		self.toggle_cursor('tearing')

	def _update_zoom(self, message):
		"""Enable/disable zoom buttons"""
		in_icon = self.mainhud.findChild(name='zoomIn')
		out_icon = self.mainhud.findChild(name='zoomOut')
		if message.zoom == VIEW.ZOOM_MIN:
			out_icon.set_inactive()
		else:
			out_icon.set_active()
		if message.zoom == VIEW.ZOOM_MAX:
			in_icon.set_inactive()
		else:
			in_icon.set_active()

	def _on_new_disaster(self, message):
		"""Called when a building is 'infected' with a disaster."""
		if message.building.owner.is_local_player and len(message.disaster._affected_buildings) == 1:
			pos = message.building.position.center
			self.message_widget.add(point=pos, string_id=message.disaster_class.NOTIFICATION_TYPE)

	def _on_new_settlement(self, message):
		player = message.settlement.owner
		self.message_widget.add(
			string_id='NEW_SETTLEMENT',
			point=message.warehouse_position,
			message_dict={'player': player.name},
			play_sound=player.is_local_player
		)

	def _on_player_level_upgrade(self, message):
		"""Called when a player's population reaches a new level."""
		if not message.sender.is_local_player:
			return

		# show notification
		self.message_widget.add(
			point=message.building.position.center,
			string_id='SETTLER_LEVEL_UP',
			message_dict={'level': message.level + 1}
		)

		# update build menu to show new buildings
		menu = self.get_cur_menu()
		if hasattr(menu, "name") and menu.name == "build_menu_tab_widget":
			self.show_build_menu(update=True)

		# TODO: Use a better measure then first tab
		# Quite fragile, makes sure the tablist in the mainsquare menu is updated
		if hasattr(menu, '_tabs') and isinstance(menu._tabs[0], MainSquareOverviewTab):
			instance = list(self.session.selected_instances)[0]
			instance.get_component(SelectableComponent).show_menu(jump_to_tabclass=type(menu.current_tab))

	def _on_mine_empty(self, message):
		self.message_widget.add(point=message.mine.position.center, string_id='MINE_EMPTY')
Example #32
0
	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)
class FreeMapsWidget:
    """Start a game by selecting an existing map."""
    def __init__(self, windows, singleplayer_menu, aidata):
        self._windows = windows
        self._singleplayer_menu = singleplayer_menu
        self._aidata = aidata

        self._gui = load_uh_widget('sp_free_maps.xml')
        self._game_settings = GameSettingsWidget()

        self._map_preview = None

    def end(self):
        pass

    def get_widget(self):
        return self._gui

    def act(self, player_name, player_color):
        map_file = self._get_selected_map()

        options = StartGameOptions.create_start_map(map_file)
        options.set_human_data(player_name, player_color)
        options.ai_players = self._aidata.get_ai_players()
        options.trader_enabled = self._game_settings.free_trader
        options.pirate_enabled = self._game_settings.pirates
        options.disasters_enabled = self._game_settings.disasters
        options.natural_resource_multiplier = self._game_settings.natural_resource_multiplier
        horizons.main.start_singleplayer(options)

    def show(self):
        self._files, maps_display = SavegameManager.get_maps()

        self._gui.distributeInitialData({'maplist': maps_display})
        self._gui.mapEvents({
            'maplist/action': self._update_map_infos,
        })
        if maps_display:  # select first entry
            self._gui.distributeData({'maplist': 0})
            self._update_map_infos()

        self._gui.findChild(name='game_settings_box').addChild(
            self._game_settings.get_widget())
        self._game_settings.show()
        self._aidata.show()

    def _update_map_infos(self):
        map_file = self._get_selected_map()

        number_of_players = SavegameManager.get_recommended_number_of_players(
            map_file)
        lbl = self._gui.findChild(name="recommended_number_of_players_lbl")
        lbl.text = T("Recommended number of players: {number}").format(
            number=number_of_players)

        self._update_map_preview(map_file)

    def _get_selected_map(self):
        selection_index = self._gui.collectData('maplist')
        assert selection_index != -1

        return self._files[self._gui.collectData('maplist')]

    def _update_map_preview(self, map_file):
        if self._map_preview:
            self._map_preview.end()

        world = load_raw_world(map_file)
        self._map_preview = Minimap(
            self._gui.findChild(name='map_preview_minimap'),
            session=None,
            view=None,
            world=world,
            targetrenderer=horizons.globals.fife.targetrenderer,
            imagemanager=horizons.globals.fife.imagemanager,
            cam_border=False,
            use_rotation=False,
            tooltip=None,
            on_click=None,
            preview=True)

        self._map_preview.draw()
Example #34
0
    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.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, 55, 120, 120)

        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,
            'helpLink':
            self.main_gui.on_help,
            'gameMenuButton':
            self.main_gui.show_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
        callbackWithArguments = pychan.tools.callbackWithArguments
        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)
class RandomMapWidget:
    """Create a random map, influence map generation with multiple sliders."""
    def __init__(self, windows, singleplayer_menu, aidata):
        self._windows = windows
        self._singleplayer_menu = singleplayer_menu
        self._aidata = aidata

        self._gui = load_uh_widget('sp_random.xml')
        self._map_parameters = {}  # stores the current values from the sliders
        self._game_settings = GameSettingsWidget()

        # Map preview
        self._last_map_parameters = None
        self._preview_process = None
        self._preview_output = None
        self._map_preview = None

    def end(self):
        if self._preview_process:
            self._preview_process.kill()
            self._preview_process = None
        ExtScheduler().rem_all_classinst_calls(self)

    def get_widget(self):
        return self._gui

    def act(self, player_name, player_color):
        self.end()

        map_file = generate_random_map(*self._get_map_parameters())

        options = StartGameOptions.create_start_map(map_file)
        options.set_human_data(player_name, player_color)
        options.ai_players = self._aidata.get_ai_players()
        options.trader_enabled = self._game_settings.free_trader
        options.pirate_enabled = self._game_settings.pirates
        options.disasters_enabled = self._game_settings.disasters
        options.natural_resource_multiplier = self._game_settings.natural_resource_multiplier
        horizons.main.start_singleplayer(options)

    def show(self):
        seed_string_field = self._gui.findChild(name='seed_string_field')
        seed_string_field.capture(self._on_random_parameter_changed)
        seed_string_field.text = generate_random_seed(seed_string_field.text)

        parameters = (
            ('map_size', T('Map size:'), 'RandomMapSize'),
            ('water_percent', T('Water:'), 'RandomMapWaterPercent'),
            ('max_island_size', T('Max island size:'),
             'RandomMapMaxIslandSize'),
            ('preferred_island_size', T('Preferred island size:'),
             'RandomMapPreferredIslandSize'),
            ('island_size_deviation', T('Island size deviation:'),
             'RandomMapIslandSizeDeviation'),
        )

        for param, __, setting_name in parameters:
            self._map_parameters[param] = int(
                horizons.globals.fife.get_uh_setting(setting_name))

        def make_on_change(param, text, setting_name):
            # When a slider is changed, update the value displayed in the label, save the value
            # in the settings and store the value in self._map_parameters
            def on_change():
                slider = self._gui.findChild(name=param + '_slider')
                self._gui.findChild(
                    name=param +
                    '_lbl').text = text + ' ' + str(int(slider.value))
                horizons.globals.fife.set_uh_setting(setting_name,
                                                     slider.value)
                horizons.globals.fife.save_settings()
                self._map_parameters[param] = int(slider.value)
                self._on_random_parameter_changed()

            return on_change

        for param, text, setting_name in parameters:
            slider = self._gui.findChild(name=param + '_slider')
            on_change = make_on_change(param, text, setting_name)
            slider.capture(on_change)
            slider.value = horizons.globals.fife.get_uh_setting(setting_name)
            on_change()

        self._gui.findChild(name='game_settings_box').addChild(
            self._game_settings.get_widget())
        self._game_settings.show()
        self._aidata.show()

    def _get_map_parameters(self):
        return (self._gui.findChild(name='seed_string_field').text,
                self._map_parameters['map_size'],
                self._map_parameters['water_percent'],
                self._map_parameters['max_island_size'],
                self._map_parameters['preferred_island_size'],
                self._map_parameters['island_size_deviation'])

    def _on_random_parameter_changed(self):
        self._update_map_preview()

    # Map preview

    def _on_preview_click(self, event, drag):
        seed_string_field = self._gui.findChild(name='seed_string_field')
        seed_string_field.text = generate_random_seed(seed_string_field.text)
        self._on_random_parameter_changed()

    def _update_map_preview(self):
        """Start a new process to generate a map preview."""
        current_parameters = self._get_map_parameters()
        if self._last_map_parameters == current_parameters:
            # nothing changed, don't generate a new preview
            return

        self._last_map_parameters = current_parameters

        if self._preview_process:
            self._preview_process.kill(
            )  # process exists, therefore up is scheduled already

        # launch process in background to calculate minimap data
        minimap_icon = self._gui.findChild(name='map_preview_minimap')
        params = json.dumps(
            ((minimap_icon.width, minimap_icon.height), current_parameters))

        args = [sys.executable, sys.argv[0], "--generate-minimap", params]
        # We're running UH in a new process, make sure fife is setup correctly
        if horizons.main.command_line_arguments.fife_path:
            args.extend([
                "--fife-path", horizons.main.command_line_arguments.fife_path
            ])

        handle, self._preview_output = tempfile.mkstemp()
        os.close(handle)
        self._preview_process = subprocess.Popen(args=args,
                                                 stdout=open(
                                                     self._preview_output,
                                                     "w"))
        self._set_map_preview_status("Generating preview…")

        ExtScheduler().add_new_object(self._poll_preview_process, self, 0.5)

    def _poll_preview_process(self):
        """This will be called regularly to see if the process ended.

		If the process has not yet finished, schedule a new callback to this function.
		Otherwise use the data to update the minimap.
		"""
        if not self._preview_process:
            return

        self._preview_process.poll()

        if self._preview_process.returncode is None:  # not finished
            ExtScheduler().add_new_object(self._poll_preview_process, self,
                                          0.1)
            return
        elif self._preview_process.returncode != 0:
            self._preview_process = None
            self._set_map_preview_status(
                "An unknown error occurred while generating the map preview")
            return

        with open(self._preview_output, 'r') as f:
            data = f.read()
            # Sometimes the subprocess outputs more then the minimap data, e.g. debug
            # information. Since we just read from its stdout, parse out the data that
            # is relevant to us.
            data = re.findall(r'^DATA (\[\[.*\]\]) ENDDATA$', data,
                              re.MULTILINE)[0]
            data = json.loads(data)

        os.unlink(self._preview_output)
        self._preview_process = None

        if self._map_preview:
            self._map_preview.end()

        self._map_preview = Minimap(
            self._gui.findChild(name='map_preview_minimap'),
            session=None,
            view=None,
            world=None,
            targetrenderer=horizons.globals.fife.targetrenderer,
            imagemanager=horizons.globals.fife.imagemanager,
            cam_border=False,
            use_rotation=False,
            tooltip=T("Click to generate a different random map"),
            on_click=self._on_preview_click,
            preview=True)

        self._map_preview.draw_data(data)
        self._set_map_preview_status("")

    def _set_map_preview_status(self, text):
        self._gui.findChild(name="map_preview_status_label").text = text
class MapPreview(object):
    """Semiprivate class dealing with the map preview icon"""
    def __init__(self, get_widget):
        """
		@param get_widget: returns the current widget (self.current)
		"""
        self.minimap = None
        self.calc_proc = None  # handle to background calculation process
        self.get_widget = get_widget
        self._last_random_map_params = None

    def update_map(self, map_file):
        """Direct map preview update.
		Only use for existing maps, it's too slow for random maps"""
        if self.minimap is not None:
            self.minimap.end()
        world = self._load_raw_world(map_file)
        self.minimap = Minimap(
            self._get_map_preview_icon(),
            session=None,
            view=None,
            world=world,
            targetrenderer=horizons.globals.fife.targetrenderer,
            imagemanager=horizons.globals.fife.imagemanager,
            cam_border=False,
            use_rotation=False,
            tooltip=None,
            on_click=None,
            preview=True)
        self.minimap.draw()

    def update_random_map(self, map_params, on_click):
        """Called when a random map parameter has changed.
		@param map_params: _get_random_map() output
		@param on_click: handler for clicks"""
        if self._last_random_map_params == map_params:
            return  # we already display this, happens on spurious slider events such as hover
        self._last_random_map_params = map_params

        def check_calc_process():
            # checks up on calc process (see below)
            if self.calc_proc is not None:
                state = self.calc_proc.poll()
                if state is None:  # not finished
                    ExtScheduler().add_new_object(check_calc_process, self,
                                                  0.1)
                elif state != 0:
                    self._set_map_preview_status(
                        u"An unknown error occured while generating the map preview"
                    )
                else:  # done

                    data = open(self.calc_proc.output_filename, "r").read()
                    os.unlink(self.calc_proc.output_filename)
                    self.calc_proc = None

                    icon = self._get_map_preview_icon()
                    if icon is None:
                        return  # dialog already gone

                    tooltip = _("Click to generate a different random map")

                    if self.minimap is not None:
                        self.minimap.end()
                    self.minimap = Minimap(
                        icon,
                        session=None,
                        view=None,
                        world=None,
                        targetrenderer=horizons.globals.fife.targetrenderer,
                        imagemanager=horizons.globals.fife.imagemanager,
                        cam_border=False,
                        use_rotation=False,
                        tooltip=tooltip,
                        on_click=on_click,
                        preview=True)
                    self.minimap.draw_data(data)
                    icon.show()
                    self._set_map_preview_status(u"")

        if self.calc_proc is not None:
            self.calc_proc.kill(
            )  # process exists, therefore up is scheduled already
        else:
            ExtScheduler().add_new_object(check_calc_process, self, 0.5)

        # launch process in background to calculate minimap data
        minimap_icon = self._get_map_preview_icon()

        params = json.dumps(
            ((minimap_icon.width, minimap_icon.height), map_params))

        args = (sys.executable, sys.argv[0], "--generate-minimap", params)
        handle, outfilename = tempfile.mkstemp()
        os.close(handle)
        self.calc_proc = subprocess.Popen(args=args,
                                          stdout=open(outfilename, "w"))
        self.calc_proc.output_filename = outfilename  # attach extra info
        self._set_map_preview_status(u"Generating preview...")

    @classmethod
    def generate_minimap(cls, size, parameters):
        """Called as subprocess, calculates minimap data and passes it via string via stdout"""
        # called as standalone basically, so init everything we need
        from horizons.main import _create_main_db
        from horizons.entities import Entities
        from horizons.ext.dummy import Dummy
        db = _create_main_db()
        Entities.load_grounds(db, load_now=False)  # create all references
        map_file = SingleplayerMenu._generate_random_map(parameters)
        world = cls._load_raw_world(map_file)
        location = Rect.init_from_topleft_and_size_tuples((0, 0), size)
        minimap = Minimap(location,
                          session=None,
                          view=None,
                          world=world,
                          targetrenderer=Dummy(),
                          imagemanager=Dummy(),
                          cam_border=False,
                          use_rotation=False,
                          preview=True)
        # communicate via stdout
        print minimap.dump_data()

    @classmethod
    def _load_raw_world(cls, map_file):
        WorldObject.reset()
        world = World(session=None)
        world.inited = True
        world.load_raw_map(SavegameAccessor(map_file, True), preview=True)
        return world

    def _get_map_preview_icon(self):
        """Returns pychan icon for map preview"""
        return self.get_widget().findChild(name="map_preview_minimap")

    def _set_map_preview_status(self, text):
        """Sets small status label next to map preview"""
        wdg = self.get_widget().findChild(name="map_preview_status_label")
        if wdg:  # might show next dialog already
            wdg.text = text
Example #37
0
class IngameGui(LivingObject):
    """Class handling all the ingame gui events."""

    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.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, 55, 120, 120)

        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,
            'helpLink':
            self.main_gui.on_help,
            'gameMenuButton':
            self.main_gui.show_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
        callbackWithArguments = pychan.tools.callbackWithArguments
        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,
            'helpLink': 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={},
                         res_usable={},
                         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 = res_needed
            self.resources_usable = 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_build_menu(self):
        # 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()
            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.show_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()

    _toggle_ingame_pause_shown = False

    def toggle_ingame_pause(self):
        """
		Called when the hotkey for pause is pressed.
		Displays pause notification and does the actual (un)pausing.
		"""
        message = _("Hit P to continue the game or click below!")
        popup = self.main_gui.build_popup(_("Game paused"), message)
        if not self._toggle_ingame_pause_shown:
            self.session.speed_pause()
            self.main_gui.on_escape = self.toggle_ingame_pause
            popup.mapEvents({'okButton': self.toggle_ingame_pause})
            popup.show()
            self._toggle_ingame_pause_shown = True
        else:
            self.main_gui.on_escape = self.main_gui.show_pause
            popup.hide()
            self.session.speed_unpause()
            self._toggle_ingame_pause_shown = False

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

    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.show_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()
Example #38
0
class IngameGui(LivingObject):
    """Class handling all the ingame gui events.
	Assumes that only 1 instance is used (class variables)

	@type session: horizons.session.Session
	@param session: instance of session the world belongs to.
	"""

    message_widget = livingProperty()
    minimap = livingProperty()
    keylistener = livingProperty()

    def __init__(self, session):
        super().__init__()
        self.session = session
        assert isinstance(self.session, horizons.session.Session)
        self.settlement = None
        self._old_menu = None

        self.cursor = None
        self.coordinates_tooltip = None

        self.keylistener = IngameKeyListener(self.session)

        self.cityinfo = CityInfo(self)
        LastActivePlayerSettlementManager.create_instance(self.session)

        self.message_widget = MessageWidget(self.session)

        # Windows
        self.windows = WindowManager()
        self.open_popup = self.windows.open_popup
        self.open_error_popup = self.windows.open_error_popup

        self.logbook = LogBook(self.session, self.windows)
        self.players_overview = PlayersOverview(self.session)
        self.players_settlements = PlayersSettlements(self.session)
        self.players_ships = PlayersShips(self.session)

        self.chat_dialog = ChatDialog(self.windows, self.session)
        self.change_name_dialog = ChangeNameDialog(self.windows, self.session)
        self.pausemenu = PauseMenu(self.session,
                                   self,
                                   self.windows,
                                   in_editor_mode=False)
        self.help_dialog = HelpDialog(self.windows)

        # Icon manager
        self.status_icon_manager = StatusIconManager(
            renderer=self.session.view.renderer['GenericRenderer'],
            layer=self.session.view.layers[LAYERS.OBJECTS])
        self.production_finished_icon_manager = ProductionFinishedIconManager(
            renderer=self.session.view.renderer['GenericRenderer'],
            layer=self.session.view.layers[LAYERS.OBJECTS])

        # 'minimap' is the guichan gui around the actual minimap, which is saved
        # in self.minimap
        self.mainhud = load_uh_widget('minimap.xml')
        self.mainhud.position_technique = "right:top"

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

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

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

        self.mainhud.mapEvents({
            'zoomIn':
            self.session.view.zoom_in,
            'zoomOut':
            self.session.view.zoom_out,
            'rotateRight':
            Callback.ChainedCallbacks(self.session.view.rotate_right,
                                      self.minimap.update_rotation),
            'rotateLeft':
            Callback.ChainedCallbacks(self.session.view.rotate_left,
                                      self.minimap.update_rotation),
            'speedUp':
            speed_up,
            'speedDown':
            speed_down,
            'destroy_tool':
            self.toggle_destroy_tool,
            'build':
            self.show_build_menu,
            'diplomacyButton':
            self.show_diplomacy_menu,
            'gameMenuButton':
            self.toggle_pause,
            'logbook':
            lambda: self.windows.toggle(self.logbook)
        })
        self.mainhud.show()

        self._replace_hotkeys_in_widgets()

        self.resource_overview = ResourceOverviewBar(self.session)

        # Register for messages
        SpeedChanged.subscribe(self._on_speed_changed)
        NewDisaster.subscribe(self._on_new_disaster)
        NewSettlement.subscribe(self._on_new_settlement)
        PlayerLevelUpgrade.subscribe(self._on_player_level_upgrade)
        MineEmpty.subscribe(self._on_mine_empty)
        ZoomChanged.subscribe(self._update_zoom)
        GuiAction.subscribe(self._on_gui_click_action)
        GuiHover.subscribe(self._on_gui_hover_action)
        GuiCancelAction.subscribe(self._on_gui_cancel_action)
        # NOTE: This has to be called after the text is replaced!
        LanguageChanged.subscribe(self._on_language_changed)

        self._display_speed(self.session.timer.ticks_per_second)

    def end(self):
        # unsubscribe early, to avoid messages coming in while we're shutting down
        SpeedChanged.unsubscribe(self._on_speed_changed)
        NewDisaster.unsubscribe(self._on_new_disaster)
        NewSettlement.unsubscribe(self._on_new_settlement)
        PlayerLevelUpgrade.unsubscribe(self._on_player_level_upgrade)
        MineEmpty.unsubscribe(self._on_mine_empty)
        ZoomChanged.unsubscribe(self._update_zoom)
        GuiAction.unsubscribe(self._on_gui_click_action)
        GuiHover.unsubscribe(self._on_gui_hover_action)
        GuiCancelAction.unsubscribe(self._on_gui_cancel_action)

        self.mainhud.mapEvents({
            'zoomIn': None,
            'zoomOut': None,
            'rotateRight': None,
            'rotateLeft': None,
            'destroy_tool': None,
            'build': None,
            'diplomacyButton': None,
            'gameMenuButton': None
        })
        self.mainhud.hide()
        self.mainhud = None

        self.windows.close_all()
        self.message_widget = None
        self.minimap = None
        self.resource_overview.end()
        self.resource_overview = None
        self.keylistener = None
        self.cityinfo.end()
        self.cityinfo = None
        self.hide_menu()

        if self.cursor:
            self.cursor.remove()
            self.cursor.end()
            self.cursor = None

        LastActivePlayerSettlementManager().remove()
        LastActivePlayerSettlementManager.destroy_instance()

        self.production_finished_icon_manager.end()
        self.production_finished_icon_manager = None
        self.status_icon_manager.end()
        self.status_icon_manager = None

        super().end()

    def show_select_savegame(self, mode):
        window = SelectSavegameDialog(mode, self.windows)
        return self.windows.open(window)

    def toggle_pause(self):
        self.set_cursor('default')
        self.windows.toggle(self.pausemenu)

    def toggle_help(self):
        self.windows.toggle(self.help_dialog)

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

    def show_diplomacy_menu(self):
        # check if the menu is already shown
        if getattr(self.get_cur_menu(), 'name', None) == "diplomacy_widget":
            self.hide_menu()
            return

        if not DiplomacyTab.is_useable(self.session.world):
            self.windows.open_popup(
                T("No diplomacy possible"),
                T("Cannot do diplomacy as there are no other players."))
            return

        tab = DiplomacyTab(self, self.session.world)
        self.show_menu(tab)

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

    def show_build_menu(self, update=False):
        """
		@param update: set when build possibilities change (e.g. after inhabitant tier 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.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.create_tabs(self.session, self._build)
            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.set_cursor('building', cls, None if unit is None else unit())

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

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

        TabWidgetChanged.broadcast(self)

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

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

    def save_selection(self, db):
        # Store instances that are selected right now.
        for instance in self.session.selected_instances:
            db("INSERT INTO selected (`group`, id) VALUES (NULL, ?)",
               instance.worldid)

        # If a single instance is selected, also store the currently displayed tab.
        # (Else, upon restoring, we display a multi-selection tab.)
        tabname = None
        if len(self.session.selected_instances) == 1:
            tabclass = self.get_cur_menu().current_tab
            tabname = tabclass.__class__.__name__
        db("INSERT INTO metadata (name, value) VALUES (?, ?)", 'selected_tab',
           tabname)

        # Store user defined unit selection groups (Ctrl+number)
        for (number, group) in enumerate(self.session.selection_groups):
            for instance in group:
                db("INSERT INTO selected (`group`, id) VALUES (?, ?)", number,
                   instance.worldid)

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

        if self.session.is_game_loaded():
            LastActivePlayerSettlementManager().load(db)
            cur_settlement = LastActivePlayerSettlementManager(
            ).get_current_settlement()
            self.cityinfo.set_settlement(cur_settlement)

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

        self.current_cursor = 'default'
        self.cursor = mousetools.SelectionTool(self.session)
        # Set cursor correctly, menus might need to be opened.
        # Open menus later; they may need unit data not yet inited
        self.cursor.apply_select()

        self.load_selection(db)

        if not self.session.is_game_loaded():
            # Fire a message for new world creation
            self.message_widget.add('NEW_WORLD')

        # Show message when the relationship between players changed
        def notify_change(caller, old_state, new_state, a, b):
            player1 = "{0!s}".format(a.name)
            player2 = "{0!s}".format(b.name)

            data = {'player1': player1, 'player2': player2}

            string_id = 'DIPLOMACY_STATUS_{old}_{new}'.format(
                old=old_state.upper(), new=new_state.upper())
            self.message_widget.add(string_id=string_id, message_dict=data)

        self.session.world.diplomacy.add_diplomacy_status_changed_listener(
            notify_change)

    def load_selection(self, db):
        # Re-select old selected instance
        for (instance_id,
             ) in db("SELECT id FROM selected WHERE `group` IS NULL"):
            obj = WorldObject.get_object_by_id(instance_id)
            self.session.selected_instances.add(obj)
            obj.get_component(SelectableComponent).select()

        # Re-show old tab (if there was one) or multiselection
        if len(self.session.selected_instances) == 1:
            tabname = db("SELECT value FROM metadata WHERE name = ?",
                         'selected_tab')[0][0]
            # This can still be None due to old savegames not storing the information
            tabclass = None if tabname is None else resolve_tab(tabname)
            obj.get_component(SelectableComponent).show_menu(
                jump_to_tabclass=tabclass)
        elif self.session.selected_instances:
            self.show_multi_select_tab(self.session.selected_instances)

        # Load user defined unit selection groups (Ctrl+number)
        for (num, group) in enumerate(self.session.selection_groups):
            for (instance_id, ) in db(
                    "SELECT id FROM selected WHERE `group` = ?", num):
                obj = WorldObject.get_object_by_id(instance_id)
                group.add(obj)

    def show_change_name_dialog(self, instance):
        """Shows a dialog where the user can change the name of an object."""
        self.windows.open(self.change_name_dialog, instance=instance)

    def on_escape(self):
        if self.windows.visible:
            self.windows.on_escape()
        elif hasattr(self.cursor, 'on_escape'):
            self.cursor.on_escape()
        else:
            self.toggle_pause()

        return True

    def on_return(self):
        if self.windows.visible:
            self.windows.on_return()

        return True

    def _on_speed_changed(self, message):
        self._display_speed(message.new)

    def _display_speed(self, tps):
        text = ''
        up_icon = self.mainhud.findChild(name='speedUp')
        down_icon = self.mainhud.findChild(name='speedDown')
        if tps == 0:  # pause
            text = '0x'
            up_icon.set_inactive()
            down_icon.set_inactive()
        else:
            if tps != GAME_SPEED.TICKS_PER_SECOND:
                text = "{0:1g}x".format(tps * 1.0 /
                                        GAME_SPEED.TICKS_PER_SECOND)
                #%1g: displays 0.5x, but 2x instead of 2.0x
            index = GAME_SPEED.TICK_RATES.index(tps)
            if index + 1 >= len(GAME_SPEED.TICK_RATES):
                up_icon.set_inactive()
            else:
                up_icon.set_active()
            if index > 0:
                down_icon.set_active()
            else:
                down_icon.set_inactive()

        wdg = self.mainhud.findChild(name="speed_text")
        wdg.text = text
        wdg.resizeToContent()
        self.mainhud.show()

    def on_key_press(self, action, evt):
        """Handle a key press in-game.

		Returns True if the key was acted upon.
		"""
        _Actions = KeyConfig._Actions
        keyval = evt.getKey().getValue()

        if action == _Actions.ESCAPE:
            return self.on_escape()
        elif keyval == fife.Key.ENTER:
            return self.on_return()

        if action == _Actions.GRID:
            gridrenderer = self.session.view.renderer['GridRenderer']
            gridrenderer.setEnabled(not gridrenderer.isEnabled())
        elif action == _Actions.COORD_TOOLTIP:
            self.coordinates_tooltip.toggle()
        elif action == _Actions.DESTROY_TOOL:
            self.toggle_destroy_tool()
        elif action == _Actions.REMOVE_SELECTED:
            message = T("Are you sure you want to delete these objects?")
            if self.windows.open_popup(T("Delete"),
                                       message,
                                       show_cancel_button=True):
                self.session.remove_selected()
            else:
                self.deselect_all()
        elif action == _Actions.ROAD_TOOL:
            self.toggle_road_tool()
        elif action == _Actions.SPEED_UP:
            SpeedUpCommand().execute(self.session)
        elif action == _Actions.SPEED_DOWN:
            SpeedDownCommand().execute(self.session)
        elif action == _Actions.ZOOM_IN:
            self.session.view.zoom_in()
        elif action == _Actions.ZOOM_OUT:
            self.session.view.zoom_out()
        elif action == _Actions.PAUSE:
            TogglePauseCommand().execute(self.session)
        elif action == _Actions.PLAYERS_OVERVIEW:
            self.logbook.toggle_stats_visibility(widget='players')
        elif action == _Actions.SETTLEMENTS_OVERVIEW:
            self.logbook.toggle_stats_visibility(widget='settlements')
        elif action == _Actions.SHIPS_OVERVIEW:
            self.logbook.toggle_stats_visibility(widget='ships')
        elif action == _Actions.LOGBOOK:
            self.windows.toggle(self.logbook)
        elif action == _Actions.DEBUG and VERSION.IS_DEV_VERSION:
            import pdb
            pdb.set_trace()
        elif action == _Actions.BUILD_TOOL:
            self.show_build_menu()
        elif action == _Actions.ROTATE_RIGHT:
            if hasattr(self.cursor, "rotate_right"):
                # used in e.g. build preview to rotate building instead of map
                self.cursor.rotate_right()
            else:
                self.session.view.rotate_right()
                self.minimap.update_rotation()
        elif action == _Actions.ROTATE_LEFT:
            if hasattr(self.cursor, "rotate_left"):
                self.cursor.rotate_left()
            else:
                self.session.view.rotate_left()
                self.minimap.update_rotation()
        elif action == _Actions.CHAT:
            self.windows.open(self.chat_dialog)
        elif action == _Actions.TRANSLUCENCY:
            self.session.world.toggle_translucency()
        elif action == _Actions.TILE_OWNER_HIGHLIGHT:
            self.session.world.toggle_owner_highlight()
        elif fife.Key.NUM_0 <= keyval <= fife.Key.NUM_9:
            num = int(keyval - fife.Key.NUM_0)
            self.handle_selection_group(num, evt.isControlPressed())
        elif action == _Actions.QUICKSAVE:
            self.session.quicksave()
        # Quickload is only handled by the MainListener.
        elif action == _Actions.PIPETTE:
            # Mode that allows copying buildings.
            self.toggle_cursor('pipette')
        elif action == _Actions.HEALTH_BAR:
            # Show health bar of every instance with a health component.
            self.session.world.toggle_health_for_all_health_instances()
        elif action == _Actions.SHOW_SELECTED:
            if self.session.selected_instances:
                # Scroll to first one, we can never guarantee to display all selected units.
                instance = next(iter(self.session.selected_instances))
                self.session.view.center(*instance.position.center.to_tuple())
                for instance in self.session.selected_instances:
                    if hasattr(instance,
                               "path") and instance.owner.is_local_player:
                        self.minimap.show_unit_path(instance)
        elif action == _Actions.HELP:
            self.toggle_help()
        else:
            return False

        return True

    def handle_selection_group(self, num, ctrl_pressed):
        """Select existing or assign new unit selection group.

		Ctrl+number creates or overwrites the group of number `num`
		with the currently selected units.
		Pressing the associated key selects a group and centers the
		camera around these units.
		"""
        if ctrl_pressed:
            # Only consider units owned by the player.
            units = {
                u
                for u in self.session.selected_instances
                if u.owner.is_local_player
            }
            self.session.selection_groups[num] = units
            # Drop units of the new group from all other groups.
            for group in self.session.selection_groups:
                current_group = self.session.selection_groups[num]
                if group != current_group:
                    group -= current_group
        else:
            # We need to make sure to have a cursor capable of selection
            # for apply_select() to work.
            # This handles deselection implicitly in the destructor.
            self.set_cursor('selection')
            # Apply new selection.
            for instance in self.session.selection_groups[num]:
                instance.get_component(SelectableComponent).select(
                    reset_cam=True)
            # Assign copy since it will be randomly changed in selection code.
            # The unit group itself should only be changed on Ctrl events.
            self.session.selected_instances = self.session.selection_groups[
                num].copy()
            # Show correct tabs depending on what's selected.
            if self.session.selected_instances:
                self.cursor.apply_select()
            else:
                # Nothing is selected here. Hide the menu since apply_select
                # doesn't handle that case.
                self.show_menu(None)

    def toggle_cursor(self, which):
        """Alternate between the cursor *which* and the default cursor."""
        if which == self.current_cursor:
            self.set_cursor()
        else:
            self.set_cursor(which)

    def set_cursor(self, which='default', *args, **kwargs):
        """Sets the mousetool (i.e. cursor).
		This is done here for encapsulation and control over destructors.
		Further arguments are passed to the mouse tool constructor."""
        self.cursor.remove()
        self.current_cursor = which
        klass = {
            'default': mousetools.SelectionTool,
            'selection': mousetools.SelectionTool,
            'tearing': mousetools.TearingTool,
            'pipette': mousetools.PipetteTool,
            'attacking': mousetools.AttackingTool,
            'building': mousetools.BuildingTool,
        }[which]
        self.cursor = klass(self.session, *args, **kwargs)

    def toggle_destroy_tool(self):
        """Initiate the destroy tool"""
        self.toggle_cursor('tearing')

    def _update_zoom(self, message):
        """Enable/disable zoom buttons"""
        in_icon = self.mainhud.findChild(name='zoomIn')
        out_icon = self.mainhud.findChild(name='zoomOut')
        if message.zoom == VIEW.ZOOM_MIN:
            out_icon.set_inactive()
        else:
            out_icon.set_active()
        if message.zoom == VIEW.ZOOM_MAX:
            in_icon.set_inactive()
        else:
            in_icon.set_active()

    def _on_new_disaster(self, message):
        """Called when a building is 'infected' with a disaster."""
        if message.building.owner.is_local_player and len(
                message.disaster._affected_buildings) == 1:
            pos = message.building.position.center
            self.message_widget.add(
                point=pos, string_id=message.disaster_class.NOTIFICATION_TYPE)

    def _on_new_settlement(self, message):
        player = message.settlement.owner
        self.message_widget.add(string_id='NEW_SETTLEMENT',
                                point=message.warehouse_position,
                                message_dict={'player': player.name},
                                play_sound=player.is_local_player)

    def _on_player_level_upgrade(self, message):
        """Called when a player's population reaches a new level."""
        if not message.sender.is_local_player:
            return

        # show notification
        self.message_widget.add(point=message.building.position.center,
                                string_id='SETTLER_LEVEL_UP',
                                message_dict={'level': message.level + 1})

        # update build menu to show new buildings
        menu = self.get_cur_menu()
        if hasattr(menu, "name") and menu.name == "build_menu_tab_widget":
            self.show_build_menu(update=True)

    def _on_mine_empty(self, message):
        self.message_widget.add(point=message.mine.position.center,
                                string_id='MINE_EMPTY')

    def _on_gui_click_action(self, msg):
        """Make a sound when a button is clicked"""
        AmbientSoundComponent.play_special('click', gain=10)

    def _on_gui_cancel_action(self, msg):
        """Make a sound when a cancelButton is clicked"""
        AmbientSoundComponent.play_special('success', gain=10)

    def _on_gui_hover_action(self, msg):
        """Make a sound when the mouse hovers over a button"""
        AmbientSoundComponent.play_special('refresh', position=None, gain=1)

    def _replace_hotkeys_in_widgets(self):
        """Replaces the `{key}` in the (translated) widget helptext with the actual hotkey"""
        hotkey_replacements = {
            'rotateRight': 'ROTATE_RIGHT',
            'rotateLeft': 'ROTATE_LEFT',
            'speedUp': 'SPEED_UP',
            'speedDown': 'SPEED_DOWN',
            'destroy_tool': 'DESTROY_TOOL',
            'build': 'BUILD_TOOL',
            'gameMenuButton': 'ESCAPE',
            'logbook': 'LOGBOOK',
        }
        for (widgetname, action) in hotkey_replacements.items():
            widget = self.mainhud.findChild(name=widgetname)
            keys = horizons.globals.fife.get_keys_for_action(action)
            # No `.upper()` here: "Pause" looks better than "PAUSE".
            keyname = HOTKEYS.DISPLAY_KEY.get(keys[0], keys[0].capitalize())
            widget.helptext = widget.helptext.format(key=keyname)

    def _on_language_changed(self, msg):
        """Replace the hotkeys after translation.

		NOTE: This should be called _after_ the texts are replaced. This
		currently relies on import order with `horizons.gui`.
		"""
        self._replace_hotkeys_in_widgets()
Example #39
0
    def _init_gui(self):
        """
		Initial init of gui.
		widgets : list of route entry widgets
		slots : dict with slots for each entry
		"""
        self._gui = load_uh_widget("configure_route.xml")

        self.widgets = []
        self.slots = {}
        self.slots_per_entry = 3

        icon = self._gui.findChild(name="minimap")

        def on_click(event, drag):
            if drag:
                return
            if event.getButton() == fife.MouseEvent.LEFT:
                map_coord = event.map_coord
                tile = self.session.world.get_tile(Point(*map_coord))
                if tile is not None and tile.settlement is not None:
                    self.append_warehouse(tile.settlement.warehouse)

        self.minimap = Minimap(
            icon,
            session=self.session,
            world=self.session.world,
            view=self.session.view,
            targetrenderer=horizons.globals.fife.targetrenderer,
            imagemanager=horizons.globals.fife.imagemanager,
            cam_border=False,
            use_rotation=False,
            on_click=on_click)

        resources = self.session.db.get_res_id_and_icon(only_tradeable=True)
        # map an icon for a resource
        # map a resource for an icon
        self.resource_for_icon = {}
        self.icon_for_resource = {}
        for res_id, icon in list(resources) + [(0, self.dummy_icon_path)]:
            self.resource_for_icon[icon] = res_id
            self.icon_for_resource[res_id] = icon

        # don't do any actions if the resource menu is shown
        self.resource_menu_shown = False
        for entry in self.instance.route.waypoints:
            self.add_gui_entry(entry['warehouse'], entry['resource_list'])

        self._check_no_entries_label()

        wait_at_unload_box = self._gui.findChild(name="wait_at_unload")
        wait_at_unload_box.marked = self.instance.route.wait_at_unload

        def toggle_wait_at_unload():
            self._route_cmd("set_wait_at_unload",
                            not self.instance.route.wait_at_unload)

        wait_at_unload_box.capture(toggle_wait_at_unload)

        wait_at_load_box = self._gui.findChild(name="wait_at_load")
        wait_at_load_box.marked = self.instance.route.wait_at_load

        def toggle_wait_at_load():
            self._route_cmd("set_wait_at_load",
                            not self.instance.route.wait_at_load)

        wait_at_load_box.capture(toggle_wait_at_load)

        self._gui.mapEvents({
            OkButton.DEFAULT_NAME: self.hide,
            'start_route/mouseClicked': self.toggle_route
        })
        self._gui.position_technique = "automatic"  # "center:center"
        """
Example #40
0
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()
	def __init__(self, session):
		super(IngameGui, self).__init__()
		self.session = session
		assert isinstance(self.session, horizons.session.Session)
		self.settlement = None
		self._old_menu = None

		self.cursor = None
		self.coordinates_tooltip = None

		self.keylistener = IngameKeyListener(self.session)

		self.cityinfo = CityInfo(self)
		LastActivePlayerSettlementManager.create_instance(self.session)

		self.message_widget = MessageWidget(self.session)

		# Windows
		self.windows = WindowManager()
		self.show_popup = self.windows.show_popup
		self.show_error_popup = self.windows.show_error_popup

		self.logbook = LogBook(self.session, self.windows)
		self.players_overview = PlayersOverview(self.session)
		self.players_settlements = PlayersSettlements(self.session)
		self.players_ships = PlayersShips(self.session)

		self.chat_dialog = ChatDialog(self.windows, self.session)
		self.change_name_dialog = ChangeNameDialog(self.windows, self.session)
		self.pausemenu = PauseMenu(self.session, self, self.windows, in_editor_mode=False)
		self.help_dialog = HelpDialog(self.windows, session=self.session)

		# Icon manager
		self.status_icon_manager = StatusIconManager(
			renderer=self.session.view.renderer['GenericRenderer'],
			layer=self.session.view.layers[LAYERS.OBJECTS]
		)
		self.production_finished_icon_manager = ProductionFinishedIconManager(
			renderer=self.session.view.renderer['GenericRenderer'],
			layer=self.session.view.layers[LAYERS.OBJECTS]
		)

		# 'minimap' is the guichan gui around the actual minimap, which is saved
		# in self.minimap
		self.mainhud = load_uh_widget('minimap.xml')
		self.mainhud.position_technique = "right:top"

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

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

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

		self.mainhud.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.toggle_destroy_tool,
			'build' : self.show_build_menu,
			'diplomacyButton' : self.show_diplomacy_menu,
			'gameMenuButton' : self.toggle_pause,
			'logbook' : lambda: self.windows.toggle(self.logbook)
		})
		self.mainhud.show()

		hotkey_replacements = {
			'rotateRight': 'ROTATE_RIGHT',
			'rotateLeft': 'ROTATE_LEFT',
			'speedUp': 'SPEED_UP',
			'speedDown': 'SPEED_DOWN',
			'destroy_tool': 'DESTROY_TOOL',
			'build': 'BUILD_TOOL',
			'gameMenuButton': 'ESCAPE',
			'logbook': 'LOGBOOK',
		}
		for (widgetname, action) in hotkey_replacements.iteritems():
			widget = self.mainhud.findChild(name=widgetname)
			keys = horizons.globals.fife.get_keys_for_action(action)
			# No `.upper()` here: "Pause" looks better than "PAUSE".
			keyname = HOTKEYS.DISPLAY_KEY.get(keys[0], keys[0].capitalize())
			widget.helptext = widget.helptext.format(key=keyname)

		self.resource_overview = ResourceOverviewBar(self.session)

		# Register for messages
		SpeedChanged.subscribe(self._on_speed_changed)
		NewDisaster.subscribe(self._on_new_disaster)
		NewSettlement.subscribe(self._on_new_settlement)
		PlayerLevelUpgrade.subscribe(self._on_player_level_upgrade)
		MineEmpty.subscribe(self._on_mine_empty)
		ZoomChanged.subscribe(self._update_zoom)

		self._display_speed(self.session.timer.ticks_per_second)
Example #42
0
	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)
		screenwidth = horizons.main.fife.engine_settings.getScreenWidth()

		cityinfo = self.widgets['city_info']
		cityinfo.child_finder = PychanChildFinder(cityinfo)
		cityinfo.position = ( screenwidth/2 - cityinfo.size[0]/2 - 10, 5 )

		self.logbook = LogBook(session)

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

		minimap = self.widgets['minimap']
		minimap.position = (screenwidth - minimap.size[0] -20, 4)
		minimap.show()

		minimap_rect = Rect.init_from_topleft_and_size(minimap.position[0]+77, 55, 120, 120)
		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)

		menupanel = self.widgets['menu_panel']
		menupanel.position = (screenwidth - menupanel.size[0] +15, 149)
		menupanel.show()
		menupanel.mapEvents({
			'destroy_tool' : self.session.destroy_tool,
			'build' : self.show_build_menu,
			'helpLink' : self.main_gui.on_help,
			'gameMenuButton' : self.main_gui.show_pause,
			'logbook' : self.logbook.toggle_visibility
		})

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

		for w in ('status','status_extra','status_gold','status_extra_gold'):
			self.widgets[w].child_finder = PychanChildFinder(self.widgets[w])
		self.widgets['status_gold'].show()

		self.message_widget = MessageWidget(self.session, \
								                        cityinfo.position[0] + cityinfo.size[0], 5)

		self.resbar = ResBar(self.session, gui, self.widgets, self.resource_source)

		# map button names to build functions calls with the building id
		building_list = horizons.main.db.get_building_id_buttonname_settlerlvl()
		self.callbacks_build = {}
		for id,button_name,settler_level in building_list:
			if not settler_level in self.callbacks_build:
				self.callbacks_build[settler_level] = {}
			self.callbacks_build[settler_level][button_name] = Callback(self._build, id)
class CreateGame(Window):
    """Interface for creating a multiplayer game"""
    def __init__(self, windows):
        super(CreateGame, self).__init__(windows)

        self._gui = load_uh_widget('multiplayer_creategame.xml')
        self._gui.mapEvents({
            'cancel': self._windows.close,
            'create': self.act,
        })

        self._files = []
        self._maps_display = []
        self._map_preview = None

    def hide(self):
        self._gui.hide()

    def show(self):
        self._files, self._maps_display = SavegameManager.get_maps()

        self._gui.distributeInitialData({
            'maplist':
            self._maps_display,
            'playerlimit':
            range(2, MULTIPLAYER.MAX_PLAYER_COUNT)
        })

        if self._maps_display:  # select first entry
            self._gui.distributeData({'maplist': 0, 'playerlimit': 0})
            self._update_infos()

        self._gui.findChild(name="maplist").mapEvents(
            {'maplist/action': self._update_infos})

        gamenametextfield = self._gui.findChild(name='gamename')

        def gamename_clicked():
            if gamenametextfield.text == 'Unnamed Game':
                gamenametextfield.text = ""

        gamenametextfield.capture(gamename_clicked, event_name='mouseClicked')
        self._gui.show()

    def act(self):
        mapindex = self._gui.collectData('maplist')
        mapname = self._maps_display[mapindex]
        maxplayers = self._gui.collectData(
            'playerlimit') + 2  # 1 is the first entry
        gamename = self._gui.collectData('gamename')
        password = self._gui.collectData('password')
        maphash = ""

        password = hashlib.sha1(password).hexdigest() if password != "" else ""
        game = NetworkInterface().creategame(mapname, maxplayers, gamename,
                                             maphash, password)
        if game:
            # FIXME When canceling the lobby, I'd like the player to return to the main mp
            # menu, and not see the 'create game' again. We need to close this window, however,
            # this will trigger the display of the main gui, which will part the game in
            # `MultiplayerMenu._check_connection`
            #self._windows.close()
            window = GameLobby(self._windows)
            self._windows.open(window)

    def _update_infos(self):
        index = self._gui.collectData('maplist')
        mapfile = self._files[index]
        number_of_players = SavegameManager.get_recommended_number_of_players(
            mapfile)

        lbl = self._gui.findChild(name="recommended_number_of_players_lbl")
        lbl.text = _("Recommended number of players: {number}").format(
            number=number_of_players)

        self._update_map_preview(mapfile)

    def _update_map_preview(self, map_file):
        if self._map_preview:
            self._map_preview.end()

        world = load_raw_world(map_file)
        self._map_preview = Minimap(
            self._gui.findChild(name='map_preview_minimap'),
            session=None,
            view=None,
            world=world,
            targetrenderer=horizons.globals.fife.targetrenderer,
            imagemanager=horizons.globals.fife.imagemanager,
            cam_border=False,
            use_rotation=False,
            tooltip=None,
            on_click=None,
            preview=True)

        self._map_preview.draw()
class MapPreview(object):
	"""Semiprivate class dealing with the map preview icon"""
	def __init__(self, get_widget):
		"""
		@param get_widget: returns the current widget (self.current)
		"""
		self.minimap = None
		self.calc_proc = None # handle to background calculation process
		self.get_widget = get_widget

	def update_map(self, map_file):
		"""Direct map preview update.
		Only use for existing maps, it's too slow for random maps"""
		if self.minimap is not None:
			self.minimap.end()
		world = self._load_raw_world(map_file)
		self.minimap = Minimap(self._get_map_preview_icon(),
			                     session=None,
			                     view=None,
			                     world=world,
			                     targetrenderer=horizons.main.fife.targetrenderer,
			                     imagemanager=horizons.main.fife.imagemanager,
			                     cam_border=False,
			                     use_rotation=False,
			                     tooltip=None,
			                     on_click=None,
			                     preview=True)
		self.minimap.draw()

	def update_random_map(self, map_params, on_click):
		"""Called when a random map parameter has changed.
		@param map_params: _get_random_map() output
		@param on_click: handler for clicks"""
		def check_calc_process():
			# checks up on calc process (see below)
			if self.calc_proc is not None:
				state = self.calc_proc.poll()
				if state is None: # not finished
					ExtScheduler().add_new_object(check_calc_process, self, 0.1)
				elif state != 0:
					self._set_map_preview_status(u"An unknown error occured while generating the map preview")
				else: # done

					data = open(self.calc_proc.output_filename, "r").read()
					os.unlink(self.calc_proc.output_filename)
					self.calc_proc = None

					icon = self._get_map_preview_icon()
					if icon is None:
						return # dialog already gone

					tooltip = _("Click to generate a different random map")

					if self.minimap is not None:
						self.minimap.end()
					self.minimap = Minimap(icon,
																 session=None,
																 view=None,
																 world=None,
																 targetrenderer=horizons.main.fife.targetrenderer,
																 imagemanager=horizons.main.fife.imagemanager,
																 cam_border=False,
																 use_rotation=False,
																 tooltip=tooltip,
																 on_click=on_click,
																 preview=True)
					self.minimap.draw_data( data )
					icon.show()
					self._set_map_preview_status(u"")

		if self.calc_proc is not None:
			self.calc_proc.kill() # process exists, therefore up is scheduled already
		else:
			ExtScheduler().add_new_object(check_calc_process, self, 0.5)

		# launch process in background to calculate minimap data
		minimap_icon = self._get_map_preview_icon()

		params =  json.dumps(((minimap_icon.width, minimap_icon.height), map_params))

		args = (sys.executable, sys.argv[0], "--generate-minimap", params)
		handle, outfilename = tempfile.mkstemp()
		os.close(handle)
		self.calc_proc = subprocess.Popen(args=args,
								                      stdout=open(outfilename, "w"))
		self.calc_proc.output_filename = outfilename # attach extra info
		self._set_map_preview_status(u"Generating preview...")


	@classmethod
	def generate_minimap(cls, size, parameters):
		"""Called as subprocess, calculates minimap data and passes it via string via stdout"""
		# called as standalone basically, so init everything we need
		from horizons.main import _create_main_db
		from horizons.entities import Entities
		from horizons.ext.dummy import Dummy
		db = _create_main_db()
		Entities.load_grounds(db, load_now=False) # create all references
		map_file = SingleplayerMenu._generate_random_map( parameters )
		world = cls._load_raw_world(map_file)
		location = Rect.init_from_topleft_and_size_tuples( (0, 0), size)
		minimap = Minimap(location,
											session=None,
											view=None,
											world=world,
											targetrenderer=Dummy(),
											imagemanager=Dummy(),
											cam_border=False,
											use_rotation=False,
											preview=True)
		# communicate via stdout
		print minimap.dump_data()

	@classmethod
	def _load_raw_world(cls, map_file):
		WorldObject.reset()
		world = World(session=None)
		world.inited = True
		world.load_raw_map( SavegameAccessor( map_file ), preview=True )
		return world

	def _get_map_preview_icon(self):
		"""Returns pychan icon for map preview"""
		return self.get_widget().findChild(name="map_preview_minimap")

	def _set_map_preview_status(self, text):
		"""Sets small status label next to map preview"""
		wdg = self.get_widget().findChild(name="map_preview_status_label")
		if wdg: # might show next dialog already
			wdg.text = text
class RandomMapWidget(object):
	"""Create a random map, influence map generation with multiple sliders."""

	map_sizes = [50, 100, 150, 200, 250]
	water_percents = [20, 30, 40, 50, 60, 70, 80]
	island_sizes = [30, 40, 50, 60, 70]
	island_size_deviations = [5, 10, 20, 30, 40]

	def __init__(self, windows, singleplayer_menu, aidata):
		self._windows = windows
		self._singleplayer_menu = singleplayer_menu
		self._aidata = aidata

		self._gui = load_uh_widget('sp_random.xml')
		self._map_parameters = {}  # stores the current values from the sliders
		self._game_settings = GameSettingsWidget()

		# Map preview
		self._last_map_parameters = None
		self._preview_process = None
		self._preview_output = None
		self._map_preview = None

	def end(self):
		if self._preview_process:
			self._preview_process.kill()
			self._preview_process = None
		ExtScheduler().rem_all_classinst_calls(self)

	def get_widget(self):
		return self._gui

	def act(self, player_name, player_color):
		self.end()

		map_file = generate_random_map(*self._get_map_parameters())

		options = StartGameOptions.create_start_map(map_file)
		options.set_human_data(player_name, player_color)
		options.ai_players = self._aidata.get_ai_players()
		options.trader_enabled = self._game_settings.free_trader
		options.pirate_enabled = self._game_settings.pirates
		options.disasters_enabled = self._game_settings.disasters
		options.natural_resource_multiplier = self._game_settings.natural_resource_multiplier
		horizons.main.start_singleplayer(options)

	def show(self):
		seed_string_field = self._gui.findChild(name='seed_string_field')
		seed_string_field.capture(self._on_random_parameter_changed)
		seed_string_field.text = generate_random_seed(seed_string_field.text)

		parameters = (
			('map_size', self.map_sizes, _('Map size:'), 'RandomMapSize'),
			('water_percent', self.water_percents, _('Water:'), 'RandomMapWaterPercent'),
			('max_island_size', self.island_sizes, _('Max island size:'), 'RandomMapMaxIslandSize'),
			('preferred_island_size', self.island_sizes, _('Preferred island size:'), 'RandomMapPreferredIslandSize'),
			('island_size_deviation', self.island_size_deviations, _('Island size deviation:'), 'RandomMapIslandSizeDeviation'),
		)

		for param, __, __, setting_name in parameters:
			self._map_parameters[param] = horizons.globals.fife.get_uh_setting(setting_name)

		def make_on_change(param, values, text, setting_name):
			# When a slider is changed, update the value displayed in the label, save the value
			# in the settings and store the value in self._map_parameters
			def on_change():
				slider = self._gui.findChild(name=param + '_slider')
				self._gui.findChild(name=param + '_lbl').text = text + u' ' + unicode(values[int(slider.value)])
				horizons.globals.fife.set_uh_setting(setting_name, slider.value)
				horizons.globals.fife.save_settings()
				self._on_random_parameter_changed()
				self._map_parameters[param] = values[int(slider.value)]
			return on_change

		for param, values, text, setting_name in parameters:
			slider = self._gui.findChild(name=param + '_slider')
			on_change = make_on_change(param, values, text, setting_name)
			slider.capture(on_change)
			slider.value = horizons.globals.fife.get_uh_setting(setting_name)
			on_change()

		self._gui.findChild(name='game_settings_box').addChild(self._game_settings.get_widget())
		self._game_settings.show()
		self._aidata.show()

	def _get_map_parameters(self):
		return (
			self._gui.findChild(name='seed_string_field').text,
			self._map_parameters['map_size'],
			self._map_parameters['water_percent'],
			self._map_parameters['max_island_size'],
			self._map_parameters['preferred_island_size'],
			self._map_parameters['island_size_deviation']
		)

	def _on_random_parameter_changed(self):
		self._update_map_preview()

	# Map preview

	def _on_preview_click(self, event, drag):
		seed_string_field = self._gui.findChild(name='seed_string_field')
		seed_string_field.text = generate_random_seed(seed_string_field.text)
		self._on_random_parameter_changed()

	def _update_map_preview(self):
		"""Start a new process to generate a map preview."""
		current_parameters = self._get_map_parameters()
		if self._last_map_parameters == current_parameters:
			# nothing changed, don't generate a new preview
			return

		self._last_map_parameters = current_parameters

		if self._preview_process:
			self._preview_process.kill() # process exists, therefore up is scheduled already

		# launch process in background to calculate minimap data
		minimap_icon = self._gui.findChild(name='map_preview_minimap')
		params = json.dumps(((minimap_icon.width, minimap_icon.height), current_parameters))

		args = [sys.executable, sys.argv[0], "--generate-minimap", params]
		# We're running UH in a new process, make sure fife is setup correctly
		if horizons.main.command_line_arguments.fife_path:
			args.extend(["--fife-path", horizons.main.command_line_arguments.fife_path])

		handle, self._preview_output = tempfile.mkstemp()
		os.close(handle)
		self._preview_process = subprocess.Popen(args=args, stdout=open(self._preview_output, "w"))
		self._set_map_preview_status(u"Generating preview…")

		ExtScheduler().add_new_object(self._poll_preview_process, self, 0.5)

	def _poll_preview_process(self):
		"""This will be called regularly to see if the process ended.

		If the process has not yet finished, schedule a new callback to this function.
		Otherwise use the data to update the minimap.
		"""
		if not self._preview_process:
			return

		self._preview_process.poll()

		if self._preview_process.returncode is None: # not finished
			ExtScheduler().add_new_object(self._poll_preview_process, self, 0.1)
			return
		elif self._preview_process.returncode != 0:
			self._preview_process = None
			self._set_map_preview_status(u"An unknown error occurred while generating the map preview")
			return

		with open(self._preview_output, 'r') as f:
			data = f.read()

		os.unlink(self._preview_output)
		self._preview_process = None

		if self._map_preview:
			self._map_preview.end()

		self._map_preview = Minimap(
			self._gui.findChild(name='map_preview_minimap'),
			session=None,
			view=None,
			world=None,
			targetrenderer=horizons.globals.fife.targetrenderer,
			imagemanager=horizons.globals.fife.imagemanager,
			cam_border=False,
			use_rotation=False,
			tooltip=_("Click to generate a different random map"),
			on_click=self._on_preview_click,
			preview=True)

		self._map_preview.draw_data(data)
		self._set_map_preview_status(u"")

	def _set_map_preview_status(self, text):
		self._gui.findChild(name="map_preview_status_label").text = text
Example #46
0
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' : 'resource_bar',
		'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)

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

		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.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.globals.fife.targetrenderer,
		                       imagemanager=horizons.globals.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)
		ResourceBarResize.subscribe(self._on_resourcebar_resize)

		# Register for messages
		SettlerUpdate.subscribe(self._on_settler_level_change)
		SettlerInhabitantsChanged.subscribe(self._on_settler_inhabitant_change)
		HoverSettlementChanged.subscribe(self._cityinfo_set)

	def _on_resourcebar_resize(self, message):
		self._update_cityinfo_position()

	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()
		SettlerUpdate.unsubscribe(self._on_settler_level_change)
		ResourceBarResize.unsubscribe(self._on_resourcebar_resize)
		HoverSettlementChanged.unsubscribe(self._cityinfo_set)
		SettlerInhabitantsChanged.unsubscribe(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.is_local_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:
				# After scrolling away from settlement, leave name on screen for some
				# seconds. Players can still click on it to rename the settlement now.
				ExtScheduler().add_new_object(self.cityinfo.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:
				# hovered settlement of other player, simply hide the widget
				self.cityinfo.hide()

		else:# do not hide if settlement is hovered and a hide was previously scheduled
			ExtScheduler().rem_call(self, self.cityinfo.hide)

			self.update_settlement() # calls show()
			settlement.add_change_listener(self.update_settlement)

	def _on_settler_inhabitant_change(self, message):
		assert isinstance(message, SettlerInhabitantsChanged)
		foundlabel = self.cityinfo.child_finder('city_inhabitants')
		old_amount = int(foundlabel.text) if foundlabel.text else 0
		foundlabel.text = u' {amount:>4d}'.format(amount=old_amount+message.change)
		foundlabel.resizeToContent()

	def update_settlement(self):
		city_name_label = self.cityinfo.child_finder('city_name')
		if self.settlement.owner.is_local_player: # allow name changes
			# Update settlement on the resource overview to make sure it
			# is setup correctly for the coming calculations
			self.resource_overview.set_inventory_instance(self.settlement)
			cb = Callback(self.show_change_name_dialog, self.settlement)
			helptext = _("Click to change the name of your settlement")
			city_name_label.enable_cursor_change_on_hover()
		else: # no name changes
			cb = lambda : AmbientSoundComponent.play_special('error')
			helptext = u""
			city_name_label.disable_cursor_change_on_hover()
		self.cityinfo.mapEvents({
			'city_name': cb
		})
		city_name_label.helptext = helptext

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

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

		foundlabel = self.cityinfo.child_finder('city_inhabitants')
		foundlabel.text = u' {amount:>4d}'.format(amount=self.settlement.inhabitants)
		foundlabel.resizeToContent()

		self._update_cityinfo_position()

	def _update_cityinfo_position(self):
		""" Places cityinfo widget depending on resource bar dimensions.

		For a normal-sized resource bar and reasonably large resolution:
		* determine resource bar length (includes gold)
		* determine empty horizontal space between resbar end and minimap start
		* display cityinfo centered in that area if it is sufficiently large

		If too close to the minimap (cityinfo larger than length of this empty space)
		move cityinfo centered to very upper screen edge. Looks bad, works usually.
		In this case, the resbar is redrawn to put the cityinfo "behind" it visually.
		"""
		width = horizons.globals.fife.engine_settings.getScreenWidth()
		resbar = self.resource_overview.get_size()
		is_foreign = (self.settlement.owner != self.session.world.player)
		blocked = self.cityinfo.size[0] + int(1.5*self.minimap.get_size()[1])
		# minimap[1] returns width! Use 1.5*width because of the GUI around it

		if is_foreign: # other player, no resbar exists
			self.cityinfo.pos = ('center', 'top')
			xoff = 0
			yoff = 19
		elif blocked < width < resbar[0] + blocked: # large resbar / small resolution
			self.cityinfo.pos = ('center', 'top')
			xoff = 0
			yoff = 0 # upper screen edge
		else:
			self.cityinfo.pos = ('left', 'top')
			xoff = resbar[0] + (width - blocked - resbar[0]) // 2
			yoff = 24

		self.cityinfo.offset = (xoff, yoff)
		self.cityinfo.position_technique = "{pos[0]}{off[0]:+d}:{pos[1]}{off[1]:+d}".format(
				pos=self.cityinfo.pos,
				off=self.cityinfo.offset)
		self.cityinfo.hide()
		self.cityinfo.show()

	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 getattr(self.get_cur_menu(), 'name', None) == "diplomacy_widget":
			self.hide_menu()
			return

		if not DiplomacyTab.is_useable(self.session.world):
			self.main_gui.show_popup(_("No diplomacy possible"),
			                         _("Cannot do diplomacy as there are no other players."))
			return

		tab = DiplomacyTab(self, self.session.world)
		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.create_tabs(self.session, self._build)
			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:
			self._build(BUILDINGS.TRAIL)
		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()

		TabWidgetChanged.broadcast(self)

	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)

		cur_settlement = LastActivePlayerSettlementManager().get_current_settlement()
		self._cityinfo_set( HoverSettlementChanged(self, cur_settlement) )

		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.DEFAULT_NAME: Callback(self.change_name, instance),
			CancelButton.DEFAULT_NAME: 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 = 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 new_name or not 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.DEFAULT_NAME: self.save_map,
			CancelButton.DEFAULT_NAME: 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)

			# TODO: Use a better measure then first tab
			# Quite fragile, makes sure the tablist in the mainsquare menu is updated
			if hasattr(menu, '_tabs') and isinstance(menu._tabs[0], MainSquareOverviewTab):
				instance = list(self.session.selected_instances)[0]
				instance.get_component(SelectableComponent).show_menu(jump_to_tabclass=type(menu.current_tab))

	def show_chat_dialog(self):
		"""Show a dialog where the user can enter a chat message"""
		events = {
			OkButton.DEFAULT_NAME: self._do_chat,
			CancelButton.DEFAULT_NAME: 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()
class FreeMapsWidget(object):
	"""Start a game by selecting an existing map."""

	def __init__(self, windows, singleplayer_menu, aidata):
		self._windows = windows
		self._singleplayer_menu = singleplayer_menu
		self._aidata = aidata

		self._gui = load_uh_widget('sp_free_maps.xml')
		self._game_settings = GameSettingsWidget()

		self._map_preview = None

	def end(self):
		pass

	def get_widget(self):
		return self._gui

	def act(self, player_name, player_color):
		map_file = self._get_selected_map()

		options = StartGameOptions.create_start_map(map_file)
		options.set_human_data(player_name, player_color)
		options.ai_players = self._aidata.get_ai_players()
		options.trader_enabled = self._game_settings.free_trader
		options.pirate_enabled = self._game_settings.pirates
		options.disasters_enabled = self._game_settings.disasters
		options.natural_resource_multiplier = self._game_settings.natural_resource_multiplier
		horizons.main.start_singleplayer(options)

	def show(self):
		self._files, maps_display = SavegameManager.get_maps()

		self._gui.distributeInitialData({'maplist': maps_display})
		self._gui.mapEvents({
			'maplist/action': self._update_map_infos,
		})
		if maps_display: # select first entry
			self._gui.distributeData({'maplist': 0})
			self._update_map_infos()

		self._gui.findChild(name='game_settings_box').addChild(self._game_settings.get_widget())
		self._game_settings.show()
		self._aidata.show()

	def _update_map_infos(self):
		map_file = self._get_selected_map()

		number_of_players = SavegameManager.get_recommended_number_of_players(map_file)
		lbl = self._gui.findChild(name="recommended_number_of_players_lbl")
		lbl.text = _("Recommended number of players: {number}").format(number=number_of_players)

		self._update_map_preview(map_file)

	def _get_selected_map(self):
		selection_index = self._gui.collectData('maplist')
		assert selection_index != -1

		return self._files[self._gui.collectData('maplist')]

	def _update_map_preview(self, map_file):
		if self._map_preview:
			self._map_preview.end()

		world = load_raw_world(map_file)
		self._map_preview = Minimap(
			self._gui.findChild(name='map_preview_minimap'),
			session=None,
			view=None,
			world=world,
			targetrenderer=horizons.globals.fife.targetrenderer,
			imagemanager=horizons.globals.fife.imagemanager,
			cam_border=False,
			use_rotation=False,
			tooltip=None,
			on_click=None,
			preview=True)

		self._map_preview.draw()
Example #48
0
	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)

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

		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.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.globals.fife.targetrenderer,
		                       imagemanager=horizons.globals.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)
		ResourceBarResize.subscribe(self._on_resourcebar_resize)

		# Register for messages
		SettlerUpdate.subscribe(self._on_settler_level_change)
		SettlerInhabitantsChanged.subscribe(self._on_settler_inhabitant_change)
		HoverSettlementChanged.subscribe(self._cityinfo_set)
Example #49
0
class IngameGui(LivingObject):
    """Class handling all the ingame gui events.
	Assumes that only 1 instance is used (class variables)"""

    message_widget = livingProperty()
    minimap = livingProperty()
    keylistener = livingProperty()

    def __init__(self, session):
        super(IngameGui, self).__init__()
        self.session = session
        assert isinstance(self.session, horizons.session.Session)
        self.settlement = None
        self._old_menu = None

        self.cursor = None
        self.coordinates_tooltip = None

        self.keylistener = IngameKeyListener(self.session)

        self.cityinfo = CityInfo(self)
        LastActivePlayerSettlementManager.create_instance(self.session)

        self.message_widget = MessageWidget(self.session)

        # Windows
        self.windows = WindowManager()
        self.show_popup = self.windows.show_popup
        self.show_error_popup = self.windows.show_error_popup

        self.logbook = LogBook(self.session, self.windows)
        self.players_overview = PlayersOverview(self.session)
        self.players_settlements = PlayersSettlements(self.session)
        self.players_ships = PlayersShips(self.session)

        self.chat_dialog = ChatDialog(self.windows, self.session)
        self.change_name_dialog = ChangeNameDialog(self.windows, self.session)
        self.pausemenu = PauseMenu(self.session,
                                   self,
                                   self.windows,
                                   in_editor_mode=False)
        self.help_dialog = HelpDialog(self.windows, session=self.session)

        # Icon manager
        self.status_icon_manager = StatusIconManager(
            renderer=self.session.view.renderer['GenericRenderer'],
            layer=self.session.view.layers[LAYERS.OBJECTS])
        self.production_finished_icon_manager = ProductionFinishedIconManager(
            renderer=self.session.view.renderer['GenericRenderer'],
            layer=self.session.view.layers[LAYERS.OBJECTS])

        # 'minimap' is the guichan gui around the actual minimap, which is saved
        # in self.minimap
        self.mainhud = load_uh_widget('minimap.xml')
        self.mainhud.position_technique = "right:top"

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

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

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

        self.mainhud.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.toggle_destroy_tool,
            'build':
            self.show_build_menu,
            'diplomacyButton':
            self.show_diplomacy_menu,
            'gameMenuButton':
            self.toggle_pause,
            'logbook':
            lambda: self.windows.toggle(self.logbook)
        })
        self.mainhud.show()

        self.resource_overview = ResourceOverviewBar(self.session)

        # Register for messages
        SpeedChanged.subscribe(self._on_speed_changed)
        NewDisaster.subscribe(self._on_new_disaster)
        NewSettlement.subscribe(self._on_new_settlement)
        PlayerLevelUpgrade.subscribe(self._on_player_level_upgrade)
        MineEmpty.subscribe(self._on_mine_empty)
        self.session.view.add_change_listener(self._update_zoom)

        self._display_speed(self.session.timer.ticks_per_second)

    def end(self):
        # unsubscribe early, to avoid messages coming in while we're shutting down
        SpeedChanged.unsubscribe(self._on_speed_changed)
        NewDisaster.unsubscribe(self._on_new_disaster)
        NewSettlement.unsubscribe(self._on_new_settlement)
        PlayerLevelUpgrade.unsubscribe(self._on_player_level_upgrade)
        MineEmpty.unsubscribe(self._on_mine_empty)
        self.session.view.remove_change_listener(self._update_zoom)

        self.mainhud.mapEvents({
            'zoomIn': None,
            'zoomOut': None,
            'rotateRight': None,
            'rotateLeft': None,
            'destroy_tool': None,
            'build': None,
            'diplomacyButton': None,
            'gameMenuButton': None
        })
        self.mainhud.hide()
        self.mainhud = None

        self.windows.close_all()
        self.message_widget = None
        self.minimap = None
        self.resource_overview.end()
        self.resource_overview = None
        self.keylistener = None
        self.cityinfo.end()
        self.cityinfo = None
        self.hide_menu()

        if self.cursor:
            self.cursor.remove()
            self.cursor.end()
            self.cursor = None

        LastActivePlayerSettlementManager().remove()
        LastActivePlayerSettlementManager.destroy_instance()

        self.production_finished_icon_manager.end()
        self.production_finished_icon_manager = None
        self.status_icon_manager.end()
        self.status_icon_manager = None

        super(IngameGui, self).end()

    def show_select_savegame(self, mode):
        window = SelectSavegameDialog(mode, self.windows)
        return self.windows.show(window)

    def toggle_pause(self):
        self.windows.toggle(self.pausemenu)

    def toggle_help(self):
        self.windows.toggle(self.help_dialog)

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

    def show_diplomacy_menu(self):
        # check if the menu is already shown
        if getattr(self.get_cur_menu(), 'name', None) == "diplomacy_widget":
            self.hide_menu()
            return

        if not DiplomacyTab.is_useable(self.session.world):
            self.windows.show_popup(
                _("No diplomacy possible"),
                _("Cannot do diplomacy as there are no other players."))
            return

        tab = DiplomacyTab(self, self.session.world)
        self.show_menu(tab)

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

    def show_build_menu(self, update=False):
        """
		@param update: set when build possibilities change (e.g. after inhabitant tier 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.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.create_tabs(self.session, self._build)
            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.set_cursor('building', cls, None if unit is None else unit())

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

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

        TabWidgetChanged.broadcast(self)

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

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

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

        if self.session.is_game_loaded():
            LastActivePlayerSettlementManager().load(db)
            cur_settlement = LastActivePlayerSettlementManager(
            ).get_current_settlement()
            self.cityinfo.set_settlement(cur_settlement)

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

        self.current_cursor = 'default'
        self.cursor = mousetools.SelectionTool(self.session)
        # Set cursor correctly, menus might need to be opened.
        # Open menus later; they may need unit data not yet inited
        self.cursor.apply_select()

        if not self.session.is_game_loaded():
            # Fire a message for new world creation
            self.session.ingame_gui.message_widget.add('NEW_WORLD')

        # Show message when the relationship between players changed
        def notify_change(caller, old_state, new_state, a, b):
            player1 = u"%s" % a.name
            player2 = u"%s" % b.name

            data = {'player1': player1, 'player2': player2}

            string_id = 'DIPLOMACY_STATUS_{old}_{new}'.format(
                old=old_state.upper(), new=new_state.upper())
            self.message_widget.add(string_id=string_id, message_dict=data)

        self.session.world.diplomacy.add_diplomacy_status_changed_listener(
            notify_change)

    def show_change_name_dialog(self, instance):
        """Shows a dialog where the user can change the name of an object."""
        self.windows.show(self.change_name_dialog, instance=instance)

    def on_escape(self):
        if self.windows.visible:
            self.windows.on_escape()
        elif hasattr(self.cursor, 'on_escape'):
            self.cursor.on_escape()
        else:
            self.toggle_pause()

        return True

    def on_return(self):
        if self.windows.visible:
            self.windows.on_return()

        return True

    def _on_speed_changed(self, message):
        self._display_speed(message.new)

    def _display_speed(self, tps):
        text = u''
        up_icon = self.mainhud.findChild(name='speedUp')
        down_icon = self.mainhud.findChild(name='speedDown')
        if tps == 0:  # pause
            text = u'0x'
            up_icon.set_inactive()
            down_icon.set_inactive()
        else:
            if tps != GAME_SPEED.TICKS_PER_SECOND:
                text = unicode("%1gx" %
                               (tps * 1.0 / GAME_SPEED.TICKS_PER_SECOND))
                #%1g: displays 0.5x, but 2x instead of 2.0x
            index = GAME_SPEED.TICK_RATES.index(tps)
            if index + 1 >= len(GAME_SPEED.TICK_RATES):
                up_icon.set_inactive()
            else:
                up_icon.set_active()
            if index > 0:
                down_icon.set_active()
            else:
                down_icon.set_inactive()

        wdg = self.mainhud.findChild(name="speed_text")
        wdg.text = text
        wdg.resizeToContent()
        self.mainhud.show()

    def on_key_press(self, action, evt):
        """Handle a key press in-game.

		Returns True if the key was acted upon.
		"""
        _Actions = KeyConfig._Actions
        keyval = evt.getKey().getValue()

        if action == _Actions.ESCAPE:
            return self.on_escape()
        elif keyval == fife.Key.ENTER:
            return self.on_return()

        if action == _Actions.GRID:
            gridrenderer = self.session.view.renderer['GridRenderer']
            gridrenderer.setEnabled(not gridrenderer.isEnabled())
        elif action == _Actions.COORD_TOOLTIP:
            self.coordinates_tooltip.toggle()
        elif action == _Actions.DESTROY_TOOL:
            self.toggle_destroy_tool()
        elif action == _Actions.REMOVE_SELECTED:
            self.session.remove_selected()
        elif action == _Actions.ROAD_TOOL:
            self.toggle_road_tool()
        elif action == _Actions.SPEED_UP:
            SpeedUpCommand().execute(self.session)
        elif action == _Actions.SPEED_DOWN:
            SpeedDownCommand().execute(self.session)
        elif action == _Actions.PAUSE:
            TogglePauseCommand().execute(self.session)
        elif action == _Actions.PLAYERS_OVERVIEW:
            self.logbook.toggle_stats_visibility(widget='players')
        elif action == _Actions.SETTLEMENTS_OVERVIEW:
            self.logbook.toggle_stats_visibility(widget='settlements')
        elif action == _Actions.SHIPS_OVERVIEW:
            self.logbook.toggle_stats_visibility(widget='ships')
        elif action == _Actions.LOGBOOK:
            self.windows.toggle(self.logbook)
        elif action == _Actions.DEBUG and VERSION.IS_DEV_VERSION:
            import pdb
            pdb.set_trace()
        elif action == _Actions.BUILD_TOOL:
            self.show_build_menu()
        elif action == _Actions.ROTATE_RIGHT:
            if hasattr(self.cursor, "rotate_right"):
                # used in e.g. build preview to rotate building instead of map
                self.cursor.rotate_right()
            else:
                self.session.view.rotate_right()
                self.minimap.rotate_right()
        elif action == _Actions.ROTATE_LEFT:
            if hasattr(self.cursor, "rotate_left"):
                self.cursor.rotate_left()
            else:
                self.session.view.rotate_left()
                self.minimap.rotate_left()
        elif action == _Actions.CHAT:
            self.windows.show(self.chat_dialog)
        elif action == _Actions.TRANSLUCENCY:
            self.session.world.toggle_translucency()
        elif action == _Actions.TILE_OWNER_HIGHLIGHT:
            self.session.world.toggle_owner_highlight()
        elif keyval in (fife.Key.NUM_0, fife.Key.NUM_1, fife.Key.NUM_2,
                        fife.Key.NUM_3, fife.Key.NUM_4, fife.Key.NUM_5,
                        fife.Key.NUM_6, fife.Key.NUM_7, fife.Key.NUM_8,
                        fife.Key.NUM_9):
            num = int(keyval - fife.Key.NUM_0)
            if evt.isControlPressed():
                # create new group (only consider units owned by the player)
                self.session.selection_groups[num] = \
                    set(filter(lambda unit : unit.owner.is_local_player,
                               self.session.selected_instances))
                # drop units of the new group from all other groups
                for group in self.session.selection_groups:
                    if group is not self.session.selection_groups[num]:
                        group -= self.session.selection_groups[num]
            else:
                # deselect
                # we need to make sure to have a cursor capable of selection (for apply_select())
                # this handles deselection implicitly in the destructor
                self.set_cursor('selection')

                # apply new selection
                for instance in self.session.selection_groups[num]:
                    instance.get_component(SelectableComponent).select(
                        reset_cam=True)
                # assign copy since it will be randomly changed, the unit should only be changed on ctrl-events
                self.session.selected_instances = self.session.selection_groups[
                    num].copy()
                # show menu depending on the entities selected
                if self.session.selected_instances:
                    self.cursor.apply_select()
                else:
                    # nothing is selected here, we need to hide the menu since apply_select doesn't handle that case
                    self.show_menu(None)
        elif action == _Actions.QUICKSAVE:
            self.session.quicksave(
            )  # load is only handled by the MainListener
        elif action == _Actions.PIPETTE:
            # copy mode: pipette tool
            self.toggle_cursor('pipette')
        elif action == _Actions.HEALTH_BAR:
            # shows health bar of every instance with an health component
            self.session.world.toggle_health_for_all_health_instances()
        elif action == _Actions.SHOW_SELECTED:
            if self.session.selected_instances:
                # scroll to first one, we can never guarantee to display all selected units
                instance = iter(self.session.selected_instances).next()
                self.session.view.center(*instance.position.center.to_tuple())
                for instance in self.session.selected_instances:
                    if hasattr(instance,
                               "path") and instance.owner.is_local_player:
                        self.minimap.show_unit_path(instance)
        elif action == _Actions.HELP:
            self.toggle_help()
        else:
            return False

        return True

    def toggle_cursor(self, which, *args, **kwargs):
        """Alternate between the cursor which and default.
		args and kwargs are used to construct which."""
        if self.current_cursor == which:
            self.set_cursor()
        else:
            self.set_cursor(which, *args, **kwargs)

    def set_cursor(self, which='default', *args, **kwargs):
        """Sets the mousetool (i.e. cursor).
		This is done here for encapsulation and control over destructors.
		Further arguments are passed to the mouse tool constructor."""
        self.cursor.remove()
        self.current_cursor = which
        klass = {
            'default': mousetools.SelectionTool,
            'selection': mousetools.SelectionTool,
            'tearing': mousetools.TearingTool,
            'pipette': mousetools.PipetteTool,
            'attacking': mousetools.AttackingTool,
            'building': mousetools.BuildingTool,
        }[which]
        self.cursor = klass(self.session, *args, **kwargs)

    def toggle_destroy_tool(self):
        """Initiate the destroy tool"""
        self.toggle_cursor('tearing')

    def _update_zoom(self):
        """Enable/disable zoom buttons"""
        zoom = self.session.view.get_zoom()
        in_icon = self.mainhud.findChild(name='zoomIn')
        out_icon = self.mainhud.findChild(name='zoomOut')
        if zoom == VIEW.ZOOM_MIN:
            out_icon.set_inactive()
        else:
            out_icon.set_active()
        if zoom == VIEW.ZOOM_MAX:
            in_icon.set_inactive()
        else:
            in_icon.set_active()

    def _on_new_disaster(self, message):
        """Called when a building is 'infected' with a disaster."""
        if message.building.owner.is_local_player and len(
                message.disaster._affected_buildings) == 1:
            pos = message.building.position.center
            self.message_widget.add(
                point=pos, string_id=message.disaster_class.NOTIFICATION_TYPE)

    def _on_new_settlement(self, message):
        player = message.settlement.owner
        self.message_widget.add(string_id='NEW_SETTLEMENT',
                                point=message.warehouse_position,
                                message_dict={'player': player.name},
                                play_sound=player.is_local_player)

    def _on_player_level_upgrade(self, message):
        """Called when a player's population reaches a new level."""
        if not message.sender.is_local_player:
            return

        # show notification
        self.message_widget.add(point=message.building.position.center,
                                string_id='SETTLER_LEVEL_UP',
                                message_dict={'level': message.level + 1})

        # update build menu to show new buildings
        menu = self.get_cur_menu()
        if hasattr(menu, "name") and menu.name == "build_menu_tab_widget":
            self.show_build_menu(update=True)

        # TODO: Use a better measure then first tab
        # Quite fragile, makes sure the tablist in the mainsquare menu is updated
        if hasattr(menu, '_tabs') and isinstance(menu._tabs[0],
                                                 MainSquareOverviewTab):
            instance = list(self.session.selected_instances)[0]
            instance.get_component(SelectableComponent).show_menu(
                jump_to_tabclass=type(menu.current_tab))

    def _on_mine_empty(self, message):
        self.message_widget.add(point=message.mine.position.center,
                                string_id='MINE_EMPTY')
class IngameGui(LivingObject):
	"""Class handling all the ingame gui events.
	Assumes that only 1 instance is used (class variables)"""

	message_widget = livingProperty()
	minimap = livingProperty()
	keylistener = livingProperty()

	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.settlement = None
		self._old_menu = None

		self.cursor = None
		self.coordinates_tooltip = None

		self.keylistener = IngameKeyListener(self.session)

		self.cityinfo = CityInfo(self)
		LastActivePlayerSettlementManager.create_instance(self.session)

		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.chat_dialog = ChatDialog(self.main_gui, self, self.session)
		self.change_name_dialog = ChangeNameDialog(self.main_gui, self, self.session)
		self.pausemenu = PauseMenu(self.session, self.main_gui, self, in_editor_mode=False)

		# Icon manager
		self.status_icon_manager = StatusIconManager(
			renderer=self.session.view.renderer['GenericRenderer'],
			layer=self.session.view.layers[LAYERS.OBJECTS]
		)
		self.production_finished_icon_manager = ProductionFinishedIconManager(
			renderer=self.session.view.renderer['GenericRenderer'],
			layer=self.session.view.layers[LAYERS.OBJECTS]
		)

		# 'minimap' is the guichan gui around the actual minimap, which is saved
		# in self.minimap
		self.mainhud = load_uh_widget('minimap.xml')
		self.mainhud.position_technique = "right+0:top+0"

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

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

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

		self.mainhud.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.toggle_destroy_tool,
			'build' : self.show_build_menu,
			'diplomacyButton' : self.show_diplomacy_menu,
			'gameMenuButton' : self.toggle_pause,
			'logbook' : self.logbook.toggle_visibility
		})
		self.mainhud.show()

		self.resource_overview = ResourceOverviewBar(self.session)

		# Register for messages
		SettlerUpdate.subscribe(self._on_settler_level_change)
		SpeedChanged.subscribe(self._on_speed_changed)
		self.session.view.add_change_listener(self._update_zoom)

		self._display_speed(self.session.timer.ticks_per_second)

	def end(self):
		self.mainhud.mapEvents({
			'zoomIn' : None,
			'zoomOut' : None,
			'rotateRight' : None,
			'rotateLeft': None,

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

		self.message_widget = None
		self.minimap = None
		self.resource_overview.end()
		self.resource_overview = None
		self.keylistener = None
		self.cityinfo.end()
		self.cityinfo = None
		self.hide_menu()
		SettlerUpdate.unsubscribe(self._on_settler_level_change)
		SpeedChanged.unsubscribe(self._on_speed_changed)
		self.session.view.remove_change_listener(self._update_zoom)

		if self.cursor:
			self.cursor.remove()
			self.cursor.end()
			self.cursor = None

		LastActivePlayerSettlementManager().remove()
		LastActivePlayerSettlementManager.destroy_instance()

		self.production_finished_icon_manager.end()
		self.production_finished_icon_manager = None
		self.status_icon_manager.end()
		self.status_icon_manager = None

		super(IngameGui, self).end()

	def toggle_pause(self):
		self.pausemenu.toggle()

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

	def show_diplomacy_menu(self):
		# check if the menu is already shown
		if getattr(self.get_cur_menu(), 'name', None) == "diplomacy_widget":
			self.hide_menu()
			return

		if not DiplomacyTab.is_useable(self.session.world):
			self.main_gui.show_popup(_("No diplomacy possible"),
			                         _("Cannot do diplomacy as there are no other players."))
			return

		tab = DiplomacyTab(self, self.session.world)
		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.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.create_tabs(self.session, self._build)
			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.set_cursor('building', cls, None if unit is None else unit())

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

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

		TabWidgetChanged.broadcast(self)

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

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

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

		if self.session.is_game_loaded():
			LastActivePlayerSettlementManager().load(db)
			cur_settlement = LastActivePlayerSettlementManager().get_current_settlement()
			self.cityinfo.set_settlement(cur_settlement)

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

		self.current_cursor = 'default'
		self.cursor = mousetools.SelectionTool(self.session)
		# Set cursor correctly, menus might need to be opened.
		# Open menus later; they may need unit data not yet inited
		self.cursor.apply_select()

		if not self.session.is_game_loaded():
			# Fire a message for new world creation
			self.session.ingame_gui.message_widget.add(point=None, string_id='NEW_WORLD')

	def show_change_name_dialog(self, instance):
		"""Shows a dialog where the user can change the name of an object."""
		self.change_name_dialog.show(instance)

	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 _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)

			# TODO: Use a better measure then first tab
			# Quite fragile, makes sure the tablist in the mainsquare menu is updated
			if hasattr(menu, '_tabs') and isinstance(menu._tabs[0], MainSquareOverviewTab):
				instance = list(self.session.selected_instances)[0]
				instance.get_component(SelectableComponent).show_menu(jump_to_tabclass=type(menu.current_tab))

	def _on_speed_changed(self, message):
		self._display_speed(message.new)

	def _display_speed(self, tps):
		text = u''
		up_icon = self.mainhud.findChild(name='speedUp')
		down_icon = self.mainhud.findChild(name='speedDown')
		if tps == 0: # pause
			text = u'0x'
			up_icon.set_inactive()
			down_icon.set_inactive()
		else:
			if tps != GAME_SPEED.TICKS_PER_SECOND:
				text = unicode("%1gx" % (tps * 1.0/GAME_SPEED.TICKS_PER_SECOND))
				#%1g: displays 0.5x, but 2x instead of 2.0x
			index = GAME_SPEED.TICK_RATES.index(tps)
			if index + 1 >= len(GAME_SPEED.TICK_RATES):
				up_icon.set_inactive()
			else:
				up_icon.set_active()
			if index > 0:
				down_icon.set_active()
			else:
				down_icon.set_inactive()

		wdg = self.mainhud.findChild(name="speed_text")
		wdg.text = text
		wdg.resizeToContent()
		self.mainhud.show()

	def on_key_press(self, action, evt):
		"""Handle a key press in-game.

		Returns True if the key was acted upon.
		"""
		_Actions = KeyConfig._Actions
		keyval = evt.getKey().getValue()

		if action == _Actions.ESCAPE:
			return self.on_escape()

		if action == _Actions.GRID:
			gridrenderer = self.session.view.renderer['GridRenderer']
			gridrenderer.setEnabled( not gridrenderer.isEnabled() )
		elif action == _Actions.COORD_TOOLTIP:
			self.coordinates_tooltip.toggle()
		elif action == _Actions.DESTROY_TOOL:
			self.toggle_destroy_tool()
		elif action == _Actions.REMOVE_SELECTED:
			self.session.remove_selected()
		elif action == _Actions.ROAD_TOOL:
			self.toggle_road_tool()
		elif action == _Actions.SPEED_UP:
			SpeedUpCommand().execute(self.session)
		elif action == _Actions.SPEED_DOWN:
			SpeedDownCommand().execute(self.session)
		elif action == _Actions.PAUSE:
			TogglePauseCommand().execute(self.session)
		elif action == _Actions.PLAYERS_OVERVIEW:
			self.logbook.toggle_stats_visibility(widget='players')
		elif action == _Actions.SETTLEMENTS_OVERVIEW:
			self.logbook.toggle_stats_visibility(widget='settlements')
		elif action == _Actions.SHIPS_OVERVIEW:
			self.logbook.toggle_stats_visibility(widget='ships')
		elif action == _Actions.LOGBOOK:
			self.logbook.toggle_visibility()
		elif action == _Actions.DEBUG and VERSION.IS_DEV_VERSION:
			import pdb; pdb.set_trace()
		elif action == _Actions.BUILD_TOOL:
			self.show_build_menu()
		elif action == _Actions.ROTATE_RIGHT:
			if hasattr(self.cursor, "rotate_right"):
				# used in e.g. build preview to rotate building instead of map
				self.cursor.rotate_right()
			else:
				self.session.view.rotate_right()
				self.minimap.rotate_right()
		elif action == _Actions.ROTATE_LEFT:
			if hasattr(self.cursor, "rotate_left"):
				self.cursor.rotate_left()
			else:
				self.session.view.rotate_left()
				self.minimap.rotate_left()
		elif action == _Actions.CHAT:
			self.chat_dialog.show()
		elif action == _Actions.TRANSLUCENCY:
			self.session.world.toggle_translucency()
		elif action == _Actions.TILE_OWNER_HIGHLIGHT:
			self.session.world.toggle_owner_highlight()
		elif keyval in (fife.Key.NUM_0, fife.Key.NUM_1, fife.Key.NUM_2, fife.Key.NUM_3, fife.Key.NUM_4,
		                fife.Key.NUM_5, fife.Key.NUM_6, fife.Key.NUM_7, fife.Key.NUM_8, fife.Key.NUM_9):
			num = int(keyval - fife.Key.NUM_0)
			if evt.isControlPressed():
				# create new group (only consider units owned by the player)
				self.session.selection_groups[num] = \
				    set(filter(lambda unit : unit.owner.is_local_player,
				               self.session.selected_instances))
				# drop units of the new group from all other groups
				for group in self.session.selection_groups:
					if group is not self.session.selection_groups[num]:
						group -= self.session.selection_groups[num]
			else:
				# deselect
				# we need to make sure to have a cursor capable of selection (for apply_select())
				# this handles deselection implicitly in the destructor
				self.set_cursor('selection')

				# apply new selection
				for instance in self.session.selection_groups[num]:
					instance.get_component(SelectableComponent).select(reset_cam=True)
				# assign copy since it will be randomly changed, the unit should only be changed on ctrl-events
				self.session.selected_instances = self.session.selection_groups[num].copy()
				# show menu depending on the entities selected
				if self.session.selected_instances:
					self.cursor.apply_select()
				else:
					# nothing is selected here, we need to hide the menu since apply_select doesn't handle that case
					self.show_menu(None)
		elif action == _Actions.QUICKSAVE:
			self.session.quicksave() # load is only handled by the MainListener
		elif action == _Actions.PIPETTE:
			# copy mode: pipette tool
			self.toggle_cursor('pipette')
		elif action == _Actions.HEALTH_BAR:
			# shows health bar of every instance with an health component
			self.session.world.toggle_health_for_all_health_instances()
		elif action == _Actions.SHOW_SELECTED:
			if self.session.selected_instances:
				# scroll to first one, we can never guarantee to display all selected units
				instance = iter(self.session.selected_instances).next()
				self.session.view.center( * instance.position.center.to_tuple())
				for instance in self.session.selected_instances:
					if hasattr(instance, "path") and instance.owner.is_local_player:
						self.minimap.show_unit_path(instance)
		else:
			return False

		return True

	def toggle_cursor(self, which, *args, **kwargs):
		"""Alternate between the cursor which and default.
		args and kwargs are used to construct which."""
		if self.current_cursor == which:
			self.set_cursor()
		else:
			self.set_cursor(which, *args, **kwargs)

	def set_cursor(self, which='default', *args, **kwargs):
		"""Sets the mousetool (i.e. cursor).
		This is done here for encapsulation and control over destructors.
		Further arguments are passed to the mouse tool constructor."""
		self.cursor.remove()
		self.current_cursor = which
		klass = {
			'default'        : mousetools.SelectionTool,
			'selection'      : mousetools.SelectionTool,
			'tearing'        : mousetools.TearingTool,
			'pipette'        : mousetools.PipetteTool,
			'attacking'      : mousetools.AttackingTool,
			'building'       : mousetools.BuildingTool,
		}[which]
		self.cursor = klass(self.session, *args, **kwargs)

	def toggle_destroy_tool(self):
		"""Initiate the destroy tool"""
		self.toggle_cursor('tearing')

	def _update_zoom(self):
		"""Enable/disable zoom buttons"""
		zoom = self.session.view.get_zoom()
		in_icon = self.mainhud.findChild(name='zoomIn')
		out_icon = self.mainhud.findChild(name='zoomOut')
		if zoom == VIEW.ZOOM_MIN:
			out_icon.set_inactive()
		else:
			out_icon.set_active()
		if zoom == VIEW.ZOOM_MAX:
			in_icon.set_inactive()
		else:
			in_icon.set_active()
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 = +15
		y_offset = +4
		if width < 800:
			x = 'left'
			x_offset = 10
			y_offset = +66
		elif width < 1020:
			x_offset = (1050 - 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)
		ResourceBarResize.subscribe(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
		SettlerUpdate.subscribe(self._on_settler_level_change)
		SettlerInhabitantsChanged.subscribe(self._on_settler_inhabitant_change)
		HoverSettlementChanged.subscribe(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()
		SettlerUpdate.unsubscribe(self._on_settler_level_change)
		ResourceBarResize.unsubscribe(self._on_resourcebar_resize)
		HoverSettlementChanged.unsubscribe(self._cityinfo_set)
		SettlerInhabitantsChanged.unsubscribe(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 = u' %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 = self.settlement.owner.name

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

		foundlabel = cityinfo.child_finder('city_inhabitants')
		foundlabel.text = u' %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:
			if isinstance(self.session.cursor, BuildingTool):
				print self.session.cursor._class.id, BUILDINGS.TRAIL
			self._build(BUILDINGS.TRAIL)
		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)

		cur_settlement = LastActivePlayerSettlementManager().get_current_settlement()
		self._cityinfo_set( HoverSettlementChanged(self, cur_settlement) )

		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.DEFAULT_NAME: Callback(self.change_name, instance),
			CancelButton.DEFAULT_NAME: 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 =  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.DEFAULT_NAME: self.save_map,
			CancelButton.DEFAULT_NAME: 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.DEFAULT_NAME: self._do_chat,
			CancelButton.DEFAULT_NAME: 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()
Example #52
0
class IngameGui(LivingObject):
	"""Class handling all the ingame gui events."""

	gui = livingProperty()
	tabwidgets = livingProperty()
	message_widget = livingProperty()
	minimap = livingProperty()
	resbar = 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)
		screenwidth = horizons.main.fife.engine_settings.getScreenWidth()

		cityinfo = self.widgets['city_info']
		cityinfo.child_finder = PychanChildFinder(cityinfo)
		cityinfo.position = ( screenwidth/2 - cityinfo.size[0]/2 - 10, 5 )

		self.logbook = LogBook(session)

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

		minimap = self.widgets['minimap']
		minimap.position = (screenwidth - minimap.size[0] -20, 4)
		minimap.show()

		minimap_rect = Rect.init_from_topleft_and_size(minimap.position[0]+77, 55, 120, 120)
		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)

		menupanel = self.widgets['menu_panel']
		menupanel.position = (screenwidth - menupanel.size[0] +15, 149)
		menupanel.show()
		menupanel.mapEvents({
			'destroy_tool' : self.session.destroy_tool,
			'build' : self.show_build_menu,
			'helpLink' : self.main_gui.on_help,
			'gameMenuButton' : self.main_gui.show_pause,
			'logbook' : self.logbook.toggle_visibility
		})

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

		for w in ('status','status_extra','status_gold','status_extra_gold'):
			self.widgets[w].child_finder = PychanChildFinder(self.widgets[w])
		self.widgets['status_gold'].show()

		self.message_widget = MessageWidget(self.session, \
								                        cityinfo.position[0] + cityinfo.size[0], 5)

		self.resbar = ResBar(self.session, gui, self.widgets, self.resource_source)

		# map button names to build functions calls with the building id
		building_list = horizons.main.db.get_building_id_buttonname_settlerlvl()
		self.callbacks_build = {}
		for id,button_name,settler_level in building_list:
			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 resourceinfo_set(self, source, res_needed = {}, res_usable = {}, res_from_ship = False):
		#TODO what is this stuff doing? I could maybe fix the problems if I understood that:
		# why that much if-checks? need to better explain each case
		# * when does this happen and * what are the steps we do then?

		# method is called without arguments in navigationtool (to update)
		# and with arguments in buildingtool to create proper build preview

		city = source if not res_from_ship else None
		self.cityinfo_set(city)  # the source we hover is a settlement,
						# cityinfo_set(None) hides the widget.

#		print source, "   ", self.resource_source
		listener = self.resbar.update_resource_source(source, res_needed)

		if source is None or self.session.world.player != source.owner:
		# player hovers enemy settlement / unsettled territory -> don't show inventory
			self.widgets['status'].hide()
			self.widgets['status_extra'].hide()
			if source is not None:
				pass # remove changelistener?
			source = None
# ----
		if source is not None and self.session.world.player == source.owner:
			# 'source' is always carried over to islandinventorydisplay.py
			# in difference from the below code where self.resource_source was
			# only modified if some conditions applied but always passed over.
			self.resbar.update_resource_source(self.resource_source, res_needed)
			self.widgets['status'].show()
		self.resbar.update_gold()

	""" Below the old code of this method for bugfixing and comparison purposes:
	def resourceinfo_set(self, source, res_needed = {}, res_usable = {}, 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 = res_needed
			self.resources_usable = res_usable
			self.update_resource_source()
			self.widgets['status'].show()
	"""

	def end(self):
		self.widgets['menu_panel'].mapEvents({
			'destroy_tool' : None,
			'build' : None,
			'helpLink' : 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 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 update_settlement(self):
		"""Assigns values to labels of cityinfo widget"""
		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 = cityinfo.child_finder('city_inhabitants')
		foundlabel.text = unicode(' '+str(self.settlement.inhabitants))
		foundlabel.resizeToContent()
		cityinfo.resizeToContent()

	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_build_menu(self):
		# check if build menu is already shown
		if hasattr(self.get_cur_menu(), 'name'):
			if self.get_cur_menu().name == "build_menu_tab_widget":
				self.hide_menu()
				return

		self.session.cursor = SelectionTool(self.session) # set cursor for build menu
		self.deselect_all()
		lvl = self.session.world.player.settler_level
		btabs = [BuildTab(i, self.callbacks_build[i]) for i in range(0, lvl+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 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.show_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 toggle_ingame_pause(self):
		"""
		Called when the hotkey for pause is pressed.
		Displays pause notification and does the actual (un)pausing.
		"""
		#TODO currently a bug occurs when the game menu is displayed (game is
		#     paused already): popup still appears, need ESC twice to return.
		message = _("Hit P to continue the game or click below!")
		popup = self.main_gui.build_popup(_("Game paused"), message)
		if not hasattr(self, "_toggle_ingame_pause_shown"):
			self._toggle_ingame_pause_shown = False
		if not self._toggle_ingame_pause_shown:
			self.session.speed_pause()
			self.main_gui.on_escape = self.toggle_ingame_pause
			popup.mapEvents({'okButton': self.toggle_ingame_pause})
			popup.show()
			self._toggle_ingame_pause_shown = True
		else:
			self.main_gui.on_escape = self.main_gui.show_pause
			popup.hide()
			self.session.speed_unpause()
			self._toggle_ingame_pause_shown = False

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

	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.show_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()
	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 = +15
		y_offset = +4
		if width < 800:
			x = 'left'
			x_offset = 10
			y_offset = +66
		elif width < 1020:
			x_offset = (1050 - 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)
		ResourceBarResize.subscribe(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
		SettlerUpdate.subscribe(self._on_settler_level_change)
		SettlerInhabitantsChanged.subscribe(self._on_settler_inhabitant_change)
		HoverSettlementChanged.subscribe(self._cityinfo_set)
Example #54
0
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': 'resource_bar',
        '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)

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

        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.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.globals.fife.targetrenderer,
            imagemanager=horizons.globals.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)
        ResourceBarResize.subscribe(self._on_resourcebar_resize)

        # Register for messages
        SettlerUpdate.subscribe(self._on_settler_level_change)
        SettlerInhabitantsChanged.subscribe(self._on_settler_inhabitant_change)
        HoverSettlementChanged.subscribe(self._cityinfo_set)

    def _on_resourcebar_resize(self, message):
        self._update_cityinfo_position()

    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()
        SettlerUpdate.unsubscribe(self._on_settler_level_change)
        ResourceBarResize.unsubscribe(self._on_resourcebar_resize)
        HoverSettlementChanged.unsubscribe(self._cityinfo_set)
        SettlerInhabitantsChanged.unsubscribe(
            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.is_local_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:
                # After scrolling away from settlement, leave name on screen for some
                # seconds. Players can still click on it to rename the settlement now.
                ExtScheduler().add_new_object(self.cityinfo.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:
                # hovered settlement of other player, simply hide the widget
                self.cityinfo.hide()

        else:  # do not hide if settlement is hovered and a hide was previously scheduled
            ExtScheduler().rem_call(self, self.cityinfo.hide)

            self.update_settlement()  # calls show()
            settlement.add_change_listener(self.update_settlement)

    def _on_settler_inhabitant_change(self, message):
        assert isinstance(message, SettlerInhabitantsChanged)
        foundlabel = self.cityinfo.child_finder('city_inhabitants')
        old_amount = int(foundlabel.text) if foundlabel.text else 0
        foundlabel.text = u' {amount:>4d}'.format(amount=old_amount +
                                                  message.change)
        foundlabel.resizeToContent()

    def update_settlement(self):
        city_name_label = self.cityinfo.child_finder('city_name')
        if self.settlement.owner.is_local_player:  # allow name changes
            # Update settlement on the resource overview to make sure it
            # is setup correctly for the coming calculations
            self.resource_overview.set_inventory_instance(self.settlement)
            cb = Callback(self.show_change_name_dialog, self.settlement)
            helptext = _("Click to change the name of your settlement")
            city_name_label.enable_cursor_change_on_hover()
        else:  # no name changes
            cb = lambda: AmbientSoundComponent.play_special('error')
            helptext = u""
            city_name_label.disable_cursor_change_on_hover()
        self.cityinfo.mapEvents({'city_name': cb})
        city_name_label.helptext = helptext

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

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

        foundlabel = self.cityinfo.child_finder('city_inhabitants')
        foundlabel.text = u' {amount:>4d}'.format(
            amount=self.settlement.inhabitants)
        foundlabel.resizeToContent()

        self._update_cityinfo_position()

    def _update_cityinfo_position(self):
        """ Places cityinfo widget depending on resource bar dimensions.

		For a normal-sized resource bar and reasonably large resolution:
		* determine resource bar length (includes gold)
		* determine empty horizontal space between resbar end and minimap start
		* display cityinfo centered in that area if it is sufficiently large

		If too close to the minimap (cityinfo larger than length of this empty space)
		move cityinfo centered to very upper screen edge. Looks bad, works usually.
		In this case, the resbar is redrawn to put the cityinfo "behind" it visually.
		"""
        width = horizons.globals.fife.engine_settings.getScreenWidth()
        resbar = self.resource_overview.get_size()
        is_foreign = (self.settlement.owner != self.session.world.player)
        blocked = self.cityinfo.size[0] + int(1.5 * self.minimap.get_size()[1])
        # minimap[1] returns width! Use 1.5*width because of the GUI around it

        if is_foreign:  # other player, no resbar exists
            self.cityinfo.pos = ('center', 'top')
            xoff = 0
            yoff = 19
        elif blocked < width < resbar[
                0] + blocked:  # large resbar / small resolution
            self.cityinfo.pos = ('center', 'top')
            xoff = 0
            yoff = 0  # upper screen edge
        else:
            self.cityinfo.pos = ('left', 'top')
            xoff = resbar[0] + (width - blocked - resbar[0]) // 2
            yoff = 24

        self.cityinfo.offset = (xoff, yoff)
        self.cityinfo.position_technique = "{pos[0]}{off[0]:+d}:{pos[1]}{off[1]:+d}".format(
            pos=self.cityinfo.pos, off=self.cityinfo.offset)
        self.cityinfo.hide()
        self.cityinfo.show()

    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 getattr(self.get_cur_menu(), 'name', None) == "diplomacy_widget":
            self.hide_menu()
            return

        if not DiplomacyTab.is_useable(self.session.world):
            self.main_gui.show_popup(
                _("No diplomacy possible"),
                _("Cannot do diplomacy as there are no other players."))
            return

        tab = DiplomacyTab(self, self.session.world)
        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.create_tabs(self.session, self._build)
            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:
            self._build(BUILDINGS.TRAIL)
        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()

        TabWidgetChanged.broadcast(self)

    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)

        cur_settlement = LastActivePlayerSettlementManager(
        ).get_current_settlement()
        self._cityinfo_set(HoverSettlementChanged(self, cur_settlement))

        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.DEFAULT_NAME: Callback(self.change_name, instance),
            CancelButton.DEFAULT_NAME: 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 = 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 new_name or not 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.DEFAULT_NAME: self.save_map,
            CancelButton.DEFAULT_NAME: 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)

            # TODO: Use a better measure then first tab
            # Quite fragile, makes sure the tablist in the mainsquare menu is updated
            if hasattr(menu, '_tabs') and isinstance(menu._tabs[0],
                                                     MainSquareOverviewTab):
                instance = list(self.session.selected_instances)[0]
                instance.get_component(SelectableComponent).show_menu(
                    jump_to_tabclass=type(menu.current_tab))

    def show_chat_dialog(self):
        """Show a dialog where the user can enter a chat message"""
        events = {
            OkButton.DEFAULT_NAME: self._do_chat,
            CancelButton.DEFAULT_NAME: 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()
Example #55
0
class IngameGui(LivingObject):
	minimap = livingProperty()
	keylistener = livingProperty()
	message_widget = livingProperty()

	def __init__(self, session):
		self.session = session

		self.cursor = None
		self.coordinates_tooltip = None
		self.keylistener = IngameKeyListener(self.session)
		# used by NavigationTool
		LastActivePlayerSettlementManager.create_instance(self.session)

		# Mocks needed to act like the real IngameGui
		self.show_menu = lambda x: 0
		self.hide_menu = lambda: 0
		# a logbook Dummy is necessary for message_widget to work
		self.logbook = DummyLogbook()

		self.mainhud = load_uh_widget('minimap.xml')
		self.mainhud.position_technique = "right+0:top+0"

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

		self.mainhud.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),
			'gameMenuButton': self.toggle_pause,
		})

		self.mainhud.show()
		ZoomChanged.subscribe(self._update_zoom)

		# Hide unnecessary buttons in hud
		for widget in ("build", "speedUp", "speedDown", "destroy_tool", "diplomacyButton", "logbook"):
			self.mainhud.findChild(name=widget).hide()

		self.windows = WindowManager()
		self.message_widget = MessageWidget(self.session)
		self.pausemenu = PauseMenu(self.session, self, self.windows, in_editor_mode=True)
		self.help_dialog = HelpDialog(self.windows)

	def end(self):
		self.mainhud.mapEvents({
			'zoomIn': None,
			'zoomOut': None,
			'rotateRight': None,
			'rotateLeft': None,
			'gameMenuButton': None
		})
		self.mainhud.hide()
		self.mainhud = None
		self._settings_tab.hide()
		self._settings_tab = None

		self.windows.close_all()
		self.minimap = None
		self.keylistener = None
		LastActivePlayerSettlementManager().remove()
		LastActivePlayerSettlementManager.destroy_instance()
		ZoomChanged.unsubscribe(self._update_zoom)

		if self.cursor:
			self.cursor.remove()
			self.cursor.end()
			self.cursor = None

		super(IngameGui, self).end()

	def handle_selection_group(self, num, ctrl_pressed):
		# Someday, maybe cool stuff will be possible here.
		# That day is not today, I'm afraid.
		pass

	def toggle_pause(self):
		self.windows.toggle(self.pausemenu)

	def toggle_help(self):
		self.windows.toggle(self.help_dialog)

	def load(self, savegame):
		self.minimap.draw()

		self.cursor = SelectionTool(self.session)

	def setup(self):
		"""Called after the world editor was initialized."""
		self._settings_tab = TabWidget(self, tabs=[SettingsTab(self.session.world_editor, self)])
		self._settings_tab.show()

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

	def show_save_map_dialog(self):
		"""Shows a dialog where the user can set the name of the saved map."""
		window = SelectSavegameDialog('editor-save', self.windows)
		savegamename = self.windows.open(window)
		if savegamename is None:
			return False # user aborted dialog
		success = self.session.save(savegamename)
		if success:
				self.message_widget.add('SAVED_GAME')

	def on_escape(self):
		pass

	def on_key_press(self, action, evt):
		_Actions = KeyConfig._Actions
		if action == _Actions.QUICKSAVE:
			self.session.quicksave()
		if action == _Actions.ESCAPE:
			if self.windows.visible:
				self.windows.on_escape()
			elif hasattr(self.cursor, 'on_escape'):
				self.cursor.on_escape()
			else:
				self.toggle_pause()
		elif action == _Actions.HELP:
			self.toggle_help()
		else:
			return False
		return True

	def set_cursor(self, which='default', *args, **kwargs):
		"""Sets the mousetool (i.e. cursor).
		This is done here for encapsulation and control over destructors.
		Further arguments are passed to the mouse tool constructor.
		"""
		self.cursor.remove()
		klass = {
			'default': SelectionTool,
			'tile_layer': TileLayingTool
		}[which]
		self.cursor = klass(self.session, *args, **kwargs)

	def _update_zoom(self, message):
		"""Enable/disable zoom buttons"""
		in_icon = self.mainhud.findChild(name='zoomIn')
		out_icon = self.mainhud.findChild(name='zoomOut')
		if message.zoom == VIEW.ZOOM_MIN:
			out_icon.set_inactive()
		else:
			out_icon.set_active()
		if message.zoom == VIEW.ZOOM_MAX:
			in_icon.set_inactive()
		else:
			in_icon.set_active()
Example #56
0
    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)

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

        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.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.globals.fife.targetrenderer,
            imagemanager=horizons.globals.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)
        ResourceBarResize.subscribe(self._on_resourcebar_resize)

        # Register for messages
        SettlerUpdate.subscribe(self._on_settler_level_change)
        SettlerInhabitantsChanged.subscribe(self._on_settler_inhabitant_change)
        HoverSettlementChanged.subscribe(self._cityinfo_set)
Example #57
0
class IngameGui(LivingObject):
    minimap = livingProperty()
    keylistener = livingProperty()
    message_widget = livingProperty()

    def __init__(self, session):
        self.session = session

        self.cursor = None
        self.coordinates_tooltip = None
        self.keylistener = IngameKeyListener(self.session)
        # used by NavigationTool
        LastActivePlayerSettlementManager.create_instance(self.session)

        # Mocks needed to act like the real IngameGui
        self.show_menu = Dummy
        self.hide_menu = Dummy
        # a logbook Dummy is necessary for message_widget to work
        self.logbook = Dummy()

        self.mainhud = load_uh_widget('minimap.xml')
        self.mainhud.position_technique = "right+0:top+0"

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

        self.mainhud.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),
            'gameMenuButton':
            self.toggle_pause,
        })

        self.mainhud.show()
        ZoomChanged.subscribe(self._update_zoom)

        # Hide unnecessary buttons in hud
        for widget in ("build", "speedUp", "speedDown", "destroy_tool",
                       "diplomacyButton", "logbook"):
            self.mainhud.findChild(name=widget).hide()

        self.windows = WindowManager()
        self.message_widget = MessageWidget(self.session)
        self.pausemenu = PauseMenu(self.session,
                                   self,
                                   self.windows,
                                   in_editor_mode=True)
        self.help_dialog = HelpDialog(self.windows)

    def end(self):
        self.mainhud.mapEvents({
            'zoomIn': None,
            'zoomOut': None,
            'rotateRight': None,
            'rotateLeft': None,
            'gameMenuButton': None
        })
        self.mainhud.hide()
        self.mainhud = None
        self._settings_tab.hide()
        self._settings_tab = None

        self.windows.close_all()
        self.minimap = None
        self.keylistener = None
        LastActivePlayerSettlementManager().remove()
        LastActivePlayerSettlementManager.destroy_instance()
        ZoomChanged.unsubscribe(self._update_zoom)

        if self.cursor:
            self.cursor.remove()
            self.cursor.end()
            self.cursor = None

        super(IngameGui, self).end()

    def handle_selection_group(self, num, ctrl_pressed):
        # Someday, maybe cool stuff will be possible here.
        # That day is not today, I'm afraid.
        pass

    def toggle_pause(self):
        self.windows.toggle(self.pausemenu)

    def toggle_help(self):
        self.windows.toggle(self.help_dialog)

    def load(self, savegame):
        self.minimap.draw()

        self.cursor = SelectionTool(self.session)

    def setup(self):
        """Called after the world editor was initialized."""
        self._settings_tab = TabWidget(
            self, tabs=[SettingsTab(self.session.world_editor, self)])
        self._settings_tab.show()

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

    def show_save_map_dialog(self):
        """Shows a dialog where the user can set the name of the saved map."""
        window = SelectSavegameDialog('editor-save', self.windows)
        savegamename = self.windows.open(window)
        if savegamename is None:
            return False  # user aborted dialog
        success = self.session.save(savegamename)
        if success:
            self.message_widget.add('SAVED_GAME')

    def on_escape(self):
        pass

    def on_key_press(self, action, evt):
        _Actions = KeyConfig._Actions
        if action == _Actions.QUICKSAVE:
            self.session.quicksave()
        if action == _Actions.ESCAPE:
            if self.windows.visible:
                self.windows.on_escape()
            elif hasattr(self.cursor, 'on_escape'):
                self.cursor.on_escape()
            else:
                self.toggle_pause()
        elif action == _Actions.HELP:
            self.toggle_help()
        else:
            return False
        return True

    def set_cursor(self, which='default', *args, **kwargs):
        """Sets the mousetool (i.e. cursor).
		This is done here for encapsulation and control over destructors.
		Further arguments are passed to the mouse tool constructor.
		"""
        self.cursor.remove()
        klass = {'default': SelectionTool, 'tile_layer': TileLayingTool}[which]
        self.cursor = klass(self.session, *args, **kwargs)

    def _update_zoom(self, message):
        """Enable/disable zoom buttons"""
        in_icon = self.mainhud.findChild(name='zoomIn')
        out_icon = self.mainhud.findChild(name='zoomOut')
        if message.zoom == VIEW.ZOOM_MIN:
            out_icon.set_inactive()
        else:
            out_icon.set_active()
        if message.zoom == VIEW.ZOOM_MAX:
            in_icon.set_inactive()
        else:
            in_icon.set_active()
Example #58
0
	def __init__(self, session, gui):
		super(IngameGui, self).__init__()
		self.session = session
		assert isinstance(self.session, horizons.session.Session)
		self.main_gui = gui
		self.settlement = None
		self._old_menu = None

		self.cursor = None
		self.coordinates_tooltip = None

		self.keylistener = IngameKeyListener(self.session)

		self.cityinfo = CityInfo(self)
		LastActivePlayerSettlementManager.create_instance(self.session)

		self.message_widget = MessageWidget(self.session)

		# Windows
		self.windows = WindowManager()

		self.logbook = LogBook(self.session, self.windows)
		self.players_overview = PlayersOverview(self.session)
		self.players_settlements = PlayersSettlements(self.session)
		self.players_ships = PlayersShips(self.session)

		self.chat_dialog = ChatDialog(self.windows, self.session)
		self.change_name_dialog = ChangeNameDialog(self.windows, self.session)
		self.pausemenu = PauseMenu(self.session, self, self.windows, in_editor_mode=False)
		self.help_dialog = HelpDialog(self.windows, session=self.session)

		# Icon manager
		self.status_icon_manager = StatusIconManager(
			renderer=self.session.view.renderer['GenericRenderer'],
			layer=self.session.view.layers[LAYERS.OBJECTS]
		)
		self.production_finished_icon_manager = ProductionFinishedIconManager(
			renderer=self.session.view.renderer['GenericRenderer'],
			layer=self.session.view.layers[LAYERS.OBJECTS]
		)

		# 'minimap' is the guichan gui around the actual minimap, which is saved
		# in self.minimap
		self.mainhud = load_uh_widget('minimap.xml')
		self.mainhud.position_technique = "right+0:top+0"

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

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

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

		self.mainhud.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.toggle_destroy_tool,
			'build' : self.show_build_menu,
			'diplomacyButton' : self.show_diplomacy_menu,
			'gameMenuButton' : self.toggle_pause,
			'logbook' : lambda: self.windows.toggle(self.logbook)
		})
		self.mainhud.show()

		self.resource_overview = ResourceOverviewBar(self.session)

		# Register for messages
		SpeedChanged.subscribe(self._on_speed_changed)
		NewDisaster.subscribe(self._on_new_disaster)
		NewSettlement.subscribe(self._on_new_settlement)
		PlayerLevelUpgrade.subscribe(self._on_player_level_upgrade)
		MineEmpty.subscribe(self._on_mine_empty)
		self.session.view.add_change_listener(self._update_zoom)

		self._display_speed(self.session.timer.ticks_per_second)
Example #59
0
    def __init__(self, session):
        super(IngameGui, self).__init__()
        self.session = session
        assert isinstance(self.session, horizons.session.Session)
        self.settlement = None
        self._old_menu = None

        self.cursor = None
        self.coordinates_tooltip = None

        self.keylistener = IngameKeyListener(self.session)

        self.cityinfo = CityInfo(self)
        LastActivePlayerSettlementManager.create_instance(self.session)

        self.message_widget = MessageWidget(self.session)

        # Windows
        self.windows = WindowManager()
        self.show_popup = self.windows.show_popup
        self.show_error_popup = self.windows.show_error_popup

        self.logbook = LogBook(self.session, self.windows)
        self.players_overview = PlayersOverview(self.session)
        self.players_settlements = PlayersSettlements(self.session)
        self.players_ships = PlayersShips(self.session)

        self.chat_dialog = ChatDialog(self.windows, self.session)
        self.change_name_dialog = ChangeNameDialog(self.windows, self.session)
        self.pausemenu = PauseMenu(self.session,
                                   self,
                                   self.windows,
                                   in_editor_mode=False)
        self.help_dialog = HelpDialog(self.windows, session=self.session)

        # Icon manager
        self.status_icon_manager = StatusIconManager(
            renderer=self.session.view.renderer['GenericRenderer'],
            layer=self.session.view.layers[LAYERS.OBJECTS])
        self.production_finished_icon_manager = ProductionFinishedIconManager(
            renderer=self.session.view.renderer['GenericRenderer'],
            layer=self.session.view.layers[LAYERS.OBJECTS])

        # 'minimap' is the guichan gui around the actual minimap, which is saved
        # in self.minimap
        self.mainhud = load_uh_widget('minimap.xml')
        self.mainhud.position_technique = "right:top"

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

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

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

        self.mainhud.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.toggle_destroy_tool,
            'build':
            self.show_build_menu,
            'diplomacyButton':
            self.show_diplomacy_menu,
            'gameMenuButton':
            self.toggle_pause,
            'logbook':
            lambda: self.windows.toggle(self.logbook)
        })
        self.mainhud.show()

        self.resource_overview = ResourceOverviewBar(self.session)

        # Register for messages
        SpeedChanged.subscribe(self._on_speed_changed)
        NewDisaster.subscribe(self._on_new_disaster)
        NewSettlement.subscribe(self._on_new_settlement)
        PlayerLevelUpgrade.subscribe(self._on_player_level_upgrade)
        MineEmpty.subscribe(self._on_mine_empty)
        self.session.view.add_change_listener(self._update_zoom)

        self._display_speed(self.session.timer.ticks_per_second)
    def __init__(self, session):
        super(IngameGui, self).__init__()
        self.session = session
        assert isinstance(self.session, horizons.session.Session)
        self.settlement = None
        self._old_menu = None

        self.cursor = None
        self.coordinates_tooltip = None

        self.keylistener = IngameKeyListener(self.session)

        self.cityinfo = CityInfo(self)
        LastActivePlayerSettlementManager.create_instance(self.session)

        self.message_widget = MessageWidget(self.session)

        # Windows
        self.windows = WindowManager()
        self.open_popup = self.windows.open_popup
        self.open_error_popup = self.windows.open_error_popup

        self.logbook = LogBook(self.session, self.windows)
        self.players_overview = PlayersOverview(self.session)
        self.players_settlements = PlayersSettlements(self.session)
        self.players_ships = PlayersShips(self.session)

        self.chat_dialog = ChatDialog(self.windows, self.session)
        self.change_name_dialog = ChangeNameDialog(self.windows, self.session)
        self.pausemenu = PauseMenu(self.session,
                                   self,
                                   self.windows,
                                   in_editor_mode=False)
        self.help_dialog = HelpDialog(self.windows)

        # Icon manager
        self.status_icon_manager = StatusIconManager(
            renderer=self.session.view.renderer['GenericRenderer'],
            layer=self.session.view.layers[LAYERS.OBJECTS])
        self.production_finished_icon_manager = ProductionFinishedIconManager(
            renderer=self.session.view.renderer['GenericRenderer'],
            layer=self.session.view.layers[LAYERS.OBJECTS])

        # 'minimap' is the guichan gui around the actual minimap, which is saved
        # in self.minimap
        self.mainhud = load_uh_widget('minimap.xml')
        self.mainhud.position_technique = "right:top"

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

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

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

        self.mainhud.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.toggle_destroy_tool,
            'build':
            self.show_build_menu,
            'diplomacyButton':
            self.show_diplomacy_menu,
            'gameMenuButton':
            self.toggle_pause,
            'logbook':
            lambda: self.windows.toggle(self.logbook)
        })
        self.mainhud.show()

        hotkey_replacements = {
            'rotateRight': 'ROTATE_RIGHT',
            'rotateLeft': 'ROTATE_LEFT',
            'speedUp': 'SPEED_UP',
            'speedDown': 'SPEED_DOWN',
            'destroy_tool': 'DESTROY_TOOL',
            'build': 'BUILD_TOOL',
            'gameMenuButton': 'ESCAPE',
            'logbook': 'LOGBOOK',
        }
        for (widgetname, action) in hotkey_replacements.iteritems():
            widget = self.mainhud.findChild(name=widgetname)
            keys = horizons.globals.fife.get_keys_for_action(action)
            # No `.upper()` here: "Pause" looks better than "PAUSE".
            keyname = HOTKEYS.DISPLAY_KEY.get(keys[0], keys[0].capitalize())
            widget.helptext = widget.helptext.format(key=keyname)

        self.resource_overview = ResourceOverviewBar(self.session)

        # Register for messages
        SpeedChanged.subscribe(self._on_speed_changed)
        NewDisaster.subscribe(self._on_new_disaster)
        NewSettlement.subscribe(self._on_new_settlement)
        PlayerLevelUpgrade.subscribe(self._on_player_level_upgrade)
        MineEmpty.subscribe(self._on_mine_empty)
        ZoomChanged.subscribe(self._update_zoom)

        self._display_speed(self.session.timer.ticks_per_second)