Add a Headings format edit form

This commit is contained in:
Veronica Berglyd Olsen
2023-05-18 19:30:42 +02:00
parent 79a28b9e43
commit a212900554
3 changed files with 244 additions and 25 deletions
+13
View File
@@ -203,6 +203,19 @@ class nwLabels:
# END Class nwLabels
class nwHeadingFormats:
TITLE = "{Title}"
CH_NUM = "{Chapter}"
CH_WORD = "{Chapter:Word}"
CH_ROMU = "{Chapter:RomanU}"
CH_ROML = "{Chapter:RomanL}"
SC_NUM = "{Scene}"
SC_ABS = "{Scene:Abs}"
# END Class nwHeadingFormats
class nwQuotes:
"""Allowed quotation marks.
Source: https://en.wikipedia.org/wiki/Quotation_mark
+8 -7
View File
@@ -31,6 +31,7 @@ from enum import Enum
from PyQt5.QtCore import QT_TRANSLATE_NOOP
from novelwriter.common import checkUuid, isHandle
from novelwriter.constants import nwHeadingFormats
from novelwriter.core.item import NWItem
from novelwriter.core.project import NWProject
@@ -45,13 +46,13 @@ SETTINGS_TEMPLATE = {
"filter.includeNovel": (bool, True),
"filter.includeNotes": (bool, False),
"filter.includeInactive": (bool, False),
"headings.fmtTitle": (str, "%title%"),
"headings.fmtChapter": (str, "%title%"),
"headings.fmtUnnumbered": (str, "%title%"),
"headings.fmtScene": (str, "%title%"),
"headings.fmtSection": (str, "%title%"),
"headings.fmtTitle": (str, nwHeadingFormats.TITLE),
"headings.fmtChapter": (str, nwHeadingFormats.TITLE),
"headings.fmtUnnumbered": (str, nwHeadingFormats.TITLE),
"headings.fmtScene": (str, "* * *"),
"headings.fmtSection": (str, ""),
"headings.hideScene": (bool, False),
"headings.hideSection": (bool, False),
"headings.hideSection": (bool, True),
"text.includeSynopsis": (bool, False),
"text.includeComments": (bool, False),
"text.includeKeywords": (bool, False),
@@ -222,8 +223,8 @@ class BuildSettings:
return False
if len(definition) == 4:
value = min(max(value, definition[2]), definition[3])
self._changed = value != self._settings[key]
self._settings[key] = value
self._changed = True
logger.debug(f"Build Setting '{key}' set to: {value}")
return True
+223 -18
View File
@@ -28,16 +28,17 @@ import logging
from typing import TYPE_CHECKING
from PyQt5.QtGui import QIcon
from PyQt5.QtGui import QColor, QIcon, QSyntaxHighlighter, QTextCharFormat, QTextDocument
from PyQt5.QtCore import QEvent, QSize, Qt, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import (
QAbstractButton, QAbstractItemView, QDialog, QDialogButtonBox, QGridLayout,
QHBoxLayout, QHeaderView, QLabel, QLineEdit, QPushButton, QSplitter,
QHBoxLayout, QHeaderView, QLabel, QLineEdit, QMenu, QPlainTextEdit, QPushButton, QSplitter,
QStackedWidget, QToolButton, QTreeWidget, QTreeWidgetItem, QVBoxLayout,
QWidget
)
from novelwriter import CONFIG
from novelwriter.constants import nwHeadingFormats
from novelwriter.core.buildsettings import BuildSettings, FilterMode
from novelwriter.extensions.switch import NSwitch
from novelwriter.extensions.switchbox import NSwitchBox
@@ -45,6 +46,7 @@ from novelwriter.extensions.pagedsidebar import NPagedSideBar
if TYPE_CHECKING:
from novelwriter.guimain import GuiMain
from novelwriter.gui.theme import GuiTheme
logger = logging.getLogger(__name__)
@@ -96,14 +98,14 @@ class GuiBuildSettings(QDialog):
self.optSideBar.setMaximumWidth(mPx)
self.optSideBar.setLabelColor(self.mainTheme.helpText)
self.optSideBar.addLabel(self.tr("Options"))
self.optSideBar.addLabel(self.tr("Content"))
self.optSideBar.addButton(self.tr("Filters"), self.OPT_FILTERS)
self.optSideBar.addButton(self.tr("Headings"), self.OPT_HEADINGS)
self.optSideBar.addButton(self.tr("Format"), self.OPT_FORMAT)
self.optSideBar.addButton(self.tr("Content"), self.OPT_CONTENT)
self.optSideBar.addSeparator()
self.optSideBar.addLabel(self.tr("Build"))
self.optSideBar.addLabel(self.tr("Output"))
self.optSideBar.addButton(self.tr("HTML"), self.BLD_HTML)
self.optSideBar.addButton(self.tr("Markdown"), self.BLD_MARKDOWN)
self.optSideBar.addButton(self.tr("Open Document"), self.BLD_ODT)
@@ -173,7 +175,8 @@ class GuiBuildSettings(QDialog):
"""Populate the child widgets.
"""
self.editBuildName.setText(self._build.name)
self.optTabSelect.loadContent(self._build)
self.optTabSelect.loadContent()
self.optTabHeadings.loadContent()
return
##
@@ -368,10 +371,9 @@ class GuiBuildFilterTab(QWidget):
return
def loadContent(self, build: BuildSettings):
def loadContent(self):
"""Populate the widgets.
"""
self._build = build
self._populateTree()
self._populateFilters()
return
@@ -540,6 +542,12 @@ class GuiBuildFilterTab(QWidget):
class GuiBuildHeadingsTab(QWidget):
EDIT_TITLE = 1
EDIT_CHAPTER = 2
EDIT_UNNUM = 3
EDIT_SCENE = 4
EDIT_SECTION = 5
def __init__(self, buildMain: GuiBuildSettings, build: BuildSettings):
super().__init__(parent=buildMain)
@@ -548,6 +556,7 @@ class GuiBuildHeadingsTab(QWidget):
self.theProject = buildMain.mainGui.theProject
self._build = build
self._editing = 0
iPx = self.mainTheme.baseIconSize
vSp = CONFIG.pxInt(12)
@@ -564,6 +573,7 @@ class GuiBuildHeadingsTab(QWidget):
self.fmtTitle.setEnabled(False)
self.btnTitle = QToolButton()
self.btnTitle.setIcon(self.mainTheme.getIcon("edit"))
self.btnTitle.clicked.connect(lambda: self._editHeading(self.EDIT_TITLE))
wrapTitle = QHBoxLayout()
wrapTitle.addWidget(self.fmtTitle)
@@ -579,6 +589,7 @@ class GuiBuildHeadingsTab(QWidget):
self.fmtChapter.setEnabled(False)
self.btnChapter = QToolButton()
self.btnChapter.setIcon(self.mainTheme.getIcon("edit"))
self.btnChapter.clicked.connect(lambda: self._editHeading(self.EDIT_CHAPTER))
wrapChapter = QHBoxLayout()
wrapChapter.addWidget(self.fmtChapter)
@@ -589,19 +600,20 @@ class GuiBuildHeadingsTab(QWidget):
self.formatBox.addLayout(wrapChapter, 1, 1, Qt.AlignLeft)
# Unnumbered Chapter Heading
self.lblUnChapter = QLabel(BuildSettings.getLabel("headings.fmtUnnumbered"))
self.fmtUnChapter = QLineEdit("")
self.fmtUnChapter.setEnabled(False)
self.btnUnChapter = QToolButton()
self.btnUnChapter.setIcon(self.mainTheme.getIcon("edit"))
self.lblUnnumbered = QLabel(BuildSettings.getLabel("headings.fmtUnnumbered"))
self.fmtUnnumbered = QLineEdit("")
self.fmtUnnumbered.setEnabled(False)
self.btnUnnumbered = QToolButton()
self.btnUnnumbered.setIcon(self.mainTheme.getIcon("edit"))
self.btnUnnumbered.clicked.connect(lambda: self._editHeading(self.EDIT_UNNUM))
wrapUnChapter = QHBoxLayout()
wrapUnChapter.addWidget(self.fmtUnChapter)
wrapUnChapter.addWidget(self.btnUnChapter)
wrapUnChapter.setSpacing(bSp)
wrapUnnumbered = QHBoxLayout()
wrapUnnumbered.addWidget(self.fmtUnnumbered)
wrapUnnumbered.addWidget(self.btnUnnumbered)
wrapUnnumbered.setSpacing(bSp)
self.formatBox.addWidget(self.lblUnChapter, 2, 0, Qt.AlignLeft)
self.formatBox.addLayout(wrapUnChapter, 2, 1, Qt.AlignLeft)
self.formatBox.addWidget(self.lblUnnumbered, 2, 0, Qt.AlignLeft)
self.formatBox.addLayout(wrapUnnumbered, 2, 1, Qt.AlignLeft)
# Scene Heading
self.lblScene = QLabel(BuildSettings.getLabel("headings.fmtScene"))
@@ -609,7 +621,9 @@ class GuiBuildHeadingsTab(QWidget):
self.fmtScene.setEnabled(False)
self.btnScene = QToolButton()
self.btnScene.setIcon(self.mainTheme.getIcon("edit"))
self.btnScene.clicked.connect(lambda: self._editHeading(self.EDIT_SCENE))
self.swtScene = NSwitch(width=2*iPx, height=iPx)
self.swtScene.toggled.connect(self._hideScene)
wrapScene = QHBoxLayout()
wrapScene.addWidget(self.fmtScene)
@@ -631,7 +645,9 @@ class GuiBuildHeadingsTab(QWidget):
self.fmtSection.setEnabled(False)
self.btnSection = QToolButton()
self.btnSection.setIcon(self.mainTheme.getIcon("edit"))
self.btnSection.clicked.connect(lambda: self._editHeading(self.EDIT_SECTION))
self.swtSection = NSwitch(width=2*iPx, height=iPx)
self.swtSection.toggled.connect(self._hideSection)
wrapSection = QHBoxLayout()
wrapSection.addWidget(self.fmtSection)
@@ -647,17 +663,165 @@ class GuiBuildHeadingsTab(QWidget):
self.formatBox.addLayout(wrapSection, 4, 1, Qt.AlignLeft)
self.formatBox.addLayout(wrapSectionHide, 4, 2, Qt.AlignLeft)
# Edit Form
# =========
self.lblEditForm = QLabel(self.tr("Editing: {0}").format(self.tr("None")))
self.editTextBox = QPlainTextEdit()
self.editTextBox.setFixedHeight(5*iPx)
self.editTextBox.setEnabled(False)
self.formSyntax = GuiHeadingSyntax(self.editTextBox.document(), self.mainTheme)
self.menuInsert = QMenu()
self.aInsTitle = self.menuInsert.addAction(self.tr("Title"))
self.aInsChNum = self.menuInsert.addAction(self.tr("Chapter Number"))
self.aInsChWord = self.menuInsert.addAction(self.tr("Chapter Number (Word)"))
self.aInsChRomU = self.menuInsert.addAction(self.tr("Chapter Number (Upper Case Roman)"))
self.aInsChRomL = self.menuInsert.addAction(self.tr("Chapter Number (Lower Case Roman)"))
self.aInsScNum = self.menuInsert.addAction(self.tr("Scene Number (In Chapter)"))
self.aInsScAbs = self.menuInsert.addAction(self.tr("Scene Number (Absolute)"))
self.aInsTitle.triggered.connect(lambda: self._insertIntoForm(nwHeadingFormats.TITLE))
self.aInsChNum.triggered.connect(lambda: self._insertIntoForm(nwHeadingFormats.CH_NUM))
self.aInsChWord.triggered.connect(lambda: self._insertIntoForm(nwHeadingFormats.CH_WORD))
self.aInsChRomU.triggered.connect(lambda: self._insertIntoForm(nwHeadingFormats.CH_ROMU))
self.aInsChRomL.triggered.connect(lambda: self._insertIntoForm(nwHeadingFormats.CH_ROML))
self.aInsScNum.triggered.connect(lambda: self._insertIntoForm(nwHeadingFormats.SC_NUM))
self.aInsScAbs.triggered.connect(lambda: self._insertIntoForm(nwHeadingFormats.SC_ABS))
self.btnInsert = QPushButton(self.tr("Insert"))
self.btnInsert.setMenu(self.menuInsert)
self.btnApply = QPushButton(self.tr("Apply"))
self.btnApply.clicked.connect(self._saveFormat)
self.formButtonBox = QHBoxLayout()
self.formButtonBox.addStretch(1)
self.formButtonBox.addWidget(self.btnInsert)
self.formButtonBox.addWidget(self.btnApply)
self.editFormBox = QVBoxLayout()
self.editFormBox.addWidget(self.lblEditForm)
self.editFormBox.addWidget(self.editTextBox)
self.editFormBox.addLayout(self.formButtonBox)
# Assemble
# ========
self.outerBox = QVBoxLayout()
self.outerBox.addLayout(self.formatBox)
self.outerBox.addSpacing(CONFIG.pxInt(16))
self.outerBox.addLayout(self.editFormBox)
self.outerBox.addStretch(1)
self.setLayout(self.outerBox)
return
def loadContent(self):
"""Populate the widgets.
"""
self.fmtTitle.setText(str(self._build.getValue("headings.fmtTitle")))
self.fmtChapter.setText(str(self._build.getValue("headings.fmtChapter")))
self.fmtUnnumbered.setText(str(self._build.getValue("headings.fmtUnnumbered")))
self.fmtScene.setText(str(self._build.getValue("headings.fmtScene")))
self.fmtSection.setText(str(self._build.getValue("headings.fmtSection")))
self.swtScene.setChecked(bool(self._build.getValue("headings.hideScene")))
self.swtSection.setChecked(bool(self._build.getValue("headings.hideSection")))
return
##
# Internal Functions
##
def _insertIntoForm(self, text: str):
"""Insert formatting text from the dropdown menu.
"""
if self._editing > 0:
cursor = self.editTextBox.textCursor()
cursor.insertText(text)
self.editTextBox.setFocus()
return
def _editHeading(self, heading: int):
"""Populate the form with a specific heading format.
"""
self._editing = heading
self.editTextBox.setEnabled(True)
if heading == self.EDIT_TITLE:
text = self.fmtTitle.text()
label = self._build.getLabel("headings.fmtTitle")
elif heading == self.EDIT_CHAPTER:
text = self.fmtChapter.text()
label = self._build.getLabel("headings.fmtChapter")
elif heading == self.EDIT_UNNUM:
text = self.fmtUnnumbered.text()
label = self._build.getLabel("headings.fmtUnnumbered")
elif heading == self.EDIT_SCENE:
text = self.fmtScene.text()
label = self._build.getLabel("headings.fmtScene")
elif heading == self.EDIT_SECTION:
text = self.fmtSection.text()
label = self._build.getLabel("headings.fmtSection")
else:
self._editing = 0
self.editTextBox.setEnabled(False)
text = ""
label = self.tr("None")
self.editTextBox.setPlainText(text.replace("//", "\n"))
self.lblEditForm.setText(self.tr("Editing: {0}").format(label))
return
##
# Private Slots
##
def _saveFormat(self):
"""Save the format from the edit text box.
"""
heading = self._editing
text = self.editTextBox.toPlainText().replace("\n", "//")
if heading == self.EDIT_TITLE:
self.fmtTitle.setText(text)
self._build.setValue("headings.fmtTitle", text)
elif heading == self.EDIT_CHAPTER:
self.fmtChapter.setText(text)
self._build.setValue("headings.fmtChapter", text)
elif heading == self.EDIT_UNNUM:
self.fmtUnnumbered.setText(text)
self._build.setValue("headings.fmtUnnumbered", text)
elif heading == self.EDIT_SCENE:
self.fmtScene.setText(text)
self._build.setValue("headings.fmtScene", text)
elif heading == self.EDIT_SECTION:
self.fmtSection.setText(text)
self._build.setValue("headings.fmtSection", text)
else:
return
self._editHeading(0)
self.editTextBox.clear()
return
@pyqtSlot(bool)
def _hideScene(self, status):
"""Hide scene heading
"""
self._build.setValue("headings.hideScene", status)
return
@pyqtSlot(bool)
def _hideSection(self, status):
"""Hide section heading
"""
self._build.setValue("headings.hideSection", status)
return
# END Class GuiBuildHeadingsTab
@@ -719,3 +883,44 @@ class GuiBuildODTTab(QWidget):
return
# END Class GuiBuildODTTab
class GuiHeadingSyntax(QSyntaxHighlighter):
def __init__(self, document: QTextDocument, mainTheme: GuiTheme):
super().__init__(document)
self._valid = [
nwHeadingFormats.TITLE.lower(),
nwHeadingFormats.CH_NUM.lower(),
nwHeadingFormats.CH_WORD.lower(),
nwHeadingFormats.CH_ROMU.lower(),
nwHeadingFormats.CH_ROML.lower(),
nwHeadingFormats.SC_NUM.lower(),
nwHeadingFormats.SC_ABS.lower(),
]
self._fmtSymbol = QTextCharFormat()
self._fmtSymbol.setForeground(QColor(*mainTheme.colHead))
self._fmtFormat = QTextCharFormat()
self._fmtFormat.setForeground(QColor(*mainTheme.colEmph))
return
def highlightBlock(self, text: str):
"""Add syntax highlighting to the text block.
"""
check = text.lower()
for heading in self._valid:
pos = check.find(heading)
if pos >= 0:
chars = len(heading)
self.setFormat(pos, chars, self._fmtSymbol)
self.setFormat(pos + 1, chars - 2, self._fmtFormat)
ddots = heading.find(":")
if ddots > 0:
self.setFormat(pos + ddots, 1, self._fmtSymbol)
return
# END Class GuiHeadingSyntax