summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTreeki <treeki@gmail.com>2011-12-02 19:07:13 +0100
committerTreeki <treeki@gmail.com>2011-12-02 19:07:13 +0100
commit3bdc780bd3eaa204b1df5d5e6727a876d5967401 (patch)
tree411f4300356797effc38bb5e599e9dff1a02d1dd
parentcbadd29918da648190cd609130f808e11cf01a25 (diff)
downloadkoopatlas-3bdc780bd3eaa204b1df5d5e6727a876d5967401.tar.gz
koopatlas-3bdc780bd3eaa204b1df5d5e6727a876d5967401.zip
a really, really big commit
-rw-r--r--Sample/AnimatedDoodad.kpmap1
-rw-r--r--src/editorui/doodads.py58
-rw-r--r--src/editorui/editormain.py69
-rw-r--r--src/editorui/objects.py2
-rw-r--r--src/editorui/paths.py13
-rw-r--r--src/main.py10
-rw-r--r--src/mapdata.py255
-rw-r--r--src/mapfile.py96
-rw-r--r--src/ui.py321
9 files changed, 596 insertions, 229 deletions
diff --git a/Sample/AnimatedDoodad.kpmap b/Sample/AnimatedDoodad.kpmap
new file mode 100644
index 0000000..93d22c0
--- /dev/null
+++ b/Sample/AnimatedDoodad.kpmap
@@ -0,0 +1 @@
+{"layers": [{"paths": [], "nodes": [], "_t": "path_layer", "name": "Paths", "_visible": true}, {"_t": "doodad_layer", "name": "Doodads - Layer 1", "objects": [{"angle": 0, "_t": "doodad", "sourceRef": 0, "animations": [["Loop", "Sinusoidial", 600.0, "X Position", 0.0, 100.0], ["Reversible Loop", "Cosinoidial", 600.0, "Y Position", 0.0, 100.0]], "position": [282.0, 237.0], "size": [172.0, 184.0]}], "_visible": true}], "doodadDefinitions": [["BlackLevel", {"_t": "pixmap", "png": "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"}]], "nextLayerNumber": 2, "_t": "map_root"} \ No newline at end of file
diff --git a/src/editorui/doodads.py b/src/editorui/doodads.py
index 632551d..f57502a 100644
--- a/src/editorui/doodads.py
+++ b/src/editorui/doodads.py
@@ -159,8 +159,8 @@ class KPEditorDoodad(KPEditorItem):
itemC.setData(0.0, QtCore.Qt.EditRole)
self.model.appendRow([QtGui.QStandardItem("Contiguous"), QtGui.QStandardItem("Linear"),
- QtGui.QStandardItem(itemA), QtGui.QStandardItem("X Position"),
- QtGui.QStandardItem(itemB), QtGui.QStandardItem(itemC)])
+ itemA, QtGui.QStandardItem("X Position"),
+ itemB, itemC])
def remAnmItem(self):
@@ -223,43 +223,10 @@ class KPEditorDoodad(KPEditorItem):
rowList.append(data)
-
- Loop = rowList[0]
- Type = rowList[3]
- Curve = rowList[1]
- Frames = rowList[2]
- StartVal = rowList[4]
- EndVal = rowList[5]
-
- Timeline = QtCore.QTimeLine()
-
- # Interpolate the correct modifier
- if Curve == "Linear":
- Timeline.setCurveShape(3)
- elif Curve == "Sinusoidial":
- Timeline.setCurveShape(4)
- elif Curve == "Cosinoidial":
- Timeline.setCurveShape(5)
-
- Timeline.setFrameRange(StartVal, EndVal)
-
- if Loop == "Contiguous":
- Timeline.setLoopCount(1)
- elif Loop == "Loop":
- Timeline.setLoopCount(1000000000) # Dollars *holds pinky to corner of mouth*
- elif Loop == "Reversible Loop":
- Timeline.setLoopCount(1)
- Timeline.finished.connect(Timeline.toggleDirection)
- Timeline.finished.connect(Timeline.start)
-
- Timeline.setDuration(Frames/60.0*1000) # Wii goes at 60 frames per second
-
- rowList.append(Timeline)
- KP.mapScene.timeLines.append(Timeline)
-
anmList.append(rowList)
-
+
doodad.animations = anmList
+ doodad.setupAnimations()
self.update()
@@ -281,13 +248,11 @@ class KPEditorDoodad(KPEditorItem):
self._doodadRef = weakref.ref(doodad)
self._layerRef = weakref.ref(layer)
- # TODO: refactor this to store source doodad data under KP.map
- sourceItem = KP.mainWindow.doodadSelector.getDoodad(doodad.index)
- self._sourceRef = weakref.ref(sourceItem)
-
self.resizing = None
self.rotating = None
+ self.source = doodad.source
+
self._updatePixmap()
self._updatePosition()
self._updateSize()
@@ -297,14 +262,16 @@ class KPEditorDoodad(KPEditorItem):
self.setAcceptHoverEvents(True)
+ if len(doodad.animations) > 0:
+ doodad.setupAnimations()
+
if not hasattr(KPEditorDoodad, 'SELECTION_PEN'):
KPEditorDoodad.SELECTION_PEN = QtGui.QPen(Qt.red, 1, Qt.DotLine)
def _updatePixmap(self):
- source = self._sourceRef()
- pixmap = source.icon().pixmap(source.icon().availableSizes()[0])
+ pixmap = self.source[1]
self.prepareGeometryChange()
w, h = pixmap.width(), pixmap.height()
@@ -314,11 +281,15 @@ class KPEditorDoodad(KPEditorItem):
def _updatePosition(self):
# NOTE: EditorDoodads originate at the centre, not the top left like the others
+ self.ignoreMovement = True
+
doodad = self._doodadRef()
x,y = doodad.position
w,h = doodad.size
self.setPos(x+floor(w/2.0), y+floor(h/2.0))
+ self.ignoreMovement = False
+
def _updateSize(self):
self.prepareGeometryChange()
@@ -514,6 +485,7 @@ class KPEditorDoodad(KPEditorItem):
layer = self._layerRef()
layer.objects.remove(doodad)
+ doodad.cleanUpAnimations()
if withItem:
self.scene().removeItem(self)
diff --git a/src/editorui/editormain.py b/src/editorui/editormain.py
index 136eee1..5575453 100644
--- a/src/editorui/editormain.py
+++ b/src/editorui/editormain.py
@@ -17,7 +17,7 @@ class KPMapScene(QtGui.QGraphicsScene):
self.playing = False
self.timeLines = []
self.ticker = QtCore.QTimeLine(100000)
- self.ticker.setLoopCount(10)
+ self.ticker.setLoopCount(0)
self.ticker.setCurveShape(4)
self.ticker.setFrameRange(0,100000)
self.ticker.valueChanged.connect(self.thing)
@@ -25,6 +25,23 @@ class KPMapScene(QtGui.QGraphicsScene):
self.grid = False
+ # create an item for everything in the map
+ for layer in KP.map.layers:
+ if isinstance(layer, KPTileLayer):
+ for obj in layer.objects:
+ self.addItem(KPEditorObject(obj, layer))
+ elif isinstance(layer, KPDoodadLayer):
+ for obj in layer.objects:
+ self.addItem(KPEditorDoodad(obj, layer))
+ elif isinstance(layer, KPPathLayer):
+ for node in layer.nodes:
+ self.addItem(KPEditorNode(node))
+
+ for path in layer.paths:
+ self.addItem(KPEditorPath(path))
+
+ layer.setActivated(False)
+
def playPause(self):
if self.playing == False:
@@ -247,30 +264,30 @@ class KPMapScene(QtGui.QGraphicsScene):
# Anm Interpolations are Linear, Sinusoidial, Cosinoidial
# Anm Types are X Position, Y Position, Angle, X Scale, Y Scale, Opacity
- for anm in animations:
+ if len(animations) > 0:
+ for anm, Timeline in zip(animations, doodad.timelines):
- Type = anm[3]
- Timeline = anm[6]
-
- modifier = Timeline.currentFrame()
+ Type = anm[3]
+
+ modifier = Timeline.currentFrame()
- if Type == "X Position":
- posRect.adjust(modifier, 0, modifier, 0)
+ if Type == "X Position":
+ posRect.adjust(modifier, 0, modifier, 0)
- elif Type == "Y Position":
- posRect.adjust(0, modifier, 0, modifier)
+ elif Type == "Y Position":
+ posRect.adjust(0, modifier, 0, modifier)
- elif Type == "Angle":
- transform.rotate(modifier)
+ elif Type == "Angle":
+ transform.rotate(modifier)
- elif Type == "X Scale":
- posRect.setWidth(posRect.width()*modifier/100.0)
-
- elif Type == "Y Scale":
- posRect.setHeight(posRect.height()*modifier/100.0)
+ elif Type == "X Scale":
+ posRect.setWidth(posRect.width()*modifier/100.0)
+
+ elif Type == "Y Scale":
+ posRect.setHeight(posRect.height()*modifier/100.0)
- elif Type == "Opacity":
- painter.setOpacity(modifier/100.0)
+ elif Type == "Opacity":
+ painter.setOpacity(modifier/100.0)
painter.setWorldTransform(transform, True)
painter.drawPixmap(posRect.x(), posRect.y(), posRect.width(), posRect.height(), item.pixmap)
@@ -299,11 +316,16 @@ class KPEditorWidget(QtGui.QGraphicsView):
self.yScrollBar = QtGui.QScrollBar(Qt.Vertical, parent)
self.setVerticalScrollBar(self.yScrollBar)
+ self.assignNewScene(scene)
+
+ def assignNewScene(self, scene):
+ self.setScene(scene)
self.centerOn(0,0)
# set up stuff for painting
- self.paintNext = None
- self.paintNextID = None
+ self.objectToPaint = None
+ self.objectIDToPaint = None
+ self.doodadToPaint = None
self._resetPaintVars()
def _resetPaintVars(self):
@@ -314,11 +336,11 @@ class KPEditorWidget(QtGui.QGraphicsView):
def _tryToPaint(self, event):
'''Called when a paint attempt is initiated'''
- paint = self.paintNext
layer = self.scene().currentLayer
if not layer.visible: return
if isinstance(layer, KPTileLayer):
+ paint = self.objectToPaint
if paint is None: return
clicked = self.mapToScene(event.x(), event.y())
@@ -346,6 +368,7 @@ class KPEditorWidget(QtGui.QGraphicsView):
self.paintBeginPosition = (x, y)
elif isinstance(layer, KPDoodadLayer):
+ paint = self.doodadToPaint
if paint is None: return
clicked = self.mapToScene(event.x(), event.y())
@@ -355,7 +378,7 @@ class KPEditorWidget(QtGui.QGraphicsView):
obj = KPDoodad()
obj.position = [x,y]
- obj.index = self.paintNextID
+ obj.source = paint
obj.setDefaultSize()
layer.objects.append(obj)
diff --git a/src/editorui/objects.py b/src/editorui/objects.py
index 1ae46f7..975b365 100644
--- a/src/editorui/objects.py
+++ b/src/editorui/objects.py
@@ -26,8 +26,10 @@ class KPEditorObject(KPEditorItem):
def _updatePosition(self):
self.ignoreMovement = True
+
x,y = self._objRef().position
self.setPos(x*24, y*24)
+
self.ignoreMovement = False
def _updateSize(self):
diff --git a/src/editorui/paths.py b/src/editorui/paths.py
index c1579be..2f4388a 100644
--- a/src/editorui/paths.py
+++ b/src/editorui/paths.py
@@ -230,10 +230,14 @@ class KPEditorNode(KPEditorItem):
def _updatePosition(self):
+ self.ignoreMovement = True
+
node = self._nodeRef()
x, y = node.position
self.setPos(x+12, y+12)
+ self.ignoreMovement = False
+
def _itemMoved(self, oldX, oldY, newX, newY):
node = self._nodeRef()
@@ -440,7 +444,13 @@ class KPEditorPath(QtGui.QGraphicsLineItem):
# Connections
- self.ExclusiveButtons.buttonReleased.connect(self.updatePathAnim)
+ # regular connect doesn't work for some reason...
+ #self.ExclusiveButtons.buttonReleased.connect(self.updatePathAnim)
+ QtCore.QObject.connect(
+ self.ExclusiveButtons,
+ QtCore.SIGNAL('buttonReleased(int)'),
+ self.updatePathAnim)
+
self.moveSpeedSpinner.valueChanged.connect(self.updateMoveSpeed)
self.linkedLayer.currentIndexChanged.connect(self.updateLinkLayer)
@@ -464,6 +474,7 @@ class KPEditorPath(QtGui.QGraphicsLineItem):
path = self._pathRef()
path.animation = buttonID
+ print path.animation
path.qtItem.update()
diff --git a/src/main.py b/src/main.py
index 1ef7039..e6b72d2 100644
--- a/src/main.py
+++ b/src/main.py
@@ -2,15 +2,11 @@ from common import *
class KP:
@staticmethod
- def newMap():
- from mapdata import KPMap
- KP.map = KPMap()
-
- @staticmethod
def run():
KP.app = QtGui.QApplication(sys.argv)
- KP.newMap()
+ from mapdata import KPMap
+ KP.map = KPMap()
from ui import KPMainWindow
@@ -18,7 +14,7 @@ class KP:
KP.mainWindow.show()
KP.app.exec_()
-
+
@classmethod
def icon(cls, name):
diff --git a/src/mapdata.py b/src/mapdata.py
index de00b10..1eb35c8 100644
--- a/src/mapdata.py
+++ b/src/mapdata.py
@@ -1,11 +1,31 @@
from common import *
import weakref
+import mapfile
+import base64
TILE_SIZE = (24,24)
MAP_SIZE_IN_TILES = (512,512)
MAP_SIZE = (MAP_SIZE_IN_TILES[0] * TILE_SIZE[0], MAP_SIZE_IN_TILES[1] * TILE_SIZE[1])
+@mapfile.dumpClassAs(QtGui.QPixmap, 'pixmap')
+def dumpPixmap(pm):
+ buf = QtCore.QBuffer()
+ buf.open(buf.WriteOnly)
+ pm.save(buf, 'PNG')
+ data = str(buf.data())
+ buf.close()
+ return {'png': base64.b64encode(data)}
+
+@mapfile.loadClassFrom('pixmap')
+def loadPixmap(source):
+ pm = QtGui.QPixmap()
+ pm.loadFromData(base64.b64decode(source['png']), 'PNG')
+ return pm
+
+@mapfile.dumpable('layer')
class KPLayer(object):
+ __dump_attribs__ = ('name', '_visible')
+
def __repr__(self):
return "<KPLayer %r>" % self.name
@@ -42,7 +62,17 @@ class KPLayer(object):
item.setFlag(flag2, value)
+@mapfile.dumpable('object')
class KPObject(object):
+ __dump_attribs__ = ('position', 'size', 'tileset')
+
+ def _load(self, mapObj, src):
+ self.kind = mapObj.loadedTilesets[self.tileset].objects[src['kind']]
+ self.updateCache()
+
+ def _dump(self, mapObj, dest):
+ dest['kind'] = mapObj.loadedTilesets[self.tileset].objects.index(self.kind)
+
def __init__(self):
self.position = (0,0)
self.size = (1,1)
@@ -55,7 +85,13 @@ class KPObject(object):
self.cache = self.kind.render(self.size)
+@mapfile.dumpable('tile_layer')
class KPTileLayer(KPLayer):
+ __dump_attribs__ = KPLayer.__dump_attribs__ + ('tileset', 'objects')
+
+ def _load(self, mapObj, src):
+ self.updateCache()
+
def __repr__(self):
return "<KPTileLayer %r with %r>" % (self.name, self.tileset)
@@ -127,21 +163,84 @@ class KPTileLayer(KPLayer):
y += 1
+@mapfile.dumpable('doodad')
class KPDoodad(object):
+ __dump_attribs__ = ('position', 'size', 'angle', 'animations')
+
+ def _dump(self, mapObj, dest):
+ dest['sourceRef'] = mapObj.refDoodad(self.source)
+
+ def _load(self, mapObj, src):
+ self.source = mapObj.derefDoodad(src['sourceRef'])
+
def __init__(self):
self.position = [0,0]
self.size = [0,0]
self.angle = 0
- self.index = 0
+ self.source = None
self.animations = []
-
+ self.timelines = None
+
def setDefaultSize(self):
- source = KP.mainWindow.doodadSelector.getDoodad(self.index)
- pixmap = source.icon().pixmap(source.icon().availableSizes()[0])
+ pixmap = self.source[1]
self.size = [pixmap.width(), pixmap.height()]
+ def cleanUpAnimations(self):
+ myTimelines = self.timelines
+ if myTimelines is None: return
+
+ timelineList = KP.mapScene.timeLines
+
+ for timeline in myTimelines:
+ try:
+ timelineList.remove(timeline)
+ except ValueError:
+ pass
+
+ self.timelines = None
+
+ def setupAnimations(self):
+ self.cleanUpAnimations()
+ timelineList = KP.mapScene.timeLines
+ myTimelines = []
+
+ for anim in self.animations:
+ Loop, Curve, Frames, Type, StartVal, EndVal = anim
+
+ Timeline = QtCore.QTimeLine()
+
+ # Interpolate the correct modifier
+ if Curve == "Linear":
+ Timeline.setCurveShape(3)
+ elif Curve == "Sinusoidial":
+ Timeline.setCurveShape(4)
+ elif Curve == "Cosinoidial":
+ Timeline.setCurveShape(5)
+
+ Timeline.setFrameRange(StartVal, EndVal)
+
+ if Loop == "Contiguous":
+ Timeline.setLoopCount(1)
+ elif Loop == "Loop":
+ Timeline.setLoopCount(0) # Dollars *holds pinky to corner of mouth*
+ elif Loop == "Reversible Loop":
+ Timeline.setLoopCount(1)
+ Timeline.finished.connect(Timeline.toggleDirection)
+ Timeline.finished.connect(Timeline.start)
+
+ Timeline.setDuration(Frames/60.0*1000) # Wii goes at 60 frames per second
+
+ timelineList.append(Timeline)
+ myTimelines.append(Timeline)
+
+ self.timelines = myTimelines
+
+
+@mapfile.dumpable('doodad_layer')
class KPDoodadLayer(KPLayer):
+ __dump_attribs__ = KPLayer.__dump_attribs__ + ('objects',)
+
def __repr__(self):
return "<KPDoodadLayer %r>" % self.name
@@ -163,7 +262,20 @@ class KPNodeAction(object):
pass
+@mapfile.dumpable('node')
class KPNode(object):
+ __dump_attribs__ = (
+ 'position', 'actions', 'level', 'isStop', 'mapChange',
+ 'transition', 'mapID', 'foreignID')
+
+ def _dump(self, mapObj, dest):
+ dest['exitIDs'] = map(mapObj.refPath, self.exits)
+
+ def _load(self, mapObj, src):
+ self.exitIDs = src['exitIDs']
+ # The exits array will be created by KPPathLayer._load after the
+ # paths have all been created.
+
def __init__(self):
self.position = (0,0)
self.actions = []
@@ -176,8 +288,27 @@ class KPNode(object):
self.foreignID = None
+@mapfile.dumpable('path')
class KPPath(object):
- def __init__(self, startNode, endNode, cloneFrom=None):
+ __dump_attribs__ = ('unlocks', 'secret', 'animation', 'movementSpeed')
+
+ def _dump(self, mapObj, dest):
+ dest['startNodeLink'] = mapObj.refNode(self._startNodeRef())
+ dest['endNodeLink'] = mapObj.refNode(self._endNodeRef())
+ dest['linkedLayer'] = mapObj.refLayer(self.linkedLayer)
+
+ def _load(self, mapObj, src):
+ self._startNodeRef = weakref.ref(mapObj.derefNode(src['startNodeLink']))
+ self._endNodeRef = weakref.ref(mapObj.derefNode(src['endNodeLink']))
+ self.linkedLayer = mapObj.derefLayer(src['linkedLayer'])
+
+ def __init__(self, startNode=None, endNode=None, cloneFrom=None):
+ if startNode is None and endNode is None:
+ # null ctor, ignore this
+ # we're probably loaded from a file, so trust
+ # that everything is correct ... _load will set it all up
+ return
+
self._startNodeRef = weakref.ref(startNode)
self._endNodeRef = weakref.ref(endNode)
@@ -213,7 +344,15 @@ class KPPath(object):
self._endNodeRef = weakref.ref(newEnd)
+@mapfile.dumpable('path_layer')
class KPPathLayer(KPLayer):
+ __dump_attribs__ = KPLayer.__dump_attribs__ + ('nodes', 'paths')
+
+ def _load(self, mapObj, src):
+ for node in self.nodes:
+ node.exits = map(mapObj.derefPath, node.exitIDs)
+ del node.exitIDs
+
def __repr__(self):
return "<KPPathLayer %r>" % self.name
@@ -241,15 +380,41 @@ class KPPathLayer(KPLayer):
item.setFlag(flag, value)
+@mapfile.dumpable('map_root')
class KPMap(object):
+ __dump_attribs__ = ('layers', 'nextLayerNumber', 'doodadDefinitions')
+
+ def _preload(self, src):
+ # we need this early so we can use the deref methods!
+ for layer in self.layers:
+ if isinstance(layer, KPPathLayer):
+ self.pathLayer = layer
+
+ def _load(self, mapObj, source):
+ self.layerModel.list = self.layers
+ self.doodadModel.list = self.doodadDefinitions
+
+ def save(self):
+ path = self.filePath
+ if path is None:
+ raise "no path specified for this map"
+
+ import mapfile
+ dumped = mapfile.dump(self)
+ open(path, 'wb').write(dumped)
+
def __init__(self):
+ self.filePath = None
+
self.nextLayerNumber = 1
self.pathLayer = self._createPathLayer()
self.layers = [self.pathLayer]
self.layerModel = KPMap.LayerModel(self.layers)
- self.nodes = []
- self.paths = []
+
+ self.doodadDefinitions = []
+ self.doodadModel = KPMap.DoodadModel(self.doodadDefinitions)
+
self.tilesets = {}
self.loadedTilesets = {}
@@ -311,7 +476,6 @@ class KPMap(object):
return False
-
def _createPathLayer(self):
layer = KPPathLayer()
layer.name = 'Paths'
@@ -373,6 +537,81 @@ class KPMap(object):
self.layerModel.endRemoveRows()
+ # DOODADS
+ class DoodadModel(QtCore.QAbstractListModel):
+ def __init__(self, doodadList):
+ QtCore.QAbstractListModel.__init__(self)
+ self.list = doodadList
+
+
+ def headerData(self, section, orientation, role = Qt.DisplayRole):
+ return 'Doodad'
+
+ def rowCount(self, parent):
+ return len(self.list)
+
+ def data(self, index, role = Qt.DisplayRole):
+ try:
+ if index.isValid():
+ doodad = self.list[index.row()]
+
+ if role == Qt.DecorationRole:
+ return doodad[1]
+ elif role == Qt.ToolTipRole:
+ return doodad[0]
+
+ except IndexError:
+ pass
+
+ return QtCore.QVariant()
+
+ def flags(self, index):
+ if not index.isValid():
+ return Qt.ItemIsEnabled
+ return QtCore.QAbstractListModel.flags(self, index)
+
+ def addDoodad(self, title, image):
+ doodad = (title, image)
+
+ index = len(self.doodadDefinitions)
+ self.doodadModel.beginInsertRows(QtCore.QModelIndex(), index, index)
+ self.doodadDefinitions.append(doodad)
+ self.doodadModel.endInsertRows()
+
+ return doodad
+
+ def removeDoodad(self, doodad):
+ if doodad not in self.doodadDefinitions:
+ raise ValueError
+
+ index = self.doodadDefinitions.index(doodad)
+ self.doodadModel.beginRemoveRows(QtCore.QModelIndex(), index, index)
+ del self.doodadDefinitions[index]
+ self.doodadModel.endRemoveRows()
+
+
+ # REFERENCES
+ def refDoodad(self, doodad):
+ return -1 if (doodad is None) else self.doodadDefinitions.index(doodad)
+ def derefDoodad(self, ref):
+ return self.doodadDefinitions[ref] if (ref >= 0) else None
+
+ def refLayer(self, layer):
+ return -1 if (layer is None) else self.layers.index(layer)
+ def derefLayer(self, ref):
+ return self.layers[ref] if (ref >= 0) else None
+
+ def refPath(self, path):
+ return -1 if (path is None) else self.pathLayer.paths.index(path)
+ def derefPath(self, ref):
+ return self.pathLayer.paths[ref] if (ref >= 0) else None
+
+ def refNode(self, node):
+ return -1 if (node is None) else self.pathLayer.nodes.index(node)
+ def derefNode(self, ref):
+ return self.pathLayer.nodes[ref] if (ref >= 0) else None
+
+ # TILESETS
def loadTilesets(self):
import os
from hashlib import sha256 as sha
diff --git a/src/mapfile.py b/src/mapfile.py
new file mode 100644
index 0000000..eb05a4a
--- /dev/null
+++ b/src/mapfile.py
@@ -0,0 +1,96 @@
+from common import *
+import json
+
+DUMPABLE_CLASSES_BY_NAME = {}
+DUMPABLE_CLASS_NAMES = {}
+
+DUMPABLE_PROXIES = {}
+LOADABLE_PROXIES = {}
+
+def dumpClassAs(cls, name):
+ def __add_it(function):
+ DUMPABLE_PROXIES[cls] = (name, function)
+ return function
+ return __add_it
+
+def loadClassFrom(name):
+ def __add_it(function):
+ LOADABLE_PROXIES[name] = function
+ return function
+ return __add_it
+
+def dumpable(name):
+ def __add_it(cls):
+ DUMPABLE_CLASSES_BY_NAME[name] = cls
+ DUMPABLE_CLASS_NAMES[cls] = name
+ return cls
+ return __add_it
+
+def dump(rootObj):
+ def _dumpPiece(piece):
+ try:
+ clsObj = type(piece)
+ clsName = DUMPABLE_CLASS_NAMES[clsObj]
+ except KeyError:
+ # let's give this one more shot with the dumpable proxies
+ try:
+ dumpName, dumpFunc = DUMPABLE_PROXIES[clsObj]
+ except KeyError:
+ return piece
+
+ dest = dumpFunc(piece)
+ dest['_t'] = dumpName
+ return dest
+
+
+ dest = {'_t': clsName}
+
+ for attrName in clsObj.__dump_attribs__:
+ dest[attrName] = getattr(piece, attrName)
+
+ if hasattr(piece, '_dump'):
+ piece._dump(rootObj, dest)
+
+ return dest
+
+ return json.dumps(rootObj, default=_dumpPiece)
+
+
+
+def load(string):
+ needsSpecialCare = []
+
+ def _loadObject(source):
+ try:
+ clsName = source['_t']
+ clsObj = DUMPABLE_CLASSES_BY_NAME[clsName]
+ print "Loading %s" % clsName
+ except KeyError:
+ # let's give this one more shot with the loadable proxies
+ try:
+ loadFunc = LOADABLE_PROXIES[clsName]
+ except KeyError:
+ return source
+
+ return loadFunc(source)
+
+ obj = clsObj()
+
+ for attrName in clsObj.__dump_attribs__:
+ setattr(obj, attrName, source[attrName])
+
+ if hasattr(obj, '_preload'):
+ obj._preload(source)
+
+ if hasattr(obj, '_load'):
+ needsSpecialCare.append((obj, source))
+
+ return obj
+
+ root = json.loads(string, object_hook=_loadObject)
+
+ for obj, source in needsSpecialCare:
+ obj._load(root, source)
+
+ return root
+
diff --git a/src/ui.py b/src/ui.py
index a0bbab7..d3b9d56 100644
--- a/src/ui.py
+++ b/src/ui.py
@@ -4,6 +4,7 @@ from common import *
from editorui.editorcommon import *
from editorui.editormain import *
import os
+import os.path
class KPLayerList(QtGui.QWidget):
@@ -13,22 +14,25 @@ class KPLayerList(QtGui.QWidget):
self.layout = QtGui.QVBoxLayout()
self.layout.setSpacing(0)
- self.model = KP.map.layerModel
-
self.listView = QtGui.QListView()
- self.listView.setModel(self.model)
- self.listView.selectionModel().currentRowChanged.connect(self.handleRowChanged)
self.layout.addWidget(self.listView)
self.toolbar = QtGui.QToolBar()
self.layout.addWidget(self.toolbar)
self.setupToolbar(self.toolbar)
- self.setButtonStates()
+ self.updateModel()
self.setLayout(self.layout)
+ def updateModel(self):
+ self.model = KP.map.layerModel
+ self.listView.setModel(self.model)
+ self.listView.selectionModel().currentRowChanged.connect(self.handleRowChanged)
+ self.setButtonStates()
+
+
def setupToolbar(self, tb):
self.actAddTile = tb.addAction(KP.icon('LayerNewTile'), 'Add Tile Layer', self.addTileLayer)
self.actAddDoodad = tb.addAction(KP.icon('LayerNewObjects'), 'Add Doodad Layer', self.addDoodadLayer)
@@ -51,8 +55,13 @@ class KPLayerList(QtGui.QWidget):
def setButtonStates(self):
index = self.selectedLayerIndex()
+ layer = KP.map.layers[index]
+
+ self.actRemove.setEnabled(
+ (index != -1) and
+ (len(KP.map.layers) > 1) and
+ (not isinstance(layer, KPPathLayer)))
- self.actRemove.setEnabled((index != -1) and (len(KP.map.layers) > 1))
self.actMoveUp.setEnabled(index > 0)
self.actMoveDown.setEnabled((index != -1) and (index < (len(KP.map.layers) - 1)))
@@ -127,9 +136,6 @@ class KPLayerList(QtGui.QWidget):
class KPDoodadSelector(QtGui.QWidget):
- objChanged = QtCore.pyqtSignal(int, QtGui.QListWidgetItem)
-
-
def __init__(self):
"""Initialises the widget."""
@@ -138,55 +144,62 @@ class KPDoodadSelector(QtGui.QWidget):
self.layout = QtGui.QVBoxLayout()
self.layout.setSpacing(0)
- self.doodadList = QtGui.QListWidget()
- self.doodadList.setViewMode(1)
- self.doodadList.setWrapping(True)
- self.doodadList.setDragDropMode(1)
- self.doodadList.setSelectionMode(1)
- self.doodadList.setResizeMode(1)
- self.doodadList.setGridSize(QtCore.QSize(128, 128))
- self.doodadList.setIconSize(QtCore.QSize(100, 100))
- self.doodadList.setSpacing(4)
+ self.listView = QtGui.QListView()
+ self.listView.setViewMode(self.listView.IconMode)
+ self.listView.setWrapping(True)
+ self.listView.setDragDropMode(self.listView.DragOnly)
+ self.listView.setSelectionMode(self.listView.SingleSelection)
+ self.listView.setResizeMode(self.listView.Adjust)
+ self.listView.setGridSize(QtCore.QSize(128, 128))
+ self.listView.setIconSize(QtCore.QSize(100, 100))
+ self.listView.setSpacing(4)
self.toolbar = QtGui.QToolBar()
- self.layout.addWidget(self.toolbar)
- self.addDoodadButton = self.toolbar.addAction(QtGui.QIcon(), 'Add', self.addDoodadfromFile)
+ self.addDoodadButton = self.toolbar.addAction(QtGui.QIcon(), 'Add', self.addDoodadFromFile)
self.removeDoodadButton = self.toolbar.addAction(QtGui.QIcon(), 'Remove', self.removeDoodad)
+ self.updateModel()
- self.layout.addWidget(self.doodadList)
-
+ self.layout.addWidget(self.toolbar)
+ self.layout.addWidget(self.listView)
self.setLayout(self.layout)
-
- self.nextIndex = 0
-
- self.doodadList.currentItemChanged.connect(self.handleRowChanged)
-
+
+
+ def updateModel(self):
+ self.model = KP.map.doodadModel
+ self.listView.setModel(self.model)
+ self.listView.selectionModel().currentRowChanged.connect(self.handleRowChanged)
+ self.setButtonStates()
def keyPressEvent(self, event):
-
- self.doodadList.keyPressEvent(event)
+ self.listView.keyPressEvent(event)
if event.key() == QtCore.Qt.Key_Delete or event.key() == QtCore.Qt.Key_Backspace:
+ doodad = self.selectedDoodad()
+ if doodad is None:
+ return
+ # TODO: Check if selected
msgBox = QtGui.QMessageBox(QtGui.QMessageBox.Warning,
"Delete Doodad?", "Are you sure you want to delete this doodad? This action cannot be undone.",
QtGui.QMessageBox.NoButton, self)
msgBox.addButton("Delete", QtGui.QMessageBox.AcceptRole)
msgBox.addButton("Cancel", QtGui.QMessageBox.RejectRole)
if msgBox.exec_() == QtGui.QMessageBox.AcceptRole:
- self.removeDoodad()
+ KP.map.removeDoodad(doodad)
def addDoodad(self, image, name):
+ # TODO: REMOVE
"""Takes a name and a QPixmap and turns it into an icon, then goes ahead and makes a doodad.
Doodads are QListWidget items with an index number as Qt.UserRole #32."""
doodie = QtGui.QListWidgetItem(QtGui.QIcon(image), name)
+ # !!
doodie.setSizeHint(QtCore.QSize(128,128))
doodie.setData(32, self.nextIndex)
doodie.setToolTip(name)
@@ -196,15 +209,13 @@ class KPDoodadSelector(QtGui.QWidget):
self.nextIndex += 1
- def addDoodadfromFile(self):
+ def addDoodadFromFile(self):
"""Asks the user for files to load in as doodads."""
- log = QtGui.QFileDialog()
- log.setFileMode(3);
-
files = QtGui.QFileDialog.getOpenFileNames(self,
"Choose an image or several image files.", "",
"Images (*.png *.jpeg *.jpg *.bmp)")
+
if files:
for image in files:
name = os.path.basename(unicode(image)).split('.')[0]
@@ -212,54 +223,34 @@ class KPDoodadSelector(QtGui.QWidget):
pix = QtGui.QPixmap()
pix.load(image)
- self.addDoodad(pix, name)
+ KP.map.addDoodad(name, pix)
def removeDoodad(self):
"""Removes selected doodad"""
- item = self.doodadList.currentRow()
-
- if item != -1:
- self.doodadList.takeItem(item)
-
-
- def getDoodad(self, index):
- """Retrieves a doodad by index"""
- index = QtCore.QVariant(index)
- widget = self.doodadList
-
- for i in xrange(widget.count()):
- item = widget.item(i)
- if item.data(32) == index:
- return item
-
-
- def getDoodadImage(self, index, width, height):
- """Retrieves a doodad pixmap by index"""
- index = QtCore.QVariant(index)
- widget = self.doodadList
+ item = self.selectedDoodad()
+ if item:
+ KP.map.removeDoodad(item)
+
- for i in xrange(widget.count()):
- item = widget.item(i)
- if item.data(32) == index:
- return item.icon().pixmap(width, height)
+ def setButtonStates(self):
+ index = self.selectedDoodadIndex()
- def getDoodads(self):
- """Returns a list of all doodads.
+ self.removeDoodadButton.setEnabled(index != -1)
- Name can be retrieved with doodad.text()
- Image with doodad.icon().pixmap(doodad.icon().availableSizes()[0])
- Index with doodad.data(32)"""
- # TODO: FIX THIS
- return self.doodadList.items()
+ def selectedDoodadIndex(self):
+ return self.listView.selectionModel().currentIndex().row()
+ def selectedDoodad(self):
+ return KP.map.doodadDefinitions[self.listView.selectionModel().currentIndex().row()]
+ selectedDoodadChanged = QtCore.pyqtSignal(object)
- @QtCore.pyqtSlot(QtGui.QListWidgetItem)
- def handleRowChanged(self, current):
- """Throws a signal emitting the current object when changed"""
- self.objChanged.emit(current.data(32).toPyObject(), current)
+ @QtCore.pyqtSlot(QtCore.QModelIndex, QtCore.QModelIndex)
+ def handleRowChanged(self, current, previous):
+ self.selectedDoodadChanged.emit(KP.map.doodadDefinitions[current.row()])
+ self.setButtonStates()
class KPObjectSelector(QtGui.QWidget):
@@ -423,6 +414,22 @@ class KPMainWindow(QtGui.QMainWindow):
self.setupDocks()
self.setupMenuBar()
+ self.refreshMapState()
+
+
+ def _createAction(self, internalName, callback, title):
+ act = QtGui.QAction(title, self)
+ act.triggered.connect(callback)
+ self.actions[internalName] = act
+ return act
+
+ def setupActions(self):
+ self.actions = {}
+
+ self._createAction('new', self.newMap, '&New')
+ self._createAction('open', self.openMap, '&Open...')
+ self._createAction('save', self.saveMap, '&Save')
+ self._createAction('saveAs', self.saveMapAs, 'Save &As...')
def setupMenuBar(self):
mb = self.menuBar()
@@ -430,12 +437,12 @@ class KPMainWindow(QtGui.QMainWindow):
from PyQt4.QtGui import QKeySequence
f = mb.addMenu('&File')
- self.fa = f.addAction('New') # N
- self.fb = f.addAction('Open...') # O
+ self.fa = f.addAction('New', self.newMap, QKeySequence("Ctrl+N"))
+ self.fb = f.addAction('Open...', self.openMap, QKeySequence("Ctrl+O"))
self.fc = f.addAction('Open Recent') #
f.addSeparator()
- self.fd = f.addAction('Save') # S
- self.fe = f.addAction('Save As...') # Shift S
+ self.fd = f.addAction('Save', self.saveMap, QKeySequence("Ctrl+S"))
+ self.fe = f.addAction('Save As...', self.saveMapAs, QKeySequence("Ctrl+Shift+S"))
self.ff = f.addAction('Export...') # E
f.addSeparator()
self.fg = f.addAction('Take Screenshot...', self.screenshot, QKeySequence("Ctrl+Alt+S"))
@@ -461,7 +468,7 @@ class KPMainWindow(QtGui.QMainWindow):
self.lg = l.addAction('Move Layer to Bottom', self.layerList.moveBottom, QKeySequence("Ctrl+Shift+Down"))
l.addSeparator()
self.lh = l.addAction('Add Tileset...', self.moveTilesetToFolder, QKeySequence("Ctrl+Shift+T"))
- self.li = l.addAction('Add Doodad...', self.doodadSelector.addDoodadfromFile, QKeySequence("Ctrl+Shift+R"))
+ self.li = l.addAction('Add Doodad...', self.doodadSelector.addDoodadFromFile, QKeySequence("Ctrl+Shift+R"))
a = mb.addMenu('Animate')
self.aa = a.addAction('Play Animations', self.playAnim, QKeySequence("Ctrl+P"))
@@ -479,9 +486,18 @@ class KPMainWindow(QtGui.QMainWindow):
self.wd = w.addAction('Actual Size', self.ZoomActual, QKeySequence("Ctrl+="))
self.wh = w.addAction('Show Wii Zoom', self.showWiiZoom)
w.addSeparator()
- self.we = w.addAction('Hide Layer Palette', self.showHideLayer, QKeySequence("Ctrl+1"))
- self.wf = w.addAction('Show Object Palette', self.showHideObject, QKeySequence("Ctrl+2"))
- self.wg = w.addAction('Show Doodad Palette', self.showHideDoodad, QKeySequence("Ctrl+3"))
+
+ layerAction = self.layerListDock.toggleViewAction()
+ layerAction.setShortcut(QKeySequence("Ctrl+1"))
+ w.addAction(layerAction)
+
+ objectAction = self.objectSelectorDock.toggleViewAction()
+ objectAction.setShortcut(QKeySequence("Ctrl+2"))
+ w.addAction(objectAction)
+
+ doodadAction = self.doodadSelectorDock.toggleViewAction()
+ doodadAction.setShortcut(QKeySequence("Ctrl+3"))
+ w.addAction(doodadAction)
h = mb.addMenu('Help')
self.ha = h.addAction('About Koopatlas')
@@ -493,26 +509,22 @@ class KPMainWindow(QtGui.QMainWindow):
self.layerList = KPLayerList()
self.layerListDock = QtGui.QDockWidget('Layers')
self.layerListDock.setWidget(self.layerList)
- self.layerListDock.visibilityChanged.connect(self.showHideLayerDock)
self.layerList.selectedLayerChanged.connect(self.handleSelectedLayerChanged)
- self.layerList.playPaused.connect(self.scene.playPause)
- self.layerList.playPaused.connect(self.playButtonChanged)
+ self.layerList.playPaused.connect(self.playAnim)
self.objectSelector = KPObjectSelector()
self.objectSelector.objChanged.connect(self.handleSelectedObjectChanged)
self.objectSelectorDock = QtGui.QDockWidget('Objects')
self.objectSelectorDock.setWidget(self.objectSelector)
- self.objectSelectorDock.visibilityChanged.connect(self.showHideObjectDock)
self.objectSelectorDock.hide()
self.doodadSelector = KPDoodadSelector()
- self.doodadSelector.objChanged.connect(self.handleSelectedDoodadChanged)
+ self.doodadSelector.selectedDoodadChanged.connect(self.handleSelectedDoodadChanged)
self.doodadSelectorDock = QtGui.QDockWidget('Doodads')
self.doodadSelectorDock.setWidget(self.doodadSelector)
- self.doodadSelectorDock.visibilityChanged.connect(self.showHideDoodadDock)
self.doodadSelectorDock.hide()
self.addDockWidget(Qt.RightDockWidgetArea, self.layerListDock)
@@ -520,6 +532,28 @@ class KPMainWindow(QtGui.QMainWindow):
self.addDockWidget(Qt.RightDockWidgetArea, self.doodadSelectorDock)
+ def refreshMapState(self):
+ self.layerList.updateModel()
+ self.doodadSelector.updateModel()
+
+ self.scene = KPMapScene()
+ self.editor.assignNewScene(self.scene)
+ self.updateTitlebar()
+
+ def updateTitlebar(self):
+ path = KP.map.filePath
+ if path is None:
+ effectiveName = 'Untitled Map'
+ else:
+ effectiveName = os.path.basename(path)
+
+ self.setWindowTitle('%s - Koopatlas' % effectiveName)
+
+ def checkDirty(self):
+ return False
+
+
+
#####################
# Slots for Widgets #
#####################
@@ -527,34 +561,31 @@ class KPMainWindow(QtGui.QMainWindow):
@QtCore.pyqtSlot(KPLayer)
def handleSelectedLayerChanged(self, layer):
self.scene.setCurrentLayer(layer)
+
+ showObjects, showDoodads = False, False
if isinstance(layer, KPDoodadLayer):
- self.objectSelectorDock.hide()
- self.doodadSelectorDock.show()
+ showDoodads = True
elif isinstance(layer, KPTileLayer):
KP.map.reloadTileset(layer.tileset)
-
- self.doodadSelectorDock.hide()
- self.objectSelectorDock.show()
+ showObjects = True
self.objectSelector.setModel(KP.map.loadedTilesets[layer.tileset].getModel())
-
- else:
- self.objectSelectorDock.hide()
- self.doodadSelectorDock.hide()
+
+ self.objectSelectorDock.setVisible(showObjects)
+ self.doodadSelectorDock.setVisible(showDoodads)
@QtCore.pyqtSlot(int, KPTileObject)
def handleSelectedObjectChanged(self, index, obj):
- self.editor.paintNext = obj
- self.editor.paintNextID = index
+ self.editor.objectToPaint = obj
+ self.editor.objectIDToPaint = index
- @QtCore.pyqtSlot(int, QtGui.QListWidgetItem)
- def handleSelectedDoodadChanged(self, index, obj):
- self.editor.paintNext = obj
- self.editor.paintNextID = index
+ @QtCore.pyqtSlot(object)
+ def handleSelectedDoodadChanged(self, doodad):
+ self.editor.doodadToPaint = doodad
########################
@@ -563,6 +594,48 @@ class KPMainWindow(QtGui.QMainWindow):
# File
########################
+ def newMap(self):
+ if self.checkDirty(): return
+
+ KP.map = KPMap()
+ self.refreshMapState()
+
+ def openMap(self):
+ if self.checkDirty(): return
+
+ target = unicode(QtGui.QFileDialog.getOpenFileName(
+ self, 'Open Map', '', 'Koopatlas map (*.kpmap)'))
+
+ if len(target) == 0:
+ return
+
+ import mapfile
+ obj = mapfile.load(open(target, 'rb').read())
+ obj.filePath = target
+ KP.map = obj
+ self.refreshMapState()
+
+ def saveMap(self, forceNewName=False):
+ target = KP.map.filePath
+
+ if target is None or forceNewName:
+ dialogDir = '' if target is None else os.path.dirname(target)
+ target = unicode(QtGui.QFileDialog.getSaveFileName(
+ self, 'Save Map', dialogDir, 'Koopatlas map (*.kpmap)'))
+
+ if len(target) == 0:
+ return
+
+ KP.map.filePath = target
+ self.updateTitlebar()
+
+ KP.map.save()
+
+
+
+ def saveMapAs(self):
+ self.saveMap(True)
+
@QtCore.pyqtSlot()
@@ -650,7 +723,7 @@ class KPMainWindow(QtGui.QMainWindow):
@QtCore.pyqtSlot()
def playButtonChanged(self):
- if self.scene.playing == True:
+ if self.scene.playing:
self.aa.setText('Stop Animations')
self.layerList.actPlayPause.setIcon(KP.icon('AStop'))
self.layerList.actPlayPause.setText('Stop')
@@ -670,52 +743,6 @@ class KPMainWindow(QtGui.QMainWindow):
# Window
########################
- @QtCore.pyqtSlot(bool)
- def showHideLayerDock(self, visible):
- if visible:
- self.we.setText('Hide Layer Palette')
- else:
- self.we.setText('Show Layer Palette')
-
-
- @QtCore.pyqtSlot(bool)
- def showHideObjectDock(self, visible):
- if visible:
- self.wf.setText('Hide Object Palette')
- else:
- self.wf.setText('Show Object Palette')
-
-
- @QtCore.pyqtSlot(bool)
- def showHideDoodadDock(self, visible):
- if visible:
- self.wg.setText('Hide Doodad Palette')
- else:
- self.wg.setText('Show Doodad Palette')
-
-
- @QtCore.pyqtSlot()
- def showHideLayer(self):
- if self.layerListDock.isVisible():
- self.layerListDock.hide()
- else:
- self.layerListDock.show()
-
- @QtCore.pyqtSlot()
- def showHideObject(self):
- if self.objectSelectorDock.isVisible():
- self.objectSelectorDock.hide()
- else:
- self.objectSelectorDock.show()
-
- @QtCore.pyqtSlot()
- def showHideDoodad(self):
- if self.doodadSelectorDock.isVisible():
- self.doodadSelectorDock.hide()
- else:
- self.doodadSelectorDock.show()
-
-
@QtCore.pyqtSlot()
def ZoomActual(self):
"""Handle zooming to the editor size"""