"""Copyright (c) 2010-2012 David Rio Vierra

Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE."""
# Moving this here to get log entries ASAP -- D.C.-G.
import logging
from pymclevel.schematic import StructureNBT
from utilities import mcworld_support

log = logging.getLogger(__name__)

#-# Modified by D.C.-G. for translation purpose
#.# Marks the layout modifications. -- D.C.-G.
from editortools.thumbview import ThumbView
import keys
import pygame
from albow.fields import FloatField
from albow import ChoiceButton, TextInputRow, CheckBoxLabel, showProgress, IntInputRow, ScrollPanel
from editortools.blockview import BlockButton
try:
    import ftp_client
    ftp_supported = True
    log.info('FPTUtil module found, FTP server support enabled')
except ImportError:
    ftp_supported = False
    log.info('FTPUtil module not found, FTP server support is disabled')
#import sys
#from pymclevel import nbt
#from editortools.select import SelectionTool
#from pymclevel.box import BoundingBox
from waypoints import WaypointManager
from editortools.timeditor import TimeEditor

"""
leveleditor.py

Viewport objects for Camera and Chunk views, which respond to some keyboard and
mouse input. LevelEditor object responds to some other keyboard and mouse
input, plus handles the undo stack and implements tile entity editors for
chests, signs, and more. Toolbar object which holds instances of EditorTool
imported from editortools/

"""

import gc
import os
#import math
import csv
#import copy
import time
import numpy
from config import config
#from config import DEF_ENC
#import frustum
import glutils
import release
import mceutils
import platform
#import functools
import editortools
import itertools
import mcplatform
import pymclevel
import renderer
import directories
import panels
import viewports
import shutil

from os.path import dirname, isdir
from datetime import datetime, timedelta
from collections import defaultdict, deque

from OpenGL import GL

from albow import alert, ask, AttrRef, Button, Column, input_text, IntField, Row, \
    TableColumn, TableView, TextFieldWrapped, TimeField, Widget, CheckBox, \
    unparented
import albow.resource

albow.resource.font_proportion = config.settings.fontProportion.get()
get_font = albow.resource.get_font
from albow.controls import Label, ValueDisplay, Image, RotatableImage
from albow.dialogs import Dialog, QuickDialog, wrapped_label
from albow.openglwidgets import GLOrtho, GLViewport
from albow.translate import _, getLang
from pygame import display, event, mouse, MOUSEMOTION, image

from depths import DepthOffset
from editortools.operation import Operation
from editortools.chunk import GeneratorPanel, ChunkTool
from glbackground import GLBackground, Panel
from glutils import Texture
from mcplatform import askSaveFile
from pymclevel.minecraft_server import alphanum_key  # ?????
from renderer import MCRenderer
#from pymclevel.entity import Entity
from pymclevel.infiniteworld import AnvilWorldFolder, SessionLockLost, MCAlphaDimension,\
    MCInfdevOldLevel
# Block and item translation
from mclangres import translate as trn
from mclangres import buildResources

try:
    import resource  # @UnresolvedImport

    resource.setrlimit(resource.RLIMIT_NOFILE, (500, -1))
except:
    pass

# Label = GLLabel


arch = platform.architecture()[0]


def DebugDisplay(obj, *attrs):
    col = []
    for attr in attrs:
        def _get(attr):
            return lambda: str(getattr(obj, attr))

        col.append(Row((Label(attr + " = "), ValueDisplay(width=600, get_value=_get(attr)))))

    col = Column(col, align="l")
    b = GLBackground()
    b.add(col)
    b.shrink_wrap()
    return b


# if self.defaultScale >= 0.5:
#            return super(ChunkViewport, self).drawCeiling()


class LevelEditor(GLViewport):
    anchor = "tlbr"
    __maxCopies = 32

    def __init__(self, mcedit):
        self.mcedit = mcedit
        rect = mcedit.rect
        GLViewport.__init__(self, rect)

        self.currentCopyPage = 0

        self.frames = 0
        self.frameStartTime = datetime.now()
        self.oldFrameStartTime = self.frameStartTime

        self.dragInProgress = False

        self.debug = 0
        self.debugString = ""

        self.testBoardKey = 0
        self.world_from_ftp = False

        self.perfSamples = 5
        self.frameSamples = [timedelta(0, 0, 0)] * 5

        self.unsavedEdits = 0
        self.undoStack = []
        self.redoStack = []
        self.copyStack = []

        self.nbtCopyBuffer = mcedit.nbtCopyBuffer

        self.level = None

        # Tracking the dimension changes.
        self.prev_dimension = None
        self.new_dimension = None

        self.cameraInputs = [0., 0., 0.]
        self.cameraPanKeys = [0., 0.]
        self.movements = [
            config.keys.left.get(),
            config.keys.right.get(),
            config.keys.forward.get(),
            config.keys.back.get(),
            config.keys.up.get(),
            config.keys.down.get()
        ]
        self.toolbarKeys = [
            config.keys.select.get(),
            config.keys.brush.get(),
            config.keys.clone.get(),
            config.keys.fillAndReplace.get(),
            config.keys.filter.get(),
            config.keys.importKey.get(),
            config.keys.players.get(),
            config.keys.worldSpawnpoint.get(),
            config.keys.chunkControl.get(),
            config.keys.nbtExplorer.get()
        ]
        self.cameraPan = [
            config.keys.panLeft.get(),
            config.keys.panRight.get(),
            config.keys.panUp.get(),
            config.keys.panDown.get()
        ]
        self.sprintKey = config.keys.sprint.get()
        self.different_keys = {
            "mouse1": "Mouse1",
            "mouse2": "Mouse2",
            "mouse3": "Button 3",
            "mouse4": "Scroll Up",
            "mouse5": "Scroll Down",
            "mouse6": "Button 4",
            "mouse7": "Button 5",
            "Delete": "Del"
        }
        self.rightClickNudge = False
        self.root = self.get_root()
        self.cameraToolDistance = self.defaultCameraToolDistance

        self.createRenderers()

        self.sixteenBlockTex = self.genSixteenBlockTexture()

        self.generateStars()

        self.optionsBar = Widget()

        self.mcEditButton = Button("Menu", action=self.showControls)

        def chooseDistance():
            self.changeViewDistance(int(self.viewDistanceReadout.get_value()))
        if self.renderer.viewDistance not in xrange(2,config.settings.maxViewDistance.get()+1,2):
            self.renderer.viewDistance = 2
        self.viewDistanceReadout = ChoiceButton(["%s"%a for a in xrange(2,config.settings.maxViewDistance.get()+1,2)], width=20, ref=AttrRef(self.renderer, "viewDistance"), choose=chooseDistance)
        self.viewDistanceReadout.selectedChoice = "%s"%self.renderer.viewDistance
        self.viewDistanceReadout.shrink_wrap()

        def showViewOptions():
            col = [CheckBoxLabel("Entities", expand=0, fg_color=(0xff, 0x22, 0x22),
                                 ref=config.settings.drawEntities),
                   CheckBoxLabel("Items", expand=0, fg_color=(0x22, 0xff, 0x22), ref=config.settings.drawItems),
                   CheckBoxLabel("TileEntities", expand=0, fg_color=(0xff, 0xff, 0x22),
                                 ref=config.settings.drawTileEntities),
                   CheckBoxLabel("TileTicks", expand=0, ref=config.settings.drawTileTicks),
                   CheckBoxLabel("Player Heads", expand=0, ref=config.settings.drawPlayerHeads),
                   CheckBoxLabel("Unpopulated Chunks", expand=0, fg_color=renderer.TerrainPopulatedRenderer.color,
                                 ref=config.settings.drawUnpopulatedChunks),
                   CheckBoxLabel("Chunks Borders", expand=0, fg_color=renderer.ChunkBorderRenderer.color,
                                 ref=config.settings.drawChunkBorders),
                   CheckBoxLabel("Sky", expand=0, ref=config.settings.drawSky),
                   CheckBoxLabel("Fog", expand=0, ref=config.settings.drawFog),
                   CheckBoxLabel("Ceiling", expand=0, ref=config.settings.showCeiling),
                   CheckBoxLabel("Chunk Redraw", expand=0, fg_color=(0xff, 0x99, 0x99),
                                 ref=config.settings.showChunkRedraw),
                   CheckBoxLabel("Hidden Ores", expand=0, ref=config.settings.showHiddenOres,
                                 tooltipText="Check to show/hide specific ores using the settings below.")]

            for ore in config.settings.hiddableOres.get():
                col.append(CheckBoxLabel("* " + _(self.level.materials[ore].name.replace(" Ore", "")), expand=0,
                                         ref=config.settings["showOre{}".format(ore)]))

            col = Column(col, align="r", spacing=4, expand='h')

            d = QuickDialog()
            d.add(col)

            d.shrink_wrap()
            d.topleft = self.viewButton.bottomleft
            d.present(centered=False)

        self.viewButton = Button("Show...", action=showViewOptions)

        self.waypointManager = WaypointManager(editor=self)
        self.waypointManager.load()
        #self.loadWaypoints()
        self.waypointsButton = Button("Waypoints/Goto", action=self.showWaypointsDialog)

        self.viewportButton = Button("Camera View", action=self.swapViewports,
                                     tooltipText=_("Shortcut: {0}").format(_(config.keys.toggleView.get())))

        self.recordUndoButton = CheckBoxLabel("Undo", ref=AttrRef(self, 'recordUndo'))

        # TODO: Mark
        print directories.getDataDir(os.path.join(u"toolicons", u"session_good.png"))
        #self.sessionLockLock = Image(image.load(open(directories.getDataDir(os.path.join(u"toolicons",
        #                                                                                 u"session_good.png")), 'rb'), 'rb'))
        self.sessionLockLock = Image(image.load(directories.getDataFile('toolicons', 'session_good.png')))
        self.sessionLockLock.tooltipText = "Session Lock is being used by MCEdit"
        self.sessionLockLock.mouse_down = self.mouse_down_session
        self.sessionLockLabel = Label("Session:", margin=0)
        self.sessionLockLabel.tooltipText = "Session Lock is being used by MCEdit"
        self.sessionLockLabel.mouse_down = self.mouse_down_session

        # TODO: Marker
        row = (self.mcEditButton, Label("View Distance:"), self.viewDistanceReadout,
               self.viewButton, self.viewportButton, self.recordUndoButton,
               Row((self.sessionLockLabel, self.sessionLockLock), spacing=2), self.waypointsButton)


        self.topRow = row = Row(row)
        self.add(row)
        self.statusLabel = ValueDisplay(width=self.width, ref=AttrRef(self, "statusText"))

        self.mainViewport = viewports.CameraViewport(self)
        self.chunkViewport = viewports.ChunkViewport(self)

        self.mainViewport.height -= row.height

        self.mainViewport.height -= self.statusLabel.height
        self.statusLabel.bottom = self.bottom
        self.statusLabel.anchor = "blrh"

        self.add(self.statusLabel)

        self.viewportContainer = Widget(is_gl_container=True, anchor="tlbr")
        self.viewportContainer.top = row.bottom
        self.viewportContainer.size = self.mainViewport.size
        self.add(self.viewportContainer)

        config.settings.viewMode.addObserver(self)
        config.settings.undoLimit.addObserver(self)

        self.reloadToolbar()

        self.currentTool = None
        self.toolbar.selectTool(0)

        self.controlPanel = panels.ControlPanel(self)

        # logger = logging.getLogger()

        # adapter = logging.StreamHandler(sys.stdout)
        # adapter.addFilter(LogFilter(self))
        # logger.addHandler(adapter)
        self.revertPlayerSkins = False

    #-# Translation live update preparation
    def set_update_ui(self, v):
        GLViewport.set_update_ui(self, v)
        if v:
            self.statusLabel.width = self.width
            self.topRow.calc_size()
            self.controlPanel.set_update_ui(v)
            # Update the unparented widgets.
            for a in unparented.values():
                a.set_update_ui(v)
    #-#

    def __del__(self):
        self.deleteAllCopiedSchematics()


    def showCreateDialog(self):
        widg = Widget()

        nameField = TextFieldWrapped(width=200)
        waypoints = self.waypointManager.waypoint_names
        defaultName = 'Waypoint{}'
        n = 1
        while n <= 50:
            if defaultName.format(n) in waypoints:
                n += 1
            else:
                break
        nameField.value = defaultName.format(n)

        xField = FloatField()
        yField = FloatField()
        zField = FloatField()
        saveCameraRotation = CheckBoxLabel("Save Rotation")

        xField.value = round(self.mainViewport.cameraPosition[0], 2)
        yField.value = round(self.mainViewport.cameraPosition[1], 2)
        zField.value = round(self.mainViewport.cameraPosition[2], 2)

        coordRow = Row((Label("X:"), xField, Label("Y:"), yField, Label("Z:"), zField))
        col = Column((Row((Label("Waypoint Name:"), nameField)), coordRow, saveCameraRotation), align="c")

        widg.add(col)
        widg.shrink_wrap()

        d = Dialog(widg, ["Create", "Cancel"])

        def click_outside(event):
            if event not in d:
                x, y, z = self.blockFaceUnderCursor[0]
                if y == 0:
                    y = 64
                y += 3
                xField.value, yField.value, zField.value = x, y, z

        def key_down(event):
            """Defines keyboard actions for the waypoint creation dialog.
            :param event: object: The event to be processed."""
            key = self.root.getKey(event)
            if key == "Return":
                d.dismiss("Create")
            elif key == "Escape":
                d.dismiss("Cancel")
            else:
                Dialog.key_down(d, event)

        d.key_down = key_down

        # Trigger escape key press when one field has focus to the dialog dismiss action.
        nameField.escape_action = xField.escape_action = yField.escape_action = zField.escape_action = d.dismiss

        d.mouse_down = click_outside
        result = d.present()
        if result == "Create":
            if nameField.value in self.waypointManager.waypoint_names:
                self.Notify("You cannot have duplicate waypoint names")
                return
            if saveCameraRotation.checkbox.value:
                self.waypointManager.add_waypoint(nameField.value, (xField.value, yField.value, zField.value), (self.mainViewport.yaw, self.mainViewport.pitch), self.level.dimNo)
            else:
                self.waypointManager.add_waypoint(nameField.value, (xField.value, yField.value, zField.value), (0.0, 0.0), self.level.dimNo)
            if "Empty" in self.waypointManager.waypoints:
                del self.waypointManager.waypoints["Empty"]
            self.waypointDialog.dismiss()
            self.waypointManager.save()

    def gotoWaypoint(self):
        if self.waypointsChoiceButton.value == "Empty":
            return
        self.gotoDimension(self.waypointManager.waypoints[self.waypointsChoiceButton.value][5])
        self.mainViewport.skyList = None
        self.mainViewport.drawSkyBackground()

        self.mainViewport.cameraPosition = self.waypointManager.waypoints[self.waypointsChoiceButton.value][:3]
        self.mainViewport.yaw = self.waypointManager.waypoints[self.waypointsChoiceButton.value][3]
        self.mainViewport.pitch = self.waypointManager.waypoints[self.waypointsChoiceButton.value][4]
        self.mainViewport.skyList = None
        self.mainViewport.drawSkyBackground()
        self.waypointDialog.dismiss()

    def deleteWaypoint(self):
        self.waypointDialog.dismiss()
        if self.waypointsChoiceButton.value == "Empty":
            return
        self.waypointManager.delete(self.waypointsChoiceButton.value)

    def gotoLastWaypoint(self, lastPos):
        #!# Added checks to verify the waypoint NBT data consistency. (Avoid crashed in case of corrupted file.)
        if lastPos.get("Dimension") and lastPos.get("Coordinates") and lastPos.get("Rotation"):
            self.gotoDimension(lastPos["Dimension"].value)
            self.mainViewport.skyList = None
            self.mainViewport.drawSkyBackground()
            self.mainViewport.cameraPosition = [lastPos["Coordinates"][0].value,
                                                lastPos["Coordinates"][1].value,
                                                lastPos["Coordinates"][2].value
                                                ]
            self.mainViewport.yaw = lastPos["Rotation"][0].value
            self.mainViewport.pitch = lastPos["Rotation"][1].value

    def showWaypointsDialog(self):
        """Displays the waypoints dialog. Also used to jump to coordinates."""

        self.waypointDialog = Dialog()

        self.waypointsChoiceButton = ChoiceButton(self.waypointManager.waypoints.keys())
        createWaypointButton = Button("Create Waypoint", action=self.showCreateDialog)
        gotoWaypointButton = Button("Goto Waypoint", action=self.gotoWaypoint)
        deleteWaypointButton = Button("Delete Waypoint", action=self.deleteWaypoint)

        saveCameraOnClose = CheckBoxLabel("Save Camera position on world close",
                                          ref=config.settings.savePositionOnClose)

        gotoPanel = Widget()
        gotoPanel.goto_coords = False
        gotoPanel.X, gotoPanel.Y, gotoPanel.Z = map(int, self.mainViewport.cameraPosition)

        def set_goto_coords():
            """Triggers camera move to the selected coordinates."""
            self.waypointDialog.dismiss()
            gotoPanel.goto_coords = True

        def click_outside(event):
            """Selects coordinates outside the dialog and updates the corresponding X, Y and Z
            fields in the dialog. Immediately moves the camera to the coordinates on double-click.
            :param event: object: The event to be processed."""
            if event not in self.waypointDialog:
                x, y, z = self.blockFaceUnderCursor[0]
                if y == 0:
                    y = 64
                y += 3
                gotoPanel.X, gotoPanel.Y, gotoPanel.Z = x, y, z

                if event.num_clicks == 2:
                    gotoPanel.goto_coords = True
                    self.waypointDialog.dismiss()

        def on_choose():
            """Handles choice for the waypoints dropdown choice button."""
            choice = self.waypointsChoiceButton.value
            waypoint = self.waypointManager.waypoints[choice]
            gotoPanel.X, gotoPanel.Y, gotoPanel.Z = map(int, waypoint[0:3])

        def key_down(event):
            """Defines keyboard actions for the waypoints dialog.
            :param event: object: The event to be processed."""
            if self.root.getKey(event) == "Escape":
                gotoPanel.goto_coords = False
                self.waypointDialog.dismiss()
            else:
                Dialog.key_down(self.waypointDialog, event)

        self.waypointsChoiceButton.choose = on_choose
        on_choose()

        x_field = IntField(ref=AttrRef(gotoPanel, "X"))
        y_field = IntField(ref=AttrRef(gotoPanel, "Y"))
        z_field = IntField(ref=AttrRef(gotoPanel, "Z"))

        # Trigger escape key press when one field has focus to the dialog dismiss action.
        x_field.escape_action = y_field.escape_action = z_field.escape_action = self.waypointDialog.dismiss

        coordinateRow = (
            Label("X: "), x_field,
            Label("Y: "), y_field,
            Label("Z: "), z_field,
        )
        gotoRow = Row(coordinateRow)
        gotoCoordinateButton = Button("Goto Coordinates", action=set_goto_coords)

        col = Column(
            (
                self.waypointsChoiceButton,
                Row(
                    (
                        createWaypointButton,
                        gotoWaypointButton,
                        deleteWaypointButton,
                    )
                ),
                saveCameraOnClose,
                gotoRow,
                gotoCoordinateButton,
                Button("Close", action=self.waypointDialog.dismiss),
            )
        )
        self.waypointDialog.add(col)
        self.waypointDialog.shrink_wrap()
        self.waypointDialog.mouse_down = click_outside
        self.waypointDialog.key_down = key_down
        self.waypointDialog.present(True)

        if gotoPanel.goto_coords:
            destPoint = [gotoPanel.X, gotoPanel.Y, gotoPanel.Z]
            if self.currentViewport is self.chunkViewport:
                self.swapViewports()
            self.mainViewport.cameraPosition = destPoint

    def mouse_down_session(self, evt):
        class SessionLockOptions(Panel):
            def __init__(self):
                Panel.__init__(self)
                self.autoChooseCheckBox = CheckBoxLabel("Override Minecraft Changes (Not Recommended)",
                                                        ref=config.session.override,
                                                        tooltipText="Always override Minecraft changes when map is open in MCEdit. (Not recommended)")

                col = Column((Label("Session Lock Options"), self.autoChooseCheckBox, Button("OK", action=self.dismiss)))

                self.add(col)
                self.shrink_wrap()

        if evt.button == 3:
            sessionLockPanel = SessionLockOptions()
            sessionLockPanel.present()

    _viewMode = None
    _level = None

    @property
    def level(self):
        return self._level

    @level.setter
    def level(self, level):
        self._level = level
        if hasattr(level, "setSessionLockCallback"):
            level.setSessionLockCallback(self.lockAcquired, self.lockLost)

    @property
    def viewMode(self):
        return self._viewMode

    @viewMode.setter
    def viewMode(self, val):
        if val == self._viewMode:
            return
        ports = {"Chunk": self.chunkViewport, "Camera": self.mainViewport}
        for p in ports.values():
            p.set_parent(None)
        port = ports.get(val, self.mainViewport)
        self.mainViewport.mouseLookOff()
        self._viewMode = val

        if val == "Camera":
            x, y, z = self.chunkViewport.cameraPosition
            try:
                h = self.level.heightMapAt(int(x), int(z))
            except:
                h = 0
            y = max(self.mainViewport.cameraPosition[1], h + 2)
            self.mainViewport.cameraPosition = x, y, z
            # self.mainViewport.yaw = 180.0
            # self.mainViewport.pitch = 90.0
            self.mainViewport.cameraVector = self.mainViewport._cameraVector()
            self.renderer.overheadMode = False
            self.viewportButton.text = "Chunk View"
        else:
            x, y, z = self.mainViewport.cameraPosition
            self.chunkViewport.cameraPosition = x, y, z
            self.renderer.overheadMode = True
            self.viewportButton.text = "Camera View"

        self.viewportContainer.add(port)
        self.currentViewport = port
        self.chunkViewport.size = self.mainViewport.size = self.viewportContainer.size
        self.renderer.loadNearbyChunks()

    @staticmethod
    def swapViewports():
        if config.settings.viewMode.get() == "Chunk":
            config.settings.viewMode.set("Camera")
        else:
            config.settings.viewMode.set("Chunk")

    def addCopiedSchematic(self, sch):
        self.copyStack.insert(0, sch)
        if len(self.copyStack) > self.maxCopies:
            self.deleteCopiedSchematic(self.copyStack[-1])
        self.updateCopyPanel()

    @staticmethod
    def _deleteSchematic(sch):
        if hasattr(sch, 'close'):
            sch.close()
        if sch.filename and os.path.exists(sch.filename):
            os.remove(sch.filename)

    def deleteCopiedSchematic(self, sch):
        self._deleteSchematic(sch)
        self.copyStack = [s for s in self.copyStack if s is not sch]
        self.updateCopyPanel()

    def deleteAllCopiedSchematics(self):
        for s in self.copyStack:
            self._deleteSchematic(s)

    copyPanel = None

    def updateCopyPanel(self):
        if self.copyPanel:
            self.copyPanel.set_parent(None)
            self.copyPanel = None
        if 0 == len(self.copyStack):
            return

        self.copyPanel = self.createCopyPanel()
        self.copyPanel.right = self.mainViewport.right
        self.copyPanel.top = self.subwidgets[0].bottom + 2
        self.add(self.copyPanel)

    thumbCache = None
    fboCache = None

    def __getMaxCopies(self):
        return config.settings.maxCopies.get() or self.__maxCopies

    def __setMaxCopies(self, *args, **kwargs):
        return

    def __delMaxCopies(self):
        return

    maxCopies = property(__getMaxCopies, __setMaxCopies, __delMaxCopies, "Copy stack size.")

    def createCopyPanel(self):
        panel = GLBackground()
        panel.bg_color = (0.0, 0.0, 0.0, 0.5)
        panel.pages = []
        if len(self.copyStack) > self.maxCopies:
            for sch in self.copyStack[self.maxCopies:]:
                self.deleteCopiedSchematic(sch)

        prevButton = Button("Previous Page")

        self.thumbCache = thumbCache = self.thumbCache or {}
        self.fboCache = self.fboCache or {}
        for k in self.thumbCache.keys():
            if k not in self.copyStack:
                del self.thumbCache[k]

        inner_height = 0
        itemNo = Label("#%s" % ("W" * len("%s" % self.maxCopies)), doNotTranslate=True)
        fixedwidth = 0 + itemNo.width
        del itemNo

        def createOneCopyPanel(sch, i):
            p = GLBackground()
            p.bg_color = (0.0, 0.0, 0.0, 0.4)
            itemNo = Label("#%s%s" % ("  " * (len("%s" % self.maxCopies) - len("%s" % (i + 1))), (i + 1)),
                           doNotTranslate=True)
            thumb = thumbCache.get(sch)
            if thumb is None:
                thumb = ThumbView(sch)
                thumb.mouse_down = lambda e: self.pasteSchematic(sch)
                thumb.tooltipText = "Click to import this item."
                thumbCache[sch] = thumb
            self.addWorker(thumb.renderer)
            deleteButton = Button("Delete", action=lambda: (self.deleteCopiedSchematic(sch)))
            saveButton = Button("Save", action=lambda: (self.exportSchematic(sch)))
            sizeLabel = Label("{0} x {1} x {2}".format(sch.Length, sch.Width, sch.Height))

            r = Row((itemNo, thumb, Column((sizeLabel, Row((deleteButton, saveButton))), spacing=5)))
            p.add(r)
            itemNo.width = 0 + fixedwidth
            p.shrink_wrap()
            return p

        page = []
        for i in xrange(len(self.copyStack)):
            sch = self.copyStack[i]
            p = createOneCopyPanel(sch, i)
            if self.netherPanel is None:
                bottom = pygame.display.get_surface().get_height() - 60
            else:
                bottom = self.netherPanel.top - 2
            if inner_height + p.height + 2 <= bottom - (self.subwidgets[0].bottom + 2) - prevButton.height - (panel.margin * 2):
                inner_height += p.height + 2
                page.append(p)
            else:
                inner_height = p.height
                panel.pages.append(Column(page, spacing=2, align="l", margin=0))
                panel.pages[-1].shrink_wrap()
                page = [p]
        if page:
            panel.pages.append(Column(page, spacing=2, align="l", margin=0))
            panel.pages[-1].shrink_wrap()

        prevButton.shrink_wrap()
        self.currentCopyPage = min(self.currentCopyPage, len(panel.pages) - 1)
        col = Column([panel.pages[self.currentCopyPage]], spacing=2, align="l", margin=0)
        col.shrink_wrap()

        def changeCopyPage(this, delta):
            if delta > 0:
                m = min
                a = self.currentCopyPage + delta, len(this.pages) - 1
            elif delta < 0:
                m = max
                a = self.currentCopyPage - 1, 0
            else:
                return
            self.currentCopyPage = m(*a)
            for i in xrange(len(this.pages)):
                page = this.pages[i]
                if i == self.currentCopyPage:
                    page.visible = True
                    this.subwidgets[0].subwidgets[1].subwidgets[0] = page
                    page.parent = this.subwidgets[0].subwidgets[1]
                else:
                    page.visible = False
            pb = this.subwidgets[0].subwidgets[0].subwidgets[0]
            nb = this.subwidgets[0].subwidgets[0].subwidgets[1]
            if self.currentCopyPage == 0:
                pb.enabled = False
                nb.enabled = True
            elif 0 < self.currentCopyPage < len(this.pages) - 1:
                pb.enabled = True
                nb.enabled = True
            elif self.currentCopyPage == len(this.pages) - 1:
                pb.enabled = True
                nb.enabled = False
            this.subwidgets[0].subwidgets[1].shrink_wrap()
            this.subwidgets[0].shrink_wrap()
            this.shrink_wrap()
            this.width = 0 + this.orgwidth

        nextButton = Button("Next Page", action=lambda: changeCopyPage(panel, 1), width=prevButton.width,
                            height=prevButton.height)
        prevButton.action = lambda: changeCopyPage(panel, -1)
        if len(panel.pages) < 2:
            prevButton.enabled = False
            nextButton.enabled = False
        elif self.currentCopyPage == 0:
            prevButton.enabled = False
            nextButton.enabled = True
        elif 0 < self.currentCopyPage < len(panel.pages) - 1:
            prevButton.enabled = True
            nextButton.enabled = True
        elif self.currentCopyPage == len(panel.pages) - 1:
            prevButton.enabled = True
            nextButton.enabled = False
        btns = Row((prevButton, nextButton), spacing=2, align='c')
        btns.shrink_wrap()
        mainCol = Column((btns, col), spacing=2, align='c')
        mainCol.shrink_wrap()
        panel.add(mainCol)

        panel.shrink_wrap()
        panel.anchor = "whrt"
        panel.orgwidth = 0 + panel.width
        return panel

    @mceutils.alertException
    def showAnalysis(self, schematic):
        self.analyzeBox(schematic, schematic.bounds)

    def analyzeBox(self, level, box):
        entityCounts = defaultdict(int)
        tileEntityCounts = defaultdict(int)
        types = numpy.zeros(65536, dtype='uint32')

        def _analyzeBox():
            i = 0
            for (chunk, slices, point) in level.getChunkSlices(box):
                i += 1
                yield i, box.chunkCount
                blocks = numpy.array(chunk.Blocks[slices], dtype='uint16')
                blocks |= (numpy.array(chunk.Data[slices], dtype='uint16') << 12)
                b = numpy.bincount(blocks.ravel())
                types[:b.shape[0]] = types[:b.shape[0]].astype(int) + b

                for ent in chunk.getEntitiesInBox(box):
                    entID = level.__class__.entityClass.getId(ent["id"].value)
                    if ent["id"].value == "Item":
                        try:
                            v = pymclevel.items.items.findItem(ent["Item"]["id"].value,
                                                               ent["Item"]["Damage"].value).name
                            v += " (Item)"
                        except pymclevel.items.ItemNotFound:
                            v = "Unknown Item"
                    else:
                        v = ent["id"].value
                    entityCounts[(entID, v)] += 1
                for ent in chunk.getTileEntitiesInBox(box):
                    tileEntityCounts[ent["id"].value] += 1

        with mceutils.setWindowCaption("ANALYZING - "):
            showProgress(_("Analyzing {0} blocks...").format(box.volume), _analyzeBox(), cancel=True)

        entitySum = numpy.sum(entityCounts.values())
        tileEntitySum = numpy.sum(tileEntityCounts.values())
        presentTypes = types.nonzero()

        blockCounts = sorted([(level.materials[t & 0xfff, t >> 12], types[t]) for t in presentTypes[0]])

        rows = blockRows = [("", "", ""), (box.volume, "<%s>" % _("Blocks"), "")]
        #rows = list(blockRows)
        rows.extend([[count, trn(block.name), ("({0}:{1})".format(block.ID, block.blockData))] for block, count in blockCounts])
        #rows.sort(key=lambda x: alphanum_key(x[2]), reverse=True)

        def extendEntities():
            if entitySum:
                rows.extend([("", "", ""), (entitySum, "<%s>" % _("Entities"), "")])
                rows.extend([(count, trn(id[1]), id[0]) for (id, count) in sorted(entityCounts.iteritems())])
            if tileEntitySum:
                rows.extend([("", "", ""), (tileEntitySum, "<%s>" % _("TileEntities"), "")])
                rows.extend([(count, trn(id), "") for (id, count) in sorted(tileEntityCounts.iteritems())])

        extendEntities()

        columns = [
            TableColumn("Count", 100),
            TableColumn("Name", 400),
            TableColumn("ID", 120),
        ]
        table = TableView(columns=columns)
        table.sortColumn = columns[2]
        table.reverseSort = True

        def sortColumn(col):
            if table.sortColumn == col:
                table.reverseSort = not table.reverseSort
            else:
                table.reverseSort = (col.title == "Count")
            colnum = columns.index(col)

            def sortKey(x):
                val = x[colnum]
                if isinstance(val, basestring):
                    alphanum_key(val)
                return val

            blockRows.sort(key=sortKey,
                           reverse=table.reverseSort)
            table.sortColumn = col
            rows[:] = blockRows
            extendEntities()

        table.num_rows = lambda: len(rows)
        table.row_data = lambda i: rows[i]
        table.row_is_selected = lambda x: False
        table.click_column_header = sortColumn

        tableBacking = Widget()
        tableBacking.add(table)
        tableBacking.shrink_wrap()

        def saveToFile():
            dt = datetime.now().strftime("%Y-%m-%d--%H-%M-%S")
            filename = askSaveFile(directories.docsFolder,
                                   title='Save analysis...',
                                   defaultName=self.level.displayName + "_analysis_" + dt + ".txt",
                                   filetype='Comma Separated Values\0*.txt\0\0',
                                   suffix="txt",
                                   )

            if filename:
                try:
                    csvfile = csv.writer(open(filename, "wb"))
                except Exception as e:
                    alert(str(e))
                else:
                    for row in rows:
                        _row=[]
                        if row == ("", "", ""):
                            _row = ["Number", "Type", "ID"]
                        else:
                            for a in row:
                                if isinstance(a, unicode):
                                    _row.append(a.encode('utf-8'))
                                else:
                                    _row.append(a)
                        csvfile.writerow(_row)

        saveButton = Button("Save to file...", action=saveToFile)
        col = Column((Label("Analysis"), tableBacking, saveButton))
        dlg = Dialog(client=col, responses=["OK"])

        def dispatch_key(name, evt):
            super(Dialog, dlg).dispatch_key(name, evt)
            if not hasattr(evt, 'key'):
                return
            if name == 'key_down':
                keyname = self.root.getKey(evt)
                if keyname == 'Up':
                    table.rows.scroll_up()
                elif keyname == 'Down':
                    table.rows.scroll_down()
                elif keyname == 'Page up':
                    table.rows.scroll_to_item(max(0, table.rows.cell_to_item_no(0, 0) - table.rows.num_rows()))
                elif keyname == 'Page down' and table.rows.cell_to_item_no(table.rows.num_rows(), 0) != None:
                    table.rows.scroll_to_item(min(len(rows), table.rows.cell_to_item_no(table.rows.num_rows(), 0)))

        dlg.dispatch_key = dispatch_key
        dlg.present()

    def exportSchematic(self, schematic):
        filename = mcplatform.askSaveSchematic(
            directories.schematicsDir, self.level.displayName, ({"Minecraft Schematics": ["schematic"], "Minecraft Structure NBT": ["nbt"]},[]))

        def save_as_nbt(schem, filename):
            structure = StructureNBT.fromSchematic(schem)
            if 'Version' in self.level.root_tag['Data']:
                structure._version = self.level.root_tag['Data']['Version'].get('Id', pymclevel.TAG_Int(1)).value
            structure.save(filename)

        if filename:
            if filename.endswith(".schematic"):
                schematic.saveToFile(filename)
            elif filename.endswith(".nbt"):
                if (schematic.Height, schematic.Length, schematic.Width) >= (50, 50, 50):
                    result = ask("You're trying to export a large selection as a Structure NBT file, this is not recommended " +
                                 "and may cause MCEdit to hang and/or crash. We recommend you export this selection as a Schematic instead.",
                                 responses=['Export as Structure anyway', 'Export as Schematic', 'Cancel Export'], wrap_width=80)
                    if result == 'Export as Structure anyway':
                        save_as_nbt(schematic, filename)
                    elif result == 'Export as Schematic':
                        schematic.saveToFile(filename.replace('.nbt', '.schematic'))
                    elif result == 'Cancel Export':
                        return
                save_as_nbt(schematic, filename)

    def getLastCopiedSchematic(self):
        if len(self.copyStack) == 0:
            return None
        return self.copyStack[0]

    toolbar = None

    def YesNoWidget(self, msg):
        self.user_yon_response = None
        self.yon = Widget()
        self.yon.bg_color = (0.0, 0.0, 0.6)
        label = Label(msg)
        ybutton = Button("Yes", action=self.yes)
        nbutton = Button("No", action=self.no)
        col = Column((label, ybutton, nbutton))
        self.yon.add(col)
        self.yon.shrink_wrap()
        self.yon.present()
        waiter = None
        while waiter is None:
            if self.user_yon_response is not None:
                return self.user_yon_response

    def yes(self):
        self.yon.dismiss()
        self.user_yon_response = True

    def no(self):
        self.yon.dismiss()
        self.user_yon_response = False

    def _resize_selection_box(self, new_box):
        import inspect # Let's get our hands dirty
        stack = inspect.stack()
        filename = os.path.basename(stack[1][1])
        old_box = self.selectionTool.selectionBox()
        msg = """
        Filter "{0}" wants to resize the selection box
        Origin: {1} -> {2}
        Size: {3} -> {4}
        Do you want to resize the Selection Box?""".format(
            filename,
            (old_box.origin[0], old_box.origin[1], old_box.origin[2]),
            (new_box.origin[0], new_box.origin[1], new_box.origin[2]),
            (old_box.size[0], old_box.size[1], old_box.size[2]),
            (new_box.size[0], new_box.size[1], new_box.size[2])
        )
        result = ask(msg, ["Yes", "No"])
        if result == "Yes":
            self.selectionTool.setSelection(new_box)
            return new_box
        else:
            return False

    def addExternalWidget(self, obj):
        if isinstance(obj, Widget):
            return self.addExternalWidget_Widget(obj)
        elif isinstance(obj, (dict, tuple, list)):
            return self.addExternalWidget_Dict(obj)

    def addExternalWidget_Widget(self, obj):
        obj.shrink_wrap()
        result = Dialog(obj, ["Ok", "Cancel"]).present()
        if result == "Ok":
            print obj
        else:
            return 'user canceled'

    def addExternalWidget_Dict(self, provided_fields):

        def addNumField(wid, name, val, minimum=None, maximum=None, increment=0.1):
            if isinstance(val, float):
                ftype = FloatField
                if isinstance(increment, int):
                    increment = float(increment)
            else:
                ftype = IntField
                if increment == 0.1:
                    increment = 1
                if isinstance(increment, float):
                    increment = int(round(increment))

            if minimum == maximum:
                minimum = None
                maximum = None

            field = ftype(value=val, width=100, min=minimum, max=maximum)
            field._increment = increment
            wid.inputDict[name] = AttrRef(field, 'value')
            return Row([Label(name), field])

        widget = Widget()
        rows = []
        cols = []
        max_height = self.mainViewport.height
        widget.inputDict = {}

        if isinstance(provided_fields, dict):
            provided_fields = [(key, value) for key, value in provided_fields.iteritems()]

        for inputName, inputType in provided_fields:
            if isinstance(inputType, tuple) and inputType != ():
                if isinstance(inputType[0], (int, long, float)):
                    if len(inputType) == 2:
                        min, max = inputType
                        val = min
                        increment = 0.1
                    elif len(inputType) == 3:
                        val, min, max = inputType
                        increment = 0.1
                    elif len(inputType) == 4:
                        val, min, max, increment = inputType
                    rows.append(addNumField(widget, inputName, val, min, max, increment))

                if isinstance(inputType[0], (str, unicode)):
                    isChoiceButton = False

                    if inputType[0] == "string":
                        keywords = []
                        width = None
                        value = None
                        for keyword in inputType:
                            if isinstance(keyword, (str, unicode)) and keyword != "string":
                                keywords.append(keyword)
                        for word in keywords:
                            splitWord = word.split('=')
                            if len(splitWord) > 1:
                                v = None

                                try:
                                    v = int(splitWord[1])
                                except:
                                    pass

                                key = splitWord[0]
                                if v is not None:
                                    if key == "width":
                                        width = v
                                else:
                                    if key == "value":
                                        value = splitWord[1]
                        if val is None:
                            value = ""
                        if width is None:
                            width = 200

                        field = TextFieldWrapped(value=value, width=width)
                        widget.inputDict[inputName] = AttrRef(field, 'value')
                        row = Row((Label(inputName), field))
                        rows.append(row)
                    else:
                        isChoiceButton = True

                    if isChoiceButton:
                        choiceButton = ChoiceButton(map(str, inputType))
                        widget.inputDict[inputName] = AttrRef(choiceButton, 'selectedChoice')
                        rows.append(Row((Label(inputName), choiceButton)))
            elif isinstance(inputType, bool):
                chkbox = CheckBox(value=inputType)
                widget.inputDict[inputName] = AttrRef(chkbox, 'value')
                row = Row((Label(inputName), chkbox))
                rows.append(row)

            elif isinstance(inputType, (int, float)):
                rows.append(addNumField(widget, inputName, inputType))

            elif inputType == "blocktype" or isinstance(inputType, pymclevel.materials.Block):
                blockButton = BlockButton(self.level.materials)
                if isinstance(inputType, pymclevel.materials.Block):
                    blockButton.blockInfo = inputType
                row = Column((Label(inputName), blockButton))
                widget.inputDict[inputName] = AttrRef(blockButton, 'blockInfo')

                rows.append(row)
            elif inputType == "label":
                rows.append(wrapped_label(inputName, 50))

            elif inputType == "string":
                input = None
                if input is not None:
                    size = input
                else:
                    size = 200
                field = TextFieldWrapped(value="")
                row = TextInputRow(inputName, ref=AttrRef(field, 'value'), width=size)
                widget.inputDict[inputName] = AttrRef(field, 'value')
                rows.append(row)
            else:
                raise ValueError(("Unknown option type", inputType))
        height = sum(r.height for r in rows)

        if height > max_height:
            h = 0
            for i, r in enumerate(rows):
                h += r.height
                if h > height / 2:
                    break
            cols.append(Column(rows[:i]))
            rows = rows[i:]
        if len(rows):
            cols.append(Column(rows))

        if len(cols):
            widget.add(Row(cols))

        widget.shrink_wrap()
        result = Dialog(widget, ["Ok", "Cancel"]).present()
        if result == "Ok":
            return dict((k, v.get()) for k, v in widget.inputDict.iteritems())
        else:
            return "user canceled"

    @staticmethod
    def Notify(msg):
        ask(msg, ["Close"], cancel=0)

    def reloadToolbar(self):
        self.toolbar = EditorToolbar(self, tools=[editortools.SelectionTool(self),
                                                  editortools.BrushTool(self),
                                                  editortools.CloneTool(self),
                                                  editortools.FillTool(self),
                                                  editortools.FilterTool(self),
                                                  editortools.ConstructionTool(self),
                                                  editortools.PlayerPositionTool(self),
                                                  editortools.PlayerSpawnPositionTool(self),
                                                  editortools.ChunkTool(self),
                                                  editortools.NBTExplorerTool(self),
                                                  ])

        self.toolbar.anchor = 'bwh'
        self.add(self.toolbar)
        self.toolbar.bottom = self.viewportContainer.bottom  # bottoms are touching
        self.toolbar.centerx = self.centerx

    is_gl_container = True

    maxDebug = 1
    allBlend = False
    onscreen = True
    mouseEntered = True
    defaultCameraToolDistance = 10
    mouseSensitivity = 5.0

    longDistanceMode = config.settings.longDistanceMode.property()

    @staticmethod
    def genSixteenBlockTexture():
        has12 = GL.glGetString(GL.GL_VERSION) >= "1.2"
        if has12:
            maxLevel = 2
            mode = GL.GL_LINEAR_MIPMAP_NEAREST
        else:
            maxLevel = 1
            mode = GL.GL_LINEAR

        def makeSixteenBlockTex():
            darkColor = (0x30, 0x30, 0x30, 0xff)
            lightColor = (0x80, 0x80, 0x80, 0xff)
            w, h, = 256, 256

            teximage = numpy.zeros((w, h, 4), dtype='uint8')
            teximage[:] = 0xff
            teximage[:, ::16] = lightColor
            teximage[::16, :] = lightColor
            teximage[:2] = darkColor
            teximage[-1:] = darkColor
            teximage[:, -1:] = darkColor
            teximage[:, :2] = darkColor
            GL.glTexParameter(GL.GL_TEXTURE_2D,
                              GL.GL_TEXTURE_MAX_LEVEL,
                              maxLevel - 1)

            for lev in xrange(maxLevel):
                step = 1 << lev
                if lev:
                    teximage[::16] = 0xff
                    teximage[:, ::16] = 0xff
                    teximage[:2] = darkColor
                    teximage[-1:] = darkColor
                    teximage[:, -1:] = darkColor
                    teximage[:, :2] = darkColor

                GL.glTexImage2D(GL.GL_TEXTURE_2D, lev, GL.GL_RGBA8,
                                w / step, h / step, 0,
                                GL.GL_RGBA, GL.GL_UNSIGNED_BYTE,
                                teximage[::step, ::step].ravel())

        return Texture(makeSixteenBlockTex, mode)

    @staticmethod
    def showProgress(*a, **kw):
        return showProgress(*a, **kw)

    def drawConstructionCube(self, box, color, texture=None):
        if texture is None:
            texture = self.sixteenBlockTex
        # textured cube faces

        GL.glEnable(GL.GL_BLEND)
        GL.glEnable(GL.GL_DEPTH_TEST)
        GL.glDepthMask(False)

        # edges within terrain
        GL.glDepthFunc(GL.GL_GREATER)
        try:
            GL.glColor(color[0], color[1], color[2], max(color[3], 0.35))
        except IndexError:
            raise
        GL.glLineWidth(1.0)
        mceutils.drawCube(box, cubeType=GL.GL_LINE_STRIP)

        # edges on or outside terrain
        GL.glDepthFunc(GL.GL_LEQUAL)
        GL.glColor(color[0], color[1], color[2], max(color[3] * 2, 0.75))
        GL.glLineWidth(2.0)
        mceutils.drawCube(box, cubeType=GL.GL_LINE_STRIP)

        GL.glDepthFunc(GL.GL_LESS)
        GL.glColor(color[0], color[1], color[2], color[3])
        GL.glDepthFunc(GL.GL_LEQUAL)
        mceutils.drawCube(box, texture=texture, selectionBox=True)
        GL.glDepthMask(True)

        GL.glDisable(GL.GL_BLEND)
        GL.glDisable(GL.GL_DEPTH_TEST)

    def loadFile(self, filename, addToRecent=True):
        """
        Called when the user picks a level using Load World or Open File.
        """
        if self.level and self.unsavedEdits > 0:
            resp = ask("Save unsaved edits before loading?", ["Cancel", "Don't Save", "Save"], default=2, cancel=0)
            if resp == "Cancel":
                return
            if resp == "Save":
                self.saveFile()

        self.root.RemoveEditFiles()
        self.freezeStatus(_("Loading ") + filename)
        if self.level:
            self.selectionTool.endSelection()
            self.level.close()

        try:
            level = pymclevel.fromFile(filename)
        except Exception as e:
            logging.exception(
                'Wasn\'t able to open a file {file => %s}' % filename
            )
            alert(_(u"I don't know how to open {0}:\n\n{1!r}").format(filename, e))
            self.closeEditor()
            return

        assert level
        log.debug("Loaded world is %s" % repr(level))

        if hasattr(level, 'materials'):
            level.materials.addJSONBlocksFromVersion(level.gamePlatform, level.gameVersionNumber)

        if addToRecent:
            self.mcedit.addRecentWorld(filename)

        if len(level.players) >= 50 and config.settings.downloadPlayerSkins.get():
            result = ask("MCEdit has detected that there are a large amount of players in this world, would you like to still download skins (This can take a decent amount of time)",
                         responses=["Download Skins", "Don't Download"])
            if result == "Don't Download":
                config.settings.downloadPlayerSkins.set(False)
                self.revertPlayerSkins = True

        try:
            self.currentViewport.cameraPosition = level.getPlayerPosition()

            y, p = level.getPlayerOrientation()
            if p == -90.0:
                p += 0.000000001
            if p == 90.0:
                p -= 0.000000001
            self.mainViewport.yaw, self.mainViewport.pitch = y, p

            pdim = level.getPlayerDimension()
            if pdim and pdim in level.dimensions:
                level = level.dimensions[pdim]

        except (KeyError, pymclevel.PlayerNotFound):  # TagNotFound
            # player tag not found, maybe
            try:
                self.currentViewport.cameraPosition = level.playerSpawnPosition()
            except KeyError:  # TagNotFound
                self.currentViewport.cameraPosition = numpy.array((0, level.Height * 0.75, 0))
                self.mainViewport.yaw = -45.
                self.mainViewport.pitch = 0.0

        self.removeNetherPanel()

        gamePlatform = level.gamePlatform
        gameVersionNumber = level.gameVersionNumber
        if gamePlatform == 'Schematic':
            log.info('Loading \'Schematic\' file.')
        elif gamePlatform == 'PE':
            log.info('Loading \'Bedrock\' world.')
            buildResources(gamePlatform, getLang())
        else:
            log.info('Loading world for version {}.'.format({True: "prior to 1.9 (detection says 'Unknown')", False: gameVersionNumber}[gameVersionNumber == 'Unknown']))
            buildResources(gameVersionNumber, getLang())

        self.loadLevel(level)

        self.renderer.position = self.currentViewport.cameraPosition
        self.renderer.loadNearbyChunks()

    def loadLevel(self, level, saveChanges=False):
        """
        Called to load a level, world, or dimension into the editor and display it in the viewport.
        """
        self.level = level
        if hasattr(level, 'acquireSessionLock'):
            level.acquireSessionLock()

        self.toolbar.removeToolPanels()
        self.selectedChunks = set()

        self.mainViewport.stopMoving()

        self.renderer.level = level
        self.addWorker(self.renderer)

        self.recordUndo = True

        if not saveChanges:
            self.undoStack = []
            self.afterSaveUndoStack = []
            self.redoStack = []
            self.clearUnsavedEdits()

        self.initWindowCaption()
        self.selectionTool.selectNone()

        for t in self.toolbar.tools:
            t.levelChanged()
        if "select" not in str(self.currentTool):
            self.toolbar.selectTool(0)

        if isinstance(self.level, pymclevel.MCInfdevOldLevel):
            if self.level.parentWorld:
                dimensions = self.level.parentWorld.dimensions
            else:
                dimensions = self.level.dimensions

            dimensionsMenu = [("Overworld", "0")]
            dimensionsMenu += [
                (pymclevel.MCAlphaDimension.dimensionNames.get(dimNo, "Dimension {0}".format(dimNo)), str(dimNo)) for
                dimNo in dimensions]
            for dim, name in pymclevel.MCAlphaDimension.dimensionNames.iteritems():
                if dim not in dimensions:
                    dimensionsMenu.append((name, str(dim)))

            def presentMenu():
                try:
                    dimNo = int([d[1] for d in dimensionsMenu if d[0] == self.netherButton.selectedChoice][0])
                except:
                    return
                self.gotoDimension(dimNo)
                self.mainViewport.skyList = None
                self.mainViewport.drawSkyBackground()

            dimensionsList = [d[0] for d in dimensionsMenu]
            self.netherButton = ChoiceButton(dimensionsList, choose=presentMenu)
            self.netherButton.selectedChoice = [d[0] for d in dimensionsMenu if d[1] == str(self.level.dimNo)][0]
            self.remove(self.topRow)
            # TODO: Marker
            self.topRow = Row((self.mcEditButton, Label("View Distance:"),
                               self.viewDistanceReadout, self.viewButton,
                               self.viewportButton, self.recordUndoButton, self.netherButton,
                               Row((self.sessionLockLabel, self.sessionLockLock), spacing=2), self.waypointsButton))
            self.add(self.topRow, 0)

        else:
            self.remove(self.topRow)
            self.topRow = Row((
                self.mcEditButton, Label("View Distance:"), self.viewDistanceReadout,
                self.viewButton, self.viewportButton, self.recordUndoButton, self.waypointsButton))
            self.add(self.topRow, 0)
            self.level.sessionLockLock = self.sessionLockLock
        #!# Adding waypoints handling for all world types
        # Need to take care of the dimension.
        # If the camera last position was saved, changing dimension is broken; the view is sticked to the overworld.
        #!#
        if self.prev_dimension == self.new_dimension:
            self.waypointManager = WaypointManager(os.path.dirname(self.level.filename), self)
            self.waypointManager.load()


        if len(list(self.level.allChunks)) == 0:
            resp = ask(
                "It looks like this level is completely empty!\nYou'll have to create some chunks before you can get started.",
                responses=["Create Chunks", "Cancel"])
            if resp == "Create Chunks":
                x, y, z = self.mainViewport.cameraPosition
                box = pymclevel.BoundingBox((x - 128, 0, z - 128), (256, self.level.Height, 256))
                self.selectionTool.setSelection(box)
                self.toolbar.selectTool(8)
                self.toolbar.tools[8].createChunks()
                self.mainViewport.cameraPosition = (x, self.level.Height, z)

    def removeNetherPanel(self):
        if self.netherPanel:
            self.remove(self.netherPanel)
            self.netherPanel = None

    @mceutils.alertException
    def gotoEarth(self):
        assert self.level.parentWorld
        self.removeNetherPanel()
        self.loadLevel(self.level.parentWorld, True)

        x, y, z = self.mainViewport.cameraPosition
        self.mainViewport.cameraPosition = [x * 8, y, z * 8]

    @mceutils.alertException
    def gotoNether(self):
        self.removeNetherPanel()
        x, y, z = self.mainViewport.cameraPosition
        self.mainViewport.cameraPosition = [x / 8, y, z / 8]
        self.loadLevel(self.level.getDimension(-1), True)

    def gotoDimension(self, dimNo):
        if dimNo == self.level.dimNo:
            return
        else:
            # Record the new dimension
            self.new_dimension = dimNo
        if dimNo == -1 and self.level.dimNo == 0:
            self.gotoNether()
        elif dimNo == 0 and self.level.dimNo == -1:
            self.gotoEarth()
        else:
            self.removeNetherPanel()
            if dimNo:
                if dimNo == 1:
                    self.mainViewport.cameraPosition = (0, 96, 0)
                self.loadLevel(self.level.getDimension(dimNo), True)

            else:
                self.loadLevel(self.level.parentWorld, True)

    netherPanel = None

    def initWindowCaption(self):
        filename = self.level.filename
        last_dir, f_name = os.path.split(filename)
        last_dir = os.path.basename(last_dir)
        title = u"{f_name} - Unified ~ {ver}".format(f_name=os.path.join(last_dir, f_name), ver=release.get_version() % _("for"))
        title = title.encode('utf-8')
        display.set_caption(title)

    @mceutils.alertException
    def reload(self):
        filename = self.level.filename
        for p in self.toolbar.tools[6].nonSavedPlayers:
            if os.path.exists(p):
                os.remove(p)
        self.loadFile(filename)

        self.mainViewport.skyList = None
        self.mainViewport.drawSkyBackground()

    @mceutils.alertException
    def saveFile(self):
        with mceutils.setWindowCaption("SAVING - "):
            if isinstance(self.level, pymclevel.ChunkedLevelMixin):  # xxx relight indev levels?
                level = self.level
                if level.parentWorld:
                    level = level.parentWorld

                if hasattr(level, 'checkSessionLock'):
                    try:
                        level.checkSessionLock()
                    except SessionLockLost as e:
                        alert(_(e.message) + _("\n\nYour changes cannot be saved."))
                        return

                if hasattr(level, 'dimensions'):
                    for level in itertools.chain(level.dimensions.itervalues(), [level]):

                        if "Canceled" == showProgress("Lighting chunks", level.generateLightsIter(), cancel=True):
                            return

                        if self.level == level:
                            if isinstance(level, pymclevel.MCInfdevOldLevel):
                                needsRefresh = [c.chunkPosition for c in level._loadedChunkData.itervalues() if c.dirty]
                                needsRefresh.extend(level.unsavedWorkFolder.listChunks())
                            elif isinstance(level, pymclevel.PocketLeveldbWorld):
                                needsRefresh = [c.chunkPosition for c in level._loadedChunks.itervalues() if c.dirty]
                            else:
                                needsRefresh = [c for c in level.allChunks if level.getChunk(*c).dirty]
                            #xxx change MCInfdevOldLevel to monitor changes since last call
                            self.invalidateChunks(needsRefresh)
                else:
                    if "Canceled" == showProgress("Lighting chunks", level.generateLightsIter(), cancel=True):
                        return

                    if self.level == level:
                        if isinstance(level, pymclevel.MCInfdevOldLevel):
                            needsRefresh = [c.chunkPosition for c in level._loadedChunkData.itervalues() if c.dirty]
                            needsRefresh.extend(level.unsavedWorkFolder.listChunks())
                        elif isinstance(level, pymclevel.PocketLeveldbWorld):
                            needsRefresh = [c.chunkPosition for c in level._loadedChunks.itervalues() if c.dirty]
                        else:
                            needsRefresh = [c for c in level.allChunks if level.getChunk(*c).dirty]
                        #xxx change MCInfdevOldLevel to monitor changes since last call
                        self.invalidateChunks(needsRefresh)

            self.freezeStatus("Saving...")
            chunks = self.level.chunkCount
            count = [0]

            def copyChunks():
                for _ in self.level.saveInPlaceGen():
                    count[0] += 1
                    yield count[0], chunks

            if "Canceled" == showProgress("Copying chunks", copyChunks(), cancel=True):
                return

        self.recordUndo = True
        self.clearUnsavedEdits(True)
        self.toolbar.tools[6].nonSavedPlayers = []

    @mceutils.alertException
    def saveAs(self):

        shortName = os.path.split(os.path.split(self.level.filename)[0])[1]
        if isinstance(self.level, pymclevel.leveldbpocket.PocketLeveldbWorld):
            filename = mcplatform.askSaveFile(directories.minecraftSaveFileDir, _("Name the new copy."),
                                              self.level.LevelName + " - Copy", _('Minecraft World\0*.*\0MCWorld File\0*.mcworld\0\0'), "")
        else:
            filename = mcplatform.askSaveFile(directories.minecraftSaveFileDir, _("Name the new copy."),
                                              shortName + " - Copy",
                                              _('Minecraft World\0*.*\0\0'), "")

        if filename is None:
            return

        if filename.endswith(".mcworld"):
            result = mcworld_support.save_world(self.level.worldFile.path, filename)
            self.Notify("Successfully saved: \"{}\"".format(result))
            return

        #if filename.endswith()
        if isinstance(self.level, pymclevel.leveldbpocket.PocketLeveldbWorld):
            shutil.copytree(self.level.worldFile.path, filename)
            self.level.worldFile.path = filename
            self.level.filename = os.path.join(filename, 'level.dat')
        else:
            shutil.copytree(self.level.worldFolder.filename, filename)
            self.level.worldFolder = AnvilWorldFolder(filename)
            self.level.filename = os.path.join(self.level.worldFolder.filename, "level.dat")
        if hasattr(self.level, "acquireSessionLock"):
            self.level.acquireSessionLock()

        self.saveFile()
        self.initWindowCaption()

    def addUnsavedEdit(self):
        if self.unsavedEdits:
            self.remove(self.saveInfoBackground)

        self.unsavedEdits += 1

        self.saveInfoBackground = GLBackground()
        self.saveInfoBackground.bg_color = (0.0, 0.0, 0.0, 0.6)

        self.saveInfoLabel = Label(self.saveInfoLabelText)
        self.saveInfoLabel.anchor = "blwh"

        self.saveInfoBackground.add(self.saveInfoLabel)
        self.saveInfoBackground.shrink_wrap()

        self.saveInfoBackground.left = 50
        self.saveInfoBackground.bottom = self.toolbar.toolbarRectInWindowCoords()[1]

        self.add(self.saveInfoBackground)
        self.saveInfoBackground = self.saveInfoBackground

    def removeUnsavedEdit(self):
        self.unsavedEdits -= 1
        self.remove(self.saveInfoBackground)
        if self.unsavedEdits:
            self.saveInfoBackground = GLBackground()
            self.saveInfoBackground.bg_color = (0.0, 0.0, 0.0, 0.6)

            self.saveInfoLabel = Label(self.saveInfoLabelText)
            self.saveInfoLabel.anchor = "blwh"

            self.saveInfoBackground.add(self.saveInfoLabel)
            self.saveInfoBackground.shrink_wrap()

            self.saveInfoBackground.left = 50
            self.saveInfoBackground.bottom = self.toolbar.toolbarRectInWindowCoords()[1]

            self.add(self.saveInfoBackground)
            self.saveInfoBackground = self.saveInfoBackground

    def clearUnsavedEdits(self, saving=False):
        if self.unsavedEdits:
            self.unsavedEdits = 0
            self.remove(self.saveInfoBackground)
            if saving:
                for operation in self.undoStack:
                    self.afterSaveUndoStack.append(operation)
                self.undoStack = []

    @property
    def saveInfoLabelText(self):
        if self.unsavedEdits == 0:
            return ""
        return _("{0} unsaved edits.  {1} to save.  {2}").format(self.unsavedEdits,
                                                                 config.keys.save.get(),
                                                                 "" if self.recordUndo else "(UNDO DISABLED)")

    @property
    def viewDistanceLabelText(self):
        return _("View Distance ({0})").format(self.renderer.viewDistance)

    def createRenderers(self):
        self.renderer = MCRenderer()

        self.workers = deque()

        if self.level:
            self.renderer.level = self.level
            self.addWorker(self.renderer)

        self.renderer.viewDistance = int(config.settings.viewDistance.get())

    def addWorker(self, chunkWorker):
        if chunkWorker not in self.workers:
            self.workers.appendleft(chunkWorker)

    def removeWorker(self, chunkWorker):
        if chunkWorker in self.workers:
            self.workers.remove(chunkWorker)

    def getFrameDuration(self):
        frameDuration = timedelta(0, 1, 0) / self.renderer.targetFPS
        return frameDuration

    lastRendererDraw = datetime.now()

    def idleevent(self):
        if any(self.cameraInputs) or any(self.cameraPanKeys):
            self.postMouseMoved()

        if (self.renderer.needsImmediateRedraw or
                (self.renderer.needsRedraw and datetime.now() - self.lastRendererDraw > timedelta(0, 1, 0) / 3)):
            self.invalidate()
            self.lastRendererDraw = datetime.now()
        if self.renderer.needsImmediateRedraw:
            self.invalidate()

        if not self.root.bonus_draw_time:
            frameDuration = self.getFrameDuration()

            while frameDuration > (datetime.now() - self.frameStartTime):
                self.doWorkUnit()
            return

        self.doWorkUnit()

    def activeevent(self, evt):
        self.mainViewport.activeevent(evt)

        if evt.state & 0x4:  # minimized
            if evt.gain == 0:
                logging.debug("Offscreen")
                self.onscreen = False

                self.mouseLookOff()

            else:
                logging.debug("Onscreen")
                self.onscreen = True
                self.invalidate()

        if evt.state & 0x1:  # mouse enter/leave
            if evt.gain == 0:
                logging.debug("Mouse left")
                self.mouseEntered = False
                self.mouseLookOff()

            else:
                logging.debug("Mouse entered")
                self.mouseEntered = True

    def swapDebugLevels(self):
        self.debug += 1
        if self.debug > self.maxDebug:
            self.debug = 0

        if self.debug:
            self.showDebugPanel()
        else:
            self.hideDebugPanel()

    def showDebugPanel(self):
        dp = GLBackground()
        debugLabel = ValueDisplay(width=1100, ref=AttrRef(self, "debugString"))
        inspectLabel = ValueDisplay(width=1100, ref=AttrRef(self, "inspectionString"))
        dp.add(Column((debugLabel, inspectLabel)))
        dp.shrink_wrap()
        dp.bg_color = (0, 0, 0, 0.6)
        self.add(dp)
        dp.top = 40
        self.debugPanel = dp

    def hideDebugPanel(self):
        self.remove(self.debugPanel)

    @property
    def statusText(self):
        try:
            return self.currentTool.statusText
        except Exception as e:
            return repr(e)

    def toolMouseDown(self, evt, f):  # xxx f is a tuple
        if self.level and f is not None:
            focusPoint, direction = f
            if focusPoint is not None and direction is not None:
                self.currentTool.mouseDown(evt, focusPoint, direction)

    def toolMouseUp(self, evt, f):  # xxx f is a tuple
        if self.level and f is not None:
            focusPoint, direction = f
            if focusPoint is not None and direction is not None:
                self.currentTool.mouseUp(evt, focusPoint, direction)

    def mouse_up(self, evt):
        button = keys.remapMouseButton(evt.button)
        evt.dict['keyname'] = "mouse{}".format(button)
        self.key_up(evt)

    def mouse_drag(self, evt):
        if self.level:
            f = self.blockFaceUnderCursor
            if f is not None:
                focusPoint, direction = f
                self.currentTool.mouseDrag(evt, focusPoint, direction)

    def mouse_down(self, evt):
        button = keys.remapMouseButton(evt.button)

        evt.dict['keyname'] = "mouse{}".format(button)
        self.mcedit.focus_switch = self
        self.turn_off_focus()
        self.key_down(evt)

    def mouseLookOff(self):
        self.mouseWasCaptured = False
        self.mainViewport.mouseLookOff()

    def mouseLookOn(self):
        self.mainViewport.mouseLookOn()

    def turn_off_focus(self):
        self.focus_switch = None

    @property
    def blockFaceUnderCursor(self):
        return self.currentViewport.blockFaceUnderCursor

    def generateStars(self):
        starDistance = 999.0
        starCount = 2000

        r = starDistance

        randPoints = (numpy.random.random(size=starCount * 3)) * 2.0 * r
        randPoints.shape = (starCount, 3)

        nearbyPoints = (randPoints[:, 0] < r) & (randPoints[:, 1] < r) & (randPoints[:, 2] < r)
        randPoints[nearbyPoints] += r

        randPoints[:starCount / 2, 0] = -randPoints[:starCount / 2, 0]
        randPoints[::2, 1] = -randPoints[::2, 1]
        randPoints[::4, 2] = -randPoints[::4, 2]
        randPoints[1::4, 2] = -randPoints[1::4, 2]

        randsizes = numpy.random.random(size=starCount) * 6 + 0.8

        vertsPerStar = 4

        vertexBuffer = numpy.zeros((starCount, vertsPerStar, 3), dtype='float32')

        def normvector(x):
            return x / numpy.sqrt(numpy.sum(x * x, 1))[:, numpy.newaxis]

        viewVector = normvector(randPoints)

        rmod = numpy.random.random(size=starCount * 3) * 2.0 - 1.0
        rmod.shape = (starCount, 3)
        referenceVector = viewVector + rmod

        rightVector = normvector(numpy.cross(referenceVector, viewVector)) * randsizes[:,
                                                                             numpy.newaxis]  # vector perpendicular to viewing line
        upVector = normvector(numpy.cross(rightVector, viewVector)) * randsizes[:,
                                                                      numpy.newaxis]  # vector perpendicular previous vector and viewing line

        p = randPoints
        p1 = p + (- upVector - rightVector)
        p2 = p + (upVector - rightVector)
        p3 = p + (upVector + rightVector)
        p4 = p + (- upVector + rightVector)

        vertexBuffer[:, 0, :] = p1
        vertexBuffer[:, 1, :] = p2
        vertexBuffer[:, 2, :] = p3
        vertexBuffer[:, 3, :] = p4

        self.starVertices = vertexBuffer.ravel()

    starColor = None

    def drawStars(self):
        pos = self.mainViewport.cameraPosition
        self.mainViewport.cameraPosition = [x / 128.0 for x in pos]
        self.mainViewport.setModelview()

        GL.glColor(.5, .5, .5, 1.)

        GL.glVertexPointer(3, GL.GL_FLOAT, 0, self.starVertices)
        GL.glDrawArrays(GL.GL_QUADS, 0, len(self.starVertices) / 3)

        self.mainViewport.cameraPosition = pos
        self.mainViewport.setModelview()

    fractionalReachAdjustment = True

    @staticmethod
    def postMouseMoved():
        evt = event.Event(MOUSEMOTION, rel=(0, 0), pos=mouse.get_pos(), buttons=mouse.get_pressed())
        event.post(evt)

    def resetReach(self):
        self.postMouseMoved()
        if self.currentTool.resetToolReach():
            return
        self.cameraToolDistance = self.defaultCameraToolDistance

    def increaseReach(self):
        self.postMouseMoved()
        if self.currentTool.increaseToolReach():
            return
        self.cameraToolDistance = self._incrementReach(self.cameraToolDistance)

    def decreaseReach(self):
        self.postMouseMoved()
        if self.currentTool.decreaseToolReach():
            return
        self.cameraToolDistance = self._decrementReach(self.cameraToolDistance)

    def _incrementReach(self, reach):
        reach += 1
        if reach > 30 and self.fractionalReachAdjustment:
            reach *= 1.05
        return reach

    def _decrementReach(self, reach):
        reach -= 1
        if reach > 30 and self.fractionalReachAdjustment:
            reach *= 0.95
        return reach

    def key_up(self, evt):
        self.currentTool.keyUp(evt)
        keyname = evt.dict.get('keyname', None) or self.root.getKey(evt)
        try:
            keyname = self.different_keys[keyname]
        except:
            pass

        if keyname == config.keys.brake.get():
            self.mainViewport.brakeOff()

        if keyname == config.keys.fastNudge.get():
            self.rightClickNudge = False

        if keyname == 'F7':
            self.testBoardKey = 0

        if keyname == config.keys.fastNudge.get():
            self.rightClickNudge = False

    def key_down(self, evt):
        if not pygame.key.get_focused():
            return

        keyname = evt.dict.get('keyname', None) or self.root.getKey(evt)
        try:
            keyname = self.different_keys[keyname]
        except:
            pass

        #!# D.C.-G.
        #!# Here we have the part which is responsible for the fallback to the
        #!# select tool when pressing 'Escape' key.
        #!# It may be interesting to work on this to be able to return to a tool
        #!# which have called another.
        if keyname == 'Escape':
            if self.selectionTool.selectionInProgress:
                self.selectionTool.cancel()
            elif "fill" in str(self.currentTool) and self.toolbar.tools[3].replacing:
                self.toolbar.tools[3].replacing = False
                self.toolbar.tools[3].showPanel()
            elif "select" not in str(self.currentTool):
                self.toolbar.selectTool(0)
            else:
                self.mouseLookOff()
                self.showControls()

        self.currentTool.keyDown(evt)

        if keyname == "Alt-F4":
            self.quit()
            return

        if keyname == config.keys.longDistanceMode.get():
            self.longDistanceMode = not self.longDistanceMode
        if keyname == "Alt-1" or keyname == "Alt-2" or keyname == "Alt-3" or keyname == "Alt-4" or keyname == "Alt-5":
            name = "option" + keyname[len(keyname) - 1:]
            if hasattr(self.currentTool, name):
                getattr(self.currentTool, name)()

        if keyname == config.keys.fastNudge.get():
            self.rightClickNudge = True

        if "clone" in str(self.currentTool):
            blocksOnlyModifier = config.keys.blocksOnlyModifier.get()
            if keyname.startswith(blocksOnlyModifier):
                tempKeyname = keyname[len(blocksOnlyModifier) + 1:]
                blocksOnly = True
            else:
                tempKeyname = keyname
                blocksOnly = False

            if tempKeyname == config.keys.flip.get():
                self.currentTool.flip(blocksOnly=blocksOnly)
            if tempKeyname == config.keys.rollClone.get():
                self.currentTool.roll(blocksOnly=blocksOnly)
            if tempKeyname == config.keys.rotateClone.get():
                self.currentTool.rotate(blocksOnly=blocksOnly)
            if tempKeyname == config.keys.mirror.get():
                self.currentTool.mirror(blocksOnly=blocksOnly)

        if "Brush" in str(self.currentTool):
            if keyname == config.keys.decreaseBrush.get():
                self.currentTool.decreaseBrushSize()
            if keyname == config.keys.increaseBrush.get():
                self.currentTool.increaseBrushSize()

            blocksOnlyModifier = config.keys.blocksOnlyModifier.get()
            if keyname.startswith(blocksOnlyModifier):
                tempKeyname = keyname[len(blocksOnlyModifier) + 1:]
                blocksOnly = True
            else:
                tempKeyname = keyname
                blocksOnly = False

            if tempKeyname == config.keys.rotateBrush.get():
                self.currentTool.rotate(blocksOnly=blocksOnly)
            if tempKeyname == config.keys.rollBrush.get():
                self.currentTool.roll(blocksOnly=blocksOnly)

        if "fill" in str(self.currentTool) and keyname == config.keys.replaceShortcut.get():
            self.currentTool.openReplace()

        if keyname == config.keys.quit.get():
            self.quit()
            return
        if keyname == config.keys.viewDistance.get():
            self.swapViewDistance()
        if keyname == config.keys.selectAll.get():
            self.selectAll()
        if keyname == config.keys.deselect.get():
            self.deselect()
        if keyname == config.keys.cut.get():
            self.cutSelection()
        if keyname == config.keys.copy.get():
            self.copySelection()
        if keyname == config.keys.paste.get():
            self.pasteSelection()

        if keyname == config.keys.reloadWorld.get():
            self.reload()

        if keyname == config.keys.open.get():
            self.askOpenFile()
        if keyname == config.keys.quickLoad.get():
            self.askLoadWorld()
        if keyname == config.keys.undo.get():
            self.undo()
        if keyname == config.keys.redo.get():
            self.redo()
        if keyname == config.keys.save.get():
            self.saveFile()
        if keyname == config.keys.newWorld.get():
            self.createNewLevel()
        if keyname == config.keys.closeWorld.get():
            self.closeEditor()
        if keyname == config.keys.worldInfo.get():
            self.showWorldInfo()
        if keyname == config.keys.gotoPanel.get():
            self.showWaypointsDialog()
        if keyname == config.keys.saveAs.get():
            self.saveAs()

        if keyname == config.keys.exportSelection.get():
            self.selectionTool.exportSelection()

        if keyname == 'Ctrl-Alt-F9':
            try:
                expr = input_text(">>> ", 600)
                expr = compile(expr, 'eval', 'single')
                alert("Result: {0!r}".format(eval(expr, globals(), locals())))
            except Exception as e:
                alert("Exception: {0!r}".format(e))

        if keyname == 'Ctrl-Alt-F10':
            alert("MCEdit, a Minecraft World Editor\n\nCopyright 2010 David Rio Vierra")

        if keyname == config.keys.toggleView.get():
            self.swapViewports()

        if keyname == config.keys.brake.get():
            self.mainViewport.brakeOn()

        if keyname == config.keys.resetReach.get():
            self.resetReach()
        if keyname == config.keys.increaseReach.get():
            self.increaseReach()
        if keyname == config.keys.decreaseReach.get():
            self.decreaseReach()
        if keyname == config.keys.swap.get():
            self.currentTool.swap()

        if keyname == config.keys.confirmConstruction.get():
            self.confirmConstruction()

        if keyname == config.keys.debugOverlay.get():
            self.swapDebugLevels()

        if keyname == config.keys.toggleRenderer.get():
            self.renderer.render = not self.renderer.render

        if keyname == config.keys.deleteBlocks.get():
            self.deleteSelectedBlocks()

        if keyname == config.keys.flyMode.get():
            config.settings.flyMode.set(not config.settings.flyMode.get())
            config.save()

        for i, keyName in enumerate(self.toolbarKeys):
            if keyname == keyName:
                self.toolbar.selectTool(i)

        if keyname in ('F1', 'F2', 'F3', 'F4', 'F5'):
            self.mcedit.loadRecentWorldNumber(int(keyname[1]))

        if keyname == 'F7':
            self.testBoardKey = 1

        if self.selectionSize():
            filter_keys = [i for (i, j) in config.config.items("Filter Keys") if j == keyname]
            if filter_keys:
                if not self.toolbar.tools[4].filterModules:
                    self.toolbar.tools[4].reloadFilters()
                filters = [i for i in self.toolbar.tools[4].filterModules if i.lower() == filter_keys[0]]
                if filters:
                    if self.currentTool != 4:
                        self.toolbar.selectTool(4)
                    if self.toolbar.tools[4].panel.selectedName != filters[0]:
                        self.toolbar.tools[4].panel.selectedName = filters[0]
                        self.toolbar.tools[4].panel.reload()

        self.root.fix_sticky_ctrl()

    # TODO: Close marker
    def closeEditor(self):
        if self.unsavedEdits:
            answer = ask("Save unsaved edits before closing?", ["Cancel", "Don't Save", "Save"], default=-1, cancel=0)
            self.root.fix_sticky_ctrl()
            if answer == "Save":
                self.saveFile()
            if answer == "Cancel":
                return

        for p in self.toolbar.tools[6].nonSavedPlayers:
            if os.path.exists(p):
                os.remove(p)
        if config.settings.savePositionOnClose.get():
            self.waypointManager.saveLastPosition(self.mainViewport, self.level.dimNo)
        self.waypointManager.save()
        self.clearUnsavedEdits()
        self.unsavedEdits = 0
        self.root.RemoveEditFiles()
        self.root.fix_sticky_ctrl()
        self.selectionTool.endSelection()
        self.mainViewport.mouseLookOff()
        if self.level:
            self.level.close()
            self.level = None
        self.renderer.stopWork()
        self.removeWorker(self.renderer)
        self.renderer.level = None
        self.mcedit.removeEditor()
        self.controlPanel.dismiss()
        display.set_caption(("MCEdit ~ " + release.get_version()%_("for")).encode('utf-8'))
        if self.revertPlayerSkins:
            config.settings.downloadPlayerSkins.set(True)
            self.revertPlayerSkins = False

    # TODO: Load marker
    def loadWorldFromFTP(self):
        widget = Widget()

        ftp_ip_lbl = Label("FTP Server IP:")
        ftp_ip_field = TextFieldWrapped(width=400)
        ip_row = Row((ftp_ip_lbl, ftp_ip_field))

        ftp_user_lbl = Label("FTP Username:")
        ftp_user_field = TextFieldWrapped(width=400)
        user_row = Row((ftp_user_lbl, ftp_user_field))

        ftp_pass_lbl = Label("FTP Password:")
        ftp_pass_field = TextFieldWrapped(width=400)
        pass_row = Row((ftp_pass_lbl, ftp_pass_field))

        note_creds = Label("NOTE: MCEdit-Unified will not use any FTP server info other than to login to the server")
        note_wait = Label("Please wait while MCEdit-Unified downloads the world. It will be opened once completed")
        col = Column((ip_row, user_row, pass_row, note_creds, note_wait))
        widget.add(col)
        widget.shrink_wrap()
        d = Dialog(widget, ["Connect", "Cancel"])
        if d.present() == "Connect":
            if ftp_user_field.get_text() == "" and ftp_pass_field.get_text() == "":
                self._ftp_client = ftp_client.FTPClient(ftp_ip_field.get_text())
            else:
                try:
                    self._ftp_client = ftp_client.FTPClient(ftp_ip_field.get_text(), username=ftp_user_field.get_text(),
                                                            password=ftp_pass_field.get_text())
                except ftp_client.InvalidCreditdentialsException as e:
                    alert(e.message)
                    return
            self._ftp_client.safe_download()
            self.mcedit.loadFile(os.path.join(self._ftp_client.get_level_path(), 'level.dat'), addToRecent=False)
            self.world_from_ftp = True

    def uploadChanges(self):
        if self.world_from_ftp and ftp_supported:
            if self.unsavedEdits:
                answer = ask("Save unsaved edits before closing?", ["Cancel", "Don't Save", "Save"], default=-1,
                             cancel=0)
                self.root.fix_sticky_ctrl()
                if answer == "Save":
                    self.saveFile()
                if answer == "Cancel":
                    return
            self._ftp_client.upload()
            self.clearUnsavedEdits()
            self.unsavedEdits = 0
            self.root.RemoveEditFiles()
            self.root.fix_sticky_ctrl()
            self.selectionTool.endSelection()
            self.mainViewport.mouseLookOff()
            if self.level:
                self.level.close()
                self.level = None
            self.renderer.stopWork()
            self.removeWorker(self.renderer)
            self.renderer.level = None
            self.mcedit.removeEditor()
            self.controlPanel.dismiss()
            display.set_caption(("MCEdit ~ " + release.get_version()%_("for")).encode('utf-8'))

            self._ftp_client.cleanup()
        elif not self.world_from_ftp:
            alert("This world was not downloaded from a FTP server. Uploading worlds that were not downloaded from a FTP server is currently not possible")
        elif not ftp_supported:
            alert('Uploading changes to an FTP server is disabled due to the \'ftputil\' module not being installed')


    def repairRegions(self):
        worldFolder = self.level.worldFolder
        for filename in worldFolder.findRegionFiles():
            rf = worldFolder.tryLoadRegionFile(filename)
            if rf:
                rf.repair()

        alert("Repairs complete.  See the console window for details.")

    @mceutils.alertException
    def showWorldInfo(self):
        worldInfoPanel = Dialog()
        items = []

        #t = functools.partial(isinstance, self.level)
        if isinstance(self.level, pymclevel.MCInfdevOldLevel):
            if self.level.version == pymclevel.MCInfdevOldLevel.VERSION_ANVIL:
                levelFormat = "Minecraft Infinite World (Anvil Format)"
            elif self.level.version == pymclevel.MCInfdevOldLevel.VERSION_MCR:
                levelFormat = "Minecraft Infinite World (Region Format)"
            else:
                levelFormat = "Minecraft Infinite World (Old Chunk Format)"
        elif isinstance(self.level, pymclevel.MCIndevLevel):
            levelFormat = "Minecraft Indev (.mclevel format)"
        elif isinstance(self.level, pymclevel.MCSchematic):
            levelFormat = "MCEdit Schematic"
        elif isinstance(self.level, pymclevel.ZipSchematic):
            levelFormat = "MCEdit Schematic (Zipped Format)"
        elif isinstance(self.level, pymclevel.MCJavaLevel):
            levelFormat = "Minecraft Classic or raw block array"
        elif isinstance(self.level, pymclevel.PocketLeveldbWorld):
            levelFormat = "Minecraft Pocket Edition"
        else:
            levelFormat = "Unknown"
        formatLabel = Label(levelFormat)
        items.append(Row([Label("Format:"), formatLabel]))

        nameField = TextFieldWrapped(width=300, value=self.level.LevelName)

        def alt21():
            nameField.insertion_point = len(nameField.text)
            nameField.insert_char(u'\xa7')

        alt21button = Button(u"\xa7", action=alt21)
        label = Label("Name:")
        items.append(Row((label, nameField, alt21button)))

        if hasattr(self.level, 'Time'):
            time = self.level.Time
            # timezone adjust -
            # minecraft time shows 0:00 on day 0 at the first sunrise
            # I want that to be 6:00 on day 1, so I add 30 hours

            time_editor = TimeEditor(current_tick_time=time)
            items.append(time_editor)

        if hasattr(self.level, 'RandomSeed'):
            seedField = IntField(width=250, value=self.level.RandomSeed)
            seedLabel = Label("RandomSeed: ")
            items.append(Row((seedLabel, seedField)))

        if hasattr(self.level, 'GameType'):
            t = self.level.GameType
            types = ["Survival", "Creative"]

            def gametype(t):
                if t < len(types):
                    return types[t]
                return "Unknown"

            def action():
                if b.gametype < 2:
                    b.gametype = 1 - b.gametype
                    b.text = gametype(b.gametype)

            b = Button(gametype(t), action=action)
            b.gametype = t

            gametypeRow = Row((Label("Game Type:"), b))
            items.append(gametypeRow)

            button = Button("Repair regions", action=self.repairRegions)
            items.append(button)

        def openFolder():
            filename = self.level.filename
            if not isdir(filename):
                filename = dirname(filename)
            mcplatform.platform_open(filename)

        revealButton = Button("Open Folder", action=openFolder)
        items.append(revealButton)

        if isinstance(self.level, pymclevel.MCInfdevOldLevel):
            chunkCount = self.level.chunkCount
            chunkCountLabel = Label(_("Number of chunks: {0}").format(chunkCount))

            items.append(chunkCountLabel)

        if hasattr(self.level, 'worldFolder') and hasattr(self.level.worldFolder, 'regionFiles'):
            worldFolder = self.level.worldFolder
            regionCount = len(worldFolder.regionFiles)
            regionCountLabel = Label(_("Number of regions: {0}").format(regionCount))
            items.append(regionCountLabel)

        size = self.level.size
        sizelabel = Label("{L}L x {W}W x {H}H".format(L=size[2], H=size[1], W=size[0]))
        items.append(sizelabel)

        if hasattr(self.level, "Entities"):
            label = Label(_("{0} Entities").format(len(self.level.Entities)))
            items.append(label)
        if hasattr(self.level, "TileEntities"):
            label = Label(_("{0} TileEntities").format(len(self.level.TileEntities)))
            items.append(label)

        col = Column(items)

        self.change = False

        def dismiss(*args, **kwargs):
            self.change = True
            worldInfoPanel.dismiss(self, *args, **kwargs)

        def cancel(*args, **kwargs):
            Changes = False
            if hasattr(self.level, 'Time'):
                time = time_editor.get_time_value()
                if self.level.Time != time:
                    Changes = True
            if hasattr(self.level, 'DayTime'):
                day_time = time_editor.get_daytime_value()
                if self.level.DayTime != day_time:
                    Changes = True
            if hasattr(self.level, 'RandomSeed') and seedField.value != self.level.RandomSeed:
                Changes = True
            if hasattr(self.level, 'LevelName') and nameField.value != self.level.LevelName:
                Changes = True
            if hasattr(self.level, 'GameType') and b.gametype != self.level.GameType:
                Changes = True

            if not Changes:
                worldInfoPanel.dismiss(self, *args, **kwargs)
                return

            result = ask("Do you want to keep your changes?", ["Yes", "No", "Cancel"])
            if result == "Cancel":
                return
            if result == "No":
                worldInfoPanel.dismiss(self, *args, **kwargs)
                return
            if result == "Yes":
                dismiss(*args, **kwargs)

        col = Column((col, Row((Button("OK", action=dismiss), Button("Cancel", action=cancel)))))

        worldInfoPanel.add(col)
        worldInfoPanel.shrink_wrap()

        def dispatchKey(name, evt):
            dispatch_key_saved(name, evt)
            if name == "key_down":
                keyname = self.get_root().getKey(evt)
                if keyname == 'Escape':
                    cancel()

        dispatch_key_saved = worldInfoPanel.dispatch_key
        worldInfoPanel.dispatch_key = dispatchKey

        worldInfoPanel.present()
        if not self.change:
            return

        class WorldInfoChangedOperation(Operation):
            def __init__(self, editor, level):
                self.editor = editor
                self.level = level
                self.canUndo = True

                self.changeLevelName = changeLevelName
                self.changeTime = changeTime
                self.changeDayTime = changeDayTime
                self.changeSeed = changeSeed
                self.changeGameType = changeGameType

            def perform(self, recordUndo=True):
                if recordUndo:
                    if changeLevelName:
                        self.UndoText = self.level.LevelName
                        self.RedoText = nameField.value

                    if changeTime:
                        self.UndoTime = self.level.Time
                        self.RedoTime = time

                    if changeDayTime:
                        self.UndoDayTime = self.level.DayTime
                        self.RedoDayTime = day_time

                    if changeSeed:
                        self.UndoSeed = self.level.RandomSeed
                        self.RedoSeed = seedField.value

                    if changeGameType:
                        self.UndoGameType = self.level.GameType
                        self.RedoGameType = b.gametype

                if changeLevelName:
                    self.level.LevelName = nameField.value
                if changeTime:
                    self.level.Time = time
                if changeDayTime:
                    self.level.DayTime = day_time
                if changeSeed:
                    self.level.RandomSeed = seedField.value
                if changeGameType:
                    self.level.GameType = b.gametype

            def undo(self):
                if self.changeLevelName:
                    self.level.LevelName = self.UndoText
                if self.changeTime:
                    self.level.Time = self.UndoTime
                if self.changeDayTime:
                    self.level.DayTime = self.UndoDayTime
                if self.changeSeed:
                    self.level.RandomSeed = self.UndoSeed
                if self.changeGameType:
                    self.level.GameType = self.UndoGameType

            def redo(self):
                if self.changeLevelName:
                    self.level.LevelName = self.RedoText
                if self.changeTime:
                    self.level.Time = self.RedoTime
                if self.changeDayTime:
                    self.level.DayTime = self.RedoDayTime
                if self.changeSeed:
                    self.level.RandomSeed = self.RedoSeed
                if self.changeGameType:
                    self.level.GameType = self.RedoGameType

        changeTime = False
        changeSeed = False
        changeLevelName = False
        changeGameType = False
        changeDayTime = False

        if hasattr(self.level, 'Time'):
            time = time_editor.get_time_value()
            if self.level.Time != time:
                changeTime = True

        if hasattr(self.level, 'DayTime'):
            day_time = time_editor.get_daytime_value()
            if self.level.DayTime != day_time:
                changeDayTime = True

        if hasattr(self.level, 'RandomSeed') and seedField.value != self.level.RandomSeed:
            changeSeed = True

        if hasattr(self.level, 'LevelName') and nameField.value != self.level.LevelName:
            changeLevelName = True

        if hasattr(self.level, 'GameType') and b.gametype != self.level.GameType:
            changeGameType = True

        if changeTime or changeSeed or changeLevelName or changeGameType:
            op = WorldInfoChangedOperation(self, self.level)
            self.addOperation(op)
            self.addUnsavedEdit()

    def swapViewDistance(self):
        if self.renderer.viewDistance >= config.settings.maxViewDistance.get():
            self.renderer.viewDistance = self.renderer.minViewDistance
        else:
            self.renderer.viewDistance += 2

        self.addWorker(self.renderer)
        config.settings.viewDistance.set(self.renderer.viewDistance)

    def changeViewDistance(self, dist):
        self.renderer.viewDistance = min(config.settings.maxViewDistance.get(), dist)
        self.addWorker(self.renderer)
        config.settings.viewDistance.set(self.renderer.viewDistance)

    @mceutils.alertException
    def askLoadWorld(self):
        if not os.path.isdir(directories.minecraftSaveFileDir):
            alert(_(u"Could not find the Minecraft saves directory!\n\n({0} was not found or is not a directory)").format(
                directories.minecraftSaveFileDir))
            return

        worldPanel = Widget()

        potentialWorlds = os.listdir(directories.minecraftSaveFileDir)
        potentialWorlds = [os.path.join(directories.minecraftSaveFileDir, p) for p in potentialWorlds]
        worldFiles = [p for p in potentialWorlds if pymclevel.MCInfdevOldLevel.isLevel(p)]
        worlds = []
        for f in worldFiles:
            try:
                lev = pymclevel.MCInfdevOldLevel(f, readonly=True)
            except Exception:
                continue
            else:
                worlds.append(lev)
        if len(worlds) == 0:
            alert("No worlds found! You should probably play Minecraft to create your first world.")
            return

        def loadWorld():
            self.mcedit.loadFile(self.worldData[worldTable.selectedWorldIndex][3].filename)
            self.root.fix_sticky_ctrl()

        def click_row(i, evt):
            worldTable.selectedWorldIndex = i
            if evt.num_clicks == 2:
                loadWorld()
                dialog.dismiss("Cancel")

        def dispatch_key(name, evt):
            if name != "key_down":
                return
            keyname = self.root.getKey(evt)
            if keyname == "Escape":
                dialog.dismiss("Cancel")
            elif keyname == "Up":
                worldTable.selectedWorldIndex = max(0, worldTable.selectedWorldIndex - 1)
                worldTable.rows.scroll_to_item(worldTable.selectedWorldIndex)
            elif keyname == "Down":
                worldTable.selectedWorldIndex = min(len(worlds) - 1, worldTable.selectedWorldIndex + 1)
                worldTable.rows.scroll_to_item(worldTable.selectedWorldIndex)
            elif keyname == 'Page up':
                worldTable.selectedWorldIndex = max(0, worldTable.selectedWorldIndex - worldTable.rows.num_rows())
                worldTable.rows.scroll_to_item(worldTable.selectedWorldIndex)
            elif keyname == 'Page down':
                worldTable.selectedWorldIndex = min(len(worlds) - 1, worldTable.selectedWorldIndex + worldTable.rows.num_rows())
                worldTable.rows.scroll_to_item(worldTable.selectedWorldIndex)
            elif keyname == "Return":
                loadWorld()
                dialog.dismiss("Cancel")
            else:
                old_dispatch_key(name, evt)
                text = fld.text.lower()
                worldsToUse = []
                splitText = text.split(" ")
                amount = len(splitText)
                for v in allWorlds:
                    nameParts = [a.lower() for a in nameFormat(v)]
                    i = 0
                    spiltTextUsed = []
                    for v2 in nameParts:
                        Start = True
                        j = 0
                        while j < len(splitText) and Start:
                            if splitText[j] in v2 and j not in spiltTextUsed:
                                i += 1
                                spiltTextUsed.append(j)
                                Start = False
                            j += 1
                    if i == amount:
                        worldsToUse.append(v)

                self.worldData = [[dateFormat(d), nameFormat(w), w, d]
                                  for w, d in ((w, dateobj(w.LastPlayed)) for w in worldsToUse)]
                self.worldData.sort(key=lambda (a, b, w, d): d, reverse=True)
                worldTable.selectedWorldIndex = 0
                worldTable.num_rows = lambda: len(self.worldData)
                worldTable.row_data = lambda i: self.worldData[i]
                worldTable.rows.scroll_to_item(0)


        def key_up(evt):
            pass

        allWorlds = worlds
        lbl = Label("Search")
        fld = TextFieldWrapped(300)
        old_dispatch_key = fld.dispatch_key
        fld.dispatch_key = dispatch_key
        row = Row((lbl, fld))

        worldTable = TableView(columns=[
            TableColumn("Last Played", 200, "l"),
            TableColumn("Level Name", 300, "l"),
            TableColumn("File Name", 300, "l")
        ])

        def dateobj(lp):
            try:
                return datetime.utcfromtimestamp(lp / 1000.0)
            except:
                return datetime.utcfromtimestamp(0.0)

        def dateFormat(lp):
            try:
                return lp.strftime("%x %X").decode('utf-8')
            except:
                return u"{0} seconds since the epoch.".format(lp)

        def nameFormat(w):
            try:
                if w.LevelName == w.displayName.decode("utf-8"):
                    return w.LevelName, w.LevelName
                return u"%s" % w.LevelName, u"%s" % w.displayName.decode("utf-8")
            except:
                try:
                    return w.LevelName, w.LevelName
                except:
                    try:
                        return w.displayName, w.displayName
                    except:
                        return "[UNABLE TO READ]", "[? ? ?]"

        self.worldData = [[dateFormat(d)] + list(nameFormat(w)) + [w, d]
                          for w, d in ((w, dateobj(w.LastPlayed)) for w in worlds)]
        self.worldData.sort(key=lambda (a, b, c, w, d): d, reverse=True)

        worldTable.selectedWorldIndex = 0
        worldTable.num_rows = lambda: len(self.worldData)
        worldTable.row_data = lambda i: self.worldData[i]
        worldTable.row_is_selected = lambda x: x == worldTable.selectedWorldIndex
        worldTable.click_row = click_row

        worldTable.top = row.bottom
        worldPanel.add(row)
        worldPanel.add(worldTable)
        worldPanel.shrink_wrap()

        if ftp_supported:
            options = ["Load", "From FTP Server", "Cancel"]
        else:
            options = ["Load", "Cancel"]
        dialog = Dialog(worldPanel, options)
        dialog.key_up = key_up
        result = dialog.present()
        if result == "Load":
            loadWorld()
        if result == "From FTP Server":
            self.loadWorldFromFTP()

    def askOpenFile(self):
        self.mouseLookOff()
        try:
            filename = mcplatform.askOpenFile(schematics=True)
            if filename:
                self.parent.loadFile(filename)
        except Exception:
            logging.exception('Error while asking user for filename')
            return

    def createNewLevel(self):
        self.mouseLookOff()

        newWorldPanel = Widget()
        newWorldPanel.w = newWorldPanel.h = 16
        newWorldPanel.x = newWorldPanel.z = newWorldPanel.f = 0
        newWorldPanel.y = 64
        newWorldPanel.seed = 0

        label = Label("Creating a new world.")
        generatorPanel = GeneratorPanel()

        xinput = IntInputRow("X: ", ref=AttrRef(newWorldPanel, "x"))
        yinput = IntInputRow("Y: ", ref=AttrRef(newWorldPanel, "y"))
        zinput = IntInputRow("Z: ", ref=AttrRef(newWorldPanel, "z"))
        finput = IntInputRow("f: ", ref=AttrRef(newWorldPanel, "f"), min=0, max=3)
        xyzrow = Row([xinput, yinput, zinput, finput])
        seedinput = IntInputRow("Seed: ", width=250, ref=AttrRef(newWorldPanel, "seed"))

        winput = IntInputRow("East-West Chunks: ", ref=AttrRef(newWorldPanel, "w"), min=0)
        hinput = IntInputRow("North-South Chunks: ", ref=AttrRef(newWorldPanel, "h"), min=0)

        gametypes = ["Survival", "Creative"]
        worldtypes = {"Default": ("DEFAULT", "default"), "Superflat": ("FLAT", "flat"),
                      "Large Biomes": ("LARGEBIOMES", "largeBiomes"), "Amplified": ("AMPLIFIED", "amplified")}

        def gametype(t):
            if t < len(gametypes):
                return gametypes[t]
            return "Unknown"

        def gametypeAction():
            if gametypeButton.gametype < 2:
                gametypeButton.gametype = 1 - gametypeButton.gametype
                gametypeButton.text = gametype(gametypeButton.gametype)


        gametypeButton = Button(gametype(0), action=gametypeAction)
        gametypeButton.gametype = 0
        gametypeRow = Row((Label("Game Type:"), gametypeButton))

        worldtypeButton = ChoiceButton(worldtypes.keys())
        worldtypeRow = Row((Label("World Type:"), worldtypeButton))

        newWorldPanel.add(
            Column((label, Row([winput, hinput]), xyzrow, seedinput, gametypeRow, worldtypeRow, generatorPanel),
                   align="l"))
        newWorldPanel.shrink_wrap()

        result = Dialog(client=newWorldPanel, responses=["Create", "Cancel"]).present()
        if result == "Cancel":
            return
        filename = mcplatform.askCreateWorld(directories.minecraftSaveFileDir)

        if not filename:
            return

        w = newWorldPanel.w
        h = newWorldPanel.h
        x = newWorldPanel.x
        y = newWorldPanel.y
        z = newWorldPanel.z
        f = newWorldPanel.f
        seed = newWorldPanel.seed or None
        generationtype = worldtypes[worldtypeButton.get_value()][0]

        self.freezeStatus("Creating world...")
        try:
            newlevel = pymclevel.MCInfdevOldLevel(filename=filename, create=True, random_seed=seed)
            # chunks = list(itertools.product(xrange(w / 2 - w + cx, w / 2 + cx), xrange(h / 2 - h + cz, h / 2 + cz)))

            if generatorPanel.generatorChoice.selectedChoice == "Flatland":
                y = generatorPanel.chunkHeight

            newlevel.setPlayerPosition((x + 0.5, y + 2.8, z + 0.5))
            newlevel.setPlayerOrientation((f * 90.0, 0.0))

            newlevel.setPlayerSpawnPosition((x, y + 1, z))
            newlevel.GameType = gametypeButton.gametype
            newlevel.GeneratorName = worldtypes[worldtypeButton.get_value()][1]
            newlevel.saveInPlace()
            worker = generatorPanel.generate(newlevel, pymclevel.BoundingBox((x - w * 8, 0, z - h * 8),
                                                                             (w * 16, newlevel.Height, h * 16)),
                                             useWorldType=generationtype)

            if "Canceled" == showProgress("Generating chunks...", worker, cancel=True):
                generatorPanel.kill_process()
                raise RuntimeError("Canceled.")

            if y < 64:
                y = 64
                newlevel.setBlockAt(x, y, z, pymclevel.alphaMaterials.Sponge.ID)
            if newlevel.parentWorld:
                newlevel = newlevel.parentWorld
            newlevel.acquireSessionLock()
            newlevel.saveInPlace()

            self.loadFile(filename)
        except Exception:
            logging.exception(
                'Error while creating world. {world => %s}' % filename
            )
            return

        return newlevel

    def confirmConstruction(self):
        self.currentTool.confirm()

    def selectionToChunks(self, remove=False, add=False):
        box = self.selectionBox()
        if box:
            if box == self.level.bounds:
                self.selectedChunks = set(self.level.allChunks)
                return

            selectedChunks = self.selectedChunks
            boxedChunks = set(box.chunkPositions)
            if boxedChunks.issubset(selectedChunks):
                remove = True

            if remove and not add:
                selectedChunks.difference_update(boxedChunks)
            else:
                selectedChunks.update(boxedChunks)

        self.selectionTool.selectNone()

    def chunksToSelection(self):
        if len(self.selectedChunks) == 0:
            return
        starting_chunk = self.selectedChunks.pop()
        box = self.selectionTool.selectionBoxForCorners((starting_chunk[0] << 4, 0, starting_chunk[1] << 4), ((starting_chunk[0] << 4) + 15, 256, (starting_chunk[1] << 4) + 15))
        for c in self.selectedChunks:
            box = box.union(self.selectionTool.selectionBoxForCorners((c[0] << 4, 0, c[1] << 4), ((c[0] << 4) + 15, 256, (c[1] << 4) + 15)))
        self.selectedChunks = set([])
        self.selectionTool.selectNone()
        self.selectionTool.setSelection(box)

    def selectAll(self):

        if self.currentViewport is self.chunkViewport:
            self.selectedChunks = set(self.level.allChunks)
        else:
            self.selectionTool.selectAll()

    def deselect(self):
        self.selectionTool.deselect()
        self.selectedChunks.clear()

    def endSelection(self):
        self.selectionTool.endSelection()

    def cutSelection(self):
        self.selectionTool.cutSelection()

    def copySelection(self):
        self.selectionTool.copySelection()

    def pasteSelection(self):
        schematic = self.getLastCopiedSchematic()
        self.pasteSchematic(schematic)

    def pasteSchematic(self, schematic):
        if schematic is None:
            return
        self.currentTool.cancel()
        self.currentTool = self.toolbar.tools[5]
        self.currentTool.loadLevel(schematic)

    def deleteSelectedBlocks(self):
        self.selectionTool.deleteBlocks()

    @mceutils.alertException
    def undo(self):
        if len(self.undoStack) == 0 and len(self.afterSaveUndoStack) == 0:
            return
        with mceutils.setWindowCaption("UNDOING - "):
            self.freezeStatus("Undoing the previous operation...")
            wasSelectionBox = False
            if self.selectionBox():
                wasSelectionBox = True
            if len(self.undoStack) > 0:
                op = self.undoStack.pop()
                normalUndo = True
            else:
                op = self.afterSaveUndoStack.pop()
                normalUndo = False

            if self.recordUndo:
                self.redoStack.append(op)
                if len(self.redoStack) > self.undoLimit:
                    self.redoStack.pop(0)
            op.undo()
            changedBox = op.dirtyBox()
            if changedBox is not None:
                self.invalidateBox(changedBox)
            if not self.selectionBox() and wasSelectionBox:
                self.toolbar.selectTool(0)
                self.toolbar.tools[0].currentCorner = 1
            if ".SelectionOperation" not in str(op) and ".NudgeSelectionOperation" not in str(op):
                if normalUndo:
                    self.removeUnsavedEdit()
                else:
                    self.addUnsavedEdit()

        self.root.fix_sticky_ctrl()

    def redo(self):
        if len(self.redoStack) == 0:
            return
        with mceutils.setWindowCaption("REDOING - "):
            self.freezeStatus("Redoing the previous operation...")
            op = self.redoStack.pop()

            if self.recordUndo:
                self.undoStack.append(op)
                if len(self.undoStack) > self.undoLimit:
                    self.undoStack.pop(0)
            op.redo()
            changedBox = op.dirtyBox()
            if changedBox is not None:
                self.invalidateBox(changedBox)
            if op.changedLevel:
                self.addUnsavedEdit()

        self.root.fix_sticky_ctrl()

    def invalidateBox(self, box):
        self.renderer.invalidateChunksInBox(box)

    def invalidateChunks(self, c):
        self.renderer.invalidateChunks(c)

    def invalidateAllChunks(self):
        self.renderer.invalidateAllChunks()

    def discardAllChunks(self):
        self.renderer.discardAllChunks()

    def addDebugString(self, string):
        if self.debug:
            self.debugString += string

    averageFPS = 0.0
    averageCPS = 0.0
    shouldLoadAndRender = True


    def gl_draw(self):
        self.debugString = ""
        self.inspectionString = ""

        if not self.level:
            return

        if not self.shouldLoadAndRender:
            return

        self.renderer.loadVisibleChunks()
        self.addWorker(self.renderer)

        if self.currentTool.previewRenderer:
            self.currentTool.previewRenderer.loadVisibleChunks()
            self.addWorker(self.currentTool.previewRenderer)

        self.frames += 1
        frameDuration = self.getFrameDuration()
        while frameDuration > (
                    datetime.now() - self.frameStartTime):  # if it's less than 0ms until the next frame, go draw.  otherwise, go work.
            self.doWorkUnit()

        frameStartTime = datetime.now()
        timeDelta = frameStartTime - self.frameStartTime

        # self.addDebugString("FrameStart: {0}  CameraTick: {1}".format(frameStartTime, self.mainViewport.lastTick))
        # self.addDebugString("D: %d, " % () )

        self.currentFrameDelta = timeDelta
        self.frameSamples.pop(0)
        self.frameSamples.append(timeDelta)

        frameTotal = numpy.sum(self.frameSamples)

        self.averageFPS = 1000000. / (
            (frameTotal.microseconds + 1000000 * frameTotal.seconds) / float(len(self.frameSamples)) + 0.00001)

        r = self.renderer

        chunkTotal = numpy.sum(r.chunkSamples)
        cps = 1000000. / (
            (chunkTotal.microseconds + 1000000 * chunkTotal.seconds) / float(len(r.chunkSamples)) + 0.00001)
        self.averageCPS = cps

        self.oldFrameStartTime = self.frameStartTime
        self.frameStartTime = frameStartTime

        if self.debug > 0:
            self.debugString = _("FPS: %0.1f/%0.1f, CPS: %0.1f, VD: %d, W: %d, WF: %d, MBv: %0.1f, ") % (
                1000000. / (float(timeDelta.microseconds) + 0.000001),
                self.averageFPS,
                cps,
                self.renderer.viewDistance,
                len(self.workers),
                self.renderer.workFactor,
                self.renderer.bufferUsage / 1000000.)

            self.debugString += _("DL: {dl} ({dlcount}), Tx: {t}, gc: {g}, ").format(
                dl=len(glutils.DisplayList.allLists), dlcount=glutils.gl.listCount,
                t=len(glutils.Texture.allTextures), g=len(gc.garbage))

            if self.renderer:
                self.renderer.addDebugInfo(self.addDebugString)

    def doWorkUnit(self, onMenu=False):
        if len(self.workers):
            try:
                w = self.workers.popleft()
                w.next()
                self.workers.append(w)
            except StopIteration:
                if hasattr(w, "needsRedraw") and w.needsRedraw:
                    self.invalidate()

        if onMenu:
            time.sleep(0.001)

    def updateInspectionString(self, blockPosition):
        self.inspectionString += str(blockPosition) + ": "
        x, y, z = blockPosition
        cx, cz = x // 16, z // 16

        try:
            if self.debug:

                if isinstance(self.level, pymclevel.MCIndevLevel):
                    bl = self.level.blockLightAt(*blockPosition)
                    blockID = self.level.blockAt(*blockPosition)
                    bdata = self.level.blockDataAt(*blockPosition)
                    self.inspectionString += _("ID: %d:%d (%s), ") % (
                        blockID, bdata, self.level.materials.names[blockID][bdata])
                    self.inspectionString += _("Data: %d, Light: %d, ") % (bdata, bl)

                elif isinstance(self.level, pymclevel.ChunkedLevelMixin):
                    sl = self.level.skylightAt(*blockPosition)
                    bl = self.level.blockLightAt(*blockPosition)
                    bdata = self.level.blockDataAt(*blockPosition)
                    blockID = self.level.blockAt(*blockPosition)
                    self.inspectionString += _("ID: %d:%d (%s), ") % (
                        blockID, bdata, self.level.materials.names[blockID][bdata])

                    try:
                        path = self.level.getChunk(cx, cz).filename
                    except:
                        path = "chunks.dat"

                    self.inspectionString += _("Data: %d, L: %d, SL: %d") % (
                        bdata, bl, sl)

                    try:
                        hm = self.level.heightMapAt(x, z)
                        self.inspectionString += _(", H: %d") % hm
                    except:
                        pass
                    try:
                        tp = self.level.getChunk(cx, cz).TerrainPopulated
                        self.inspectionString += _(", TP: %d") % tp
                    except:
                        pass

                    self.inspectionString += _(", D: %d") % self.level.getChunk(cx, cz).dirty
                    self.inspectionString += _(", NL: %d") % self.level.getChunk(cx, cz).needsLighting
                    try:
                        biome = self.level.getChunk(cx, cz).Biomes[x & 15, z & 15]
                        from pymclevel import biome_types

                        self.inspectionString += _(", Bio: %s") % biome_types.biome_types[biome]
                    except AttributeError:
                        pass

                    if isinstance(self.level, pymclevel.pocket.PocketWorld):
                        ch = self.level.getChunk(cx, cz)
                        self.inspectionString += _(", DC: %s") % ch.DirtyColumns[z & 15, x & 15]

                    self.inspectionString += _(", Ch(%d, %d): %s") % (cx, cz, path)

                else:  # classic
                    blockID = self.level.blockAt(*blockPosition)
                    self.inspectionString += _("ID: %d (%s), ") % (
                        blockID, self.level.materials.names[blockID][0])

        except Exception as e:
            self.inspectionString += _("Chunk {0} had an error: {1!r}").format(
                (int(numpy.floor(blockPosition[0])) >> 4, int(numpy.floor(blockPosition[2])) >> 4), e)
            import traceback
            traceback.print_exc()

    def drawWireCubeReticle(self, color=(1.0, 1.0, 1.0, 1.0), position=None):
        GL.glPolygonOffset(DepthOffset.TerrainWire, DepthOffset.TerrainWire)
        GL.glEnable(GL.GL_POLYGON_OFFSET_FILL)

        blockPosition, faceDirection = self.blockFaceUnderCursor
        blockPosition = position or blockPosition

        mceutils.drawTerrainCuttingWire(pymclevel.BoundingBox(blockPosition, (1, 1, 1)), c1=color)

        GL.glDisable(GL.GL_POLYGON_OFFSET_FILL)

    @staticmethod
    def drawString(x, y, color, string):
        return

    @staticmethod
    def freezeStatus(string):
        return

    def selectionSize(self):
        return self.selectionTool.selectionSize()

    def selectionBox(self):
        return self.selectionTool.selectionBox()

    def selectionChanged(self):
        if not self.currentTool.toolEnabled():
            self.toolbar.selectTool(0)

        self.currentTool.selectionChanged()

    def addOperation(self, op):
        self.performWithRetry(op)

        if self.recordUndo and op.canUndo:
            self.undoStack.append(op)
            if len(self.undoStack) > self.undoLimit:
                self.undoStack.pop(0)

    recordUndo = True

    def performWithRetry(self, op):
        try:
            op.perform(self.recordUndo)
        except MemoryError:
            self.invalidateAllChunks()
            op.perform(self.recordUndo)

    def quit(self):
        if config.settings.savePositionOnClose.get():
            self.waypointManager.saveLastPosition(self.mainViewport, self.level.getPlayerDimension())
        self.waypointManager.save()
        self.mouseLookOff()
        self.mcedit.confirm_quit()

    mouseWasCaptured = False

    def showControls(self):
        #.#
        self.controlPanel.top = self.subwidgets[0].bottom
        self.controlPanel.left = (self.width - self.controlPanel.width) / 2
        #.#
        self.controlPanel.present(False)

    infoPanel = None

    def showChunkRendererInfo(self):
        if self.infoPanel:
            self.infoPanel.set_parent(None)
            return

        self.infoPanel = infoPanel = Widget(bg_color=(0, 0, 0, 80))
        infoPanel.add(Label(""))

        def idleHandler(evt):
            x, y, z = self.blockFaceUnderCursor[0]
            cx, cz = x // 16, z // 16
            cr = self.renderer.chunkRenderers.get((cx, cz))
            if cr is None:
                return

            crNames = [_("%s - %0.1fkb") % (type(br).__name__, br.bufferSize() / 1000.0) for br in cr.blockRenderers]
            infoLabel = Label("\n".join(crNames))

            infoPanel.remove(infoPanel.subwidgets[0])
            infoPanel.add(infoLabel)
            infoPanel.shrink_wrap()
            self.invalidate()

        infoPanel.idleevent = idleHandler

        infoPanel.topleft = self.viewportContainer.topleft
        self.add(infoPanel)
        infoPanel.click_outside_response = -1

    def handleMemoryError(self):
        if self.renderer.viewDistance <= 2:
            raise MemoryError("Out of memory. Please restart MCEdit.")
        if hasattr(self.level, 'compressAllChunks'):
            self.level.compressAllChunks()
        self.toolbar.selectTool(0)

        self.renderer.viewDistance -= 4
        self.renderer.discardAllChunks()

        logging.warning(
            'Out of memory, decreasing view distance. {view => %s}' % (
                self.renderer.viewDistance
            )
        )

        config.settings.viewDistance.set(self.renderer.viewDistance)
        config.save()

    def lockLost(self):
        #image_path = directories.getDataDir(os.path.join("toolicons", "session_bad.png"))
        image_path = directories.getDataFile('toolicons', 'session_bad.png')
        self.sessionLockLock.set_image(get_image(image_path, prefix=""))
        self.sessionLockLock.tooltipText = "Session Lock is being used by Minecraft"
        self.sessionLockLabel.tooltipText = "Session Lock is being used by Minecraft"
        if not self.waypointManager.already_saved and config.settings.savePositionOnClose.get():
            self.waypointManager.saveLastPosition(self.mainViewport, self.level.getPlayerDimension())

    def lockAcquired(self):
        #image_path = directories.getDataDir(os.path.join("toolicons", "session_good.png"))
        image_path = directories.getDataFile('toolicons', 'session_good.png')
        self.sessionLockLock.set_image(get_image(image_path, prefix=""))
        self.sessionLockLock.tooltipText = "Session Lock is being used by MCEdit"
        self.sessionLockLabel.tooltipText = "Session Lock is being used by MCEdit"
        self.root.sessionStolen = False
        self.waypointManager.already_saved = False

class EditorToolbar(GLOrtho):
    toolbarSize = (204, 24)
    tooltipsUp = True

    toolbarTextureSize = (202., 22.)

    currentToolTextureRect = (0., 22., 24., 24.)
    toolbarWidthRatio = 0.5  # toolbar's width as fraction of screen width.

    def toolbarSizeForScreenWidth(self, width):
        f = max(1, int(width + 418) / 420)

        return [x * f for x in self.toolbarSize]

    def __init__(self, rect, tools, *args, **kw):
        GLOrtho.__init__(self, xmin=0, ymin=0,
                         xmax=self.toolbarSize[0], ymax=self.toolbarSize[1],
                         near=-4.0, far=4.0)
        self.size = self.toolbarTextureSize
        self.tools = tools
        for i, t in enumerate(tools):
            t.toolNumber = i
            t.hotkey = i + 1

        self.toolTextures = {}
        self.toolbarDisplayList = glutils.DisplayList()
        self.reloadTextures()

    def set_parent(self, parent, index=None):
        GLOrtho.set_parent(self, parent, index)
        self.parent_resized(0, 0)

    def parent_resized(self, dw, dh):
        self.size = self.toolbarSizeForScreenWidth(self.parent.width)
        self.centerx = self.parent.centerx
        self.bottom = self.parent.viewportContainer.bottom
        # xxx resize children when get

    def getTooltipText(self):
        toolNumber = self.toolNumberUnderMouse(mouse.get_pos())
        return self.tools[toolNumber].tooltipText

    tooltipText = property(getTooltipText)

    def toolNumberUnderMouse(self, pos):
        x, y = self.global_to_local(pos)

        (tx, ty, tw, th) = self.toolbarRectInWindowCoords()

        toolNumber = float(len(self.tools)) * x / tw
        return min(int(toolNumber), len(self.tools) - 1)

    def set_update_ui(self, v):
        for tool in self.tools:
            if tool.optionsPanel:
                tool.optionsPanel.set_update_ui(v)
        GLOrtho.set_update_ui(self, v)

    def mouse_down(self, evt):
        if self.parent.level:
            toolNo = self.toolNumberUnderMouse(evt.pos)
            if toolNo < 0 or toolNo > len(self.tools) - 1:
                return
            if evt.button == 1:
                self.selectTool(toolNo)
            if evt.button == 3:
                self.showToolOptions(toolNo)

    def showToolOptions(self, toolNumber):
        if len(self.tools) > toolNumber >= 0:
            t = self.tools[toolNumber]
            # if not t.toolEnabled():
            #    return
            if t.optionsPanel:
                t.optionsPanel.present()

    def selectTool(self, toolNumber):
        ''' pass a number outside the bounds to pick the selection tool'''
        if toolNumber >= len(self.tools) or toolNumber < 0:
            toolNumber = 0

        t = self.tools[toolNumber]
        if not t.toolEnabled():
            return
        if self.parent.currentTool == t:
            self.parent.currentTool.toolReselected()
            return
        else:
            self.parent.selectionTool.hidePanel()
            if self.parent.currentTool is not None:
                self.parent.currentTool.cancel()
                self.parent.currentTool.toolDeselected()
            self.parent.currentTool = t
            self.parent.currentTool.toolSelected()
            return

    def removeToolPanels(self):
        for tool in self.tools:
            tool.hidePanel()

    def toolbarRectInWindowCoords(self):
        """returns a rectangle (x, y, w, h) representing the toolbar's
        location in the window.  use for hit testing."""

        (pw, ph) = self.parent.size
        pw = float(pw)
        ph = float(ph)
        x, y = self.toolbarSizeForScreenWidth(pw)
        tw = x * 200. / 202.
        th = y * 20. / 22.

        tx = (pw - tw) / 2
        ty = ph - th * 22. / 20.

        return tx, ty, tw, th

    def toolTextureChanged(self):
        self.toolbarDisplayList.invalidate()

    def reloadTextures(self):
        self.toolTextureChanged()
        self.guiTexture = mceutils.loadPNGTexture('gui.png')
        self.toolTextures = {}

        for tool in self.tools:
            if hasattr(tool, 'reloadTextures'):
                tool.reloadTextures()
            if hasattr(tool, 'markerList'):
                tool.markerList.invalidate()

    def drawToolbar(self):
        GL.glEnableClientState(GL.GL_TEXTURE_COORD_ARRAY)

        GL.glColor(1., 1., 1., 1.)
        w, h = self.toolbarTextureSize

        self.guiTexture.bind()

        GL.glVertexPointer(3, GL.GL_FLOAT, 0, numpy.array((
            1, h + 1, 0.5,
            w + 1, h + 1, 0.5,
            w + 1, 1, 0.5,
            1, 1, 0.5,
        ), dtype="f4"))
        GL.glTexCoordPointer(2, GL.GL_FLOAT, 0, numpy.array((
            0, 0,
            w, 0,
            w, h,
            0, h,
        ), dtype="f4"))

        GL.glDrawArrays(GL.GL_QUADS, 0, 4)

        for i in xrange(len(self.tools)):
            tool = self.tools[i]
            if tool.toolIconName is None:
                continue
            try:
                if tool.toolIconName not in self.toolTextures:
                    filename = "toolicons" + os.sep + "{0}.png".format(tool.toolIconName)
                    self.toolTextures[tool.toolIconName] = mceutils.loadPNGTexture(filename)
                x = 20 * i + 4
                y = 4
                w = 16
                h = 16

                self.toolTextures[tool.toolIconName].bind()
                GL.glVertexPointer(3, GL.GL_FLOAT, 0, numpy.array((
                    x, y + h, 1,
                    x + w, y + h, 1,
                    x + w, y, 1,
                    x, y, 1,
                ), dtype="f4"))
                GL.glTexCoordPointer(2, GL.GL_FLOAT, 0, numpy.array((
                    0, 0,
                    w * 16, 0,
                    w * 16, h * 16,
                    0, h * 16,
                ), dtype="f4"))

                GL.glDrawArrays(GL.GL_QUADS, 0, 4)
            except Exception:
                logging.exception('Error while drawing toolbar.')
        GL.glDisableClientState(GL.GL_TEXTURE_COORD_ARRAY)

    gfont = None

    def gl_draw(self):

        GL.glEnable(GL.GL_TEXTURE_2D)
        GL.glEnable(GL.GL_BLEND)
        self.toolbarDisplayList.call(self.drawToolbar)
        GL.glColor(1.0, 1.0, 0.0)

        try:
            currentToolNumber = self.tools.index(self.parent.currentTool)
        except ValueError:
            pass
        else:
            # draw a bright rectangle around the current tool
            (texx, texy, texw, texh) = self.currentToolTextureRect

            tx = 20. * float(currentToolNumber)
            ty = 0.
            tw = 24.
            th = 24.
            GL.glEnableClientState(GL.GL_TEXTURE_COORD_ARRAY)

            self.guiTexture.bind()
            GL.glVertexPointer(3, GL.GL_FLOAT, 0, numpy.array((
                tx, ty, 2,
                tx + tw, ty, 2,
                tx + tw, ty + th, 2,
                tx, ty + th, 2,
            ), dtype="f4"))

            GL.glTexCoordPointer(2, GL.GL_FLOAT, 0, numpy.array((
                texx, texy + texh,
                texx + texw, texy + texh,
                texx + texw, texy,
                texx, texy,
            ), dtype="f4"))

            GL.glDrawArrays(GL.GL_QUADS, 0, 4)

        GL.glDisableClientState(GL.GL_TEXTURE_COORD_ARRAY)
        GL.glDisable(GL.GL_TEXTURE_2D)

        redOutBoxes = numpy.zeros(9 * 4 * 2, dtype='float32')
        cursor = 0
        for i in xrange(len(self.tools)):
            t = self.tools[i]
            if t.toolEnabled():
                continue
            redOutBoxes[cursor:cursor + 8] = [
                4 + i * 20, 4,
                4 + i * 20, 20,
                20 + i * 20, 20,
                20 + i * 20, 4,
            ]
            cursor += 8

        if cursor:
            GL.glColor(1.0, 0.0, 0.0, 0.3)
            GL.glVertexPointer(2, GL.GL_FLOAT, 0, redOutBoxes)
            GL.glDrawArrays(GL.GL_QUADS, 0, cursor / 2)

        GL.glDisable(GL.GL_BLEND)


from albow.resource import get_image
