More work on NBT find/replace
Find/replace settings are saved and loaded Checkboxes are sync'd with field contents Find results are shown in a separate dock widget Result entries unambiguously point to an entity or tile entity
This commit is contained in:
parent
ae0e1bed1f
commit
e483424e17
@ -7,6 +7,7 @@ from PySide import QtGui, QtCore
|
||||
import logging
|
||||
from PySide.QtCore import Qt
|
||||
from mcedit2.command import SimpleRevisionCommand
|
||||
from mcedit2.util import settings
|
||||
from mcedit2.util.load_ui import load_ui
|
||||
from mcedit2.util.resources import resourcePath
|
||||
from mcedit2.util.showprogress import showProgress
|
||||
@ -15,7 +16,14 @@ from mcedit2.widgets.layout import Row, Column
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
NBTResultsEntry = namedtuple("NBTResultsEntry", "displayName path value")
|
||||
class NBTResultsEntry(namedtuple("NBTResultsEntry", "displayName location value resultType")):
|
||||
EntityResult = "ENTITY"
|
||||
TileEntityResult = "TILE_ENTITY"
|
||||
ItemResult = "ITEM"
|
||||
PlayerResult = "PLAYER"
|
||||
ChunkResult = "CHUNK"
|
||||
FileResult = "FILE"
|
||||
|
||||
|
||||
class NBTResultsModel(QtCore.QAbstractItemModel):
|
||||
def __init__(self):
|
||||
@ -71,7 +79,10 @@ class NBTResultsModel(QtCore.QAbstractItemModel):
|
||||
elif column == 1:
|
||||
return entry.value
|
||||
elif column == 2:
|
||||
return entry.path
|
||||
if entry.resultType == entry.EntityResult:
|
||||
return "%s@%s/%s (%s)" % entry.location
|
||||
elif entry.resultType == entry.TileEntityResult:
|
||||
return "%s@%s/%s" % entry.location[:3]
|
||||
else:
|
||||
return ""
|
||||
# value = entry.value
|
||||
@ -202,27 +213,87 @@ class FindReplaceBlocks(QtCore.QObject):
|
||||
showProgress("Replacing...", task)
|
||||
self.editorSession.pushCommand(command)
|
||||
|
||||
nbtReplaceSettings = settings.Settings().getNamespace("findreplace/nbt")
|
||||
nbtReplaceSettings.nameField = nbtReplaceSettings.getOption("nameField", unicode)
|
||||
nbtReplaceSettings.valueField = nbtReplaceSettings.getOption("valueField", unicode)
|
||||
|
||||
nbtReplaceSettings.entityIDField = nbtReplaceSettings.getOption("entityIDField", unicode)
|
||||
nbtReplaceSettings.searchEntitiesChecked = nbtReplaceSettings.getOption("searchEntitiesChecked", bool)
|
||||
|
||||
nbtReplaceSettings.tileEntityIDField = nbtReplaceSettings.getOption("tileEntityIDField", unicode)
|
||||
nbtReplaceSettings.searchTileEntitiesChecked = nbtReplaceSettings.getOption("searchTileEntitiesChecked", bool)
|
||||
|
||||
|
||||
class FindReplaceNBT(QtCore.QObject):
|
||||
def __init__(self, editorSession):
|
||||
def __init__(self, editorSession, dialog):
|
||||
super(FindReplaceNBT, self).__init__()
|
||||
self.editorSession = editorSession
|
||||
self.widget = load_ui("find_replace_nbt.ui")
|
||||
self.dialog = dialog
|
||||
|
||||
self.resultsWidgetContents = load_ui("find_replace_nbt_results.ui")
|
||||
self.resultsWidget = QtGui.QDockWidget("NBT Search", objectName="nbtSearch")
|
||||
self.resultsWidget.setWidget(self.resultsWidgetContents)
|
||||
self.resultsWidget.hide()
|
||||
|
||||
self.resultsModel = NBTResultsModel()
|
||||
self.widget.resultsView.setModel(self.resultsModel)
|
||||
self.resultsWidgetContents.resultsView.setModel(self.resultsModel)
|
||||
|
||||
self.widget.findButton.clicked.connect(self.find)
|
||||
self.widget.stopButton.clicked.connect(self.stop)
|
||||
self.widget.stopButton.setVisible(False)
|
||||
|
||||
self.resultsWidgetContents.stopButton.clicked.connect(self.stop)
|
||||
|
||||
self.widget.searchNameCheckbox.toggled.connect(self.searchForToggled)
|
||||
self.widget.searchValueCheckbox.toggled.connect(self.searchForToggled)
|
||||
self.findTimer = None
|
||||
self.finder = None
|
||||
|
||||
self.widget.nameField.setText(nbtReplaceSettings.nameField.value(""))
|
||||
self.widget.searchNameCheckbox.setChecked(len(self.widget.nameField.text()) > 0)
|
||||
self.widget.nameField.textChanged.connect(self.nameFieldChanged)
|
||||
|
||||
self.widget.valueField.setText(nbtReplaceSettings.valueField.value(""))
|
||||
self.widget.searchValueCheckbox.setChecked(len(self.widget.valueField.text()) > 0)
|
||||
self.widget.valueField.textChanged.connect(self.valueFieldChanged)
|
||||
|
||||
self.widget.searchEntitiesCheckbox.setChecked(nbtReplaceSettings.searchEntitiesChecked.value(False))
|
||||
self.widget.searchEntitiesCheckbox.toggled.connect(nbtReplaceSettings.searchEntitiesChecked.setValue)
|
||||
|
||||
self.widget.entityIDField.setText(nbtReplaceSettings.entityIDField.value(""))
|
||||
self.widget.entityIDField.textChanged.connect(self.entityIDFieldChanged)
|
||||
|
||||
self.widget.searchTileEntitiesCheckbox.setChecked(nbtReplaceSettings.searchTileEntitiesChecked.value(False))
|
||||
self.widget.searchTileEntitiesCheckbox.toggled.connect(nbtReplaceSettings.searchTileEntitiesChecked.setValue)
|
||||
|
||||
self.widget.tileEntityIDField.setText(nbtReplaceSettings.tileEntityIDField.value(""))
|
||||
self.widget.tileEntityIDField.textChanged.connect(self.tileEntityIDFieldChanged)
|
||||
|
||||
def dialogOpened(self):
|
||||
currentSelection = self.editorSession.currentSelection
|
||||
self.widget.inSelectionCheckbox.setChecked(currentSelection is not None and currentSelection.volume > 0)
|
||||
|
||||
def searchForToggled(self):
|
||||
canSearch = self.widget.searchNameCheckbox.isChecked() or self.widget.searchValueCheckbox.isChecked()
|
||||
self.widget.findButton.setEnabled(canSearch)
|
||||
|
||||
def nameFieldChanged(self, value):
|
||||
nbtReplaceSettings.nameField.setValue(value)
|
||||
self.widget.searchNameCheckbox.setChecked(len(value) > 0)
|
||||
|
||||
def valueFieldChanged(self, value):
|
||||
nbtReplaceSettings.valueField.setValue(value)
|
||||
self.widget.searchValueCheckbox.setChecked(len(value) > 0)
|
||||
|
||||
def entityIDFieldChanged(self, value):
|
||||
nbtReplaceSettings.entityIDField.setValue(value)
|
||||
if len(value):
|
||||
self.widget.searchEntitiesCheckbox.setChecked(True)
|
||||
|
||||
def tileEntityIDFieldChanged(self, value):
|
||||
nbtReplaceSettings.tileEntityIDField.setValue(value)
|
||||
if len(value):
|
||||
self.widget.searchTileEntitiesCheckbox.setChecked(True)
|
||||
|
||||
def find(self):
|
||||
searchNames = self.widget.searchNameCheckbox.isChecked()
|
||||
targetName = self.widget.nameField.text()
|
||||
@ -239,7 +310,6 @@ class FindReplaceNBT(QtCore.QObject):
|
||||
if len(targetTileEntityIDs):
|
||||
targetTileEntityIDs = targetEntityIDs.split(';')
|
||||
|
||||
|
||||
if not searchNames and not searchValues:
|
||||
return
|
||||
|
||||
@ -278,39 +348,46 @@ class FindReplaceNBT(QtCore.QObject):
|
||||
if len(targetEntityIDs) and entity.id not in targetEntityIDs:
|
||||
continue
|
||||
|
||||
try:
|
||||
uuid = entity.UUID
|
||||
except KeyError:
|
||||
uuid = None # Don't want to use find/replace on entities without UUIDs
|
||||
|
||||
tag = entity.raw_tag()
|
||||
|
||||
for name, subtag, path in walkNBT(tag):
|
||||
result = _findTag(name, subtag, path)
|
||||
if result:
|
||||
name, path, value = result
|
||||
path = "%s@%s/%s" % (entity.id, entity.Position, path)
|
||||
location = entity.id, entity.Position, path, uuid
|
||||
|
||||
results.append(NBTResultsEntry(name, path, value))
|
||||
results.append(NBTResultsEntry(name, location, value, NBTResultsEntry.EntityResult))
|
||||
|
||||
self.resultsModel.addResults(results)
|
||||
|
||||
def _findTileEntitiesInChunk(chunk):
|
||||
results = []
|
||||
for entity in chunk.TileEntities:
|
||||
if entity.Position not in selection:
|
||||
for tileEntity in chunk.TileEntities:
|
||||
if tileEntity.Position not in selection:
|
||||
continue
|
||||
if len(targetTileEntityIDs) and entity.id not in targetTileEntityIDs:
|
||||
if len(targetTileEntityIDs) and tileEntity.id not in targetTileEntityIDs:
|
||||
continue
|
||||
|
||||
tag = entity.raw_tag()
|
||||
tag = tileEntity.raw_tag()
|
||||
for name, subtag, path in walkNBT(tag):
|
||||
result = _findTag(name, subtag, path)
|
||||
if result:
|
||||
name, path, value = result
|
||||
path = "%s@%s/%s" % (entity.id, entity.Position, path)
|
||||
location = tileEntity.id, tileEntity.Position, path, None
|
||||
|
||||
results.append(NBTResultsEntry(name, path, value))
|
||||
results.append(NBTResultsEntry(name, location, value, NBTResultsEntry.TileEntityResult))
|
||||
|
||||
self.resultsModel.addResults(results)
|
||||
|
||||
|
||||
def _find():
|
||||
self.widget.progressBar.setMaximum(selection.chunkCount)
|
||||
self.resultsWidget.show()
|
||||
self.dialog.accept()
|
||||
self.resultsWidgetContents.progressBar.setMaximum(selection.chunkCount-1)
|
||||
for i, cPos in enumerate(selection.chunkPositions()):
|
||||
if dim.containsChunk(*cPos):
|
||||
chunk = dim.getChunk(*cPos)
|
||||
@ -319,8 +396,8 @@ class FindReplaceNBT(QtCore.QObject):
|
||||
if searchTileEntities:
|
||||
_findTileEntitiesInChunk(chunk)
|
||||
|
||||
self.widget.progressBar.setValue(i)
|
||||
yield
|
||||
self.resultsWidgetContents.progressBar.setValue(i)
|
||||
|
||||
self.stop()
|
||||
|
||||
@ -334,15 +411,14 @@ class FindReplaceNBT(QtCore.QObject):
|
||||
|
||||
self.findTimer = QtCore.QTimer(timeout=find, interval=1.0)
|
||||
self.findTimer.start()
|
||||
self.widget.findButton.setVisible(False)
|
||||
self.widget.stopButton.setVisible(True)
|
||||
self.resultsWidgetContents.stopButton.setEnabled(True)
|
||||
|
||||
def stop(self):
|
||||
if self.findTimer:
|
||||
self.findTimer.stop()
|
||||
self.widget.findButton.setVisible(True)
|
||||
self.widget.stopButton.setVisible(False)
|
||||
self.widget.progressBar.setValue(0)
|
||||
self.resultsWidgetContents.stopButton.setEnabled(False)
|
||||
self.widget.findButton.setEnabled(True)
|
||||
|
||||
|
||||
def walkNBT(tag, path=""):
|
||||
if tag.isCompound():
|
||||
@ -365,10 +441,20 @@ class FindReplaceDialog(QtGui.QDialog):
|
||||
|
||||
self.findReplaceBlocks = FindReplaceBlocks(editorSession, self)
|
||||
|
||||
self.findReplaceNBT = FindReplaceNBT(editorSession)
|
||||
self.findReplaceNBT = FindReplaceNBT(editorSession, self)
|
||||
self.nbtTab.setLayout(Column(self.findReplaceNBT.widget, margin=0))
|
||||
|
||||
|
||||
self.resultsWidgets = [
|
||||
# self.findReplaceBlocks.resultsWidget,
|
||||
self.findReplaceNBT.resultsWidget,
|
||||
|
||||
]
|
||||
self.adjustSize()
|
||||
|
||||
def exec_(self):
|
||||
self.findReplaceNBT.dialogOpened()
|
||||
# self.findReplaceBlocks.dialogOpened()
|
||||
super(FindReplaceDialog, self).exec_()
|
||||
|
||||
|
||||
|
||||
|
@ -237,6 +237,10 @@ class EditorSession(QtCore.QObject):
|
||||
|
||||
self.undoStack.indexChanged.connect(self.undoIndexChanged)
|
||||
|
||||
self.findReplaceDialog = FindReplaceDialog(self)
|
||||
for resultsWidget in self.findReplaceDialog.resultsWidgets:
|
||||
self.dockWidgets.append((Qt.BottomDockWidgetArea, resultsWidget))
|
||||
|
||||
if len(self.toolActions):
|
||||
self.toolActions[0].trigger() # Must be called after toolChanged is connected to editorTab
|
||||
|
||||
@ -327,8 +331,7 @@ class EditorSession(QtCore.QObject):
|
||||
NotImplementedYet()
|
||||
|
||||
def findReplace(self):
|
||||
dialog = FindReplaceDialog(self)
|
||||
dialog.exec_()
|
||||
self.findReplaceDialog.exec_()
|
||||
|
||||
def deleteSelection(self):
|
||||
command = SimpleRevisionCommand(self, "Delete")
|
||||
|
@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>741</width>
|
||||
<height>760</height>
|
||||
<height>394</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@ -364,31 +364,20 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTableView" name="resultsView">
|
||||
<property name="verticalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAlwaysOn</enum>
|
||||
</property>
|
||||
<attribute name="horizontalHeaderStretchLastSection">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_21">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_11">
|
||||
<property name="text">
|
||||
<string>Progress:</string>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QProgressBar" name="progressBar">
|
||||
<property name="value">
|
||||
<number>0</number>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="inSelectionCheckbox">
|
||||
@ -403,27 +392,6 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="replaceSelectedButton">
|
||||
<property name="text">
|
||||
<string>Replace Selected</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="replaceAllButton">
|
||||
<property name="text">
|
||||
<string>Replace All</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="stopButton">
|
||||
<property name="text">
|
||||
<string>Stop</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="findButton">
|
||||
<property name="text">
|
||||
|
73
src/mcedit2/ui/find_replace_nbt_results.ui
Normal file
73
src/mcedit2/ui/find_replace_nbt_results.ui
Normal file
@ -0,0 +1,73 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Form</class>
|
||||
<widget class="QWidget" name="Form">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>578</width>
|
||||
<height>345</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QTableView" name="resultsView">
|
||||
<property name="verticalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAlwaysOn</enum>
|
||||
</property>
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectRows</enum>
|
||||
</property>
|
||||
<attribute name="horizontalHeaderStretchLastSection">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_11">
|
||||
<property name="text">
|
||||
<string>Progress:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QProgressBar" name="progressBar">
|
||||
<property name="value">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="replaceAllButton">
|
||||
<property name="text">
|
||||
<string>Replace All</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="replaceSelectedButton">
|
||||
<property name="text">
|
||||
<string>Replace Selected</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="stopButton">
|
||||
<property name="text">
|
||||
<string>Stop</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
Reference in New Issue
Block a user