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 'src')
-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""" |