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_timeline = typ_calendar.svg
|
||||||
cls_trash = typ_trash.svg
|
cls_trash = typ_trash.svg
|
||||||
cls_world = typ_location.svg
|
cls_world = typ_location.svg
|
||||||
|
copy = mixed_copy.svg
|
||||||
cross = typ_times.svg
|
cross = typ_times.svg
|
||||||
document = typ_document.svg
|
document = typ_document.svg
|
||||||
down = typ_chevron-down.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_timeline = typ_calendar.svg
|
||||||
cls_trash = typ_trash.svg
|
cls_trash = typ_trash.svg
|
||||||
cls_world = typ_location.svg
|
cls_world = typ_location.svg
|
||||||
|
copy = mixed_copy.svg
|
||||||
cross = typ_times.svg
|
cross = typ_times.svg
|
||||||
document = typ_document.svg
|
document = typ_document.svg
|
||||||
down = typ_chevron-down.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
|
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:
|
class BuildCollection:
|
||||||
"""Core: Build Collection Class
|
"""Core: Build Collection Class
|
||||||
|
|||||||
@@ -503,10 +503,11 @@ class GuiIcons:
|
|||||||
"fmt_strike-md", "fmt_subscript", "fmt_superscript", "fmt_underline",
|
"fmt_strike-md", "fmt_subscript", "fmt_superscript", "fmt_underline",
|
||||||
|
|
||||||
# General Button Icons
|
# General Button Icons
|
||||||
"add", "add_document", "backward", "bookmark", "browse", "checked", "close", "cross",
|
"add", "add_document", "backward", "bookmark", "browse", "checked", "close", "copy",
|
||||||
"document", "down", "edit", "export", "font", "forward", "import", "list", "maximise",
|
"cross", "document", "down", "edit", "export", "font", "forward", "import", "list",
|
||||||
"menu", "minimise", "more", "noncheckable", "open", "panel", "quote", "refresh", "remove",
|
"maximise", "menu", "minimise", "more", "noncheckable", "open", "panel", "quote",
|
||||||
"revert", "search_replace", "search", "settings", "star", "unchecked", "up", "view",
|
"refresh", "remove", "revert", "search_replace", "search", "settings", "star", "unchecked",
|
||||||
|
"up", "view",
|
||||||
|
|
||||||
# Switches
|
# Switches
|
||||||
"sticky-on", "sticky-off",
|
"sticky-on", "sticky-off",
|
||||||
|
|||||||
@@ -116,6 +116,11 @@ class GuiManuscript(NToolDialog):
|
|||||||
self.tbDel.setStyleSheet(buttonStyle)
|
self.tbDel.setStyleSheet(buttonStyle)
|
||||||
self.tbDel.clicked.connect(self._deleteSelectedBuild)
|
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 = NIconToolButton(self, iSz, "edit")
|
||||||
self.tbEdit.setToolTip(self.tr("Edit Selected Build"))
|
self.tbEdit.setToolTip(self.tr("Edit Selected Build"))
|
||||||
self.tbEdit.setStyleSheet(buttonStyle)
|
self.tbEdit.setStyleSheet(buttonStyle)
|
||||||
@@ -128,6 +133,7 @@ class GuiManuscript(NToolDialog):
|
|||||||
self.listToolBox.addStretch(1)
|
self.listToolBox.addStretch(1)
|
||||||
self.listToolBox.addWidget(self.tbAdd)
|
self.listToolBox.addWidget(self.tbAdd)
|
||||||
self.listToolBox.addWidget(self.tbDel)
|
self.listToolBox.addWidget(self.tbDel)
|
||||||
|
self.listToolBox.addWidget(self.tbCopy)
|
||||||
self.listToolBox.addWidget(self.tbEdit)
|
self.listToolBox.addWidget(self.tbEdit)
|
||||||
self.listToolBox.setSpacing(0)
|
self.listToolBox.setSpacing(0)
|
||||||
|
|
||||||
@@ -291,6 +297,17 @@ class GuiManuscript(NToolDialog):
|
|||||||
self._openSettingsDialog(build)
|
self._openSettingsDialog(build)
|
||||||
return
|
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*")
|
@pyqtSlot("QListWidgetItem*", "QListWidgetItem*")
|
||||||
def _updateBuildDetails(self, current: QListWidgetItem, previous: QListWidgetItem) -> None:
|
def _updateBuildDetails(self, current: QListWidgetItem, previous: QListWidgetItem) -> None:
|
||||||
"""Process change of build selection to update the details."""
|
"""Process change of build selection to update the details."""
|
||||||
@@ -460,9 +477,8 @@ class GuiManuscript(NToolDialog):
|
|||||||
|
|
||||||
def _updateBuildItem(self, build: BuildSettings) -> None:
|
def _updateBuildItem(self, build: BuildSettings) -> None:
|
||||||
"""Update the entry of a specific build item."""
|
"""Update the entry of a specific build item."""
|
||||||
bItem = self._buildMap.get(build.buildID, None)
|
if item := self._buildMap.get(build.buildID):
|
||||||
if isinstance(bItem, QListWidgetItem):
|
item.setText(build.name)
|
||||||
bItem.setText(build.name)
|
|
||||||
else: # Probably a new item
|
else: # Probably a new item
|
||||||
self._updateBuildsList()
|
self._updateBuildsList()
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -465,3 +465,45 @@ def testCoreBuildSettings_Collection(monkeypatch, mockGUI, fncPath: Path, mockRn
|
|||||||
]
|
]
|
||||||
assert another.lastBuild == buildIDOne
|
assert another.lastBuild == buildIDOne
|
||||||
assert another.defaultBuild == buildIDTwo
|
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 build.name == "Test Build"
|
||||||
assert manus.buildList.count() == 1
|
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
|
# Close the dialog should also close the child dialogs
|
||||||
manus.btnClose.click()
|
manus.btnClose.click()
|
||||||
if isinstance(bSettings, GuiBuildSettings):
|
if isinstance(bSettings, GuiBuildSettings):
|
||||||
|
|||||||
Reference in New Issue
Block a user