view gui/inventorygui.py @ 183:7b6aba7839ea

getGameEnvironment of GameState now returns a locals dictionary that is empty at the beginning of the game and will be saved in save games. The vals and funcs are now both in the globals dictionary. This WILL break old saves.
author Beliar <KarstenBock@gmx.net>
date Thu, 15 Mar 2012 16:24:05 +0100
parents 5b880b8cff6d
children
line wrap: on
line source

#   This file is part of PARPG.

#   PARPG is free software: you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or
#   (at your option) any later version.

#   PARPG is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.

#   You should have received a copy of the GNU General Public License
#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
import logging
from types import StringTypes

from fife.extensions.pychan.tools import callbackWithArguments as cbwa
from fife.extensions import pychan
from slot import Slot
from fife.extensions.pychan.attrs import UnicodeAttr

from parpg.gui import drag_drop_data as data_drag
from parpg.gui.containergui_base import ContainerGUIBase
from parpg.entities.action import ACTIONS
from parpg.components import equip, equipable, container
from parpg.entities import General

logger = logging.getLogger('action')

class EquipmentSlot(pychan.VBox):
    def __init__(self, min_size=(50, 50),
                 max_size=(50, 50), margins=None,
                 **kwargs):
        pychan.VBox.__init__(self, min_size=min_size, max_size=max_size,
                             **kwargs)
        self.background_image = 'gui/inv_images/inv_background.png'
        icon = Slot(name="Icon")
        self.addChild(icon)
        self.adaptLayout()
        if self.parent is not None:
            self.beforeShow()

    @property
    def image(self):
        icon = self.findChildByName("Icon")
        return icon.image
    
    @image.setter
    def image(self, image):
        icon = self.findChildByName("Icon")
        icon.image = image        

class InventoryGrid(pychan.VBox):
    ATTRIBUTES = pychan.VBox.ATTRIBUTES + [pychan.attrs.PointAttr('grid_size'), 
                                            pychan.attrs.PointAttr('cell_size')]
    
    
    def _setNColumns(self, n_columns):
        n_rows = self.grid_size[1]
        self.grid_size = (n_columns, n_rows)
    
    def _getNColumns(self):
        n_columns = self.grid_size[0]
        return n_columns
    n_columns = property(fget=_getNColumns, fset=_setNColumns)
    
    def _setNRows(self, n_rows):
        n_columns = self.grid_size[0]
        self.grid_size = (n_columns, n_rows)
    
    def _getNRows(self):
        n_rows = self.grid_size[1]
        return n_rows
    n_rows = property(fget=_getNRows, fset=_getNColumns)
    
    def _setGridSize(self, grid_size):
        n_columns, n_rows = grid_size
        self.removeAllChildren()
        for row_n in xrange(n_rows):
            row_size = (n_columns * self.cell_size[0], self.cell_size[1])
            row = pychan.HBox(min_size=row_size, max_size=row_size,
                              padding=self.padding)
            row.border_size = 1
            row.opaque = 0
            for column_n in xrange(n_columns):
                index = (row_n * n_columns + column_n)
                slot = Slot(min_size=(self.cell_size), 
                                    max_size=(self.cell_size))
                slot.border_size = 1
                slot.name = "Slot_%d" % index
                slot.index = index
                slot.image = None
                slot.size = self.cell_size
                row.addChild(slot)
            self.addChild(row)
        self.min_size = ((n_columns * self.cell_size[0]) + 2, 
                        (n_rows * self.cell_size[1]) + 2)
        self.max_size = self.min_size
    
    def _getGridSize(self):
        n_rows = len(self.children)
        n_columns = len(self.children[0].children)
        return (n_rows, n_columns)
    grid_size = property(fget=_getGridSize, fset=_setGridSize)
    
    def getSlot(self, row_or_index, col=None):
        if col:
            index = row * self.n_columns + col
        else:
            index = row_or_index
        return self.findChildByName("Slot_%d" % index)
    
    def __init__(self, 
                grid_size=(2, 2), 
                cell_size=(50, 50), 
                parent = None, 
                name = None,
                size = None,
                min_size = None, 
                max_size = None, 
                helptext = None, 
                position = None, 
                style = None, 
                hexpand = None,
                vexpand = None,
                font = None,
                base_color = None,
                background_color = None,
                foreground_color = None,
                selection_color = None,
                position_technique = None,
                is_focusable = None,
                comment = None,
                background_image = None,
                _real_widget = None):
        pychan.VBox.__init__(self, 
                            parent=parent, 
                            name=name, 
                            size=size, 
                            min_size=min_size, 
                            max_size=max_size,
                            helptext=helptext, 
                            position=position,
                            style=style, 
                            hexpand=hexpand, 
                            vexpand=vexpand,
                            font=font,
                            base_color=base_color,
                            background_color=background_color,
                            foreground_color=foreground_color,
                            selection_color=selection_color,
                            border_size=1,
                            position_technique=position_technique,
                            is_focusable=is_focusable,
                            comment=comment,
                            padding=0,
                            background_image=background_image,
                            opaque=0,
                            margins=None,
                            _real_widget=_real_widget)
        self.cell_size = cell_size
        self.grid_size = grid_size


class EquipmentGUI(ContainerGUIBase):
    def __init__(self, controller, gui, equip, callbacks, slot_size = (50, 50)):
        ContainerGUIBase.__init__(self, controller, gui)
        self.equip = equip
        self.equip_to_gui = {
            "head": "headSlot",
            "neck": "neckSlot",
            "body": "shirtSlot",
            "belt": "beltSlot",
            "leg": "pantsSlot",
            "feet": "bootsSlot",
            "l_arm": "leftHandSlot",
            "r_arm": "rightHandSlot",
        }
        self.setSlotEvents()
        self.slot_size = slot_size
        
    def updateImages(self):
        for eq_slot, gui_slot in self.equip_to_gui.iteritems():
            widget = self.gui.findChildByName(gui_slot)            
            equipable = equip.get_equipable(self.equip, eq_slot)
            widget.item = equipable.entity if equipable else None
            self.updateImage(widget) 
               
    def updateImage(self, slot):
        assert(isinstance(slot, EquipmentSlot))
        if (slot.item):
            image = slot.item.containable.image
        else:
            image = None
        slot.image = image
                
    def dragObject(self, obj):
        """Drag the selected object.
           @type obj: string
           @param obj: The name of the object
           @return: None"""
        # get the widget from the gui with the name obj
        drag_widget = self.gui.findChildByName(obj)
        drag_item = drag_widget.item
        # only drag if the widget is not empty
        if (drag_item != None):
            if isinstance(drag_item, General):
                drag_item = drag_item.containable
            elif isinstance(drag_item, equipable):
                drag_item = drag_item.entity.containable
            drag_eq = drag_item.entity.equipable
            # get the image of the widget
            image = drag_widget.image
            self.setDragData(drag_item, image, image)
            equip.take_equipable(self.equip, drag_eq.in_slot)
            
            # after dragging the 'item', set the widgets' images
            # so that it has it's default 'empty' images
            drag_widget.item = None
            self.updateImage(drag_widget)

    def dropObject(self, obj):
        """Drops the object being dropped
           @type obj: string
           @param obj: The name of the object
           @return: None"""
        try:
            drop_widget = self.gui.findChildByName(obj)
            drop_slot = drop_widget.slot
            replace_item = None
    
            if data_drag.dragging:
                drag_item = data_drag.dragged_item.entity
                if drag_item.equipable:
                    drag_item = drag_item.equipable
                else:
                    return                
                #this will get the replacement item and the data for drag_drop
                #if there is an item all ready occupying the slot
                replace_item = (
                    equip.equip(self.equip, drag_item, drop_slot)
                )
                
            #if there was no item the stop dragging and reset cursor
            if replace_item:
                image = drop_widget.image
                self.setDragData(replace_item.entity.containable, image, image)
            else:
                data_drag.dragging = False
                #reset the mouse cursor to the normal cursor
                self.controller.resetMouseCursor()
            drop_widget.item = drag_item.entity
            self.updateImage(drop_widget)
        except (equip.AlreadyEquippedError, equip.CannotBeEquippedInSlot):
            #Do we want to notify the player why the item can't be dropped?
            pass

    def mousePressedOnSlot(self, event, widget):
        if event.getButton() == event.LEFT:
            self.dragDrop(widget.name)

    def setSlotEvents(self):
        events_to_map = {}
        for eq_slot, gui_slot in self.equip_to_gui.iteritems():
            widget = self.gui.findChildByName(gui_slot)
            slot_name = widget.name
            widget.slot = eq_slot
            events_to_map[slot_name + "/mousePressed"] = (
                self.mousePressedOnSlot
            )
            events_to_map[slot_name + "/mouseReleased"] = self.showContextMenu

        self.gui.mapEvents(events_to_map)            
        
class InventoryGUI(ContainerGUIBase):
    def __init__(self, controller, gui, container, callbacks):
        ContainerGUIBase.__init__(self, controller, gui)
        self.grid = self.gui.findChildByName("Grid")
        assert(isinstance(self.grid, InventoryGrid))
        self.container = container
        self.setSlotEvents()

    def setSlotEvents(self):
        slot_count = self.grid.n_rows * self.grid.n_columns
        events_to_map = {}
        for counter in xrange(0, slot_count):
            widget = self.grid.getSlot(counter)
            slot_name = widget.name
            widget.index = counter
            events_to_map[slot_name + "/mousePressed"] = (
                self.mousePressedOnSlot
            )
            events_to_map[slot_name + "/mouseReleased"] = self.showContextMenu

        self.grid.mapEvents(events_to_map)
        self.updateImages()

    def updateImages(self):
        for index, child in enumerate(self.container.children):
            slot = self.grid.getSlot(index)
            if child:
                slot.item = child.entity
            else:
                slot.item = None
            self.updateImage(slot)
            
    def updateImage(self, slot):
        assert(isinstance(slot, Slot))
        if (slot.item):
            image = slot.item.containable.image

        else:
            image = None
        slot.image = image
        
    def mousePressedOnSlot(self, event, widget):
        if event.getButton() == event.LEFT:
            self.dragDrop(widget.name)

    def dragObject(self, obj):
        """Drag the selected object.
           @type obj: string
           @param obj: The name of the object
           @return: None"""
        # get the widget from the gui with the name obj
        drag_widget = self.gui.findChild(name = obj)
        drag_item = drag_widget.item
        # only drag if the widget is not empty
        if (drag_item != None):
            if isinstance(drag_item, General):
                drag_item = drag_item.containable
            # get the image of the widget
            image = drag_widget.image
            self.setDragData(drag_item, image, image)
            container.take_item(self.container, drag_item.slot)
            
            # after dragging the 'item', set the widgets' images
            # so that it has it's default 'empty' images
            drag_widget.item = None
            self.updateImage(drag_widget)
            
    def dropObject(self, obj):
        """Drops the object being dropped
           @type obj: string
           @param obj: The name of the object
           @return: None"""
        try:
            drop_widget = self.gui.findChildByName(obj)
            drop_index = drop_widget.index
            replace_item = None
    
            if data_drag.dragging:
                drag_item = data_drag.dragged_item
                #this will get the replacement item and the data for drag_drop if
                ## there is an item all ready occupying the slot
                replace_item = (
                    container.put_item(self.container, drag_item, drop_index)
                )
                
            #if there was no item the stop dragging and reset cursor
            if replace_item:
                image = drop_widget.image
                self.setDragData(replace_item, image, image)
            else:
                data_drag.dragging = False
                #reset the mouse cursor to the normal cursor
                self.controller.resetMouseCursor()
            drop_widget.item = drag_item.entity
            self.updateImage(drop_widget)
        except (container.BulkLimitError):
            #Do we want to notify the player why the item can't be dropped?
            pass
        
    def createMenuItems(self, item, actions):
        """Creates context menu items for the InventoryGUI"""
        menu_actions = ContainerGUIBase.createMenuItems(self, item, actions)
        param_dict = {}
        param_dict["controller"] = self.controller
        param_dict["commands"] = {}
        param_dict["item"] = item.containable
        param_dict["container_gui"] = self
        menu_actions.append(["Drop",
                             "Drop", 
                             self.executeMenuItem, 
                             ACTIONS["DropFromInventory"](**param_dict)])        
        return menu_actions
    
class CharacterGUI(object):
    def __init__(self, controller, gui, container, equip, callbacks):
        self.engine = controller.engine
        self.inventory_shown = False
        if isinstance(gui, pychan.Widget):
            self.gui = gui
        elif isinstance(gui, StringTypes):
            xml_file = vfs.VFS.open(gui)
            self.gui = pychan.loadXML(xml_file)
        else:
            self.gui = pychan.loadXML(gui)
        
        render_backend = self.engine.getRenderBackend()
        screen_mode = render_backend.getCurrentScreenMode()
        screen_width, screen_height = (screen_mode.getWidth(),
                                       screen_mode.getHeight())
        widget_width, widget_height = self.gui.size
        self.gui.position = ((screen_width - widget_width) / 2,
                             (screen_height - widget_height) / 2)
        self.equip_gui = EquipmentGUI(
            controller,
            self.gui.findChildByName("equipmentPage"),
            equip, callbacks
        )
        self.inv_gui = InventoryGUI(
            controller,
            self.gui.findChildByName("inventoryPage"),
            container, callbacks
        )
    
    def toggleInventory(self, toggleImage=True):
        """Pause the game and enter the inventory screen, or close the
           inventory screen and resume the game.
           @type toggleImage: bool
           @param toggleImage:
               Call toggleInventoryCallback if True. Toggling via a
               keypress requires that we toggle the Hud inventory image
               explicitly. Clicking on the Hud inventory button toggles the
               image implicitly, so we don't change it.
           @return: None"""
        if not self.inventory_shown:
            self.showInventory()
            self.inventory_shown = True
        else:
            self.closeInventory()
            self.inventory_shown = False
            
    def updateImages(self):
        self.equip_gui.updateImages()
        self.inv_gui.updateImages()
    
    def showInventory(self):
        self.updateImages()
        self.gui.show()
    
    def closeInventory(self):
        self.gui.hide()