Add list of builds to manuscript build dialog
This commit is contained in:
@@ -85,6 +85,7 @@ class nwFiles:
|
|||||||
OPTS_FILE = "guiOptions.json"
|
OPTS_FILE = "guiOptions.json"
|
||||||
RECENT_FILE = "recentProjects.json"
|
RECENT_FILE = "recentProjects.json"
|
||||||
BUILD_CACHE = "prevBuild.json"
|
BUILD_CACHE = "prevBuild.json"
|
||||||
|
BUILDS_FILE = "builds.json"
|
||||||
|
|
||||||
# END Class nwFiles
|
# END Class nwFiles
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ novelWriter – Build Settings Class
|
|||||||
A class to hold build settings for the build tool
|
A class to hold build settings for the build tool
|
||||||
|
|
||||||
File History:
|
File History:
|
||||||
Created: 2023-02-14 [2.1b1]
|
Created: 2023-02-14 [2.1b1] BuildSettings
|
||||||
|
Created: 2023-05-22 [2.1b1] BuildCollection
|
||||||
|
|
||||||
This file is a part of novelWriter
|
This file is a part of novelWriter
|
||||||
Copyright 2018–2023, Veronica Berglyd Olsen
|
Copyright 2018–2023, Veronica Berglyd Olsen
|
||||||
@@ -24,17 +25,21 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
import uuid
|
import uuid
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
from typing import Iterable
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from PyQt5.QtCore import QT_TRANSLATE_NOOP
|
from PyQt5.QtCore import QT_TRANSLATE_NOOP
|
||||||
|
|
||||||
from novelwriter.common import checkUuid, isHandle
|
from novelwriter.common import checkUuid, isHandle, jsonEncode
|
||||||
from novelwriter.constants import nwHeadingFormats
|
from novelwriter.constants import nwFiles, nwHeadingFormats
|
||||||
from novelwriter.core.item import NWItem
|
from novelwriter.core.item import NWItem
|
||||||
from novelwriter.core.project import NWProject
|
from novelwriter.core.project import NWProject
|
||||||
|
from novelwriter.error import logException
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -305,18 +310,21 @@ class BuildSettings:
|
|||||||
"name": self._name,
|
"name": self._name,
|
||||||
"uuid": self._uuid,
|
"uuid": self._uuid,
|
||||||
"settings": self._settings.copy(),
|
"settings": self._settings.copy(),
|
||||||
"included": list(self._included),
|
"content": {
|
||||||
"excluded": list(self._excluded),
|
"included": list(self._included),
|
||||||
"skipRoot": list(self._skipRoot),
|
"excluded": list(self._excluded),
|
||||||
|
"skipRoot": list(self._skipRoot),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def unpack(self, data: dict):
|
def unpack(self, data: dict):
|
||||||
"""Unpack a dictionary and populate the class.
|
"""Unpack a dictionary and populate the class.
|
||||||
"""
|
"""
|
||||||
included = data.get("included", [])
|
|
||||||
excluded = data.get("excluded", [])
|
|
||||||
skipRoot = data.get("skipRoot", [])
|
|
||||||
settings = data.get("settings", {})
|
settings = data.get("settings", {})
|
||||||
|
content = data.get("content", {})
|
||||||
|
included = content.get("included", [])
|
||||||
|
excluded = content.get("excluded", [])
|
||||||
|
skipRoot = content.get("skipRoot", [])
|
||||||
|
|
||||||
self.setName(data.get("name", ""))
|
self.setName(data.get("name", ""))
|
||||||
self.setBuildID(data.get("uuid", ""))
|
self.setBuildID(data.get("uuid", ""))
|
||||||
@@ -337,3 +345,90 @@ class BuildSettings:
|
|||||||
return
|
return
|
||||||
|
|
||||||
# END Class BuildSettings
|
# END Class BuildSettings
|
||||||
|
|
||||||
|
|
||||||
|
class BuildCollection:
|
||||||
|
|
||||||
|
def __init__(self, project: NWProject):
|
||||||
|
self._project = project
|
||||||
|
self._builds = {}
|
||||||
|
return
|
||||||
|
|
||||||
|
def getBuild(self, buildID: str) -> BuildSettings | None:
|
||||||
|
"""
|
||||||
|
"""
|
||||||
|
if buildID not in self._builds:
|
||||||
|
return None
|
||||||
|
build = BuildSettings()
|
||||||
|
build.unpack(self._builds[buildID])
|
||||||
|
return build
|
||||||
|
|
||||||
|
def setBuild(self, build: BuildSettings) -> bool:
|
||||||
|
"""
|
||||||
|
"""
|
||||||
|
if not isinstance(build, BuildSettings):
|
||||||
|
return False
|
||||||
|
buildID = build.buildID
|
||||||
|
self._builds[buildID] = build.pack()
|
||||||
|
return True
|
||||||
|
|
||||||
|
def builds(self) -> Iterable[tuple[str, str]]:
|
||||||
|
"""
|
||||||
|
"""
|
||||||
|
for buildID in self._builds:
|
||||||
|
yield buildID, self._builds[buildID].get("name", "")
|
||||||
|
return
|
||||||
|
|
||||||
|
def loadCollection(self) -> bool:
|
||||||
|
"""
|
||||||
|
"""
|
||||||
|
buildsFile = self._project.storage.getMetaFile(nwFiles.BUILDS_FILE)
|
||||||
|
if not isinstance(buildsFile, Path):
|
||||||
|
return False
|
||||||
|
|
||||||
|
data = {}
|
||||||
|
if buildsFile.exists():
|
||||||
|
logger.debug("Loading builds file")
|
||||||
|
try:
|
||||||
|
with open(buildsFile, mode="r", encoding="utf-8") as inFile:
|
||||||
|
data = json.load(inFile)
|
||||||
|
except Exception:
|
||||||
|
logger.error("Failed to load builds file")
|
||||||
|
logException()
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not isinstance(data, dict):
|
||||||
|
logger.error("Builds file is not a JSON object")
|
||||||
|
return False
|
||||||
|
|
||||||
|
builds = data.get("novelWriter.builds", None)
|
||||||
|
if not isinstance(builds, dict):
|
||||||
|
logger.error("No novelWriter.builds in the builds file")
|
||||||
|
return False
|
||||||
|
|
||||||
|
for key, entry in builds.items():
|
||||||
|
if isinstance(entry, dict):
|
||||||
|
self._builds[key] = entry
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def saveCollection(self) -> bool:
|
||||||
|
"""
|
||||||
|
"""
|
||||||
|
buildsFile = self._project.storage.getMetaFile(nwFiles.BUILDS_FILE)
|
||||||
|
if not isinstance(buildsFile, Path):
|
||||||
|
return False
|
||||||
|
|
||||||
|
logger.debug("Saving builds file")
|
||||||
|
try:
|
||||||
|
data = {"novelWriter.builds": self._builds}
|
||||||
|
with open(buildsFile, mode="w+", encoding="utf-8") as outFile:
|
||||||
|
outFile.write(jsonEncode(data, nmax=4))
|
||||||
|
except Exception:
|
||||||
|
logger.error("Failed to save builds file")
|
||||||
|
logException()
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
# END Class BuildCollection
|
||||||
|
|||||||
@@ -28,14 +28,14 @@ import logging
|
|||||||
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtSlot
|
from PyQt5.QtCore import Qt, pyqtSlot
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
QDialog, QGridLayout, QPushButton, QSplitter, QTextBrowser, QVBoxLayout,
|
QDialog, QHBoxLayout, QListWidget, QListWidgetItem, QPushButton, QSplitter,
|
||||||
QWidget, qApp
|
QTextBrowser, QVBoxLayout, QWidget, qApp
|
||||||
)
|
)
|
||||||
|
|
||||||
from novelwriter import CONFIG
|
from novelwriter import CONFIG
|
||||||
from novelwriter.core.buildsettings import BuildSettings
|
from novelwriter.core.buildsettings import BuildCollection, BuildSettings
|
||||||
from novelwriter.tools.manussettings import GuiBuildSettings
|
from novelwriter.tools.manussettings import GuiBuildSettings
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@@ -53,6 +53,9 @@ class GuiBuildManuscript(QDialog):
|
|||||||
self.mainTheme = mainGui.mainTheme
|
self.mainTheme = mainGui.mainTheme
|
||||||
self.theProject = mainGui.theProject
|
self.theProject = mainGui.theProject
|
||||||
|
|
||||||
|
self._builds = BuildCollection(self.theProject)
|
||||||
|
self._buildMap = {}
|
||||||
|
|
||||||
self.setWindowTitle(self.tr("Build Manuscript"))
|
self.setWindowTitle(self.tr("Build Manuscript"))
|
||||||
self.setMinimumWidth(CONFIG.pxInt(600))
|
self.setMinimumWidth(CONFIG.pxInt(600))
|
||||||
self.setMinimumHeight(CONFIG.pxInt(500))
|
self.setMinimumHeight(CONFIG.pxInt(500))
|
||||||
@@ -69,19 +72,32 @@ class GuiBuildManuscript(QDialog):
|
|||||||
# Controls
|
# Controls
|
||||||
# ========
|
# ========
|
||||||
|
|
||||||
self.btnNew = QPushButton(self.tr("New Build"))
|
self.buildList = QListWidget()
|
||||||
|
|
||||||
|
self.btnNew = QPushButton(self.tr("New"))
|
||||||
self.btnNew.clicked.connect(self._createNewBuild)
|
self.btnNew.clicked.connect(self._createNewBuild)
|
||||||
|
|
||||||
self.optsGrid = QGridLayout()
|
self.btnEdit = QPushButton(self.tr("Edit"))
|
||||||
self.optsGrid.addWidget(self.btnNew, 0, 0)
|
self.btnEdit.clicked.connect(self._editSelectedBuild)
|
||||||
|
|
||||||
|
self.btnDel = QPushButton(self.tr("Delete"))
|
||||||
|
|
||||||
|
self.buttonBox = QHBoxLayout()
|
||||||
|
self.buttonBox.addWidget(self.btnNew)
|
||||||
|
self.buttonBox.addWidget(self.btnEdit)
|
||||||
|
self.buttonBox.addWidget(self.btnDel)
|
||||||
|
|
||||||
self.manPreview = GuiManuscriptPreview(self)
|
self.manPreview = GuiManuscriptPreview(self)
|
||||||
|
|
||||||
# Assemble GUI
|
# Assemble GUI
|
||||||
# ============
|
# ============
|
||||||
|
|
||||||
|
self.controlBox = QVBoxLayout()
|
||||||
|
self.controlBox.addWidget(self.buildList)
|
||||||
|
self.controlBox.addLayout(self.buttonBox)
|
||||||
|
|
||||||
self.optsWidget = QWidget()
|
self.optsWidget = QWidget()
|
||||||
self.optsWidget.setLayout(self.optsGrid)
|
self.optsWidget.setLayout(self.controlBox)
|
||||||
|
|
||||||
self.mainSplit = QSplitter()
|
self.mainSplit = QSplitter()
|
||||||
self.mainSplit.addWidget(self.optsWidget)
|
self.mainSplit.addWidget(self.optsWidget)
|
||||||
@@ -101,6 +117,8 @@ class GuiBuildManuscript(QDialog):
|
|||||||
def loadContent(self):
|
def loadContent(self):
|
||||||
"""
|
"""
|
||||||
"""
|
"""
|
||||||
|
self._builds.loadCollection()
|
||||||
|
self._updateBuildsList()
|
||||||
return
|
return
|
||||||
|
|
||||||
##
|
##
|
||||||
@@ -124,22 +142,27 @@ class GuiBuildManuscript(QDialog):
|
|||||||
"""
|
"""
|
||||||
build = BuildSettings()
|
build = BuildSettings()
|
||||||
build.setName(self.tr("My Manuscript"))
|
build.setName(self.tr("My Manuscript"))
|
||||||
|
self._openSettingsDialog(build)
|
||||||
dlgSettings = GuiBuildSettings(self.mainGui, build)
|
|
||||||
dlgSettings.setModal(False)
|
|
||||||
dlgSettings.show()
|
|
||||||
dlgSettings.raise_()
|
|
||||||
qApp.processEvents()
|
|
||||||
dlgSettings.loadContent()
|
|
||||||
dlgSettings.newSettingsReady.connect(self._processNewSettings)
|
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@pyqtSlot(dict)
|
@pyqtSlot()
|
||||||
def _processNewSettings(self, data: dict):
|
def _editSelectedBuild(self):
|
||||||
|
"""Edit the currently selected build settings entry.
|
||||||
"""
|
"""
|
||||||
|
bItems = self.buildList.selectedItems()
|
||||||
|
if bItems:
|
||||||
|
build = self._builds.getBuild(bItems[0].data(Qt.UserRole))
|
||||||
|
if isinstance(build, BuildSettings):
|
||||||
|
self._openSettingsDialog(build)
|
||||||
|
return
|
||||||
|
|
||||||
|
@pyqtSlot(BuildSettings)
|
||||||
|
def _processNewSettings(self, build: BuildSettings):
|
||||||
|
"""Process new build settings from the settings dialog.
|
||||||
"""
|
"""
|
||||||
print(data)
|
self._builds.setBuild(build)
|
||||||
|
self._builds.saveCollection()
|
||||||
|
self._updateBuildItem(build)
|
||||||
return
|
return
|
||||||
|
|
||||||
##
|
##
|
||||||
@@ -167,6 +190,40 @@ class GuiBuildManuscript(QDialog):
|
|||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def _openSettingsDialog(self, build: BuildSettings):
|
||||||
|
"""Open a new build settings dialog.
|
||||||
|
"""
|
||||||
|
dlgSettings = GuiBuildSettings(self.mainGui, build)
|
||||||
|
dlgSettings.setModal(False)
|
||||||
|
dlgSettings.show()
|
||||||
|
dlgSettings.raise_()
|
||||||
|
qApp.processEvents()
|
||||||
|
dlgSettings.loadContent()
|
||||||
|
dlgSettings.newSettingsReady.connect(self._processNewSettings)
|
||||||
|
return
|
||||||
|
|
||||||
|
def _updateBuildsList(self):
|
||||||
|
"""Update the list of available builds.
|
||||||
|
"""
|
||||||
|
self.buildList.clear()
|
||||||
|
for key, name in self._builds.builds():
|
||||||
|
bItem = QListWidgetItem()
|
||||||
|
bItem.setText(name)
|
||||||
|
bItem.setData(Qt.UserRole, key)
|
||||||
|
self.buildList.addItem(bItem)
|
||||||
|
self._buildMap[key] = bItem
|
||||||
|
return
|
||||||
|
|
||||||
|
def _updateBuildItem(self, build: BuildSettings):
|
||||||
|
"""Update the entry of a specific build item.
|
||||||
|
"""
|
||||||
|
bItem = self._buildMap.get(build.buildID, None)
|
||||||
|
if isinstance(bItem, QListWidgetItem):
|
||||||
|
bItem.setText(build.name)
|
||||||
|
else: # Propbably a new item
|
||||||
|
self._updateBuildsList()
|
||||||
|
return
|
||||||
|
|
||||||
# END Class GuiBuildManuscript
|
# END Class GuiBuildManuscript
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ class GuiBuildSettings(QDialog):
|
|||||||
OPT_CONTENT = 4
|
OPT_CONTENT = 4
|
||||||
OPT_OUTPUT = 5
|
OPT_OUTPUT = 5
|
||||||
|
|
||||||
newSettingsReady = pyqtSignal(dict)
|
newSettingsReady = pyqtSignal(BuildSettings)
|
||||||
|
|
||||||
def __init__(self, mainGui: GuiMain, build: BuildSettings):
|
def __init__(self, mainGui: GuiMain, build: BuildSettings):
|
||||||
super().__init__(parent=mainGui)
|
super().__init__(parent=mainGui)
|
||||||
@@ -194,7 +194,7 @@ class GuiBuildSettings(QDialog):
|
|||||||
role = self.dlgButtons.buttonRole(button)
|
role = self.dlgButtons.buttonRole(button)
|
||||||
if role in (QDialogButtonBox.ApplyRole, QDialogButtonBox.AcceptRole):
|
if role in (QDialogButtonBox.ApplyRole, QDialogButtonBox.AcceptRole):
|
||||||
self._build.setName(self.editBuildName.text())
|
self._build.setName(self.editBuildName.text())
|
||||||
self.newSettingsReady.emit(self._build.pack())
|
self.newSettingsReady.emit(self._build)
|
||||||
|
|
||||||
self._saveSettings()
|
self._saveSettings()
|
||||||
if role == QDialogButtonBox.AcceptRole:
|
if role == QDialogButtonBox.AcceptRole:
|
||||||
|
|||||||
Reference in New Issue
Block a user