class TrackingMouseAction(ViewAction): button = Qt.NoButton hidden = True modifiers = None labelText = "Mouse Tracking (Don't change!)" settingsKey = None editorTab = weakrefprop() def __init__(self, editorTab): super(TrackingMouseAction, self).__init__() self.editorTab = editorTab def mouseMoveEvent(self, event): self.editorTab.editorSession.viewMouseMove(event)
class EntityListProxy(collections.MutableSequence): """ A proxy for the Entities and TileEntities lists of a WorldEditorChunk. Accessing an element returns an EntityRef or TileEntityRef wrapping the element of the underlying NBT compound, with a reference to the WorldEditorChunk. These Refs cannot be created at load time as they hold a reference to the chunk, preventing the chunk from being unloaded when its refcount reaches zero. """ chunk = weakrefprop() def __init__(self, chunk, attrName, refClass): self.attrName = attrName self.refClass = refClass self.chunk = chunk def __getitem__(self, key): return self.refClass( getattr(self.chunk.chunkData, self.attrName)[key], self.chunk) def __setitem__(self, key, value): tagList = getattr(self.chunk.chunkData, self.attrName) if isinstance(key, slice): tagList[key] = [v.rootTag for v in value] else: tagList[key] = value.rootTag self.chunk.dirty = True def __delitem__(self, key): del getattr(self.chunk.chunkData, self.attrName)[key] self.chunk.dirty = True def __len__(self): return len(getattr(self.chunk.chunkData, self.attrName)) def insert(self, index, value): getattr(self.chunk.chunkData, self.attrName).insert(index, value.rootTag) self.chunk.dirty = True def remove(self, value): getattr(self.chunk.chunkData, self.attrName).remove(value.rootTag) self.chunk.dirty = True
class UseToolMouseAction(ViewAction): button = Qt.LeftButton labelText = "Use Tool (Don't change!)" hidden = True settingsKey = None editorTab = weakrefprop() def __init__(self, editorTab): super(UseToolMouseAction, self).__init__() self.editorTab = editorTab def mousePressEvent(self, event): self.editorTab.editorSession.viewMousePress(event) event.view.update() def mouseMoveEvent(self, event): self.editorTab.editorSession.viewMouseDrag(event) event.view.update() def mouseReleaseEvent(self, event): self.editorTab.editorSession.viewMouseRelease(event) event.view.update()
class EditorTool(QtCore.QObject): name = "Unnamed tool" iconName = None toolWidget = None cursorNode = None overlayNode = None editorSession = weakrefprop() def __init__(self, editorSession, *args, **kwargs): """ Initialize toolWidget here. :type editorSession: EditorSession """ super(EditorTool, self).__init__(*args, **kwargs) self.editorSession = editorSession def mousePress(self, event): """ :type event: QMouseEvent event has been augmented with these attributes: point, ray, blockPosition, blockFace """ def mouseMove(self, event): """ :type event: QMouseEvent event has been augmented """ def mouseDrag(self, event): """ :type event: QMouseEvent event has been augmented """ def mouseRelease(self, event): """ :type event: QMouseEvent event has been augmented """ def toolActive(self): """ Called when this tool is selected. :return: :rtype: """ def toolInactive(self): """ Called when a different tool is selected. :return: :rtype: """ toolPicked = QtCore.Signal(object) def pick(self): self.toolPicked.emit(self.name) def pickToolAction(self): name = self.name iconName = self.iconName if iconName: iconPath = resourcePath("mcedit2/assets/mcedit2/toolicons/%s.png" % iconName) if not os.path.exists(iconPath): log.error("Tool icon %s not found", iconPath) icon = None else: icon = QtGui.QIcon(iconPath) else: icon = None action = QtGui.QAction( self.tr(name), self, #shortcut=self.toolShortcut(name), # xxxx coordinate with view movement keys triggered=self.pick, checkable=True, icon=icon, ) action.toolName = name return action
class EditorTab(QtGui.QWidget): def __init__(self, editorSession): """ EditorTab is the widget containing the editor viewports, the minimap, and the settings panel for the currently selected tool and its dockwidget. :type editorSession: mcedit2.editorsession.EditorSession :rtype: EditorTab """ QtGui.QWidget.__init__(self) self.setContentsMargins(0, 0, 0, 0) self.editorSession = editorSession self.editorSession.dimensionChanged.connect(self.dimensionDidChange) self.debugLastCenters = [] self.viewButtonGroup = QtGui.QButtonGroup(self) self.viewButtonToolbar = QtGui.QToolBar() self.viewButtons = {} self.views = [] for name, handler in ( ("2D", self.showCutawayView), ("Over", self.showOverheadView), # ("Iso", self.showIsoView), ("Cam", self.showCameraView), # ("4-up", self.showFourUpView), ): button = QtGui.QToolButton(text=name, checkable=True) button.clicked.connect(handler) self.viewButtonGroup.addButton(button) self.viewButtonToolbar.addWidget(button) self.viewButtons[name] = button self.viewStack = QtGui.QStackedWidget() self.miniMap = MinimapWorldView(editorSession.currentDimension, editorSession.textureAtlas, editorSession.geometryCache) self.miniMapDockWidget = QtGui.QDockWidget("Minimap", objectName="MinimapWidget", floating=True) self.miniMapDockWidget.setWidget(self.miniMap) self.miniMapDockWidget.setFixedSize(256, 256) self.views.append(self.miniMap) self.toolOptionsArea = QtGui.QScrollArea() self.toolOptionsArea.setWidgetResizable(True) self.toolOptionsDockWidget = QtGui.QDockWidget("Tool Options", objectName="ToolOptionsWidget", floating=True) self.toolOptionsDockWidget.setWidget(self.toolOptionsArea) editorSession.dockWidgets.append((Qt.LeftDockWidgetArea, self.miniMapDockWidget)) editorSession.dockWidgets.append((Qt.LeftDockWidgetArea, self.toolOptionsDockWidget)) editorSession.loader.addClient(self.miniMap) self.overheadViewFrame = OverheadWorldViewFrame(editorSession.currentDimension, editorSession.textureAtlas, editorSession.geometryCache, self.miniMap) self.overheadViewFrame.worldView.viewID = "Over" self._addView(self.overheadViewFrame) self.cutawayViewFrame = CutawayWorldViewFrame(editorSession.currentDimension, editorSession.textureAtlas, editorSession.geometryCache, self.miniMap) self.cutawayViewFrame.worldView.viewID = "2D" self._addView(self.cutawayViewFrame) # # self.fourUpViewFrame = FourUpWorldViewFrame(editorSession.currentDimension, editorSession.textureAtlas, editorSession.geometryCache, self.miniMap) # self.fourUpViewFrame.worldView.viewID = "4-up" # self._addView(self.fourUpViewFrame) self.cameraViewFrame = CameraWorldViewFrame(editorSession.currentDimension, editorSession.textureAtlas, editorSession.geometryCache, self.miniMap) self.cameraViewFrame.worldView.viewID = "Cam" self.cameraView = self.cameraViewFrame.worldView self._addView(self.cameraViewFrame) self.viewStack.currentChanged.connect(self._viewChanged) self.viewChanged.connect(self.viewDidChange) self.setLayout(Column(self.viewButtonToolbar, Row(self.viewStack, margin=0), margin=0)) currentViewName = currentViewSetting.value() if currentViewName not in self.viewButtons: currentViewName = "Cam" self.viewButtons[currentViewName].click() self.editorSession.configuredBlocksChanged.connect(self.configuredBlocksDidChange) def destroy(self): self.editorSession = None for view in self.views: view.destroy() super(EditorTab, self).destroy() editorSession = weakrefprop() def configuredBlocksDidChange(self): for view in self.views: view.setTextureAtlas(self.editorSession.textureAtlas) def dimensionDidChange(self, dim): for view in self.views: view.setDimension(dim) # EditorSession has a new loader now, so re-add minimap and current view self.editorSession.loader.addClient(self.miniMap) view = self.currentView() if view is not None: self.editorSession.loader.addClient(view) def toolDidChange(self, tool): if tool.toolWidget: self.toolOptionsArea.takeWidget() # setWidget gives ownership to the scroll area self.toolOptionsArea.setWidget(tool.toolWidget) self.toolOptionsDockWidget.setWindowTitle(self.tr(tool.name) + self.tr(" Tool Options")) log.info("Setting cursor %r for tool %r on view %r", tool.cursorNode, tool, self.currentView()) self.currentView().setToolCursor(tool.cursorNode) def saveState(self): pass viewChanged = QtCore.Signal(object) def _viewChanged(self, index): self.viewChanged.emit(self.currentView()) def viewDidChange(self, view): self.miniMap.centerOnPoint(view.viewCenter()) if self.editorSession.currentTool: view.setToolCursor(self.editorSession.currentTool.cursorNode) overlayNodes = [tool.overlayNode for tool in self.editorSession.tools if tool.overlayNode is not None] overlayNodes.insert(0, self.editorSession.editorOverlay) view.setToolOverlays(overlayNodes) view.setFocus() def viewOffsetChanged(self, view): self.miniMap.centerOnPoint(view.viewCenter()) self.miniMap.currentViewMatrixChanged(view) def _addView(self, frame): self.views.append(frame.worldView) frame.stackIndex = self.viewStack.addWidget(frame) frame.worldView.viewportMoved.connect(self.viewOffsetChanged) frame.worldView.viewActions.extend([ UseToolMouseAction(self), TrackingMouseAction(self) ]) def currentView(self): """ :rtype: mcedit2.worldview.worldview.WorldView """ widget = self.viewStack.currentWidget() if widget is None: return None return widget.worldView def showViewFrame(self, frame): center = self.currentView().viewCenter() self.debugLastCenters.append(center) log.info("Going from %s to %s: Center was %s", self.currentView(), frame.worldView, center) self.editorSession.loader.removeClient(self.currentView()) self.editorSession.loader.addClient(frame.worldView, 0) self.viewStack.setCurrentIndex(frame.stackIndex) frame.worldView.centerOnPoint(center) log.info("Center is now %s", self.currentView().viewCenter()) def showOverheadView(self): self.showViewFrame(self.overheadViewFrame) # # def showIsoView(self): # self.showViewFrame(self.isoViewFrame) # # def showFourUpView(self): # self.showViewFrame(self.fourUpViewFrame) def showCutawayView(self): self.showViewFrame(self.cutawayViewFrame) def showCameraView(self): self.showViewFrame(self.cameraViewFrame)
class PlayerPanel(QtGui.QWidget, Ui_playerWidget): def __init__(self, editorSession): """ :type editorSession: mcedit2.editorsession.EditorSession :rtype: PlayerPanel """ super(PlayerPanel, self).__init__(QtGui.qApp.mainWindow, f=Qt.Tool) self.setupUi(self) self.editorSession = editorSession self.selectedUUID = None self.nbtEditor.editorSession = self.editorSession self.inventoryEditor = InventoryEditor(PLAYER_SLOT_LAYOUT) self.inventoryGroupBox.setLayout(Row(self.inventoryEditor)) self.movePlayerButton.clicked.connect(self.movePlayerToCamera) self.viewPlayerButton.clicked.connect(self.showPlayerView) playerUUIDs = list(editorSession.worldEditor.listPlayers()) try: sp = editorSession.worldEditor.getPlayer("") singlePlayerUUID = sp.UUID except PlayerNotFound: log.info("No single-player.") singlePlayerUUID = None except KeyError: log.info("Failed to get single-player UUID.") singlePlayerUUID = None if "" in playerUUIDs: # Move singleplayer to beginning of list playerUUIDs.remove("") playerUUIDs.insert(0, "") for UUID in playerUUIDs: # xxx live update? if UUID == "": displayName = "[Single-player](%s)" % singlePlayerUUID else: displayName = UUID # xxx mojang api here try: UUID = uuid.UUID(hex=UUID) if UUID == singlePlayerUUID: displayName = "[Multiplayer](%s)" % singlePlayerUUID except ValueError: # badly formed uuid? log.warn("Could not get a UUID from %s", UUID) continue self.playerListBox.addItem(displayName, UUID) self.playerListBox.currentIndexChanged[int].connect( self.setSelectedPlayerIndex) if len(playerUUIDs): self.setSelectedPlayerIndex(0) icon = QtGui.QIcon( resourcePath("mcedit2/assets/mcedit2/icons/edit_player.png")) action = QtGui.QAction(icon, "Edit Player", self) action.setCheckable(True) action.triggered.connect(self.toggleView) self._toggleViewAction = action self.editorSession.revisionChanged.connect(self.revisionDidChange) self.initPropertiesWidget() centerWidgetInScreen(self) editorSession = weakrefprop() def initPropertiesWidget(self): if self.selectedPlayer is None: self.playerPropertiesWidget.setModel(None) return model = PropertyListModel(self.selectedPlayer.rootTag) addWidget = model.addNBTProperty addWidget("AbsorptionAmount") addWidget("Air") addWidget("DeathTime") addWidget("Dimension") addWidget("FallDistance", valueType=float) addWidget("Fire") addWidget("foodExhaustionLevel", valueType=float) addWidget("foodLevel") addWidget("foodSaturationLevel", valueType=float) addWidget("foodTickTimer") addWidget("HealF", valueType=float) addWidget("Health") addWidget("HurtByTimestamp") addWidget("HurtTime") addWidget("Invulnerable", bool) addWidget("OnGround", bool) addWidget("playerGameType", [(0, "Survival"), (1, "Creative"), (2, "Adventure")]) addWidget("PortalCooldown") addWidget("Score") addWidget("SelectedItemSlot") # xxx inventory addWidget("Sleeping", bool) addWidget("SleepTimer") addWidget("XpLevel") addWidget("XpP", float) addWidget("XpSeed") addWidget("XpTotal") self.playerPropertiesWidget.setModel(model) model.propertyChanged.connect(self.propertyDidChange) def updateNBTTree(self): self.nbtEditor.undoCommandPrefixText = ( "Player %s: " % self.selectedUUID) if self.selectedUUID else "Single-player: " self.nbtEditor.setRootTagRef(self.selectedPlayer) def updateInventory(self): self.inventoryEditor.editorSession = self.editorSession self.inventoryEditor.inventoryRef = self.selectedPlayer.Inventory def revisionDidChange(self): self.initPropertiesWidget() self.updateNBTTree() def propertyDidChange(self, name, value): if self.selectedUUID != "": text = "Change player %s property %s" % (self.selectedUUID, name) else: text = "Change single-player property %s" % name command = PlayerPropertyChangeCommand(self.editorSession, text) with command.begin(): self.selectedPlayer.dirty = True self.editorSession.worldEditor.syncToDisk() self.editorSession.pushCommand(command) def toggleView(self): if self.isHidden(): self.show() self._toggleViewAction.setChecked(True) else: self.hide() self._toggleViewAction.setChecked(False) def closeEvent(self, event): self.toggleView() def toggleViewAction(self): return self._toggleViewAction def setSelectedPlayerIndex(self, index): UUID = self.playerListBox.itemData(index) self.setSelectedPlayerUUID(UUID) def setSelectedPlayerUUID(self, UUID): self.selectedUUID = UUID self.updateNBTTree() self.updateInventory() @property def selectedPlayer(self): try: return self.editorSession.worldEditor.getPlayer(self.selectedUUID) except PlayerNotFound: log.info("PlayerPanel: player %s not found!", self.selectedUUID) def movePlayerToCamera(self): view = self.editorSession.editorTab.currentView() if view.viewID == "Cam": command = SimpleRevisionCommand(self.editorSession, "Move Player") with command.begin(): self.selectedPlayer.Position = view.centerPoint try: self.selectedPlayer.Rotation = view.yawPitch except AttributeError: pass self.selectedPlayer.dirty = True # xxx do in AnvilPlayerRef self.editorSession.pushCommand(command) else: raise ValueError("Current view is not camera view.") def showPlayerView(self): self.editorSession.editorTab.showCameraView() view = self.editorSession.editorTab.cameraView view.setPerspective(False) view.centerPoint = self.selectedPlayer.Position view.yawPitch = self.selectedPlayer.Rotation
class NBTEditorWidget(QtGui.QWidget): """ NBT Editor widget. Suitable for use as a custom QTreeView class in QDesigner. Attributes ---------- editorSession: EditorSession """ undoCommandPrefixText = "" editorSession = weakrefprop() proxyModel = None rootTagRef = None def __init__(self, *args, **kwargs): super(NBTEditorWidget, self).__init__(*args, **kwargs) self.model = None """:type : EditorSession""" self.editorSession = None self.treeView = QtGui.QTreeView() self.treeView.setAlternatingRowColors(True) self.treeView.clicked.connect(self.itemClicked) self.treeView.expanded.connect(self.itemExpanded) self.setLayout(Column(self.treeView)) self.nbtTypesMenu = QtGui.QMenu() self.nbtTypesMenu.addAction(NBTIcon(1), self.tr("Byte"), self.addByte) self.nbtTypesMenu.addAction(NBTIcon(2), self.tr("Short"), self.addShort) self.nbtTypesMenu.addAction(NBTIcon(3), self.tr("Int"), self.addInt) self.nbtTypesMenu.addAction(NBTIcon(4), self.tr("Long"), self.addLong) self.nbtTypesMenu.addAction(NBTIcon(5), self.tr("Float"), self.addFloat) self.nbtTypesMenu.addAction(NBTIcon(6), self.tr("Double"), self.addDouble) self.nbtTypesMenu.addAction(NBTIcon(8), self.tr("String"), self.addString) self.nbtTypesMenu.addAction(NBTIcon(9), self.tr("List"), self.addList) self.nbtTypesMenu.addAction(NBTIcon(10), self.tr("Compound"), self.addCompound) self.nbtTypesMenu.addAction(NBTIcon(7), self.tr("Byte Array"), self.addByteArray) self.nbtTypesMenu.addAction(NBTIcon(11), self.tr("Int Array"), self.addIntArray) # self.nbtTypesMenu.addAction(NBTIcon(12), self.tr("Short Array"), self.addShortArray) self.treeView.setItemDelegate(NBTEditorItemDelegate()) def setRootTagRef(self, rootTagRef, keepExpanded=False): if rootTagRef is self.rootTagRef: return self.rootTagRef = rootTagRef if rootTagRef is None: self.treeView.setModel(None) self.model = None return self.model = NBTTreeModel(rootTagRef.rootTag, self.editorSession.worldEditor.blocktypes) expanded = [] current = None if keepExpanded and self.proxyModel: current = self.proxyModel.data(self.treeView.currentIndex(), NBTTreeModel.NBTPathRole) def addExpanded(parentIndex): for row in range(self.proxyModel.rowCount(parentIndex)): index = self.proxyModel.index(row, 0, parentIndex) if self.treeView.isExpanded(index): expanded.append( self.proxyModel.data(index, NBTTreeModel.NBTPathRole)) addExpanded(index) addExpanded(QtCore.QModelIndex()) self.model.dataChanged.connect(self.dataDidChange) self.model.rowsInserted.connect(self.rowsDidInsert) self.model.rowsRemoved.connect(self.rowsDidRemove) self.proxyModel = NBTFilterProxyModel(self) self.proxyModel.setSourceModel(self.model) # self.proxyModel.setDynamicSortFilter(True) self.treeView.setModel(self.proxyModel) header = self.treeView.header() header.setStretchLastSection(False) header.setResizeMode(1, header.ResizeMode.Stretch) header.setResizeMode(2, header.ResizeMode.Fixed) header.setResizeMode(3, header.ResizeMode.Fixed) if keepExpanded: for path in expanded: matches = self.proxyModel.match( self.proxyModel.index(0, 0, QtCore.QModelIndex()), NBTTreeModel.NBTPathRole, path, flags=Qt.MatchExactly | Qt.MatchRecursive) for i in matches: self.treeView.setExpanded(i, True) if current is not None: matches = self.proxyModel.match( self.proxyModel.index(0, 0, QtCore.QModelIndex()), NBTTreeModel.NBTPathRole, current, flags=Qt.MatchExactly | Qt.MatchRecursive) if len(matches): self.treeView.setCurrentIndex(matches[0]) else: self.treeView.expandToDepth(0) self.treeView.sortByColumn(0, Qt.AscendingOrder) self.treeView.resizeColumnToContents(0) self.treeView.resizeColumnToContents(1) self.treeView.resizeColumnToContents(2) self.treeView.resizeColumnToContents(3) def itemExpanded(self): self.treeView.resizeColumnToContents(0) indexAddingTo = None def itemClicked(self, index): index = self.proxyModel.mapToSource(index) item = self.model.getItem(index) if index.column() == 2: if item.isList and item.tag.list_type: row = item.childCount() self.model.insertRow(row, index) newItemIndex = self.model.index(row, 1, index) # self.treeView.setCurrentIndex(self.proxyModel.mapFromSource(newItemIndex)) # self.treeView.edit(self.proxyModel.mapFromSource(newItemIndex)) if item.isCompound or (item.isList and not item.tag.list_type): self.indexAddingTo = index self.nbtTypesMenu.move(QtGui.QCursor.pos()) self.nbtTypesMenu.show() if index.column() == 3: parent = self.model.parent(index) self.doomedTagName = self.tagNameForUndo(index) self.model.removeRow(index.row(), parent) def addItemWithType(self, tagID): if not self.indexAddingTo: return item = self.model.getItem(self.indexAddingTo) row = item.childCount() self.model.insertRow(row, self.indexAddingTo, tagID) newItemIndex = self.model.index(row, 0 if item.isCompound else 1, self.indexAddingTo) # self.treeView.setCurrentIndex(self.proxyModel.mapFromSource(newItemIndex)) # self.treeView.edit(self.proxyModel.mapFromSource(newItemIndex)) self.indexAddingTo = None def addByte(self): self.addItemWithType(1) def addShort(self): self.addItemWithType(2) def addInt(self): self.addItemWithType(3) def addLong(self): self.addItemWithType(4) def addFloat(self): self.addItemWithType(5) def addDouble(self): self.addItemWithType(6) def addByteArray(self): self.addItemWithType(7) def addString(self): self.addItemWithType(8) def addList(self): self.addItemWithType(9) def addCompound(self): self.addItemWithType(10) def addIntArray(self): self.addItemWithType(11) def addShortArray(self): self.addItemWithType(12) def tagNameForUndo(self, index): parent = self.model.parent(index) item = self.model.getItem(index) parentItem = self.model.getItem(parent) name = "(root)" if parentItem is not None: if parentItem.isList: name = "%s #%d" % (self.tagNameForUndo(parent), parentItem.tag.index(item.tag)) elif parentItem.isCompound: name = item.tag.name return name def dataDidChange(self, index): name = self.tagNameForUndo(index) if index.column() == 0: text = "%sRename NBT tag %s" % (self.undoCommandPrefixText, name) elif index.column() == 1: text = "%sChange value of NBT tag %s" % ( self.undoCommandPrefixText, name) else: text = "Unknown data changed." command = NBTDataChangeCommand(self.editorSession, text) with command.begin(): self.rootTagRef.dirty = True self.editorSession.worldEditor.syncToDisk() self.editorSession.pushCommand(command) self.tagValueChanged.emit(index.data(NBTTreeModel.NBTPathRole)) def rowsDidInsert(self, index): name = self.tagNameForUndo(index.parent()) text = "%sInsert NBT tag under %s" % (self.undoCommandPrefixText, name) command = NBTDataChangeCommand(self.editorSession, text) with command.begin(): self.rootTagRef.dirty = True self.editorSession.worldEditor.syncToDisk() self.editorSession.pushCommand(command) doomedTagName = None def rowsDidRemove(self, index, start, end): name = self.tagNameForUndo(index) text = "%sRemove NBT tag %s from %s" % (self.undoCommandPrefixText, self.doomedTagName, name) command = NBTDataChangeCommand(self.editorSession, text) with command.begin(): self.rootTagRef.dirty = True self.editorSession.worldEditor.syncToDisk() self.editorSession.pushCommand(command) tagValueChanged = QtCore.Signal(list) tagAdded = QtCore.Signal(list) tagRemoved = QtCore.Signal(list)