Add list of builds to manuscript build dialog
This commit is contained in:
@@ -85,6 +85,7 @@ class nwFiles:
|
||||
OPTS_FILE = "guiOptions.json"
|
||||
RECENT_FILE = "recentProjects.json"
|
||||
BUILD_CACHE = "prevBuild.json"
|
||||
BUILDS_FILE = "builds.json"
|
||||
|
||||
# END Class nwFiles
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@ novelWriter – Build Settings Class
|
||||
A class to hold build settings for the build tool
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
import json
|
||||
import uuid
|
||||
import logging
|
||||
|
||||
from enum import Enum
|
||||
from typing import Iterable
|
||||
from pathlib import Path
|
||||
|
||||
from PyQt5.QtCore import QT_TRANSLATE_NOOP
|
||||
|
||||
from novelwriter.common import checkUuid, isHandle
|
||||
from novelwriter.constants import nwHeadingFormats
|
||||
from novelwriter.common import checkUuid, isHandle, jsonEncode
|
||||
from novelwriter.constants import nwFiles, nwHeadingFormats
|
||||
from novelwriter.core.item import NWItem
|
||||
from novelwriter.core.project import NWProject
|
||||
from novelwriter.error import logException
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -305,18 +310,21 @@ class BuildSettings:
|
||||
"name": self._name,
|
||||
"uuid": self._uuid,
|
||||
"settings": self._settings.copy(),
|
||||
"included": list(self._included),
|
||||
"excluded": list(self._excluded),
|
||||
"skipRoot": list(self._skipRoot),
|
||||
"content": {
|
||||
"included": list(self._included),
|
||||
"excluded": list(self._excluded),
|
||||
"skipRoot": list(self._skipRoot),
|
||||
}
|
||||
}
|
||||
|
||||
def unpack(self, data: dict):
|
||||
"""Unpack a dictionary and populate the class.
|
||||
"""
|
||||
included = data.get("included", [])
|
||||
excluded = data.get("excluded", [])
|
||||
skipRoot = data.get("skipRoot", [])
|
||||
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.setBuildID(data.get("uuid", ""))
|
||||
@@ -337,3 +345,90 @@ class BuildSettings:
|
||||
return
|
||||
|
||||
# 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 PyQt5.QtCore import pyqtSlot
|
||||
from PyQt5.QtCore import Qt, pyqtSlot
|
||||
from PyQt5.QtWidgets import (
|
||||
QDialog, QGridLayout, QPushButton, QSplitter, QTextBrowser, QVBoxLayout,
|
||||
QWidget, qApp
|
||||
QDialog, QHBoxLayout, QListWidget, QListWidgetItem, QPushButton, QSplitter,
|
||||
QTextBrowser, QVBoxLayout, QWidget, qApp
|
||||
)
|
||||
|
||||
from novelwriter import CONFIG
|
||||
from novelwriter.core.buildsettings import BuildSettings
|
||||
from novelwriter.core.buildsettings import BuildCollection, BuildSettings
|
||||
from novelwriter.tools.manussettings import GuiBuildSettings
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -53,6 +53,9 @@ class GuiBuildManuscript(QDialog):
|
||||
self.mainTheme = mainGui.mainTheme
|
||||
self.theProject = mainGui.theProject
|
||||
|
||||
self._builds = BuildCollection(self.theProject)
|
||||
self._buildMap = {}
|
||||
|
||||
self.setWindowTitle(self.tr("Build Manuscript"))
|
||||
self.setMinimumWidth(CONFIG.pxInt(600))
|
||||
self.setMinimumHeight(CONFIG.pxInt(500))
|
||||
@@ -69,19 +72,32 @@ class GuiBuildManuscript(QDialog):
|
||||
# Controls
|
||||
# ========
|
||||
|
||||
self.btnNew = QPushButton(self.tr("New Build"))
|
||||
self.buildList = QListWidget()
|
||||
|
||||
self.btnNew = QPushButton(self.tr("New"))
|
||||
self.btnNew.clicked.connect(self._createNewBuild)
|
||||
|
||||
self.optsGrid = QGridLayout()
|
||||
self.optsGrid.addWidget(self.btnNew, 0, 0)
|
||||
self.btnEdit = QPushButton(self.tr("Edit"))
|
||||
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)
|
||||
|
||||
# Assemble GUI
|
||||
# ============
|
||||
|
||||
self.controlBox = QVBoxLayout()
|
||||
self.controlBox.addWidget(self.buildList)
|
||||
self.controlBox.addLayout(self.buttonBox)
|
||||
|
||||
self.optsWidget = QWidget()
|
||||
self.optsWidget.setLayout(self.optsGrid)
|
||||
self.optsWidget.setLayout(self.controlBox)
|
||||
|
||||
self.mainSplit = QSplitter()
|
||||
self.mainSplit.addWidget(self.optsWidget)
|
||||
@@ -101,6 +117,8 @@ class GuiBuildManuscript(QDialog):
|
||||
def loadContent(self):
|
||||
"""
|
||||
"""
|
||||
self._builds.loadCollection()
|
||||
self._updateBuildsList()
|
||||
return
|
||||
|
||||
##
|
||||
@@ -124,22 +142,27 @@ class GuiBuildManuscript(QDialog):
|
||||
"""
|
||||
build = BuildSettings()
|
||||
build.setName(self.tr("My Manuscript"))
|
||||
|
||||
dlgSettings = GuiBuildSettings(self.mainGui, build)
|
||||
dlgSettings.setModal(False)
|
||||
dlgSettings.show()
|
||||
dlgSettings.raise_()
|
||||
qApp.processEvents()
|
||||
dlgSettings.loadContent()
|
||||
dlgSettings.newSettingsReady.connect(self._processNewSettings)
|
||||
|
||||
self._openSettingsDialog(build)
|
||||
return
|
||||
|
||||
@pyqtSlot(dict)
|
||||
def _processNewSettings(self, data: dict):
|
||||
@pyqtSlot()
|
||||
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
|
||||
|
||||
##
|
||||
@@ -167,6 +190,40 @@ class GuiBuildManuscript(QDialog):
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ class GuiBuildSettings(QDialog):
|
||||
OPT_CONTENT = 4
|
||||
OPT_OUTPUT = 5
|
||||
|
||||
newSettingsReady = pyqtSignal(dict)
|
||||
newSettingsReady = pyqtSignal(BuildSettings)
|
||||
|
||||
def __init__(self, mainGui: GuiMain, build: BuildSettings):
|
||||
super().__init__(parent=mainGui)
|
||||
@@ -194,7 +194,7 @@ class GuiBuildSettings(QDialog):
|
||||
role = self.dlgButtons.buttonRole(button)
|
||||
if role in (QDialogButtonBox.ApplyRole, QDialogButtonBox.AcceptRole):
|
||||
self._build.setName(self.editBuildName.text())
|
||||
self.newSettingsReady.emit(self._build.pack())
|
||||
self.newSettingsReady.emit(self._build)
|
||||
|
||||
self._saveSettings()
|
||||
if role == QDialogButtonBox.AcceptRole:
|
||||
|
||||
Reference in New Issue
Block a user