Improve build settings dialog (#1499)

This commit is contained in:
Veronica Berglyd Olsen
2023-08-06 22:08:31 +01:00
committed by GitHub
17 changed files with 261 additions and 171 deletions
@@ -21,7 +21,7 @@ backward = typ_chevron-left.svg
bookmark = typ_bookmark.svg bookmark = typ_bookmark.svg
browse = typ_folder-open.svg browse = typ_folder-open.svg
build_excluded = typ_cancel.svg build_excluded = typ_cancel.svg
build_filtered = typ_filter.svg build_filtered = typ_arrow-forward.svg
build_included = typ_pin.svg build_included = typ_pin.svg
bullet-off = typ_media-record-outline.svg bullet-off = typ_media-record-outline.svg
bullet-on = typ_media-record.svg bullet-on = typ_media-record.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="m13.25 3.5625c-0.32 0-0.64 0.12125-0.88375 0.365-0.24375 0.245-0.36625 0.565-0.36625 0.885v3.1988c-5.625 0.6225-10 5.3862-10 11.176v1.25c2.8062-4.2788 6.5625-4.9 10-4.9862v3.1112c0 0.31875 0.1225 0.64 0.36625 0.88375s0.56375 0.36625 0.88375 0.36625 0.64-0.1225 0.88375-0.36625l7.8662-7.7588-7.8662-7.76c-0.24375-0.24375-0.56375-0.365-0.88375-0.365z" fill="#9c9" stroke-width="1.25"/>
</svg>

After

Width:  |  Height:  |  Size: 538 B

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24" height="24" version="1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="m20.694 2h-17.387c-1.3661 0-1.7387 0.85714-0.74516 2l5.2161 6c0.99354 1.1429 1.7387 3.2857 1.7387 4.8571v7.1429l4.9677-2.8571v-5c0-1.1429 0.74516-3 1.7387-4.1429l5.2161-6c0.99354-1.1429 0.62096-2-0.74516-2z" fill="#f99157" stroke-width="1.332"/>
</svg>

Before

Width:  |  Height:  |  Size: 398 B

@@ -21,7 +21,7 @@ backward = typ_chevron-left.svg
bookmark = typ_bookmark.svg bookmark = typ_bookmark.svg
browse = typ_folder-open.svg browse = typ_folder-open.svg
build_excluded = typ_cancel.svg build_excluded = typ_cancel.svg
build_filtered = typ_filter.svg build_filtered = typ_arrow-forward.svg
build_included = typ_pin.svg build_included = typ_pin.svg
bullet-off = typ_media-record-outline.svg bullet-off = typ_media-record-outline.svg
bullet-on = typ_media-record.svg bullet-on = typ_media-record.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="m13.25 3.5625c-0.32 0-0.64 0.12125-0.88375 0.365-0.24375 0.245-0.36625 0.565-0.36625 0.885v3.1988c-5.625 0.6225-10 5.3862-10 11.176v1.25c2.8062-4.2788 6.5625-4.9 10-4.9862v3.1112c0 0.31875 0.1225 0.64 0.36625 0.88375s0.56375 0.36625 0.88375 0.36625 0.64-0.1225 0.88375-0.36625l7.8662-7.7588-7.8662-7.76c-0.24375-0.24375-0.56375-0.365-0.88375-0.365z" fill="#718c00" stroke-width="1.25"/>
</svg>

After

Width:  |  Height:  |  Size: 541 B

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24" height="24" version="1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="m20.694 2h-17.387c-1.3661 0-1.7387 0.85714-0.74516 2l5.2161 6c0.99354 1.1429 1.7387 3.2857 1.7387 4.8571v7.1429l4.9677-2.8571v-5c0-1.1429 0.74516-3 1.7387-4.1429l5.2161-6c0.99354-1.1429 0.62096-2-0.74516-2z" fill="#f5871f" stroke-width="1.332"/>
</svg>

Before

Width:  |  Height:  |  Size: 398 B

+1
View File
@@ -257,6 +257,7 @@ class nwLabels:
class nwHeadFmt: class nwHeadFmt:
BR = "{BR}"
TITLE = "{Title}" TITLE = "{Title}"
CH_NUM = "{Chapter}" CH_NUM = "{Chapter}"
CH_WORD = "{Chapter:Word}" CH_WORD = "{Chapter:Word}"
+3 -3
View File
@@ -84,7 +84,7 @@ SETTINGS_TEMPLATE = {
} }
SETTINGS_LABELS = { SETTINGS_LABELS = {
"filter": QT_TRANSLATE_NOOP("Builds", "Document Types"), "filter": QT_TRANSLATE_NOOP("Builds", "Document Filters"),
"filter.includeNovel": QT_TRANSLATE_NOOP("Builds", "Novel Documents"), "filter.includeNovel": QT_TRANSLATE_NOOP("Builds", "Novel Documents"),
"filter.includeNotes": QT_TRANSLATE_NOOP("Builds", "Project Notes"), "filter.includeNotes": QT_TRANSLATE_NOOP("Builds", "Project Notes"),
"filter.includeInactive": QT_TRANSLATE_NOOP("Builds", "Inactive Documents"), "filter.includeInactive": QT_TRANSLATE_NOOP("Builds", "Inactive Documents"),
@@ -125,10 +125,10 @@ SETTINGS_LABELS = {
"format.leftMargin": QT_TRANSLATE_NOOP("Builds", "Left Margin"), "format.leftMargin": QT_TRANSLATE_NOOP("Builds", "Left Margin"),
"format.rightMargin": QT_TRANSLATE_NOOP("Builds", "Right Margin"), "format.rightMargin": QT_TRANSLATE_NOOP("Builds", "Right Margin"),
"odt": QT_TRANSLATE_NOOP("Builds", "Open Document"), "odt": QT_TRANSLATE_NOOP("Builds", "Open Document (.odt)"),
"odt.addColours": QT_TRANSLATE_NOOP("Builds", "Add Highlight Colours"), "odt.addColours": QT_TRANSLATE_NOOP("Builds", "Add Highlight Colours"),
"html": QT_TRANSLATE_NOOP("Builds", "HTML"), "html": QT_TRANSLATE_NOOP("Builds", "HTML (.html)"),
"html.addStyles": QT_TRANSLATE_NOOP("Builds", "Add CSS Styles"), "html.addStyles": QT_TRANSLATE_NOOP("Builds", "Add CSS Styles"),
} }
+7 -7
View File
@@ -31,7 +31,7 @@ from pathlib import Path
from novelwriter import CONFIG from novelwriter import CONFIG
from novelwriter.common import formatTimeStamp from novelwriter.common import formatTimeStamp
from novelwriter.constants import nwKeyWords, nwLabels, nwHtmlUnicode from novelwriter.constants import nwHeadFmt, nwKeyWords, nwLabels, nwHtmlUnicode
from novelwriter.core.project import NWProject from novelwriter.core.project import NWProject
from novelwriter.core.tokenizer import Tokenizer, stripEscape from novelwriter.core.tokenizer import Tokenizer, stripEscape
@@ -244,27 +244,27 @@ class ToHtml(Tokenizer):
parStyle = None parStyle = None
elif tType == self.T_TITLE: elif tType == self.T_TITLE:
tHead = tText.replace(r"\\", "<br/>") tHead = tText.replace(nwHeadFmt.BR, "<br/>")
tmpResult.append(f"<h1 class='title'{hStyle}>{aNm}{tHead}</h1>\n") tmpResult.append(f"<h1 class='title'{hStyle}>{aNm}{tHead}</h1>\n")
elif tType == self.T_UNNUM: elif tType == self.T_UNNUM:
tHead = tText.replace(r"\\", "<br/>") tHead = tText.replace(nwHeadFmt.BR, "<br/>")
tmpResult.append(f"<{h2}{hStyle}>{aNm}{tHead}</{h2}>\n") tmpResult.append(f"<{h2}{hStyle}>{aNm}{tHead}</{h2}>\n")
elif tType == self.T_HEAD1: elif tType == self.T_HEAD1:
tHead = tText.replace(r"\\", "<br/>") tHead = tText.replace(nwHeadFmt.BR, "<br/>")
tmpResult.append(f"<{h1}{h1Cl}{hStyle}>{aNm}{tHead}</{h1}>\n") tmpResult.append(f"<{h1}{h1Cl}{hStyle}>{aNm}{tHead}</{h1}>\n")
elif tType == self.T_HEAD2: elif tType == self.T_HEAD2:
tHead = tText.replace(r"\\", "<br/>") tHead = tText.replace(nwHeadFmt.BR, "<br/>")
tmpResult.append(f"<{h2}{hStyle}>{aNm}{tHead}</{h2}>\n") tmpResult.append(f"<{h2}{hStyle}>{aNm}{tHead}</{h2}>\n")
elif tType == self.T_HEAD3: elif tType == self.T_HEAD3:
tHead = tText.replace(r"\\", "<br/>") tHead = tText.replace(nwHeadFmt.BR, "<br/>")
tmpResult.append(f"<{h3}{hStyle}>{aNm}{tHead}</{h3}>\n") tmpResult.append(f"<{h3}{hStyle}>{aNm}{tHead}</{h3}>\n")
elif tType == self.T_HEAD4: elif tType == self.T_HEAD4:
tHead = tText.replace(r"\\", "<br/>") tHead = tText.replace(nwHeadFmt.BR, "<br/>")
tmpResult.append(f"<{h4}{hStyle}>{aNm}{tHead}</{h4}>\n") tmpResult.append(f"<{h4}{hStyle}>{aNm}{tHead}</{h4}>\n")
elif tType == self.T_SEP: elif tType == self.T_SEP:
+7 -7
View File
@@ -27,7 +27,7 @@ import logging
from pathlib import Path from pathlib import Path
from novelwriter.constants import nwLabels from novelwriter.constants import nwHeadFmt, nwLabels
from novelwriter.core.project import NWProject from novelwriter.core.project import NWProject
from novelwriter.core.tokenizer import Tokenizer from novelwriter.core.tokenizer import Tokenizer
@@ -122,27 +122,27 @@ class ToMarkdown(Tokenizer):
thisPar = [] thisPar = []
elif tType == self.T_TITLE: elif tType == self.T_TITLE:
tHead = tText.replace(r"\\", "\n") tHead = tText.replace(nwHeadFmt.BR, "\n")
tmpResult.append(f"# {tHead}\n\n") tmpResult.append(f"# {tHead}\n\n")
elif tType == self.T_UNNUM: elif tType == self.T_UNNUM:
tHead = tText.replace(r"\\", "\n") tHead = tText.replace(nwHeadFmt.BR, "\n")
tmpResult.append(f"## {tHead}\n\n") tmpResult.append(f"## {tHead}\n\n")
elif tType == self.T_HEAD1: elif tType == self.T_HEAD1:
tHead = tText.replace(r"\\", "\n") tHead = tText.replace(nwHeadFmt.BR, "\n")
tmpResult.append(f"# {tHead}\n\n") tmpResult.append(f"# {tHead}\n\n")
elif tType == self.T_HEAD2: elif tType == self.T_HEAD2:
tHead = tText.replace(r"\\", "\n") tHead = tText.replace(nwHeadFmt.BR, "\n")
tmpResult.append(f"## {tHead}\n\n") tmpResult.append(f"## {tHead}\n\n")
elif tType == self.T_HEAD3: elif tType == self.T_HEAD3:
tHead = tText.replace(r"\\", "\n") tHead = tText.replace(nwHeadFmt.BR, "\n")
tmpResult.append(f"### {tHead}\n\n") tmpResult.append(f"### {tHead}\n\n")
elif tType == self.T_HEAD4: elif tType == self.T_HEAD4:
tHead = tText.replace(r"\\", "\n") tHead = tText.replace(nwHeadFmt.BR, "\n")
tmpResult.append(f"#### {tHead}\n\n") tmpResult.append(f"#### {tHead}\n\n")
elif tType == self.T_SEP: elif tType == self.T_SEP:
+7 -7
View File
@@ -36,7 +36,7 @@ from datetime import datetime
from novelwriter import __version__ from novelwriter import __version__
from novelwriter.common import xmlIndent from novelwriter.common import xmlIndent
from novelwriter.constants import nwKeyWords, nwLabels from novelwriter.constants import nwHeadFmt, nwKeyWords, nwLabels
from novelwriter.core.project import NWProject from novelwriter.core.project import NWProject
from novelwriter.core.tokenizer import Tokenizer, stripEscape from novelwriter.core.tokenizer import Tokenizer, stripEscape
@@ -445,27 +445,27 @@ class ToOdt(Tokenizer):
parStyle = None parStyle = None
elif tType == self.T_TITLE: elif tType == self.T_TITLE:
tHead = tText.replace(r"\\", "\n") tHead = tText.replace(nwHeadFmt.BR, "\n")
self._addTextPar("Title", oStyle, tHead, isHead=False) # Title must be text:p self._addTextPar("Title", oStyle, tHead, isHead=False) # Title must be text:p
elif tType == self.T_UNNUM: elif tType == self.T_UNNUM:
tHead = tText.replace(r"\\", "\n") tHead = tText.replace(nwHeadFmt.BR, "\n")
self._addTextPar("Heading_20_2", oStyle, tHead, isHead=True, oLevel="2") self._addTextPar("Heading_20_2", oStyle, tHead, isHead=True, oLevel="2")
elif tType == self.T_HEAD1: elif tType == self.T_HEAD1:
tHead = tText.replace(r"\\", "\n") tHead = tText.replace(nwHeadFmt.BR, "\n")
self._addTextPar("Heading_20_1", oStyle, tHead, isHead=True, oLevel="1") self._addTextPar("Heading_20_1", oStyle, tHead, isHead=True, oLevel="1")
elif tType == self.T_HEAD2: elif tType == self.T_HEAD2:
tHead = tText.replace(r"\\", "\n") tHead = tText.replace(nwHeadFmt.BR, "\n")
self._addTextPar("Heading_20_2", oStyle, tHead, isHead=True, oLevel="2") self._addTextPar("Heading_20_2", oStyle, tHead, isHead=True, oLevel="2")
elif tType == self.T_HEAD3: elif tType == self.T_HEAD3:
tHead = tText.replace(r"\\", "\n") tHead = tText.replace(nwHeadFmt.BR, "\n")
self._addTextPar("Heading_20_3", oStyle, tHead, isHead=True, oLevel="3") self._addTextPar("Heading_20_3", oStyle, tHead, isHead=True, oLevel="3")
elif tType == self.T_HEAD4: elif tType == self.T_HEAD4:
tHead = tText.replace(r"\\", "\n") tHead = tText.replace(nwHeadFmt.BR, "\n")
self._addTextPar("Heading_20_4", oStyle, tHead, isHead=True, oLevel="4") self._addTextPar("Heading_20_4", oStyle, tHead, isHead=True, oLevel="4")
elif tType == self.T_SEP: elif tType == self.T_SEP:
+45 -28
View File
@@ -1,7 +1,6 @@
""" """
novelWriter Custom Widget: Paged SideBar novelWriter Custom Widget: Paged SideBar
========================================== ==========================================
A custom widget for making a sidebar for flipping through pages
File History: File History:
Created: 2023-02-21 [2.1b1] NPagedSideBar Created: 2023-02-21 [2.1b1] NPagedSideBar
@@ -24,20 +23,26 @@ General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
from __future__ import annotations
from PyQt5.QtGui import QColor, QPainter from PyQt5.QtGui import QColor, QPaintEvent, QPainter, QPolygon
from PyQt5.QtCore import QRectF, Qt, pyqtSignal, pyqtSlot from PyQt5.QtCore import QPoint, QRectF, Qt, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QButtonGroup, QLabel, QSizePolicy, QStyle, QStyleOptionToolButton, QToolBar, QAbstractButton, QAction, QButtonGroup, QLabel, QSizePolicy, QStyle,
QToolButton, QWidget QStyleOptionToolButton, QToolBar, QToolButton, QWidget
) )
class NPagedSideBar(QToolBar): class NPagedSideBar(QToolBar):
"""Extensions: Paged Side Bar
A side bar widget that holds buttons that mimic tabs. It is designed
to be used in combination with a QStackedWidget for options panels.
"""
buttonClicked = pyqtSignal(int) buttonClicked = pyqtSignal(int)
def __init__(self, parent): def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent) super().__init__(parent=parent)
self._buttons = [] self._buttons = []
@@ -58,7 +63,7 @@ class NPagedSideBar(QToolBar):
return return
def setLabelColor(self, color): def setLabelColor(self, color: list | QColor) -> None:
"""Set the text color for the labels.""" """Set the text color for the labels."""
if isinstance(color, list): if isinstance(color, list):
self._labelCol = QColor(*color) self._labelCol = QColor(*color)
@@ -66,7 +71,7 @@ class NPagedSideBar(QToolBar):
self._labelCol = color self._labelCol = color
return return
def addSeparator(self): def addSeparator(self) -> None:
"""Add a spacer widget.""" """Add a spacer widget."""
spacer = QWidget(self) spacer = QWidget(self)
spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
@@ -74,16 +79,16 @@ class NPagedSideBar(QToolBar):
self.insertWidget(self._stretchAction, spacer) self.insertWidget(self._stretchAction, spacer)
return return
def addLabel(self, text): def addLabel(self, text: str) -> None:
"""Add a new label to the toolbar.""" """Add a new label to the toolbar."""
label = NPagedToolLabel(self, self._labelCol) label = _NPagedToolLabel(self, self._labelCol)
label.setText(text) label.setText(text)
self.insertWidget(self._stretchAction, label) self.insertWidget(self._stretchAction, label)
return return
def addButton(self, text, buttonId=-1): def addButton(self, text: str, buttonId: int = -1) -> QAction:
"""Add a new button to the toolbar.""" """Add a new button to the toolbar."""
button = NPagedToolButton(self) button = _NPagedToolButton(self)
button.setText(text) button.setText(text)
action = self.insertWidget(self._stretchAction, button) action = self.insertWidget(self._stretchAction, button)
@@ -94,7 +99,7 @@ class NPagedSideBar(QToolBar):
return action return action
def setSelected(self, buttonId): def setSelected(self, buttonId: int) -> None:
"""Set the selected button.""" """Set the selected button."""
self._group.button(buttonId).setChecked(True) self._group.button(buttonId).setChecked(True)
return return
@@ -104,7 +109,7 @@ class NPagedSideBar(QToolBar):
## ##
@pyqtSlot("QAbstractButton*") @pyqtSlot("QAbstractButton*")
def _buttonClicked(self, button): def _buttonClicked(self, button: QAbstractButton) -> None:
"""A button was clicked in the group, emit its id.""" """A button was clicked in the group, emit its id."""
buttonId = self._group.id(button) buttonId = self._group.id(button)
if buttonId != -1: if buttonId != -1:
@@ -114,11 +119,11 @@ class NPagedSideBar(QToolBar):
# END Class NPagedSideBar # END Class NPagedSideBar
class NPagedToolButton(QToolButton): class _NPagedToolButton(QToolButton):
__slots__ = ("_bH", "_tM", "_lM", "_cR") __slots__ = ("_bH", "_tM", "_lM", "_cR", "_aH")
def __init__(self, parent): def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent) super().__init__(parent=parent)
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
@@ -126,14 +131,15 @@ class NPagedToolButton(QToolButton):
fH = self.fontMetrics().height() fH = self.fontMetrics().height()
self._bH = round(fH * 1.7) self._bH = round(fH * 1.7)
self._tM = (self._bH - fH) // 2 self._tM = (self._bH - fH)//2
self._lM = self.style().pixelMetric(QStyle.PM_ButtonMargin) self._lM = 3*self.style().pixelMetric(QStyle.PM_ButtonMargin)//2
self._cR = self._lM // 2 self._cR = self._lM//2
self._aH = 2*fH//7
self.setFixedHeight(self._bH) self.setFixedHeight(self._bH)
return return
def paintEvent(self, event): def paintEvent(self, event: QPaintEvent) -> None:
"""Overload the paint event to draw a simple, left aligned text """Overload the paint event to draw a simple, left aligned text
label, with a highlight when selected and a transparent base label, with a highlight when selected and a transparent base
colour when hovered. colour when hovered.
@@ -144,6 +150,7 @@ class NPagedToolButton(QToolButton):
paint = QPainter(self) paint = QPainter(self)
paint.setRenderHint(QPainter.Antialiasing, True) paint.setRenderHint(QPainter.Antialiasing, True)
paint.setPen(Qt.NoPen) paint.setPen(Qt.NoPen)
paint.setBrush(Qt.NoBrush)
width = self.width() width = self.width()
height = self.height() height = self.height()
@@ -171,31 +178,41 @@ class NPagedToolButton(QToolButton):
paint.setOpacity(1.0) paint.setOpacity(1.0)
paint.drawText(QRectF(self._lM, self._tM, tW, tH), Qt.AlignLeft, self.text()) paint.drawText(QRectF(self._lM, self._tM, tW, tH), Qt.AlignLeft, self.text())
tC = self.height()//2
tW = self.width() - self._aH - self._lM
if self.isChecked():
paint.setBrush(textCol)
paint.drawPolygon(QPolygon([
QPoint(tW, tC - self._aH),
QPoint(tW + self._aH, tC),
QPoint(tW, tC + self._aH),
]))
return return
# END Class NPagedToolButton # END Class _NPagedToolButton
class NPagedToolLabel(QLabel): class _NPagedToolLabel(QLabel):
__slots__ = ("_bH", "_tM", "_lM", "_textCol") __slots__ = ("_bH", "_tM", "_lM", "_textCol")
def __init__(self, parent, textColor=None): def __init__(self, parent: QWidget, textColor: QColor | None = None) -> None:
super().__init__(parent=parent) super().__init__(parent=parent)
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
fH = self.fontMetrics().height() fH = self.fontMetrics().height()
self._bH = round(fH * 1.7) self._bH = round(fH * 1.7)
self._tM = (self._bH - fH) // 2 self._tM = (self._bH - fH)//2
self._lM = self.style().pixelMetric(QStyle.PM_ButtonMargin) self._lM = self.style().pixelMetric(QStyle.PM_ButtonMargin)//2
self.setFixedHeight(self._bH) self.setFixedHeight(self._bH)
self._textCol = textColor or self.palette().text().color() self._textCol = textColor or self.palette().text().color()
return return
def paintEvent(self, event): def paintEvent(self, event: QPaintEvent) -> None:
"""Overload the paint event to draw a simple, left aligned text """Overload the paint event to draw a simple, left aligned text
label that matches the button style. label that matches the button style.
""" """
@@ -215,4 +232,4 @@ class NPagedToolLabel(QLabel):
return return
# END Class NPagedToolLabel # END Class _NPagedToolLabel
+2 -1
View File
@@ -1202,6 +1202,7 @@ class GuiProjectTree(QTreeWidget):
open a context menu in-place. open a context menu in-place.
""" """
tItem = None tItem = None
tHandle = None
hasChild = False hasChild = False
selItem = self.itemAt(clickPos) selItem = self.itemAt(clickPos)
if isinstance(selItem, QTreeWidgetItem): if isinstance(selItem, QTreeWidgetItem):
@@ -1209,7 +1210,7 @@ class GuiProjectTree(QTreeWidget):
tItem = self.theProject.tree[tHandle] tItem = self.theProject.tree[tHandle]
hasChild = selItem.childCount() > 0 hasChild = selItem.childCount() > 0
if tItem is None: if tItem is None or tHandle is None:
logger.debug("No item found") logger.debug("No item found")
return False return False
+28 -17
View File
@@ -30,7 +30,7 @@ from time import time
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from datetime import datetime from datetime import datetime
from PyQt5.QtGui import QColor, QCursor, QFont, QPalette, QResizeEvent from PyQt5.QtGui import QCloseEvent, QColor, QCursor, QFont, QPalette, QResizeEvent
from PyQt5.QtCore import QSize, QTimer, Qt, pyqtSlot from PyQt5.QtCore import QSize, QTimer, Qt, pyqtSlot
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QDialog, QGridLayout, QHBoxLayout, QLabel, QListWidget, QListWidgetItem, QPushButton, QDialog, QGridLayout, QHBoxLayout, QLabel, QListWidget, QListWidgetItem, QPushButton,
@@ -231,13 +231,13 @@ class GuiManuscript(QDialog):
# Events # Events
## ##
def closeEvent(self, event): def closeEvent(self, event: QCloseEvent):
"""Capture the user closing the window so we can save GUI """Capture the user closing the window so we can save GUI
settings. We also check that we don't have a build settings settings. We also check that we don't have a build settings
dialog open. dialog open.
""" """
self._saveSettings() self._saveSettings()
for obj in self.children(): for obj in self.mainGui.children():
# Make sure we don't have any settings windows open # Make sure we don't have any settings windows open
if isinstance(obj, GuiBuildSettings) and obj.isVisible(): if isinstance(obj, GuiBuildSettings) and obj.isVisible():
obj.close() obj.close()
@@ -330,15 +330,13 @@ class GuiManuscript(QDialog):
def _buildManuscript(self): def _buildManuscript(self):
"""Open the build dialog and build the manuscript.""" """Open the build dialog and build the manuscript."""
build = self._getSelectedBuild() build = self._getSelectedBuild()
if build is None: if isinstance(build, BuildSettings):
return dlgBuild = GuiManuscriptBuild(self, self.mainGui, build)
dlgBuild.exec_()
dlgBuild = GuiManuscriptBuild(self, self.mainGui, build) # After the build is done, save build settings changes
dlgBuild.exec_() if build.changed:
self._builds.setBuild(build)
# After the build is done, save build settings changes
if build.changed:
self._builds.setBuild(build)
return return
@@ -376,10 +374,13 @@ class GuiManuscript(QDialog):
return return
def _getSelectedBuild(self) -> BuildSettings | None: def _getSelectedBuild(self) -> BuildSettings | None:
"""Get the currently selected build.""" """Get the currently selected build. If none are selected,
bItems = self.buildList.selectedItems() automatically select the first one.
if bItems: """
build = self._builds.getBuild(bItems[0].data(self.D_KEY)) items = self.buildList.selectedItems()
item = items[0] if items else self.buildList.item(0)
if item:
build = self._builds.getBuild(item.data(self.D_KEY))
if isinstance(build, BuildSettings): if isinstance(build, BuildSettings):
return build return build
return None return None
@@ -406,13 +407,23 @@ class GuiManuscript(QDialog):
def _openSettingsDialog(self, build: BuildSettings): def _openSettingsDialog(self, build: BuildSettings):
"""Open the build settings dialog.""" """Open the build settings dialog."""
dlgSettings = GuiBuildSettings(self, self.mainGui, build) for obj in self.mainGui.children():
# Don't open a second dialog if one exists
if isinstance(obj, GuiBuildSettings):
if obj.buildID == build.buildID:
logger.debug("Found instance of GuiBuildSettings")
obj.show()
obj.raise_()
return
dlgSettings = GuiBuildSettings(self.mainGui, build)
dlgSettings.setModal(False) dlgSettings.setModal(False)
dlgSettings.show() dlgSettings.show()
dlgSettings.raise_() dlgSettings.raise_()
qApp.processEvents() qApp.processEvents()
dlgSettings.loadContent() dlgSettings.loadContent()
dlgSettings.newSettingsReady.connect(self._processNewSettings) dlgSettings.newSettingsReady.connect(self._processNewSettings)
return return
def _updateBuildsList(self): def _updateBuildsList(self):
@@ -634,7 +645,7 @@ class _PreviewWidget(QTextBrowser):
) )
else: else:
strBuildTime = self.tr("Unknown") strBuildTime = self.tr("Unknown")
text = "{0} {1}".format(self.tr("Built"), strBuildTime) text = "{0}: {1}".format(self.tr("Built"), strBuildTime)
if self._buildName: if self._buildName:
text = "<b>{0}</b><br>{1}".format(self._buildName, text) text = "<b>{0}</b><br>{1}".format(self._buildName, text)
self.ageLabel.setText(text) self.ageLabel.setText(text)
+113 -67
View File
@@ -69,8 +69,8 @@ class GuiBuildSettings(QDialog):
newSettingsReady = pyqtSignal(BuildSettings) newSettingsReady = pyqtSignal(BuildSettings)
def __init__(self, parent: QWidget, mainGui: GuiMain, build: BuildSettings): def __init__(self, mainGui: GuiMain, build: BuildSettings) -> None:
super().__init__(parent=parent) super().__init__(parent=mainGui)
logger.debug("Create: GuiBuildSettings") logger.debug("Create: GuiBuildSettings")
self.setObjectName("GuiBuildSettings") self.setObjectName("GuiBuildSettings")
@@ -106,7 +106,7 @@ class GuiBuildSettings(QDialog):
self.optSideBar.setLabelColor(self.mainTheme.helpText) self.optSideBar.setLabelColor(self.mainTheme.helpText)
self.optSideBar.addLabel(self.tr("Options")) self.optSideBar.addLabel(self.tr("Options"))
self.optSideBar.addButton(self.tr("Filters"), self.OPT_FILTERS) self.optSideBar.addButton(self.tr("Selection"), 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("Content"), self.OPT_CONTENT) self.optSideBar.addButton(self.tr("Content"), self.OPT_CONTENT)
self.optSideBar.addButton(self.tr("Format"), self.OPT_FORMAT) self.optSideBar.addButton(self.tr("Format"), self.OPT_FORMAT)
@@ -169,10 +169,11 @@ class GuiBuildSettings(QDialog):
return return
def __del__(self): # pragma: no cover def __del__(self) -> None: # pragma: no cover
logger.debug("Delete: GuiBuildSettings") logger.debug("Delete: GuiBuildSettings")
return
def loadContent(self): def loadContent(self) -> None:
"""Populate the child widgets.""" """Populate the child widgets."""
self.editBuildName.setText(self._build.name) self.editBuildName.setText(self._build.name)
self.optTabSelect.loadContent() self.optTabSelect.loadContent()
@@ -182,12 +183,21 @@ class GuiBuildSettings(QDialog):
self.optTabOutput.loadContent() self.optTabOutput.loadContent()
return return
##
# Properties
##
@property
def buildID(self) -> str:
"""The build ID of the build of the dialog."""
return self._build.buildID
## ##
# Private Slots # Private Slots
## ##
@pyqtSlot(int) @pyqtSlot(int)
def _stackPageSelected(self, pageId: int): def _stackPageSelected(self, pageId: int) -> None:
"""Process a user request to switch page.""" """Process a user request to switch page."""
if pageId == self.OPT_FILTERS: if pageId == self.OPT_FILTERS:
self.toolStack.setCurrentWidget(self.optTabSelect) self.toolStack.setCurrentWidget(self.optTabSelect)
@@ -202,7 +212,7 @@ class GuiBuildSettings(QDialog):
return return
@pyqtSlot("QAbstractButton*") @pyqtSlot("QAbstractButton*")
def _dialogButtonClicked(self, button: QAbstractButton): def _dialogButtonClicked(self, button: QAbstractButton) -> None:
"""Handle button clicks from the dialog button box.""" """Handle button clicks from the dialog button box."""
role = self.dlgButtons.buttonRole(button) role = self.dlgButtons.buttonRole(button)
if role == QDialogButtonBox.ApplyRole: if role == QDialogButtonBox.ApplyRole:
@@ -218,7 +228,7 @@ class GuiBuildSettings(QDialog):
# Events # Events
## ##
def closeEvent(self, event: QEvent): def closeEvent(self, event: QEvent) -> None:
"""Capture the user closing the window so we can save """Capture the user closing the window so we can save
settings. settings.
""" """
@@ -233,7 +243,7 @@ class GuiBuildSettings(QDialog):
# Internal Functions # Internal Functions
## ##
def _askToSaveBuild(self): def _askToSaveBuild(self) -> None:
"""Check if there are unsaved changes, and if there are, ask if """Check if there are unsaved changes, and if there are, ask if
it's ok to reject them. it's ok to reject them.
""" """
@@ -247,7 +257,7 @@ class GuiBuildSettings(QDialog):
self._build.resetChangedState() self._build.resetChangedState()
return return
def _saveSettings(self): def _saveSettings(self) -> None:
"""Save the various user settings.""" """Save the various user settings."""
logger.debug("Saving GuiBuildSettings settings") logger.debug("Saving GuiBuildSettings settings")
@@ -265,7 +275,7 @@ class GuiBuildSettings(QDialog):
return return
def _emitBuildData(self): def _emitBuildData(self) -> None:
"""Assemble the build data and emit the signal.""" """Assemble the build data and emit the signal."""
self._build.setName(self.editBuildName.text()) self._build.setName(self.editBuildName.text())
self.optTabHeadings.saveContent() self.optTabHeadings.saveContent()
@@ -294,23 +304,26 @@ class _FilterTab(QWidget):
F_INCLUDED = 2 F_INCLUDED = 2
F_EXCLUDED = 3 F_EXCLUDED = 3
def __init__(self, buildMain: GuiBuildSettings, build: BuildSettings): def __init__(self, buildMain: GuiBuildSettings, build: BuildSettings) -> None:
super().__init__(parent=buildMain) super().__init__(parent=buildMain)
self.mainGui = buildMain.mainGui self.mainGui = buildMain.mainGui
self.mainTheme = buildMain.mainGui.mainTheme self.mainTheme = buildMain.mainGui.mainTheme
self.theProject = buildMain.mainGui.theProject self.theProject = buildMain.mainGui.theProject
self._treeMap = {} self._treeMap: dict[str, QTreeWidgetItem] = {}
self._build = build self._build = build
self._statusFlags = { self._statusFlags: dict[int, QIcon] = {
self.F_NONE: ("", QIcon()), self.F_NONE: QIcon(),
self.F_FILTERED: (self.tr("Filtered"), self.mainTheme.getIcon("build_filtered")), self.F_FILTERED: self.mainTheme.getIcon("build_filtered"),
self.F_INCLUDED: (self.tr("Included"), self.mainTheme.getIcon("build_included")), self.F_INCLUDED: self.mainTheme.getIcon("build_included"),
self.F_EXCLUDED: (self.tr("Excluded"), self.mainTheme.getIcon("build_excluded")), self.F_EXCLUDED: self.mainTheme.getIcon("build_excluded"),
} }
self._trIncluded = self.tr("Included in manuscript")
self._trExcluded = self.tr("Excluded from manuscript")
# Project Tree # Project Tree
# ============ # ============
@@ -341,27 +354,28 @@ class _FilterTab(QWidget):
# Filters # Filters
# ======= # =======
self.filteredButton = QToolButton(self)
self.filteredButton.setToolTip(self._statusFlags[self.F_FILTERED][0])
self.filteredButton.setIcon(self._statusFlags[self.F_FILTERED][1])
self.filteredButton.clicked.connect(lambda: self._setSelectedMode(self.F_FILTERED))
self.includedButton = QToolButton(self) self.includedButton = QToolButton(self)
self.includedButton.setToolTip(self._statusFlags[self.F_INCLUDED][0]) self.includedButton.setToolTip(self.tr("Always included"))
self.includedButton.setIcon(self._statusFlags[self.F_INCLUDED][1]) self.includedButton.setIcon(self._statusFlags[self.F_INCLUDED])
self.includedButton.clicked.connect(lambda: self._setSelectedMode(self.F_INCLUDED)) self.includedButton.clicked.connect(lambda: self._setSelectedMode(self.F_INCLUDED))
self.excludedButton = QToolButton(self) self.excludedButton = QToolButton(self)
self.excludedButton.setToolTip(self._statusFlags[self.F_EXCLUDED][0]) self.excludedButton.setToolTip(self.tr("Always excluded"))
self.excludedButton.setIcon(self._statusFlags[self.F_EXCLUDED][1]) self.excludedButton.setIcon(self._statusFlags[self.F_EXCLUDED])
self.excludedButton.clicked.connect(lambda: self._setSelectedMode(self.F_EXCLUDED)) self.excludedButton.clicked.connect(lambda: self._setSelectedMode(self.F_EXCLUDED))
self.resetButton = QToolButton(self)
self.resetButton.setToolTip(self.tr("Reset to default"))
self.resetButton.setIcon(self.mainTheme.getIcon("revert"))
self.resetButton.clicked.connect(lambda: self._setSelectedMode(self.F_FILTERED))
self.modeBox = QHBoxLayout() self.modeBox = QHBoxLayout()
self.modeBox.addWidget(QLabel(self.tr("Mark selection as"))) self.modeBox.addWidget(QLabel(self.tr("Mark selection as")))
self.modeBox.addStretch(1) self.modeBox.addStretch(1)
self.modeBox.addWidget(self.filteredButton)
self.modeBox.addWidget(self.includedButton) self.modeBox.addWidget(self.includedButton)
self.modeBox.addWidget(self.excludedButton) self.modeBox.addWidget(self.excludedButton)
self.modeBox.addWidget(self.resetButton)
self.modeBox.setSpacing(CONFIG.pxInt(4))
# Filer Options # Filer Options
self.filterOpt = NSwitchBox(self, iPx) self.filterOpt = NSwitchBox(self, iPx)
@@ -401,7 +415,7 @@ class _FilterTab(QWidget):
return return
def loadContent(self): def loadContent(self) -> None:
"""Populate the widgets.""" """Populate the widgets."""
self._populateTree() self._populateTree()
self._populateFilters() self._populateFilters()
@@ -418,7 +432,7 @@ class _FilterTab(QWidget):
## ##
@pyqtSlot(str, bool) @pyqtSlot(str, bool)
def _applyFilterSwitch(self, key: str, state: bool): def _applyFilterSwitch(self, key: str, state: bool) -> None:
"""Apply filter switch and update the settings.""" """Apply filter switch and update the settings."""
if key.startswith("doc:"): if key.startswith("doc:"):
self._build.setValue(key[4:], state) self._build.setValue(key[4:], state)
@@ -432,7 +446,7 @@ class _FilterTab(QWidget):
# Internal Functions # Internal Functions
## ##
def _populateTree(self): def _populateTree(self) -> None:
"""Build the tree of project items.""" """Build the tree of project items."""
logger.debug("Building project tree") logger.debug("Building project tree")
self._treeMap = {} self._treeMap = {}
@@ -486,7 +500,7 @@ class _FilterTab(QWidget):
return return
def _populateFilters(self): def _populateFilters(self) -> None:
"""Populate the filter options switches.""" """Populate the filter options switches."""
self.filterOpt.clear() self.filterOpt.clear()
self.filterOpt.addLabel(self._build.getLabel("filter")) self.filterOpt.addLabel(self._build.getLabel("filter"))
@@ -512,7 +526,7 @@ class _FilterTab(QWidget):
self.filterOpt.addSeparator() self.filterOpt.addSeparator()
# Root Classes # Root Classes
self.filterOpt.addLabel(self.tr("Root Folders")) self.filterOpt.addLabel(self.tr("Select Root Folders"))
for tHandle, nwItem in self.theProject.tree.iterRoots(None): for tHandle, nwItem in self.theProject.tree.iterRoots(None):
if not nwItem.isInactiveClass(): if not nwItem.isInactiveClass():
itemIcon = self.mainTheme.getItemIcon( itemIcon = self.mainTheme.getItemIcon(
@@ -525,9 +539,13 @@ class _FilterTab(QWidget):
return return
def _setSelectedMode(self, mode: int): def _setSelectedMode(self, mode: int) -> None:
"""Set the mode for the selected items.""" """Set the mode for the selected items."""
for item in self.optTree.selectedItems(): items = self.optTree.selectedItems()
if len(items) == 1 and isinstance(items[0], QTreeWidgetItem):
items = self._scanChildren(items[0], [])
for item in items:
if isinstance(item, QTreeWidgetItem): if isinstance(item, QTreeWidgetItem):
tHandle = item.data(self.C_DATA, self.D_HANDLE) tHandle = item.data(self.C_DATA, self.D_HANDLE)
isFile = item.data(self.C_DATA, self.D_FILE) isFile = item.data(self.C_DATA, self.D_FILE)
@@ -543,21 +561,34 @@ class _FilterTab(QWidget):
return return
def _setTreeItemMode(self): def _setTreeItemMode(self) -> None:
"""Update the filtered mode icon on all items.""" """Update the filtered mode icon on all items."""
filtered = self._build.buildItemFilter(self.theProject) filtered = self._build.buildItemFilter(self.theProject)
for tHandle, item in self._treeMap.items(): for tHandle, item in self._treeMap.items():
allow, mode = filtered.get(tHandle, (False, FilterMode.UNKNOWN)) allow, mode = filtered.get(tHandle, (False, FilterMode.UNKNOWN))
if mode == FilterMode.INCLUDED: if mode == FilterMode.INCLUDED:
item.setIcon(self.C_STATUS, self._statusFlags[self.F_INCLUDED][1]) item.setIcon(self.C_STATUS, self._statusFlags[self.F_INCLUDED])
item.setToolTip(self.C_STATUS, self._trIncluded)
elif mode == FilterMode.EXCLUDED: elif mode == FilterMode.EXCLUDED:
item.setIcon(self.C_STATUS, self._statusFlags[self.F_EXCLUDED][1]) item.setIcon(self.C_STATUS, self._statusFlags[self.F_EXCLUDED])
item.setToolTip(self.C_STATUS, self._trExcluded)
elif mode == FilterMode.FILTERED and allow: elif mode == FilterMode.FILTERED and allow:
item.setIcon(self.C_STATUS, self._statusFlags[self.F_FILTERED][1]) item.setIcon(self.C_STATUS, self._statusFlags[self.F_FILTERED])
item.setToolTip(self.C_STATUS, self._trIncluded)
else: else:
item.setIcon(self.C_STATUS, self._statusFlags[self.F_NONE][1]) item.setIcon(self.C_STATUS, self._statusFlags[self.F_NONE])
return return
def _scanChildren(self, item: QTreeWidgetItem | None, items: list) -> list[QTreeWidgetItem]:
"""This is a recursive function returning all items in a tree
starting at a given QTreeWidgetItem.
"""
if isinstance(item, QTreeWidgetItem):
items.append(item)
for i in range(item.childCount()):
self._scanChildren(item.child(i), items)
return items
# END Class _FilterTab # END Class _FilterTab
@@ -569,7 +600,7 @@ class _HeadingsTab(QWidget):
EDIT_SCENE = 4 EDIT_SCENE = 4
EDIT_SECTION = 5 EDIT_SECTION = 5
def __init__(self, buildMain: GuiBuildSettings, build: BuildSettings): def __init__(self, buildMain: GuiBuildSettings, build: BuildSettings) -> None:
super().__init__(parent=buildMain) super().__init__(parent=buildMain)
self.mainGui = buildMain.mainGui self.mainGui = buildMain.mainGui
@@ -747,7 +778,7 @@ class _HeadingsTab(QWidget):
return return
def loadContent(self): def loadContent(self) -> None:
"""Populate the widgets.""" """Populate the widgets."""
self.fmtTitle.setText(self._build.getStr("headings.fmtTitle")) self.fmtTitle.setText(self._build.getStr("headings.fmtTitle"))
self.fmtChapter.setText(self._build.getStr("headings.fmtChapter")) self.fmtChapter.setText(self._build.getStr("headings.fmtChapter"))
@@ -758,7 +789,7 @@ class _HeadingsTab(QWidget):
self.swtSection.setChecked(self._build.getBool("headings.hideSection")) self.swtSection.setChecked(self._build.getBool("headings.hideSection"))
return return
def saveContent(self): def saveContent(self) -> None:
"""Save choices back into build object.""" """Save choices back into build object."""
self._build.setValue("headings.hideScene", self.swtScene.isChecked()) self._build.setValue("headings.hideScene", self.swtScene.isChecked())
self._build.setValue("headings.hideSection", self.swtSection.isChecked()) self._build.setValue("headings.hideSection", self.swtSection.isChecked())
@@ -768,7 +799,7 @@ class _HeadingsTab(QWidget):
# Internal Functions # Internal Functions
## ##
def _insertIntoForm(self, text: str): def _insertIntoForm(self, text: str) -> None:
"""Insert formatting text from the dropdown menu.""" """Insert formatting text from the dropdown menu."""
if self._editing > 0: if self._editing > 0:
cursor = self.editTextBox.textCursor() cursor = self.editTextBox.textCursor()
@@ -776,7 +807,7 @@ class _HeadingsTab(QWidget):
self.editTextBox.setFocus() self.editTextBox.setFocus()
return return
def _editHeading(self, heading: int): def _editHeading(self, heading: int) -> None:
"""Populate the form with a specific heading format.""" """Populate the form with a specific heading format."""
self._editing = heading self._editing = heading
self.editTextBox.setEnabled(True) self.editTextBox.setEnabled(True)
@@ -801,7 +832,7 @@ class _HeadingsTab(QWidget):
text = "" text = ""
label = self.tr("None") label = self.tr("None")
self.editTextBox.setPlainText(text.replace("//", "\n")) self.editTextBox.setPlainText(text.replace(nwHeadFmt.BR, "\n"))
self.lblEditForm.setText(self.tr("Editing: {0}").format(label)) self.lblEditForm.setText(self.tr("Editing: {0}").format(label))
return return
@@ -810,10 +841,10 @@ class _HeadingsTab(QWidget):
# Private Slots # Private Slots
## ##
def _saveFormat(self): def _saveFormat(self) -> None:
"""Save the format from the edit text box.""" """Save the format from the edit text box."""
heading = self._editing heading = self._editing
text = self.editTextBox.toPlainText().strip().replace("\n", "//") text = self.editTextBox.toPlainText().strip().replace("\n", nwHeadFmt.BR)
if heading == self.EDIT_TITLE: if heading == self.EDIT_TITLE:
self.fmtTitle.setText(text) self.fmtTitle.setText(text)
self._build.setValue("headings.fmtTitle", text) self._build.setValue("headings.fmtTitle", text)
@@ -842,7 +873,7 @@ class _HeadingsTab(QWidget):
class _HeadingSyntaxHighlighter(QSyntaxHighlighter): class _HeadingSyntaxHighlighter(QSyntaxHighlighter):
def __init__(self, document: QTextDocument, mainTheme: GuiTheme): def __init__(self, document: QTextDocument, mainTheme: GuiTheme) -> None:
super().__init__(document) super().__init__(document)
self._fmtSymbol = QTextCharFormat() self._fmtSymbol = QTextCharFormat()
self._fmtSymbol.setForeground(QColor(*mainTheme.colHead)) self._fmtSymbol.setForeground(QColor(*mainTheme.colHead))
@@ -850,7 +881,7 @@ class _HeadingSyntaxHighlighter(QSyntaxHighlighter):
self._fmtFormat.setForeground(QColor(*mainTheme.colEmph)) self._fmtFormat.setForeground(QColor(*mainTheme.colEmph))
return return
def highlightBlock(self, text: str): def highlightBlock(self, text: str) -> None:
"""Add syntax highlighting to the text block.""" """Add syntax highlighting to the text block."""
for heading in nwHeadFmt.ALL: for heading in nwHeadFmt.ALL:
pos = text.find(heading) pos = text.find(heading)
@@ -868,7 +899,7 @@ class _HeadingSyntaxHighlighter(QSyntaxHighlighter):
class _ContentTab(QWidget): class _ContentTab(QWidget):
def __init__(self, buildMain: GuiBuildSettings, build: BuildSettings): def __init__(self, buildMain: GuiBuildSettings, build: BuildSettings) -> None:
super().__init__(parent=buildMain) super().__init__(parent=buildMain)
self.mainGui = buildMain.mainGui self.mainGui = buildMain.mainGui
@@ -917,7 +948,7 @@ class _ContentTab(QWidget):
return return
def loadContent(self): def loadContent(self) -> None:
"""Populate the widgets.""" """Populate the widgets."""
self.incSynopsis.setChecked(self._build.getBool("text.includeSynopsis")) self.incSynopsis.setChecked(self._build.getBool("text.includeSynopsis"))
self.incComments.setChecked(self._build.getBool("text.includeComments")) self.incComments.setChecked(self._build.getBool("text.includeComments"))
@@ -926,7 +957,7 @@ class _ContentTab(QWidget):
self.addNoteHead.setChecked(self._build.getBool("text.addNoteHeadings")) self.addNoteHead.setChecked(self._build.getBool("text.addNoteHeadings"))
return return
def saveContent(self): def saveContent(self) -> None:
"""Save choices back into build object.""" """Save choices back into build object."""
self._build.setValue("text.includeSynopsis", self.incSynopsis.isChecked()) self._build.setValue("text.includeSynopsis", self.incSynopsis.isChecked())
self._build.setValue("text.includeComments", self.incComments.isChecked()) self._build.setValue("text.includeComments", self.incComments.isChecked())
@@ -940,7 +971,7 @@ class _ContentTab(QWidget):
class _FormatTab(QWidget): class _FormatTab(QWidget):
def __init__(self, buildMain: GuiBuildSettings, build: BuildSettings): def __init__(self, buildMain: GuiBuildSettings, build: BuildSettings) -> None:
super().__init__(parent=buildMain) super().__init__(parent=buildMain)
self.buildMain = buildMain self.buildMain = buildMain
@@ -1033,10 +1064,12 @@ class _FormatTab(QWidget):
self.pageWidth = QDoubleSpinBox(self) self.pageWidth = QDoubleSpinBox(self)
self.pageWidth.setFixedWidth(dbW) self.pageWidth.setFixedWidth(dbW)
self.pageWidth.setMaximum(500.0) self.pageWidth.setMaximum(500.0)
self.pageWidth.valueChanged.connect(self._pageSizeValueChanged)
self.pageHeight = QDoubleSpinBox(self) self.pageHeight = QDoubleSpinBox(self)
self.pageHeight.setFixedWidth(dbW) self.pageHeight.setFixedWidth(dbW)
self.pageHeight.setMaximum(500.0) self.pageHeight.setMaximum(500.0)
self.pageHeight.valueChanged.connect(self._pageSizeValueChanged)
self.topMargin = QDoubleSpinBox(self) self.topMargin = QDoubleSpinBox(self)
self.topMargin.setFixedWidth(dbW) self.topMargin.setFixedWidth(dbW)
@@ -1085,7 +1118,7 @@ class _FormatTab(QWidget):
return return
def loadContent(self): def loadContent(self) -> None:
"""Populate the widgets.""" """Populate the widgets."""
langIdx = self.buildLang.findData(self._build.getStr("format.buildLang")) langIdx = self.buildLang.findData(self._build.getStr("format.buildLang"))
if langIdx != -1: if langIdx != -1:
@@ -1128,7 +1161,7 @@ class _FormatTab(QWidget):
return return
def saveContent(self): def saveContent(self) -> None:
"""Save choices back into build object.""" """Save choices back into build object."""
self._build.setValue("format.buildLang", str(self.buildLang.currentData())) self._build.setValue("format.buildLang", str(self.buildLang.currentData()))
self._build.setValue("format.textFont", self.textFont.text()) self._build.setValue("format.textFont", self.textFont.text())
@@ -1154,7 +1187,7 @@ class _FormatTab(QWidget):
## ##
@pyqtSlot() @pyqtSlot()
def _selectFont(self): def _selectFont(self) -> None:
"""Open the QFontDialog and set a font for the font style.""" """Open the QFontDialog and set a font for the font style."""
currFont = QFont() currFont = QFont()
currFont.setFamily(self.textFont.text()) currFont.setFamily(self.textFont.text())
@@ -1166,7 +1199,7 @@ class _FormatTab(QWidget):
return return
@pyqtSlot(int) @pyqtSlot(int)
def _changeUnit(self, index: int): def _changeUnit(self, index: int) -> None:
"""The current unit change, so recalculate sizes.""" """The current unit change, so recalculate sizes."""
newUnit = self.pageUnit.itemData(index) newUnit = self.pageUnit.itemData(index)
newScale = nwLabels.UNIT_SCALE.get(newUnit, 1.0) newScale = nwLabels.UNIT_SCALE.get(newUnit, 1.0)
@@ -1185,15 +1218,19 @@ class _FormatTab(QWidget):
pMax = 500.0 if isMM else 50.0 pMax = 500.0 if isMM else 50.0
mMax = 150.0 if isMM else 15.0 mMax = 150.0 if isMM else 15.0
self.pageWidth.blockSignals(True)
self.pageWidth.setDecimals(nDec) self.pageWidth.setDecimals(nDec)
self.pageWidth.setSingleStep(nStep) self.pageWidth.setSingleStep(nStep)
self.pageWidth.setMaximum(pMax) self.pageWidth.setMaximum(pMax)
self.pageWidth.setValue(pageWidth) self.pageWidth.setValue(pageWidth)
self.pageWidth.blockSignals(False)
self.pageHeight.blockSignals(True)
self.pageHeight.setDecimals(nDec) self.pageHeight.setDecimals(nDec)
self.pageHeight.setSingleStep(nStep) self.pageHeight.setSingleStep(nStep)
self.pageHeight.setMaximum(pMax) self.pageHeight.setMaximum(pMax)
self.pageHeight.setValue(pageHeight) self.pageHeight.setValue(pageHeight)
self.pageHeight.blockSignals(False)
self.topMargin.setDecimals(nDec) self.topMargin.setDecimals(nDec)
self.topMargin.setSingleStep(nStep) self.topMargin.setSingleStep(nStep)
@@ -1216,22 +1253,31 @@ class _FormatTab(QWidget):
self.rightMargin.setValue(rightMargin) self.rightMargin.setValue(rightMargin)
self._unitScale = newScale self._unitScale = newScale
self._changePageSize(self.pageSize.currentIndex())
return return
@pyqtSlot(int) @pyqtSlot(int)
def _changePageSize(self, index: int): def _changePageSize(self, index: int) -> None:
"""The page size has changed.""" """The page size has changed."""
self.pageWidth.setEnabled(True)
self.pageHeight.setEnabled(True)
w, h = nwLabels.PAPER_SIZE[self.pageSize.itemData(index)] if index >= 0 else (-1.0, -1.0) w, h = nwLabels.PAPER_SIZE[self.pageSize.itemData(index)] if index >= 0 else (-1.0, -1.0)
if w > 0.0 and h > 0.0: if w > 0.0 and h > 0.0:
self.pageWidth.setEnabled(False) self.pageWidth.blockSignals(True)
self.pageHeight.setEnabled(False)
self.pageWidth.setValue(w/self._unitScale) self.pageWidth.setValue(w/self._unitScale)
self.pageWidth.blockSignals(False)
self.pageHeight.blockSignals(True)
self.pageHeight.setValue(h/self._unitScale) self.pageHeight.setValue(h/self._unitScale)
self.pageHeight.blockSignals(False)
return
@pyqtSlot()
def _pageSizeValueChanged(self):
"""The user has changed the page size spin boxes, so we flip
the page size box to Custom.
"""
index = self.pageSize.findData("Custom")
if index >= 0:
self.pageSize.setCurrentIndex(index)
return return
# END Class _FormatTab # END Class _FormatTab
@@ -1239,7 +1285,7 @@ class _FormatTab(QWidget):
class _OutputTab(QWidget): class _OutputTab(QWidget):
def __init__(self, buildMain: GuiBuildSettings, build: BuildSettings): def __init__(self, buildMain: GuiBuildSettings, build: BuildSettings) -> None:
super().__init__(parent=buildMain) super().__init__(parent=buildMain)
self.mainGui = buildMain.mainGui self.mainGui = buildMain.mainGui
@@ -1282,13 +1328,13 @@ class _OutputTab(QWidget):
return return
def loadContent(self): def loadContent(self) -> None:
"""Populate the widgets.""" """Populate the widgets."""
self.odtAddColours.setChecked(self._build.getBool("odt.addColours")) self.odtAddColours.setChecked(self._build.getBool("odt.addColours"))
self.htmlAddStyles.setChecked(self._build.getBool("html.addStyles")) self.htmlAddStyles.setChecked(self._build.getBool("html.addStyles"))
return return
def saveContent(self): def saveContent(self) -> None:
"""Save choices back into build object.""" """Save choices back into build object."""
self._build.setValue("odt.addColours", self.odtAddColours.isChecked()) self._build.setValue("odt.addColours", self.odtAddColours.isChecked())
self._build.setValue("html.addStyles", self.htmlAddStyles.isChecked()) self._build.setValue("html.addStyles", self.htmlAddStyles.isChecked())
+2 -7
View File
@@ -168,11 +168,12 @@ def testManuscript_Features(monkeypatch, qtbot: QtBot, nwGUI: GuiMain, projPath:
# ======== # ========
# No build selected # No build selected
manus.buildList.clearSelection() manus.buildList.clear()
manus.btnPreview.click() manus.btnPreview.click()
qtbot.wait(200) # Should be enough to run the build qtbot.wait(200) # Should be enough to run the build
assert manus.docPreview.toPlainText().strip() == "" assert manus.docPreview.toPlainText().strip() == ""
assert cacheFile.exists() is False assert cacheFile.exists() is False
manus._updateBuildsList()
# Preview the first, but fail to save cache # Preview the first, but fail to save cache
manus.buildList.setCurrentRow(0) manus.buildList.setCurrentRow(0)
@@ -207,12 +208,6 @@ def testManuscript_Features(monkeypatch, qtbot: QtBot, nwGUI: GuiMain, projPath:
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr("novelwriter.tools.manusbuild.GuiManuscriptBuild.exec_", lambda *a: None) mp.setattr("novelwriter.tools.manusbuild.GuiManuscriptBuild.exec_", lambda *a: None)
# With no selection, no dialog should be created
manus.btnBuild.click()
for obj in manus.children():
assert not isinstance(obj, GuiManuscriptBuild)
# With a selection, there should be one
manus.buildList.setCurrentRow(0) manus.buildList.setCurrentRow(0)
manus.btnBuild.click() manus.btnBuild.click()
for obj in manus.children(): for obj in manus.children():
+36 -17
View File
@@ -47,7 +47,7 @@ def testBuildSettings_Init(qtbot: QtBot, nwGUI: GuiMain, projPath: Path, mockRnd
build = BuildSettings() build = BuildSettings()
# Create the dialog and populate it # Create the dialog and populate it
bSettings = GuiBuildSettings(nwGUI, nwGUI, build) bSettings = GuiBuildSettings(nwGUI, build)
bSettings.show() bSettings.show()
bSettings.loadContent() bSettings.loadContent()
@@ -132,10 +132,10 @@ def testBuildSettings_Filter(qtbot: QtBot, nwGUI: GuiMain, projPath: Path, mockR
hCharDoc = nwGUI.theProject.newFile("Jane Doe", C.hCharRoot) hCharDoc = nwGUI.theProject.newFile("Jane Doe", C.hCharRoot)
nwGUI.projView.projTree.revealNewTreeItem(hPlotDoc) nwGUI.projView.projTree.revealNewTreeItem(hPlotDoc)
nwGUI.projView.projTree.revealNewTreeItem(hCharDoc) nwGUI.projView.projTree.revealNewTreeItem(hCharDoc)
nwGUI.theProject.tree[hPlotDoc].setActive(False) nwGUI.theProject.tree[hPlotDoc].setActive(False) # type: ignore
# Create the dialog and populate it # Create the dialog and populate it
bSettings = GuiBuildSettings(nwGUI, nwGUI, build) bSettings = GuiBuildSettings(nwGUI, build)
bSettings.show() bSettings.show()
bSettings.loadContent() bSettings.loadContent()
@@ -147,7 +147,7 @@ def testBuildSettings_Filter(qtbot: QtBot, nwGUI: GuiMain, projPath: Path, mockR
assert filterTab.optTree.topLevelItemCount() == 4 # The 4 root folders assert filterTab.optTree.topLevelItemCount() == 4 # The 4 root folders
assert filterTab.filterOpt._index == 10 # 2 headers, 1 sep, 3 opt and 4 roots assert filterTab.filterOpt._index == 10 # 2 headers, 1 sep, 3 opt and 4 roots
# Untoggle note folders # Un-toggle note folders
filterTab.filterOpt._widgets[switchMap["worldRoot"]].setChecked(False) # World Root filterTab.filterOpt._widgets[switchMap["worldRoot"]].setChecked(False) # World Root
assert filterTab.optTree.topLevelItemCount() == 3 assert filterTab.optTree.topLevelItemCount() == 3
assert C.hWorldRoot in build._skipRoot assert C.hWorldRoot in build._skipRoot
@@ -229,8 +229,8 @@ def testBuildSettings_Filter(qtbot: QtBot, nwGUI: GuiMain, projPath: Path, mockR
# Set char and plot docs to excluded # Set char and plot docs to excluded
filterTab.optTree.clearSelection() filterTab.optTree.clearSelection()
filterTab._treeMap[hPlotDoc].setSelected(True) filterTab._treeMap[hPlotDoc].setSelected(True) # type: ignore
filterTab._treeMap[hCharDoc].setSelected(True) filterTab._treeMap[hCharDoc].setSelected(True) # type: ignore
filterTab.excludedButton.click() filterTab.excludedButton.click()
assert build.buildItemFilter(nwGUI.theProject) == { assert build.buildItemFilter(nwGUI.theProject) == {
C.hNovelRoot: (False, FilterMode.SKIPPED), C.hNovelRoot: (False, FilterMode.SKIPPED),
@@ -260,13 +260,30 @@ def testBuildSettings_Filter(qtbot: QtBot, nwGUI: GuiMain, projPath: Path, mockR
C.hWorldRoot: (False, FilterMode.SKIPPED), C.hWorldRoot: (False, FilterMode.SKIPPED),
} }
# Selecting only novel root should iterate through all children
filterTab.optTree.clearSelection()
filterTab._treeMap[C.hNovelRoot].setSelected(True)
filterTab.resetButton.click()
assert build.buildItemFilter(nwGUI.theProject) == {
C.hNovelRoot: (False, FilterMode.SKIPPED),
C.hTitlePage: (True, FilterMode.FILTERED),
C.hChapterDir: (False, FilterMode.SKIPPED),
C.hChapterDoc: (True, FilterMode.FILTERED),
C.hSceneDoc: (True, FilterMode.FILTERED),
C.hPlotRoot: (False, FilterMode.SKIPPED),
hPlotDoc: (False, FilterMode.EXCLUDED),
C.hCharRoot: (False, FilterMode.SKIPPED),
hCharDoc: (False, FilterMode.EXCLUDED),
C.hWorldRoot: (False, FilterMode.SKIPPED),
}
# Set everything back to filtered # Set everything back to filtered
filterTab.optTree.clearSelection() filterTab.optTree.clearSelection()
filterTab._treeMap[C.hChapterDoc].setSelected(True) filterTab._treeMap[C.hNovelRoot].setSelected(True)
filterTab._treeMap[C.hSceneDoc].setSelected(True) filterTab._treeMap[C.hSceneDoc].setSelected(True)
filterTab._treeMap[hPlotDoc].setSelected(True) filterTab._treeMap[hPlotDoc].setSelected(True) # type: ignore
filterTab._treeMap[hCharDoc].setSelected(True) filterTab._treeMap[hCharDoc].setSelected(True) # type: ignore
filterTab.filteredButton.click() filterTab.resetButton.click()
assert build.buildItemFilter(nwGUI.theProject) == { assert build.buildItemFilter(nwGUI.theProject) == {
C.hNovelRoot: (False, FilterMode.SKIPPED), C.hNovelRoot: (False, FilterMode.SKIPPED),
C.hTitlePage: (True, FilterMode.FILTERED), C.hTitlePage: (True, FilterMode.FILTERED),
@@ -285,8 +302,8 @@ def testBuildSettings_Filter(qtbot: QtBot, nwGUI: GuiMain, projPath: Path, mockR
C.hNovelRoot, C.hTitlePage, C.hChapterDir, C.hChapterDoc, C.hSceneDoc, C.hNovelRoot, C.hTitlePage, C.hChapterDir, C.hChapterDoc, C.hSceneDoc,
C.hPlotRoot, hPlotDoc, C.hCharRoot, hCharDoc, C.hPlotRoot, hPlotDoc, C.hCharRoot, hCharDoc,
] ]
nwGUI.theProject.tree[hCharDoc].setRoot(None) # Char doc has no root handle nwGUI.theProject.tree[hCharDoc].setRoot(None) # type: ignore
nwGUI.theProject.tree[hPlotDoc].setParent(None) # Plot doc has no parent handle nwGUI.theProject.tree[hPlotDoc].setParent(None) # type: ignore
filterTab._populateTree() filterTab._populateTree()
assert list(filterTab._treeMap.keys()) == [ assert list(filterTab._treeMap.keys()) == [
C.hNovelRoot, C.hTitlePage, C.hChapterDir, C.hChapterDoc, C.hSceneDoc, C.hNovelRoot, C.hTitlePage, C.hChapterDir, C.hChapterDoc, C.hSceneDoc,
@@ -320,7 +337,7 @@ def testBuildSettings_Headings(qtbot: QtBot, nwGUI: GuiMain):
build.setValue("headings.hideSection", False) build.setValue("headings.hideSection", False)
# Create the dialog and populate it # Create the dialog and populate it
bSettings = GuiBuildSettings(nwGUI, nwGUI, build) bSettings = GuiBuildSettings(nwGUI, build)
bSettings.show() bSettings.show()
bSettings.loadContent() bSettings.loadContent()
@@ -412,7 +429,9 @@ def testBuildSettings_Headings(qtbot: QtBot, nwGUI: GuiMain):
headTab.btnChapter.click() headTab.btnChapter.click()
headTab.editTextBox.setPlainText(f"Chapter {nwHeadFmt.CH_NUM}\n{nwHeadFmt.TITLE}\n") headTab.editTextBox.setPlainText(f"Chapter {nwHeadFmt.CH_NUM}\n{nwHeadFmt.TITLE}\n")
headTab.btnApply.click() headTab.btnApply.click()
assert build.getStr("headings.fmtChapter") == f"Chapter {nwHeadFmt.CH_NUM}//{nwHeadFmt.TITLE}" assert build.getStr("headings.fmtChapter") == (
f"Chapter {nwHeadFmt.CH_NUM}{nwHeadFmt.BR}{nwHeadFmt.TITLE}"
)
# Set all to plain title # Set all to plain title
headTab.btnTitle.click() headTab.btnTitle.click()
@@ -467,7 +486,7 @@ def testBuildSettings_Content(qtbot: QtBot, nwGUI: GuiMain):
build.setValue("text.addNoteHeadings", False) build.setValue("text.addNoteHeadings", False)
# Create the dialog and populate it # Create the dialog and populate it
bSettings = GuiBuildSettings(nwGUI, nwGUI, build) bSettings = GuiBuildSettings(nwGUI, build)
bSettings.show() bSettings.show()
bSettings.loadContent() bSettings.loadContent()
@@ -534,7 +553,7 @@ def testBuildSettings_Format(monkeypatch, qtbot: QtBot, nwGUI: GuiMain):
build.setValue("format.rightMargin", 15.0) build.setValue("format.rightMargin", 15.0)
# Create the dialog and populate it # Create the dialog and populate it
bSettings = GuiBuildSettings(nwGUI, nwGUI, build) bSettings = GuiBuildSettings(nwGUI, build)
bSettings.show() bSettings.show()
bSettings.loadContent() bSettings.loadContent()
@@ -622,7 +641,7 @@ def testBuildSettings_Output(qtbot: QtBot, nwGUI: GuiMain):
build.setValue("html.addStyles", False) build.setValue("html.addStyles", False)
# Create the dialog and populate it # Create the dialog and populate it
bSettings = GuiBuildSettings(nwGUI, nwGUI, build) bSettings = GuiBuildSettings(nwGUI, build)
bSettings.show() bSettings.show()
bSettings.loadContent() bSettings.loadContent()