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): 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')
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()
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 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()
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 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()
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', '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()
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()