# -*- coding: UTF-8 -*- from common import * from editorui.editorcommon import * from editorui.editormain import * import os import os.path class KPLayerList(QtGui.QWidget): def __init__(self): QtGui.QWidget.__init__(self) self.layout = QtGui.QVBoxLayout() self.layout.setSpacing(0) self.listView = QtGui.QListView() self.layout.addWidget(self.listView) self.toolbar = QtGui.QToolBar() self.layout.addWidget(self.toolbar) self.setupToolbar(self.toolbar) 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) self.actRemove = tb.addAction(KP.icon('LayerRemove'), 'Remove', self.removeLayer) self.actMoveUp = tb.addAction(QtGui.QIcon(), 'Move Up', self.moveUp) self.actMoveDown = tb.addAction(QtGui.QIcon(), 'Move Down', self.moveDown) self.actPlayPause = tb.addAction(KP.icon('APlay'), 'Play', self.toggleAnimatingScene) playPaused = QtCore.pyqtSignal() def toggleAnimatingScene(self): self.playPaused.emit() if self.actPlayPause.text() == 'Play': self.actPlayPause.setIcon(KP.icon('AStop')) self.actPlayPause.setText('Stop') else: self.actPlayPause.setIcon(KP.icon('APlay')) self.actPlayPause.setText('Play') 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.actMoveUp.setEnabled(index > 0) self.actMoveDown.setEnabled((index != -1) and (index < (len(KP.map.layers) - 1))) def selectedLayerIndex(self): return self.listView.selectionModel().currentIndex().row() def selectedLayer(self): return KP.map.layers[self.listView.selectionModel().currentIndex().row()] selectedLayerChanged = QtCore.pyqtSignal(KPLayer) @QtCore.pyqtSlot(QtCore.QModelIndex, QtCore.QModelIndex) def handleRowChanged(self, current, previous): self.selectedLayerChanged.emit(KP.map.layers[current.row()]) self.setButtonStates() def addTileLayer(self): from dialogs import KPTilesetChooserDialog tilesetName = KPTilesetChooserDialog.run('Choose a tileset for the new layer') if tilesetName is None: return layer = KP.map.createNewTileLayer(tilesetName) KP.map.appendLayer(layer) self.setButtonStates() def addDoodadLayer(self): layer = KP.map.createNewDoodadLayer() KP.map.appendLayer(layer) self.setButtonStates() def removeLayer(self): layer = self.selectedLayer() scene = KP.mainWindow.scene if isinstance(layer, KPPathLayer): return for obj in layer.objects: item = obj.qtItem if item: scene.removeItem(item) KP.map.removeLayer(self.selectedLayer()) self.setButtonStates() def moveUp(self): index = self.selectedLayerIndex() KP.map.moveLayer(index, index - 1) KP.mainWindow.editor.viewport().update() def moveDown(self): index = self.selectedLayerIndex() KP.map.moveLayer(index, index + 2) KP.mainWindow.editor.viewport().update() def moveTop(self): index = self.selectedLayerIndex() KP.map.moveLayer(index, 0) KP.mainWindow.editor.viewport().update() def moveBottom(self): index = self.selectedLayerIndex() KP.map.moveLayer(index, len(KP.map.layers)) KP.mainWindow.editor.viewport().update() class KPDoodadSelector(QtGui.QWidget): def __init__(self): """Initialises the widget.""" QtGui.QWidget.__init__(self) self.layout = QtGui.QVBoxLayout() self.layout.setSpacing(0) 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.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.toolbar) self.layout.addWidget(self.listView) self.setLayout(self.layout) 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.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: 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) doodie.setTextAlignment(Qt.AlignBottom | Qt.AlignHCenter) self.doodadList.addItem(doodie) self.nextIndex += 1 def addDoodadFromFile(self): """Asks the user for files to load in as doodads.""" 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] pix = QtGui.QPixmap() pix.load(image) KP.map.addDoodad(name, pix) def removeDoodad(self): """Removes selected doodad""" item = self.selectedDoodad() if item: KP.map.removeDoodad(item) def setButtonStates(self): index = self.selectedDoodadIndex() self.removeDoodadButton.setEnabled(index != -1) 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(QtCore.QModelIndex, QtCore.QModelIndex) def handleRowChanged(self, current, previous): self.selectedDoodadChanged.emit(KP.map.doodadDefinitions[current.row()]) self.setButtonStates() class KPObjectSelector(QtGui.QWidget): def __init__(self): """Initialises the widget. Remember to call setModel() on it with a KPGroupModel whenever the layer changes.""" QtGui.QWidget.__init__(self) self.menuSetup = False self.sorterButton = QtGui.QToolButton() self.sorterButton.setText('Pick a Layer') self.sorterButton.setEnabled(False) self.sorterButton.setPopupMode(self.sorterButton.InstantPopup) self.sorterButton.setToolButtonStyle(Qt.ToolButtonTextOnly) self.sorterButton.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) self.sorterMenu = QtGui.QMenu() self.sorterButton.setMenu(self.sorterMenu) self.layout = QtGui.QVBoxLayout() self.layout.setSpacing(0) self.toolbar = QtGui.QToolBar() self.toolbar.setFixedHeight(28) self.toolbar.addWidget(self.sorterButton) self.layout.addWidget(self.toolbar) self.listView = QtGui.QListView() self.listView.setFlow(QtGui.QListView.LeftToRight) self.listView.setLayoutMode(QtGui.QListView.SinglePass) self.listView.setMovement(QtGui.QListView.Static) self.listView.setResizeMode(QtGui.QListView.Adjust) self.listView.setWrapping(True) self.listView.setEnabled(False) self.layout.addWidget(self.listView) self.setLayout(self.layout) self.model = None # Borrowed the signals and junk from Reggie, figure we'll need em' # Some more signals are set in setModel self.listView.clicked.connect(self.handleObjReplace) self.sorterMenu.aboutToShow.connect(self.fixUpMenuSize) self.sorterMenu.triggered.connect(self.toggleTopLevel) def beginUsingMenu(self): if self.menuSetup: return font = QtGui.QFont() font.setPixelSize(18) font.setBold(True) self.sorterButton.setFont(font) self.sorterButton.setEnabled(True) self.menuSetup = True def fixUpMenuSize(self): self.sorterMenu.setFixedWidth(self.sorterButton.width()) def currentSelectedObject(self): """Returns the currently selected object reference, for painting purposes.""" index = self.listView.currentIndex().row() object = self.model.groupItem().getItem(index) return object def setModel(self, model): """Sets the model and the menu sorting list""" if model == self.model: return self.model = model self.listView.setModel(model) self.listView.setEnabled(True) model.view = self.listView menuList = model.groupItem().getGroupList() self.beginUsingMenu() string = QtCore.QString(QtCore.QChar(0x25BE)) string.append(' All Groups') self.sorterButton.setText(string) self.sorterMenu.clear() for item in menuList: actionMan = self.sorterMenu.addAction(item[0]) actionMan.setData((item[1], item[2])) # a Quick Fix self.listView.setRowHidden(0, True) # set up signals self.listView.selectionModel().currentRowChanged.connect(self.handleRowChanged) def toggleTopLevel(self, action): """Changes the top level group in the list view.""" name = str(action.text()).strip() startRow = action.data().toPyObject()[0] + 1 endRow = action.data().toPyObject()[1] for row in xrange(self.model.rowCount()): if (row < startRow) or (row > endRow): self.listView.setRowHidden(row, True) else: self.listView.setRowHidden(row, False) string = QtCore.QString(QtCore.QChar(0x25BE)) string.append(' ' + name) self.sorterButton.setText(string) @QtCore.pyqtSlot(QtCore.QModelIndex, QtCore.QModelIndex) def handleRowChanged(self, current, previous): """Throws a signal emitting the current object when changed""" i = current.row() object, depth = self.model.groupItem().getItem(i) self.objChanged.emit(i, object) def handleObjReplace(self, index): """Throws a signal when the selected object is used as a replacement""" if QtGui.QApplication.keyboardModifiers() == QtCore.Qt.AltModifier: i = current.row() object, depth = self.model.groupItem().getItem(i) self.objReplaced.emit(i, object) objChanged = QtCore.pyqtSignal(int, KPTileObject) objReplaced = QtCore.pyqtSignal(int, KPTileObject) class KPMainWindow(QtGui.QMainWindow): def __init__(self): QtGui.QMainWindow.__init__(self) self.scene = KPMapScene() self.editor = KPEditorWidget(self.scene) self.setCentralWidget(self.editor) self.ZoomLevel = 5 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() from PyQt4.QtGui import QKeySequence f = mb.addMenu('&File') 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', 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")) f.addSeparator() # self.fi = f.addAction('Quit') e = mb.addMenu('Edit') self.ea = e.addAction('Copy') # C self.eb = e.addAction('Cut') # X self.ec = e.addAction('Paste') # V e.addSeparator() self.ed = e.addAction('Select All', self.selectAll, QKeySequence.SelectAll) self.ee = e.addAction('Deselect', self.deSelect, QKeySequence("Ctrl+D")) l = mb.addMenu('Layers') self.la = l.addAction('Add Tileset Layer', self.layerList.addTileLayer, QKeySequence("Ctrl+T")) self.lb = l.addAction('Add Doodad Layer', self.layerList.addDoodadLayer, QKeySequence("Ctrl+R")) self.lc = l.addAction('Remove Layer', self.layerList.removeLayer, QKeySequence("Ctrl+Del")) l.addSeparator() self.ld = l.addAction('Move Layer Up', self.layerList.moveUp, QKeySequence("Ctrl+Up")) self.le = l.addAction('Move Layer Down', self.layerList.moveDown, QKeySequence("Ctrl+Down")) self.lf = l.addAction('Move Layer to Top', self.layerList.moveTop, QKeySequence("Ctrl+Shift+Up")) 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")) a = mb.addMenu('Animate') self.aa = a.addAction('Play Animations', self.playAnim, QKeySequence("Ctrl+P")) self.ac = a.addAction('Reset Animations', self.resetAnim, QKeySequence("Ctrl+Shift+P")) a.addSeparator() self.ad = a.addAction('Load Animation Presets...') self.ae = a.addAction('Save Animation Presets...') self.af = a.addAction('Clear Animation Presets') w = mb.addMenu('Window') self.wa = w.addAction('Show Grid', self.showGrid, QKeySequence("Ctrl+G")) w.addSeparator() self.wb = w.addAction('Zoom In', self.ZoomIn, QKeySequence.ZoomIn) self.wc = w.addAction('Zoom Out', self.ZoomOut, QKeySequence.ZoomOut) self.wd = w.addAction('Actual Size', self.ZoomActual, QKeySequence("Ctrl+=")) self.wh = w.addAction('Show Wii Zoom', self.showWiiZoom) w.addSeparator() 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') self.hb = h.addAction('Koopatlas Documentation') self.hc = h.addAction('Keyboard Shortcuts') def setupDocks(self): self.layerList = KPLayerList() self.layerListDock = QtGui.QDockWidget('Layers') self.layerListDock.setWidget(self.layerList) self.layerList.selectedLayerChanged.connect(self.handleSelectedLayerChanged) 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.hide() self.doodadSelector = KPDoodadSelector() self.doodadSelector.selectedDoodadChanged.connect(self.handleSelectedDoodadChanged) self.doodadSelectorDock = QtGui.QDockWidget('Doodads') self.doodadSelectorDock.setWidget(self.doodadSelector) self.doodadSelectorDock.hide() self.addDockWidget(Qt.RightDockWidgetArea, self.layerListDock) self.addDockWidget(Qt.RightDockWidgetArea, self.objectSelectorDock) 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 # ##################### @QtCore.pyqtSlot(KPLayer) def handleSelectedLayerChanged(self, layer): self.scene.setCurrentLayer(layer) showObjects, showDoodads = False, False if isinstance(layer, KPDoodadLayer): showDoodads = True elif isinstance(layer, KPTileLayer): KP.map.reloadTileset(layer.tileset) showObjects = True self.objectSelector.setModel(KP.map.loadedTilesets[layer.tileset].getModel()) self.objectSelectorDock.setVisible(showObjects) self.doodadSelectorDock.setVisible(showDoodads) @QtCore.pyqtSlot(int, KPTileObject) def handleSelectedObjectChanged(self, index, obj): self.editor.objectToPaint = obj self.editor.objectIDToPaint = index @QtCore.pyqtSlot(object) def handleSelectedDoodadChanged(self, doodad): self.editor.doodadToPaint = doodad ######################## # Slots for Menu Items # ######################## # 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() def screenshot(self): items = ("Current Window", "Entire Map") item, ok = QtGui.QInputDialog.getItem(self, "QInputDialog.getItem()", "Choose a Screenshot Source:", items, 0, False) if ok and item: fn = QtGui.QFileDialog.getSaveFileName(self, 'Choose a new filename', '/untitled.png', 'Portable Network Graphics (*.png)') if fn == '': return fn = unicode(fn) if item == "Current Window": ScreenshotImage = QtGui.QImage(self.editor.width(), self.editor.height(), QtGui.QImage.Format_ARGB32) ScreenshotImage.fill(QtCore.Qt.transparent) RenderPainter = QtGui.QPainter(ScreenshotImage) self.editor.render(RenderPainter, QtCore.QRectF(0,0,self.editor.width(), self.editor.height()), QtCore.QRect(QtCore.QPoint(0,0), QtCore.QSize(self.editor.width(), self.editor.height()))) RenderPainter.end() else: ScreenshotImage = QtGui.QImage(self.scene.itemsBoundingRect().width()+100, self.scene.itemsBoundingRect().height()+100, QtGui.QImage.Format_ARGB32) ScreenshotImage.fill(QtCore.Qt.transparent) RenderPainter = QtGui.QPainter(ScreenshotImage) self.scene.render(RenderPainter, QtCore.QRectF(ScreenshotImage.rect()), self.scene.itemsBoundingRect().adjusted(-50.0, -50.0, 50.0, 50.0)) RenderPainter.end() ScreenshotImage.save(fn, 'PNG', 50) # Edit ######################## @QtCore.pyqtSlot() def selectAll(self): path = QtGui.QPainterPath() path.addRect(self.scene.sceneRect()) self.scene.setSelectionArea(path) @QtCore.pyqtSlot() def deSelect(self): self.scene.clearSelection() # Layers ######################## @QtCore.pyqtSlot() def moveTilesetToFolder(self): log = QtGui.QFileDialog() path = QtGui.QFileDialog.getOpenFileNames(self, "Choose a tileset. Tileset will be copied to the Koopatlas Tilesets Folder.", "", "Koopuzzle Tilesets (*.arc)") if path: import shutil import os from hashlib import sha256 as sha name = os.path.basename(path[:-4]) shutil.copy(path, 'Tilesets') filehandler = open(path) data = filehandler.read() filehandler.close() hash = sha(data).hexdigest() KP.map.tilesets[name] = {'path': path, 'hash': hash} KP.map.loadedTilesets[name] = KPTileset.loadFromArc(path) # Animate ######################## @QtCore.pyqtSlot() def playAnim(self): self.scene.playPause() self.playButtonChanged() @QtCore.pyqtSlot() def playButtonChanged(self): if self.scene.playing: self.aa.setText('Stop Animations') self.layerList.actPlayPause.setIcon(KP.icon('AStop')) self.layerList.actPlayPause.setText('Stop') else: self.aa.setText('Play Animations') self.layerList.actPlayPause.setIcon(KP.icon('APlay')) self.layerList.actPlayPause.setText('Play') @QtCore.pyqtSlot() def resetAnim(self): if self.scene.playing == True: self.scene.playPause() self.scene.playPause() # Window ######################## @QtCore.pyqtSlot() def ZoomActual(self): """Handle zooming to the editor size""" self.ZoomLevel = 5 self.ZoomTo() @QtCore.pyqtSlot() def ZoomIn(self): """Handle zooming in""" self.ZoomLevel += 1 self.ZoomTo() @QtCore.pyqtSlot() def ZoomOut(self): """Handle zooming out""" self.ZoomLevel -= 1 self.ZoomTo() def ZoomTo(self): """Zoom to a specific level""" tr = QtGui.QTransform() zooms = [5.0, 10.0, 25.0, 50.0, 75.0, 100.0, 150.0, 200.0, 400.0] scale = zooms[self.ZoomLevel] / 100.0 tr.scale(scale, scale) self.editor.setTransform(tr) self.wb.setEnabled(self.ZoomLevel != 8) self.wc.setEnabled(self.ZoomLevel != 0) self.wd.setEnabled(self.ZoomLevel != 5) self.scene.update() @QtCore.pyqtSlot() def showGrid(self): """Handle toggling of the grid being showed""" # settings.setValue('GridEnabled', checked) if self.scene.grid == True: self.scene.grid = False else: self.scene.grid = True print self.scene.grid self.scene.update() @QtCore.pyqtSlot(bool) def showWiiZoom(self): pass # Help ########################