view mamba/habitats/editor.py @ 279:162bddbbc5d0

Protect against bad tilesets as well
author Neil Muller <drnlmuller@gmail.com>
date Thu, 15 Sep 2011 18:21:09 +0200
parents f10d06fc64b2
children 96b4ad9b4d30
line wrap: on
line source

"""Habitat for editing levels."""

import pygame.display
from pygame.locals import SWSURFACE, KEYDOWN, K_1, K_2

from mamba.engine import Habitat, NewHabitatEvent
from mamba.widgets.level import EditLevelWidget
from mamba.widgets.text import TextWidget, TextButton
from mamba.widgets.imagebutton import ImageButtonWidget
from mamba.widgets.messagebox import MessageBox
from mamba.widgets.entrybox import EntryBox
from mamba.widgets.listbox import ListBox
from mamba.widgets.toollist import ToolListWidget
from mamba.level import Level, Tileset, TILE_MAP, THING_MAP, InvalidMapError
from mamba.data import check_level_exists, list_levels, list_tilesets
from mamba.constants import (SCREEN, EDIT_SCREEN, NAME, ESCAPE_KEYS,
        RESERVED_NAMES)

MAX_TOOLS = 6


class EditorHabitat(Habitat):
    def __init__(self, level_name):
        super(EditorHabitat, self).__init__(EDIT_SCREEN)
        self.level = Level(level_name)
        self.edit_widget = EditLevelWidget(self.level)
        self.container.add(self.edit_widget)
        self.container.add_callback(KEYDOWN, self.keydown_event)
        self.mode = 'Tile'

    def on_enter(self):
        # We need to juggle the display to the correct size
        # This is a horrible hack
        pygame.display.quit()
        pygame.display.init()
        pygame.display.set_mode(EDIT_SCREEN, SWSURFACE)
        pygame.display.set_caption('%s Level editor' % NAME)
        super(EditorHabitat, self).on_enter()
        self.setup_toolbar()

    def on_exit(self):
        # We need to juggle the display to the correct size
        # This is a horrible hack
        super(EditorHabitat, self).on_exit()
        pygame.display.quit()
        pygame.display.init()
        pygame.display.set_mode(SCREEN, SWSURFACE)
        pygame.display.set_caption(NAME)

    def keydown_event(self, ev, widget):
        if ev.key in ESCAPE_KEYS:
            from mamba.habitats.mainmenu import MainMenu
            NewHabitatEvent.post(MainMenu())
        elif ev.key == K_1:
            # Activate floor button
            self.floor_button.forced_click()

    def setup_toolbar(self):
        """Draw the editor toolbar"""
        button_height = 5
        button_left = 820
        button_padding = 2

        filename = TextButton(
                (button_left, button_height),
                'File: %s' % self.level.level_name, color='white')
        filename.add_callback('clicked', self.do_edit,
                'Specify filename', self.level.level_name, self.check_file)
        self.container.add(filename)
        button_height += filename.rect.height + button_padding
        levelname = TextButton((button_left, button_height),
                'Level: %s' % self.level.name, color='white')
        levelname.add_callback('clicked', self.do_edit,
                'Edit Level Title', self.level.name, self.update_name)
        self.container.add(levelname)
        button_height += levelname.rect.height + button_padding

        tilesetname = TextButton((button_left, button_height),
                'Tileset: %s' % self.level.tileset.name, color='white')
        tilesetname.add_callback('clicked', self.list_tilesets)
        self.container.add(tilesetname)
        button_height += tilesetname.surface.get_height() + button_padding
        # TODO: Add Image widget for the current tool
        self.current_tool = TextWidget((button_left, button_height),
                'Tool: Floor', color='white')
        self.container.add(self.current_tool)
        button_height += self.current_tool.surface.get_height()
        button_height += button_padding
        self.floor_button = ImageButtonWidget(
                (button_left, button_height), self.level.tileset.floor,
                'Floor', color='white')
        self.container.add(self.floor_button)
        self.floor_button.add_callback('clicked', self.change_tool,
                '.', 'Floor')
        self.edit_widget.set_tool('.')
        button_height += (self.floor_button.surface.get_height()
                + button_padding)
        if self.mode == 'Tile':
            tile_map = TILE_MAP
            change_mode_text = 'Switch to Things'
        elif self.mode == 'Thing':
            tile_map = THING_MAP
            change_mode_text = 'Switch to Tiles'
        tool_list = []
        for tile_char in sorted(tile_map):
            try:
                tile = self.level.tileset[tile_char]
            except pygame.error:
                # Ignore stuff we can't load for now
                continue
            if tile is None:
                continue
            if tile.name:
                text = tile.name
            else:
                text = 'Tile'
            tile_button = ImageButtonWidget((0, 0), tile.image, text,
                    color='white')
            tile_button.add_callback('clicked', self.change_tool,
                    tile_char, text)
            tool_list.append(tile_button)
        self.tool_widget = ToolListWidget((button_left, button_height),
                tool_list, MAX_TOOLS, start_key=K_2)
        self.container.add(self.tool_widget)
        button_height += self.tool_widget.rect.height + 2

        mode_button = TextButton((button_left, button_height),
                change_mode_text)
        mode_button.add_callback('clicked', self.change_toolbar)
        self.container.add(mode_button)
        button_height += mode_button.surface.get_height() + button_padding
        button_height += 2

        new = TextButton((button_left, button_height), "New")
        new.add_callback('clicked', self.new)
        self.container.add(new)
        load = TextButton((button_left + 60, button_height), "Load")
        load.add_callback('clicked', self.load)
        self.container.add(load)

        save = TextButton((button_left + 120, button_height), "Save")
        save.add_callback('clicked', self.save)
        self.container.add(save)

    def change_tool(self, ev, widget, new_tool, text):
        self.edit_widget.set_tool(new_tool)
        self.current_tool.text = 'Tool: %s' % text
        self.current_tool.prepare()

    def save(self, ev, widget):
        message = None
        if not self.level.level_name:
            message = MessageBox((300, 300), 'Please enter a name')
        elif self.level.level_name in RESERVED_NAMES:
            message = MessageBox((300, 300), 'Reserved level name')
        if message:
            self.container.add(message)
            message.grab_focus()
            return
        self.level.save_level()

    def new(self, ev, widget):
        self.load_level(ev, widget, 'blank')

    def load(self, ev, widget):
        levels = list_levels()
        load_list = []
        for level_name in levels:
            if level_name in RESERVED_NAMES:
                continue
            load_button = TextButton((0, 0), level_name)
            load_button.add_callback('clicked', self.load_level, level_name)
            load_list.append(load_button)
        load_dialog = ListBox((200, 200), 'Select Level', load_list)
        self.container.add(load_dialog)
        load_dialog.grab_focus()

    def load_level(self, ev, widget, level_name):
        try:
            new_level = Level(level_name)
        except (IOError, InvalidMapError, pygame.error), error:
            message = MessageBox((300, 300),
                    'Loading Level Failed: %s' % error, color='red')
            self.container.add(message)
            message.grab_focus()
            return False
        self.container.remove(self.edit_widget)
        self.level = new_level
        if level_name in RESERVED_NAMES:
            self.level.level_name = ''  # Special case for new level
        self.edit_widget = EditLevelWidget(self.level)
        self.container.add(self.edit_widget)
        self.clear_toolbar()
        self.setup_toolbar()

    def change_toolbar(self, ev, widget):
        if self.mode == 'Tile':
            self.mode = 'Thing'
        elif self.mode == 'Thing':
            self.mode = 'Tile'
        self.clear_toolbar()
        self.setup_toolbar()

    def clear_toolbar(self):
        """Remove every non-edit widget from the container"""
        for widget in self.container.children[:]:
            if widget is not self.edit_widget:
                self.container.remove(widget)

    def do_edit(self, ev, widget, message, init_value, callback):
        editbox = EntryBox((200, 200), message, init_value, callback)
        self.container.add(editbox)
        editbox.grab_focus()

    def update_name(self, new_name):
        self.level.name = new_name
        self.clear_toolbar()
        self.setup_toolbar()
        return True

    def list_tilesets(self, ev, widget):
        tilesets = list_tilesets()
        load_list = []
        for name in tilesets:
            if name == 'common':
                continue
            load_button = TextButton((0, 0), name)
            load_button.add_callback('clicked', self.change_tileset, name)
            load_list.append(load_button)
        load_dialog = ListBox((200, 200), 'Select Level', load_list)
        self.container.add(load_dialog)
        load_dialog.grab_focus()

    def change_tileset(self, ev, widget, new_name):
        self.level.update_tiles_ascii()
        old_tileset = self.level.tileset
        try:
            self.level.tileset = Tileset(new_name)
            self.level.restart()
        except pygame.error, error:
            self.level.tileset = old_tileset
            message = MessageBox((300, 300),
                    'Unable to change tileset:: %s' % error,
                    self.refresh_display, color='red')
            self.container.add(message)
            message.grab_focus()
            return True
        self.clear_toolbar()
        self.setup_toolbar()
        return True

    def refresh_display(self):
        self.level.restart()
        self.clear_toolbar()
        self.setup_toolbar()
        return True

    def check_file(self, new_name):
        message = None
        if new_name == self.level.level_name:
            return True  # No-op change
        if not new_name:
            message = MessageBox((300, 300), 'Please enter a name')
        if new_name in RESERVED_NAMES:
            # This case is caught by the existance check, but the
            # importance of the reserved names means we use a different
            # message
            message = MessageBox((300, 300), 'Reserved level name')
        elif check_level_exists(new_name):
            message = MessageBox((300, 300), 'Name already in use')
        if message:
            self.container.add(message)
            message.grab_focus()
            return False
        self.level.level_name = new_name
        self.clear_toolbar()
        self.setup_toolbar()
        return True