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 # 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: class nwQuotes:
"""Allowed quotation marks. """Allowed quotation marks.
Source: https://en.wikipedia.org/wiki/Quotation_mark 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 PyQt5.QtCore import QT_TRANSLATE_NOOP
from novelwriter.common import checkUuid, isHandle from novelwriter.common import checkUuid, isHandle
from novelwriter.constants import 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
@@ -45,13 +46,13 @@ SETTINGS_TEMPLATE = {
"filter.includeNovel": (bool, True), "filter.includeNovel": (bool, True),
"filter.includeNotes": (bool, False), "filter.includeNotes": (bool, False),
"filter.includeInactive": (bool, False), "filter.includeInactive": (bool, False),
"headings.fmtTitle": (str, "%title%"), "headings.fmtTitle": (str, nwHeadingFormats.TITLE),
"headings.fmtChapter": (str, "%title%"), "headings.fmtChapter": (str, nwHeadingFormats.TITLE),
"headings.fmtUnnumbered": (str, "%title%"), "headings.fmtUnnumbered": (str, nwHeadingFormats.TITLE),
"headings.fmtScene": (str, "%title%"), "headings.fmtScene": (str, "* * *"),
"headings.fmtSection": (str, "%title%"), "headings.fmtSection": (str, ""),
"headings.hideScene": (bool, False), "headings.hideScene": (bool, False),
"headings.hideSection": (bool, False), "headings.hideSection": (bool, True),
"text.includeSynopsis": (bool, False), "text.includeSynopsis": (bool, False),
"text.includeComments": (bool, False), "text.includeComments": (bool, False),
"text.includeKeywords": (bool, False), "text.includeKeywords": (bool, False),
@@ -222,8 +223,8 @@ class BuildSettings:
return False return False
if len(definition) == 4: if len(definition) == 4:
value = min(max(value, definition[2]), definition[3]) value = min(max(value, definition[2]), definition[3])
self._changed = value != self._settings[key]
self._settings[key] = value self._settings[key] = value
self._changed = True
logger.debug(f"Build Setting '{key}' set to: {value}") logger.debug(f"Build Setting '{key}' set to: {value}")
return True return True
+223 -18
View File
@@ -28,16 +28,17 @@ import logging
from typing import TYPE_CHECKING 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.QtCore import QEvent, QSize, Qt, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QAbstractButton, QAbstractItemView, QDialog, QDialogButtonBox, QGridLayout, QAbstractButton, QAbstractItemView, QDialog, QDialogButtonBox, QGridLayout,
QHBoxLayout, QHeaderView, QLabel, QLineEdit, QPushButton, QSplitter, QHBoxLayout, QHeaderView, QLabel, QLineEdit, QMenu, QPlainTextEdit, QPushButton, QSplitter,
QStackedWidget, QToolButton, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QStackedWidget, QToolButton, QTreeWidget, QTreeWidgetItem, QVBoxLayout,
QWidget QWidget
) )
from novelwriter import CONFIG from novelwriter import CONFIG
from novelwriter.constants import nwHeadingFormats
from novelwriter.core.buildsettings import BuildSettings, FilterMode from novelwriter.core.buildsettings import BuildSettings, FilterMode
from novelwriter.extensions.switch import NSwitch from novelwriter.extensions.switch import NSwitch
from novelwriter.extensions.switchbox import NSwitchBox from novelwriter.extensions.switchbox import NSwitchBox
@@ -45,6 +46,7 @@ from novelwriter.extensions.pagedsidebar import NPagedSideBar
if TYPE_CHECKING: if TYPE_CHECKING:
from novelwriter.guimain import GuiMain from novelwriter.guimain import GuiMain
from novelwriter.gui.theme import GuiTheme
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -96,14 +98,14 @@ class GuiBuildSettings(QDialog):
self.optSideBar.setMaximumWidth(mPx) self.optSideBar.setMaximumWidth(mPx)
self.optSideBar.setLabelColor(self.mainTheme.helpText) 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("Filters"), self.OPT_FILTERS)
self.optSideBar.addButton(self.tr("Headings"), self.OPT_HEADINGS) self.optSideBar.addButton(self.tr("Headings"), self.OPT_HEADINGS)
self.optSideBar.addButton(self.tr("Format"), self.OPT_FORMAT) self.optSideBar.addButton(self.tr("Format"), self.OPT_FORMAT)
self.optSideBar.addButton(self.tr("Content"), self.OPT_CONTENT) self.optSideBar.addButton(self.tr("Content"), self.OPT_CONTENT)
self.optSideBar.addSeparator() 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("HTML"), self.BLD_HTML)
self.optSideBar.addButton(self.tr("Markdown"), self.BLD_MARKDOWN) self.optSideBar.addButton(self.tr("Markdown"), self.BLD_MARKDOWN)
self.optSideBar.addButton(self.tr("Open Document"), self.BLD_ODT) self.optSideBar.addButton(self.tr("Open Document"), self.BLD_ODT)
@@ -173,7 +175,8 @@ class GuiBuildSettings(QDialog):
"""Populate the child widgets. """Populate the child widgets.
""" """
self.editBuildName.setText(self._build.name) self.editBuildName.setText(self._build.name)
self.optTabSelect.loadContent(self._build) self.optTabSelect.loadContent()
self.optTabHeadings.loadContent()
return return
## ##
@@ -368,10 +371,9 @@ class GuiBuildFilterTab(QWidget):
return return
def loadContent(self, build: BuildSettings): def loadContent(self):
"""Populate the widgets. """Populate the widgets.
""" """
self._build = build
self._populateTree() self._populateTree()
self._populateFilters() self._populateFilters()
return return
@@ -540,6 +542,12 @@ class GuiBuildFilterTab(QWidget):
class GuiBuildHeadingsTab(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): def __init__(self, buildMain: GuiBuildSettings, build: BuildSettings):
super().__init__(parent=buildMain) super().__init__(parent=buildMain)
@@ -548,6 +556,7 @@ class GuiBuildHeadingsTab(QWidget):
self.theProject = buildMain.mainGui.theProject self.theProject = buildMain.mainGui.theProject
self._build = build self._build = build
self._editing = 0
iPx = self.mainTheme.baseIconSize iPx = self.mainTheme.baseIconSize
vSp = CONFIG.pxInt(12) vSp = CONFIG.pxInt(12)
@@ -564,6 +573,7 @@ class GuiBuildHeadingsTab(QWidget):
self.fmtTitle.setEnabled(False) self.fmtTitle.setEnabled(False)
self.btnTitle = QToolButton() self.btnTitle = QToolButton()
self.btnTitle.setIcon(self.mainTheme.getIcon("edit")) self.btnTitle.setIcon(self.mainTheme.getIcon("edit"))
self.btnTitle.clicked.connect(lambda: self._editHeading(self.EDIT_TITLE))
wrapTitle = QHBoxLayout() wrapTitle = QHBoxLayout()
wrapTitle.addWidget(self.fmtTitle) wrapTitle.addWidget(self.fmtTitle)
@@ -579,6 +589,7 @@ class GuiBuildHeadingsTab(QWidget):
self.fmtChapter.setEnabled(False) self.fmtChapter.setEnabled(False)
self.btnChapter = QToolButton() self.btnChapter = QToolButton()
self.btnChapter.setIcon(self.mainTheme.getIcon("edit")) self.btnChapter.setIcon(self.mainTheme.getIcon("edit"))
self.btnChapter.clicked.connect(lambda: self._editHeading(self.EDIT_CHAPTER))
wrapChapter = QHBoxLayout() wrapChapter = QHBoxLayout()
wrapChapter.addWidget(self.fmtChapter) wrapChapter.addWidget(self.fmtChapter)
@@ -589,19 +600,20 @@ class GuiBuildHeadingsTab(QWidget):
self.formatBox.addLayout(wrapChapter, 1, 1, Qt.AlignLeft) self.formatBox.addLayout(wrapChapter, 1, 1, Qt.AlignLeft)
# Unnumbered Chapter Heading # Unnumbered Chapter Heading
self.lblUnChapter = QLabel(BuildSettings.getLabel("headings.fmtUnnumbered")) self.lblUnnumbered = QLabel(BuildSettings.getLabel("headings.fmtUnnumbered"))
self.fmtUnChapter = QLineEdit("") self.fmtUnnumbered = QLineEdit("")
self.fmtUnChapter.setEnabled(False) self.fmtUnnumbered.setEnabled(False)
self.btnUnChapter = QToolButton() self.btnUnnumbered = QToolButton()
self.btnUnChapter.setIcon(self.mainTheme.getIcon("edit")) self.btnUnnumbered.setIcon(self.mainTheme.getIcon("edit"))
self.btnUnnumbered.clicked.connect(lambda: self._editHeading(self.EDIT_UNNUM))
wrapUnChapter = QHBoxLayout() wrapUnnumbered = QHBoxLayout()
wrapUnChapter.addWidget(self.fmtUnChapter) wrapUnnumbered.addWidget(self.fmtUnnumbered)
wrapUnChapter.addWidget(self.btnUnChapter) wrapUnnumbered.addWidget(self.btnUnnumbered)
wrapUnChapter.setSpacing(bSp) wrapUnnumbered.setSpacing(bSp)
self.formatBox.addWidget(self.lblUnChapter, 2, 0, Qt.AlignLeft) self.formatBox.addWidget(self.lblUnnumbered, 2, 0, Qt.AlignLeft)
self.formatBox.addLayout(wrapUnChapter, 2, 1, Qt.AlignLeft) self.formatBox.addLayout(wrapUnnumbered, 2, 1, Qt.AlignLeft)
# Scene Heading # Scene Heading
self.lblScene = QLabel(BuildSettings.getLabel("headings.fmtScene")) self.lblScene = QLabel(BuildSettings.getLabel("headings.fmtScene"))
@@ -609,7 +621,9 @@ class GuiBuildHeadingsTab(QWidget):
self.fmtScene.setEnabled(False) self.fmtScene.setEnabled(False)
self.btnScene = QToolButton() self.btnScene = QToolButton()
self.btnScene.setIcon(self.mainTheme.getIcon("edit")) 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 = NSwitch(width=2*iPx, height=iPx)
self.swtScene.toggled.connect(self._hideScene)
wrapScene = QHBoxLayout() wrapScene = QHBoxLayout()
wrapScene.addWidget(self.fmtScene) wrapScene.addWidget(self.fmtScene)
@@ -631,7 +645,9 @@ class GuiBuildHeadingsTab(QWidget):
self.fmtSection.setEnabled(False) self.fmtSection.setEnabled(False)
self.btnSection = QToolButton() self.btnSection = QToolButton()
self.btnSection.setIcon(self.mainTheme.getIcon("edit")) 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 = NSwitch(width=2*iPx, height=iPx)
self.swtSection.toggled.connect(self._hideSection)
wrapSection = QHBoxLayout() wrapSection = QHBoxLayout()
wrapSection.addWidget(self.fmtSection) wrapSection.addWidget(self.fmtSection)
@@ -647,17 +663,165 @@ class GuiBuildHeadingsTab(QWidget):
self.formatBox.addLayout(wrapSection, 4, 1, Qt.AlignLeft) self.formatBox.addLayout(wrapSection, 4, 1, Qt.AlignLeft)
self.formatBox.addLayout(wrapSectionHide, 4, 2, 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 # Assemble
# ======== # ========
self.outerBox = QVBoxLayout() self.outerBox = QVBoxLayout()
self.outerBox.addLayout(self.formatBox) self.outerBox.addLayout(self.formatBox)
self.outerBox.addSpacing(CONFIG.pxInt(16))
self.outerBox.addLayout(self.editFormBox)
self.outerBox.addStretch(1) self.outerBox.addStretch(1)
self.setLayout(self.outerBox) self.setLayout(self.outerBox)
return 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 # END Class GuiBuildHeadingsTab
@@ -719,3 +883,44 @@ class GuiBuildODTTab(QWidget):
return return
# END Class GuiBuildODTTab # 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