diff options
| author | Treeki <treeki@gmail.com> | 2011-12-02 19:07:13 +0100 | 
|---|---|---|
| committer | Treeki <treeki@gmail.com> | 2011-12-02 19:07:13 +0100 | 
| commit | 3bdc780bd3eaa204b1df5d5e6727a876d5967401 (patch) | |
| tree | 411f4300356797effc38bb5e599e9dff1a02d1dd /src | |
| parent | cbadd29918da648190cd609130f808e11cf01a25 (diff) | |
| download | koopatlas-3bdc780bd3eaa204b1df5d5e6727a876d5967401.tar.gz koopatlas-3bdc780bd3eaa204b1df5d5e6727a876d5967401.zip | |
a really, really big commit
Diffstat (limited to '')
| -rw-r--r-- | src/editorui/doodads.py | 58 | ||||
| -rw-r--r-- | src/editorui/editormain.py | 69 | ||||
| -rw-r--r-- | src/editorui/objects.py | 2 | ||||
| -rw-r--r-- | src/editorui/paths.py | 13 | ||||
| -rw-r--r-- | src/main.py | 10 | ||||
| -rw-r--r-- | src/mapdata.py | 255 | ||||
| -rw-r--r-- | src/mapfile.py | 96 | ||||
| -rw-r--r-- | src/ui.py | 321 | 
8 files changed, 595 insertions, 229 deletions
| 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 + @@ -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""" | 
