Add build copy feature (#2084)
This commit is contained in:
@@ -44,6 +44,7 @@ cls_template = mixed_document-new.svg
|
||||
cls_timeline = typ_calendar.svg
|
||||
cls_trash = typ_trash.svg
|
||||
cls_world = typ_location.svg
|
||||
copy = mixed_copy.svg
|
||||
cross = typ_times.svg
|
||||
document = typ_document.svg
|
||||
down = typ_chevron-down.svg
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m7.8824 2c-1.2988 0-2.3529 1.0541-2.3529 2.3529v2.3529h-1.1765c-1.2988 0-2.3529 1.0541-2.3529 2.3529v10.588c0 1.2988 1.0541 2.3529 2.3529 2.3529h10.588c1.2988 0 2.3529-1.0541 2.3529-2.3529v-1.1765h2.3529c1.2988 0 2.3529-1.0541 2.3529-2.3529v-11.765c0-1.2988-1.0541-2.3529-2.3529-2.3529zm0 2.3529h11.765v11.765h-2.3529v-7.0588c0-1.2988-1.0541-2.3529-2.3529-2.3529h-7.0588zm-3.5294 4.7059h10.588c0.32353 0 0-0.32353 0 0v10.588h-10.588v-9.8943z" fill="#69c" stroke-width="1.1765"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 633 B |
@@ -44,6 +44,7 @@ cls_template = mixed_document-new.svg
|
||||
cls_timeline = typ_calendar.svg
|
||||
cls_trash = typ_trash.svg
|
||||
cls_world = typ_location.svg
|
||||
copy = mixed_copy.svg
|
||||
cross = typ_times.svg
|
||||
document = typ_document.svg
|
||||
down = typ_chevron-down.svg
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m7.8824 2c-1.2988 0-2.3529 1.0541-2.3529 2.3529v2.3529h-1.1765c-1.2988 0-2.3529 1.0541-2.3529 2.3529v10.588c0 1.2988 1.0541 2.3529 2.3529 2.3529h10.588c1.2988 0 2.3529-1.0541 2.3529-2.3529v-1.1765h2.3529c1.2988 0 2.3529-1.0541 2.3529-2.3529v-11.765c0-1.2988-1.0541-2.3529-2.3529-2.3529zm0 2.3529h11.765v11.765h-2.3529v-7.0588c0-1.2988-1.0541-2.3529-2.3529-2.3529h-7.0588zm-3.5294 4.7059h10.588c0.32353 0 0-0.32353 0 0v10.588h-10.588v-9.8943z" fill="#4271ae" stroke-width="1.1765"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 636 B |
@@ -502,6 +502,15 @@ class BuildSettings:
|
||||
|
||||
return
|
||||
|
||||
@classmethod
|
||||
def duplicate(cls, source: BuildSettings) -> BuildSettings:
|
||||
"""Make a copy of another build."""
|
||||
cls = BuildSettings()
|
||||
cls.unpack(source.pack())
|
||||
cls._uuid = str(uuid.uuid4())
|
||||
cls._name = f"{source.name} 2"
|
||||
return cls
|
||||
|
||||
|
||||
class BuildCollection:
|
||||
"""Core: Build Collection Class
|
||||
|
||||
@@ -503,10 +503,11 @@ class GuiIcons:
|
||||
"fmt_strike-md", "fmt_subscript", "fmt_superscript", "fmt_underline",
|
||||
|
||||
# General Button Icons
|
||||
"add", "add_document", "backward", "bookmark", "browse", "checked", "close", "cross",
|
||||
"document", "down", "edit", "export", "font", "forward", "import", "list", "maximise",
|
||||
"menu", "minimise", "more", "noncheckable", "open", "panel", "quote", "refresh", "remove",
|
||||
"revert", "search_replace", "search", "settings", "star", "unchecked", "up", "view",
|
||||
"add", "add_document", "backward", "bookmark", "browse", "checked", "close", "copy",
|
||||
"cross", "document", "down", "edit", "export", "font", "forward", "import", "list",
|
||||
"maximise", "menu", "minimise", "more", "noncheckable", "open", "panel", "quote",
|
||||
"refresh", "remove", "revert", "search_replace", "search", "settings", "star", "unchecked",
|
||||
"up", "view",
|
||||
|
||||
# Switches
|
||||
"sticky-on", "sticky-off",
|
||||
|
||||
@@ -116,6 +116,11 @@ class GuiManuscript(NToolDialog):
|
||||
self.tbDel.setStyleSheet(buttonStyle)
|
||||
self.tbDel.clicked.connect(self._deleteSelectedBuild)
|
||||
|
||||
self.tbCopy = NIconToolButton(self, iSz, "copy")
|
||||
self.tbCopy.setToolTip(self.tr("Duplicate Selected Build"))
|
||||
self.tbCopy.setStyleSheet(buttonStyle)
|
||||
self.tbCopy.clicked.connect(self._copySelectedBuild)
|
||||
|
||||
self.tbEdit = NIconToolButton(self, iSz, "edit")
|
||||
self.tbEdit.setToolTip(self.tr("Edit Selected Build"))
|
||||
self.tbEdit.setStyleSheet(buttonStyle)
|
||||
@@ -128,6 +133,7 @@ class GuiManuscript(NToolDialog):
|
||||
self.listToolBox.addStretch(1)
|
||||
self.listToolBox.addWidget(self.tbAdd)
|
||||
self.listToolBox.addWidget(self.tbDel)
|
||||
self.listToolBox.addWidget(self.tbCopy)
|
||||
self.listToolBox.addWidget(self.tbEdit)
|
||||
self.listToolBox.setSpacing(0)
|
||||
|
||||
@@ -291,6 +297,17 @@ class GuiManuscript(NToolDialog):
|
||||
self._openSettingsDialog(build)
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _copySelectedBuild(self) -> None:
|
||||
"""Copy the currently selected build settings entry."""
|
||||
if build := self._getSelectedBuild():
|
||||
new = BuildSettings.duplicate(build)
|
||||
self._builds.setBuild(new)
|
||||
self._updateBuildsList()
|
||||
if item := self._buildMap.get(new.buildID):
|
||||
item.setSelected(True)
|
||||
return
|
||||
|
||||
@pyqtSlot("QListWidgetItem*", "QListWidgetItem*")
|
||||
def _updateBuildDetails(self, current: QListWidgetItem, previous: QListWidgetItem) -> None:
|
||||
"""Process change of build selection to update the details."""
|
||||
@@ -460,9 +477,8 @@ class GuiManuscript(NToolDialog):
|
||||
|
||||
def _updateBuildItem(self, build: BuildSettings) -> None:
|
||||
"""Update the entry of a specific build item."""
|
||||
bItem = self._buildMap.get(build.buildID, None)
|
||||
if isinstance(bItem, QListWidgetItem):
|
||||
bItem.setText(build.name)
|
||||
if item := self._buildMap.get(build.buildID):
|
||||
item.setText(build.name)
|
||||
else: # Probably a new item
|
||||
self._updateBuildsList()
|
||||
return
|
||||
|
||||
@@ -465,3 +465,45 @@ def testCoreBuildSettings_Collection(monkeypatch, mockGUI, fncPath: Path, mockRn
|
||||
]
|
||||
assert another.lastBuild == buildIDOne
|
||||
assert another.defaultBuild == buildIDTwo
|
||||
|
||||
|
||||
@pytest.mark.core
|
||||
def testCoreBuildSettings_Duplicate(monkeypatch, mockGUI, fncPath: Path, mockRnd):
|
||||
"""Test duplicating builds."""
|
||||
project = NWProject()
|
||||
buildTestProject(project, fncPath)
|
||||
buildsFile = project.storage.getMetaFile(nwFiles.BUILDS_FILE)
|
||||
assert isinstance(buildsFile, Path)
|
||||
|
||||
# No initial builds in a fresh project
|
||||
builds = BuildCollection(project)
|
||||
assert len(builds) == 0
|
||||
assert not buildsFile.exists()
|
||||
assert builds.lastBuild == ""
|
||||
assert builds.defaultBuild == ""
|
||||
|
||||
# Create a default build
|
||||
buildOne = BuildSettings()
|
||||
buildOne.setName("Test Build")
|
||||
buildOne.setValue("headings.fmtScene", nwHeadFmt.TITLE)
|
||||
buildOne.setValue("headings.fmtAltScene", nwHeadFmt.TITLE)
|
||||
buildOne.setValue("headings.fmtSection", nwHeadFmt.TITLE)
|
||||
|
||||
# Copy it
|
||||
buildTwo = BuildSettings().duplicate(buildOne)
|
||||
assert buildTwo.name == "Test Build 2"
|
||||
|
||||
# Raw data
|
||||
dataOne = buildOne.pack()
|
||||
dataTwo = buildTwo.pack()
|
||||
|
||||
# Name and UUID should be different
|
||||
assert dataOne["name"] != dataTwo["name"]
|
||||
assert dataOne["uuid"] != dataTwo["uuid"]
|
||||
|
||||
# The rest should be equal
|
||||
assert dataOne["path"] == dataTwo["path"]
|
||||
assert dataOne["build"] == dataTwo["build"]
|
||||
assert dataOne["format"] == dataTwo["format"]
|
||||
assert dataOne["settings"] == dataTwo["settings"]
|
||||
assert dataOne["content"] == dataTwo["content"]
|
||||
|
||||
@@ -138,6 +138,15 @@ def testToolManuscript_Builds(qtbot, nwGUI, projPath):
|
||||
assert build.name == "Test Build"
|
||||
assert manus.buildList.count() == 1
|
||||
|
||||
# Copy the build
|
||||
manus._buildMap[build.buildID].setSelected(True)
|
||||
manus._copySelectedBuild()
|
||||
assert manus.buildList.count() == 2
|
||||
|
||||
new = manus._getSelectedBuild()
|
||||
assert new is not None
|
||||
assert new.name == "Test Build 2"
|
||||
|
||||
# Close the dialog should also close the child dialogs
|
||||
manus.btnClose.click()
|
||||
if isinstance(bSettings, GuiBuildSettings):
|
||||
|
||||
Reference in New Issue
Block a user