1322 lines
48 KiB
Python
1322 lines
48 KiB
Python
"""
|
||
novelWriter – GUI Build Settings
|
||
================================
|
||
|
||
File History:
|
||
Created: 2023-02-13 [2.1b1] GuiBuildSettings
|
||
|
||
This file is a part of novelWriter
|
||
Copyright 2018–2023, Veronica Berglyd Olsen
|
||
|
||
This program is free software: you can redistribute it and/or modify
|
||
it under the terms of the GNU General Public License as published by
|
||
the Free Software Foundation, either version 3 of the License, or
|
||
(at your option) any later version.
|
||
|
||
This program is distributed in the hope that it will be useful, but
|
||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||
General Public License for more details.
|
||
|
||
You should have received a copy of the GNU General Public License
|
||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import logging
|
||
|
||
from typing import TYPE_CHECKING
|
||
|
||
from PyQt5.QtGui import (
|
||
QColor, QFont, QIcon, QSyntaxHighlighter, QTextCharFormat, QTextDocument
|
||
)
|
||
from PyQt5.QtCore import QEvent, QSize, Qt, pyqtSignal, pyqtSlot
|
||
from PyQt5.QtWidgets import (
|
||
QAbstractButton, QAbstractItemView, QComboBox, QDialog, QDialogButtonBox,
|
||
QDoubleSpinBox, QFontDialog, QFrame, QGridLayout, QHBoxLayout, QHeaderView,
|
||
QLabel, QLineEdit, QMenu, QPlainTextEdit, QPushButton, QSpinBox, QSplitter,
|
||
QStackedWidget, QToolButton, QTreeWidget, QTreeWidgetItem, QVBoxLayout,
|
||
QWidget
|
||
)
|
||
|
||
from novelwriter import CONFIG, SHARED
|
||
from novelwriter.constants import nwHeadFmt, nwLabels, trConst
|
||
from novelwriter.core.buildsettings import BuildSettings, FilterMode
|
||
from novelwriter.extensions.switch import NSwitch
|
||
from novelwriter.extensions.switchbox import NSwitchBox
|
||
from novelwriter.extensions.configlayout import NConfigLayout, NSimpleLayout
|
||
from novelwriter.extensions.pagedsidebar import NPagedSideBar
|
||
|
||
if TYPE_CHECKING: # pragma: no cover
|
||
from novelwriter.guimain import GuiMain
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
class GuiBuildSettings(QDialog):
|
||
"""GUI Tools: Manuscript Build Settings Dialog
|
||
|
||
The main tool for configuring manuscript builds. It's a GUI tool for
|
||
editing JSON build definitions, wrapped as a BuildSettings object.
|
||
"""
|
||
|
||
OPT_FILTERS = 1
|
||
OPT_HEADINGS = 2
|
||
OPT_CONTENT = 3
|
||
OPT_FORMAT = 4
|
||
OPT_OUTPUT = 5
|
||
|
||
newSettingsReady = pyqtSignal(BuildSettings)
|
||
|
||
def __init__(self, mainGui: GuiMain, build: BuildSettings) -> None:
|
||
super().__init__(parent=mainGui)
|
||
|
||
logger.debug("Create: GuiBuildSettings")
|
||
self.setObjectName("GuiBuildSettings")
|
||
if CONFIG.osDarwin:
|
||
self.setWindowFlag(Qt.WindowType.Tool)
|
||
|
||
self._build = build
|
||
|
||
self.setWindowTitle(self.tr("Manuscript Build Settings"))
|
||
self.setMinimumWidth(CONFIG.pxInt(700))
|
||
self.setMinimumHeight(CONFIG.pxInt(400))
|
||
|
||
mPx = CONFIG.pxInt(150)
|
||
wWin = CONFIG.pxInt(750)
|
||
hWin = CONFIG.pxInt(550)
|
||
|
||
pOptions = SHARED.project.options
|
||
self.resize(
|
||
CONFIG.pxInt(pOptions.getInt("GuiBuildSettings", "winWidth", wWin)),
|
||
CONFIG.pxInt(pOptions.getInt("GuiBuildSettings", "winHeight", hWin))
|
||
)
|
||
|
||
# Options SideBar
|
||
# ===============
|
||
|
||
self.optSideBar = NPagedSideBar(self)
|
||
self.optSideBar.setMinimumWidth(mPx)
|
||
self.optSideBar.setMaximumWidth(mPx)
|
||
self.optSideBar.setLabelColor(SHARED.theme.helpText)
|
||
|
||
self.optSideBar.addLabel(self.tr("Options"))
|
||
self.optSideBar.addButton(self.tr("Selection"), self.OPT_FILTERS)
|
||
self.optSideBar.addButton(self.tr("Headings"), self.OPT_HEADINGS)
|
||
self.optSideBar.addButton(self.tr("Content"), self.OPT_CONTENT)
|
||
self.optSideBar.addButton(self.tr("Format"), self.OPT_FORMAT)
|
||
self.optSideBar.addButton(self.tr("Output"), self.OPT_OUTPUT)
|
||
|
||
self.optSideBar.buttonClicked.connect(self._stackPageSelected)
|
||
|
||
# Options Area
|
||
# ============
|
||
|
||
# Create Tabs
|
||
self.optTabSelect = _FilterTab(self, self._build)
|
||
self.optTabHeadings = _HeadingsTab(self, self._build)
|
||
self.optTabContent = _ContentTab(self, self._build)
|
||
self.optTabFormat = _FormatTab(self, self._build)
|
||
self.optTabOutput = _OutputTab(self, self._build)
|
||
|
||
# Add Tabs
|
||
self.toolStack = QStackedWidget(self)
|
||
self.toolStack.addWidget(self.optTabSelect)
|
||
self.toolStack.addWidget(self.optTabHeadings)
|
||
self.toolStack.addWidget(self.optTabFormat)
|
||
self.toolStack.addWidget(self.optTabContent)
|
||
self.toolStack.addWidget(self.optTabOutput)
|
||
|
||
# Main Settings + Buttons
|
||
# =======================
|
||
|
||
self.lblBuildName = QLabel(self.tr("Name"))
|
||
self.editBuildName = QLineEdit()
|
||
self.dlgButtons = QDialogButtonBox(
|
||
QDialogButtonBox.Apply | QDialogButtonBox.Save | QDialogButtonBox.Close
|
||
)
|
||
self.dlgButtons.clicked.connect(self._dialogButtonClicked)
|
||
|
||
self.buttonBox = QHBoxLayout()
|
||
self.buttonBox.addWidget(self.lblBuildName)
|
||
self.buttonBox.addWidget(self.editBuildName)
|
||
self.buttonBox.addWidget(self.dlgButtons)
|
||
|
||
# Assemble GUI
|
||
# ============
|
||
|
||
self.mainBox = QHBoxLayout()
|
||
self.mainBox.addWidget(self.optSideBar)
|
||
self.mainBox.addWidget(self.toolStack)
|
||
self.mainBox.setContentsMargins(0, 0, 0, 0)
|
||
|
||
self.outerBox = QVBoxLayout()
|
||
self.outerBox.addLayout(self.mainBox)
|
||
self.outerBox.addLayout(self.buttonBox)
|
||
self.outerBox.setSpacing(CONFIG.pxInt(12))
|
||
|
||
self.setLayout(self.outerBox)
|
||
|
||
# Set Default Tab
|
||
self.optSideBar.setSelected(self.OPT_FILTERS)
|
||
|
||
logger.debug("Ready: GuiBuildSettings")
|
||
|
||
return
|
||
|
||
def __del__(self) -> None: # pragma: no cover
|
||
logger.debug("Delete: GuiBuildSettings")
|
||
return
|
||
|
||
def loadContent(self) -> None:
|
||
"""Populate the child widgets."""
|
||
self.editBuildName.setText(self._build.name)
|
||
self.optTabSelect.loadContent()
|
||
self.optTabHeadings.loadContent()
|
||
self.optTabContent.loadContent()
|
||
self.optTabFormat.loadContent()
|
||
self.optTabOutput.loadContent()
|
||
return
|
||
|
||
##
|
||
# Properties
|
||
##
|
||
|
||
@property
|
||
def buildID(self) -> str:
|
||
"""The build ID of the build of the dialog."""
|
||
return self._build.buildID
|
||
|
||
##
|
||
# Private Slots
|
||
##
|
||
|
||
@pyqtSlot(int)
|
||
def _stackPageSelected(self, pageId: int) -> None:
|
||
"""Process a user request to switch page."""
|
||
if pageId == self.OPT_FILTERS:
|
||
self.toolStack.setCurrentWidget(self.optTabSelect)
|
||
elif pageId == self.OPT_HEADINGS:
|
||
self.toolStack.setCurrentWidget(self.optTabHeadings)
|
||
elif pageId == self.OPT_CONTENT:
|
||
self.toolStack.setCurrentWidget(self.optTabContent)
|
||
elif pageId == self.OPT_FORMAT:
|
||
self.toolStack.setCurrentWidget(self.optTabFormat)
|
||
elif pageId == self.OPT_OUTPUT:
|
||
self.toolStack.setCurrentWidget(self.optTabOutput)
|
||
return
|
||
|
||
@pyqtSlot("QAbstractButton*")
|
||
def _dialogButtonClicked(self, button: QAbstractButton) -> None:
|
||
"""Handle button clicks from the dialog button box."""
|
||
role = self.dlgButtons.buttonRole(button)
|
||
if role == QDialogButtonBox.ApplyRole:
|
||
self._emitBuildData()
|
||
elif role == QDialogButtonBox.AcceptRole:
|
||
self._emitBuildData()
|
||
self.close()
|
||
elif role == QDialogButtonBox.RejectRole:
|
||
self.close()
|
||
return
|
||
|
||
##
|
||
# Events
|
||
##
|
||
|
||
def closeEvent(self, event: QEvent) -> None:
|
||
"""Capture the user closing the window so we can save
|
||
settings.
|
||
"""
|
||
logger.debug("Closing: GuiBuildSettings")
|
||
self._askToSaveBuild()
|
||
self._saveSettings()
|
||
event.accept()
|
||
self.deleteLater()
|
||
return
|
||
|
||
##
|
||
# Internal Functions
|
||
##
|
||
|
||
def _askToSaveBuild(self) -> None:
|
||
"""Check if there are unsaved changes, and if there are, ask
|
||
whether the user wants to save them.
|
||
"""
|
||
if self._build.changed:
|
||
response = SHARED.question(self.tr(
|
||
"Do you want to save your changes to '{0}'?".format(self._build.name)
|
||
))
|
||
if response:
|
||
self._emitBuildData()
|
||
self._build.resetChangedState()
|
||
return
|
||
|
||
def _saveSettings(self) -> None:
|
||
"""Save the various user settings."""
|
||
logger.debug("Saving GuiBuildSettings settings")
|
||
|
||
winWidth = CONFIG.rpxInt(self.width())
|
||
winHeight = CONFIG.rpxInt(self.height())
|
||
|
||
treeWidth, filterWidth = self.optTabSelect.mainSplitSizes()
|
||
|
||
pOptions = SHARED.project.options
|
||
pOptions.setValue("GuiBuildSettings", "winWidth", winWidth)
|
||
pOptions.setValue("GuiBuildSettings", "winHeight", winHeight)
|
||
pOptions.setValue("GuiBuildSettings", "treeWidth", treeWidth)
|
||
pOptions.setValue("GuiBuildSettings", "filterWidth", filterWidth)
|
||
pOptions.saveSettings()
|
||
|
||
return
|
||
|
||
def _emitBuildData(self) -> None:
|
||
"""Assemble the build data and emit the signal."""
|
||
self._build.setName(self.editBuildName.text())
|
||
self.optTabHeadings.saveContent()
|
||
self.optTabContent.saveContent()
|
||
self.optTabFormat.saveContent()
|
||
self.optTabOutput.saveContent()
|
||
self.newSettingsReady.emit(self._build)
|
||
self._build.resetChangedState()
|
||
return
|
||
|
||
# END Class GuiBuildSettings
|
||
|
||
|
||
class _FilterTab(QWidget):
|
||
|
||
C_DATA = 0
|
||
C_NAME = 0
|
||
C_ACTIVE = 1
|
||
C_STATUS = 2
|
||
|
||
D_HANDLE = Qt.ItemDataRole.UserRole
|
||
D_FILE = Qt.ItemDataRole.UserRole + 1
|
||
|
||
F_NONE = 0
|
||
F_FILTERED = 1
|
||
F_INCLUDED = 2
|
||
F_EXCLUDED = 3
|
||
|
||
def __init__(self, buildMain: GuiBuildSettings, build: BuildSettings) -> None:
|
||
super().__init__(parent=buildMain)
|
||
|
||
self._treeMap: dict[str, QTreeWidgetItem] = {}
|
||
self._build = build
|
||
|
||
self._statusFlags: dict[int, QIcon] = {
|
||
self.F_NONE: QIcon(),
|
||
self.F_FILTERED: SHARED.theme.getIcon("build_filtered"),
|
||
self.F_INCLUDED: SHARED.theme.getIcon("build_included"),
|
||
self.F_EXCLUDED: SHARED.theme.getIcon("build_excluded"),
|
||
}
|
||
|
||
self._trIncluded = self.tr("Included in manuscript")
|
||
self._trExcluded = self.tr("Excluded from manuscript")
|
||
|
||
# Project Tree
|
||
# ============
|
||
|
||
# Tree Settings
|
||
iPx = SHARED.theme.baseIconSize
|
||
cMg = CONFIG.pxInt(6)
|
||
|
||
# Tree Widget
|
||
self.optTree = QTreeWidget(self)
|
||
self.optTree.setIconSize(QSize(iPx, iPx))
|
||
self.optTree.setUniformRowHeights(True)
|
||
self.optTree.setAllColumnsShowFocus(True)
|
||
self.optTree.setHeaderHidden(True)
|
||
self.optTree.setIndentation(iPx)
|
||
self.optTree.setColumnCount(3)
|
||
|
||
treeHeader = self.optTree.header()
|
||
treeHeader.setStretchLastSection(False)
|
||
treeHeader.setSectionResizeMode(self.C_NAME, QHeaderView.Stretch)
|
||
treeHeader.setSectionResizeMode(self.C_ACTIVE, QHeaderView.Fixed)
|
||
treeHeader.setSectionResizeMode(self.C_STATUS, QHeaderView.Fixed)
|
||
treeHeader.resizeSection(self.C_ACTIVE, iPx + cMg)
|
||
treeHeader.resizeSection(self.C_STATUS, iPx + cMg)
|
||
|
||
self.optTree.setSelectionMode(QAbstractItemView.ExtendedSelection)
|
||
self.optTree.setDragDropMode(QAbstractItemView.NoDragDrop)
|
||
|
||
# Filters
|
||
# =======
|
||
|
||
self.includedButton = QToolButton(self)
|
||
self.includedButton.setToolTip(self.tr("Always included"))
|
||
self.includedButton.setIcon(self._statusFlags[self.F_INCLUDED])
|
||
self.includedButton.clicked.connect(lambda: self._setSelectedMode(self.F_INCLUDED))
|
||
|
||
self.excludedButton = QToolButton(self)
|
||
self.excludedButton.setToolTip(self.tr("Always excluded"))
|
||
self.excludedButton.setIcon(self._statusFlags[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(SHARED.theme.getIcon("revert"))
|
||
self.resetButton.clicked.connect(lambda: self._setSelectedMode(self.F_FILTERED))
|
||
|
||
self.modeBox = QHBoxLayout()
|
||
self.modeBox.addWidget(QLabel(self.tr("Mark selection as")))
|
||
self.modeBox.addStretch(1)
|
||
self.modeBox.addWidget(self.includedButton)
|
||
self.modeBox.addWidget(self.excludedButton)
|
||
self.modeBox.addWidget(self.resetButton)
|
||
self.modeBox.setSpacing(CONFIG.pxInt(4))
|
||
|
||
# Filer Options
|
||
self.filterOpt = NSwitchBox(self, iPx)
|
||
self.filterOpt.switchToggled.connect(self._applyFilterSwitch)
|
||
self.filterOpt.setFrameStyle(QFrame.NoFrame)
|
||
|
||
# Assemble GUI
|
||
# ============
|
||
|
||
pOptions = SHARED.project.options
|
||
|
||
self.selectionBox = QVBoxLayout()
|
||
self.selectionBox.addWidget(self.optTree)
|
||
self.selectionBox.addLayout(self.modeBox)
|
||
self.selectionBox.setContentsMargins(0, 0, 0, 0)
|
||
|
||
self.selectionWidget = QWidget()
|
||
self.selectionWidget.setLayout(self.selectionBox)
|
||
|
||
self.mainSplit = QSplitter()
|
||
self.mainSplit.addWidget(self.selectionWidget)
|
||
self.mainSplit.addWidget(self.filterOpt)
|
||
self.mainSplit.setCollapsible(0, False)
|
||
self.mainSplit.setCollapsible(1, False)
|
||
self.mainSplit.setStretchFactor(0, 0)
|
||
self.mainSplit.setStretchFactor(1, 1)
|
||
self.mainSplit.setSizes([
|
||
CONFIG.pxInt(pOptions.getInt("GuiBuildSettings", "treeWidth", 1)),
|
||
CONFIG.pxInt(pOptions.getInt("GuiBuildSettings", "filterWidth", 1))
|
||
])
|
||
|
||
self.outerBox = QHBoxLayout()
|
||
self.outerBox.addWidget(self.mainSplit)
|
||
self.outerBox.setContentsMargins(0, 0, 0, 0)
|
||
|
||
self.setLayout(self.outerBox)
|
||
|
||
return
|
||
|
||
def loadContent(self) -> None:
|
||
"""Populate the widgets."""
|
||
self._populateTree()
|
||
self._populateFilters()
|
||
return
|
||
|
||
def mainSplitSizes(self) -> tuple[int, int]:
|
||
"""Extract the sizes of the main splitter."""
|
||
sizes = self.mainSplit.sizes()
|
||
m, n = (sizes[0], sizes[1]) if len(sizes) >= 2 else (0, 0)
|
||
return CONFIG.rpxInt(m), CONFIG.rpxInt(n)
|
||
|
||
##
|
||
# Slots
|
||
##
|
||
|
||
@pyqtSlot(str, bool)
|
||
def _applyFilterSwitch(self, key: str, state: bool) -> None:
|
||
"""Apply filter switch and update the settings."""
|
||
if key.startswith("doc:"):
|
||
self._build.setValue(key[4:], state)
|
||
self._setTreeItemMode()
|
||
elif key.startswith("root:"):
|
||
self._build.setAllowRoot(key[5:], state)
|
||
self._populateTree()
|
||
return
|
||
|
||
##
|
||
# Internal Functions
|
||
##
|
||
|
||
def _populateTree(self) -> None:
|
||
"""Build the tree of project items."""
|
||
logger.debug("Building project tree")
|
||
self._treeMap = {}
|
||
self.optTree.clear()
|
||
for nwItem in SHARED.project.iterProjectItems():
|
||
|
||
tHandle = nwItem.itemHandle
|
||
pHandle = nwItem.itemParent
|
||
rHandle = nwItem.itemRoot
|
||
|
||
if tHandle is None or rHandle is None:
|
||
continue
|
||
|
||
isFile = nwItem.isFileType()
|
||
isActive = nwItem.isActive
|
||
|
||
if nwItem.isInactiveClass() or not self._build.isRootAllowed(rHandle):
|
||
continue
|
||
|
||
hLevel = nwItem.mainHeading
|
||
itemIcon = SHARED.theme.getItemIcon(
|
||
nwItem.itemType, nwItem.itemClass, nwItem.itemLayout, hLevel
|
||
)
|
||
|
||
if isFile:
|
||
iconName = "checked" if isActive else "unchecked"
|
||
else:
|
||
iconName = "noncheckable"
|
||
|
||
trItem = QTreeWidgetItem()
|
||
trItem.setIcon(self.C_NAME, itemIcon)
|
||
trItem.setText(self.C_NAME, nwItem.itemName)
|
||
trItem.setData(self.C_DATA, self.D_HANDLE, tHandle)
|
||
trItem.setData(self.C_DATA, self.D_FILE, isFile)
|
||
trItem.setIcon(self.C_ACTIVE, SHARED.theme.getIcon(iconName))
|
||
|
||
trItem.setTextAlignment(self.C_NAME, Qt.AlignLeft)
|
||
|
||
if pHandle is None and nwItem.isRootType():
|
||
self.optTree.addTopLevelItem(trItem)
|
||
elif pHandle in self._treeMap:
|
||
self._treeMap[pHandle].addChild(trItem)
|
||
else:
|
||
logger.debug("Skipping item '%s'", tHandle)
|
||
continue
|
||
|
||
self._treeMap[tHandle] = trItem
|
||
trItem.setExpanded(True)
|
||
|
||
self._setTreeItemMode()
|
||
|
||
return
|
||
|
||
def _populateFilters(self) -> None:
|
||
"""Populate the filter options switches."""
|
||
self.filterOpt.clear()
|
||
self.filterOpt.addLabel(self._build.getLabel("filter"))
|
||
self.filterOpt.addItem(
|
||
SHARED.theme.getIcon("proj_scene"),
|
||
self._build.getLabel("filter.includeNovel"),
|
||
"doc:filter.includeNovel",
|
||
default=self._build.getBool("filter.includeNovel")
|
||
)
|
||
self.filterOpt.addItem(
|
||
SHARED.theme.getIcon("proj_note"),
|
||
self._build.getLabel("filter.includeNotes"),
|
||
"doc:filter.includeNotes",
|
||
default=self._build.getBool("filter.includeNotes")
|
||
)
|
||
self.filterOpt.addItem(
|
||
SHARED.theme.getIcon("unchecked"),
|
||
self._build.getLabel("filter.includeInactive"),
|
||
"doc:filter.includeInactive",
|
||
default=self._build.getBool("filter.includeInactive")
|
||
)
|
||
|
||
self.filterOpt.addSeparator()
|
||
|
||
# Root Classes
|
||
self.filterOpt.addLabel(self.tr("Select Root Folders"))
|
||
for tHandle, nwItem in SHARED.project.tree.iterRoots(None):
|
||
if not nwItem.isInactiveClass():
|
||
itemIcon = SHARED.theme.getItemIcon(
|
||
nwItem.itemType, nwItem.itemClass, nwItem.itemLayout
|
||
)
|
||
self.filterOpt.addItem(
|
||
itemIcon, nwItem.itemName, f"root:{tHandle}",
|
||
default=self._build.isRootAllowed(tHandle)
|
||
)
|
||
|
||
return
|
||
|
||
def _setSelectedMode(self, mode: int) -> None:
|
||
"""Set the mode for the selected items."""
|
||
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):
|
||
tHandle = item.data(self.C_DATA, self.D_HANDLE)
|
||
isFile = item.data(self.C_DATA, self.D_FILE)
|
||
if isFile:
|
||
if mode == self.F_FILTERED:
|
||
self._build.setFiltered(tHandle)
|
||
elif mode == self.F_INCLUDED:
|
||
self._build.setIncluded(tHandle)
|
||
elif mode == self.F_EXCLUDED:
|
||
self._build.setExcluded(tHandle)
|
||
|
||
self._setTreeItemMode()
|
||
|
||
return
|
||
|
||
def _setTreeItemMode(self) -> None:
|
||
"""Update the filtered mode icon on all items."""
|
||
filtered = self._build.buildItemFilter(SHARED.project)
|
||
for tHandle, item in self._treeMap.items():
|
||
allow, mode = filtered.get(tHandle, (False, FilterMode.UNKNOWN))
|
||
if mode == FilterMode.INCLUDED:
|
||
item.setIcon(self.C_STATUS, self._statusFlags[self.F_INCLUDED])
|
||
item.setToolTip(self.C_STATUS, self._trIncluded)
|
||
elif mode == FilterMode.EXCLUDED:
|
||
item.setIcon(self.C_STATUS, self._statusFlags[self.F_EXCLUDED])
|
||
item.setToolTip(self.C_STATUS, self._trExcluded)
|
||
elif mode == FilterMode.FILTERED and allow:
|
||
item.setIcon(self.C_STATUS, self._statusFlags[self.F_FILTERED])
|
||
item.setToolTip(self.C_STATUS, self._trIncluded)
|
||
else:
|
||
item.setIcon(self.C_STATUS, self._statusFlags[self.F_NONE])
|
||
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
|
||
|
||
|
||
class _HeadingsTab(QWidget):
|
||
|
||
EDIT_TITLE = 1
|
||
EDIT_CHAPTER = 2
|
||
EDIT_UNNUM = 3
|
||
EDIT_SCENE = 4
|
||
EDIT_SECTION = 5
|
||
|
||
def __init__(self, buildMain: GuiBuildSettings, build: BuildSettings) -> None:
|
||
super().__init__(parent=buildMain)
|
||
|
||
self._build = build
|
||
self._editing = 0
|
||
|
||
iPx = SHARED.theme.baseIconSize
|
||
vSp = CONFIG.pxInt(12)
|
||
bSp = CONFIG.pxInt(6)
|
||
|
||
# Format Boxes
|
||
# ============
|
||
self.formatBox = QGridLayout()
|
||
self.formatBox.setHorizontalSpacing(vSp)
|
||
|
||
# Title Heading
|
||
self.lblTitle = QLabel(self._build.getLabel("headings.fmtTitle"))
|
||
self.fmtTitle = QLineEdit("")
|
||
self.fmtTitle.setReadOnly(True)
|
||
self.btnTitle = QToolButton()
|
||
self.btnTitle.setIcon(SHARED.theme.getIcon("edit"))
|
||
self.btnTitle.clicked.connect(lambda: self._editHeading(self.EDIT_TITLE))
|
||
|
||
wrapTitle = QHBoxLayout()
|
||
wrapTitle.addWidget(self.fmtTitle)
|
||
wrapTitle.addWidget(self.btnTitle)
|
||
wrapTitle.setSpacing(bSp)
|
||
|
||
self.formatBox.addWidget(self.lblTitle, 0, 0, Qt.AlignLeft)
|
||
self.formatBox.addLayout(wrapTitle, 0, 1, Qt.AlignLeft)
|
||
|
||
# Chapter Heading
|
||
self.lblChapter = QLabel(self._build.getLabel("headings.fmtChapter"))
|
||
self.fmtChapter = QLineEdit("")
|
||
self.fmtChapter.setReadOnly(True)
|
||
self.btnChapter = QToolButton()
|
||
self.btnChapter.setIcon(SHARED.theme.getIcon("edit"))
|
||
self.btnChapter.clicked.connect(lambda: self._editHeading(self.EDIT_CHAPTER))
|
||
|
||
wrapChapter = QHBoxLayout()
|
||
wrapChapter.addWidget(self.fmtChapter)
|
||
wrapChapter.addWidget(self.btnChapter)
|
||
wrapChapter.setSpacing(bSp)
|
||
|
||
self.formatBox.addWidget(self.lblChapter, 1, 0, Qt.AlignLeft)
|
||
self.formatBox.addLayout(wrapChapter, 1, 1, Qt.AlignLeft)
|
||
|
||
# Unnumbered Chapter Heading
|
||
self.lblUnnumbered = QLabel(self._build.getLabel("headings.fmtUnnumbered"))
|
||
self.fmtUnnumbered = QLineEdit("")
|
||
self.fmtUnnumbered.setReadOnly(True)
|
||
self.btnUnnumbered = QToolButton()
|
||
self.btnUnnumbered.setIcon(SHARED.theme.getIcon("edit"))
|
||
self.btnUnnumbered.clicked.connect(lambda: self._editHeading(self.EDIT_UNNUM))
|
||
|
||
wrapUnnumbered = QHBoxLayout()
|
||
wrapUnnumbered.addWidget(self.fmtUnnumbered)
|
||
wrapUnnumbered.addWidget(self.btnUnnumbered)
|
||
wrapUnnumbered.setSpacing(bSp)
|
||
|
||
self.formatBox.addWidget(self.lblUnnumbered, 2, 0, Qt.AlignLeft)
|
||
self.formatBox.addLayout(wrapUnnumbered, 2, 1, Qt.AlignLeft)
|
||
|
||
# Scene Heading
|
||
sceneHideTip = self._build.getLabel("headings.hideScene")
|
||
self.lblScene = QLabel(self._build.getLabel("headings.fmtScene"))
|
||
self.fmtScene = QLineEdit("")
|
||
self.fmtScene.setReadOnly(True)
|
||
self.btnScene = QToolButton()
|
||
self.btnScene.setIcon(SHARED.theme.getIcon("edit"))
|
||
self.btnScene.clicked.connect(lambda: self._editHeading(self.EDIT_SCENE))
|
||
self.hdeScene = QLabel(self.tr("Hide"))
|
||
self.hdeScene.setToolTip(sceneHideTip)
|
||
self.swtScene = NSwitch(self, width=2*iPx, height=iPx)
|
||
self.swtScene.setToolTip(sceneHideTip)
|
||
|
||
wrapScene = QHBoxLayout()
|
||
wrapScene.addWidget(self.fmtScene)
|
||
wrapScene.addWidget(self.btnScene)
|
||
wrapScene.setSpacing(bSp)
|
||
|
||
wrapSceneHide = QHBoxLayout()
|
||
wrapSceneHide.addWidget(self.hdeScene)
|
||
wrapSceneHide.addWidget(self.swtScene)
|
||
wrapSceneHide.setSpacing(bSp)
|
||
|
||
self.formatBox.addWidget(self.lblScene, 3, 0, Qt.AlignLeft)
|
||
self.formatBox.addLayout(wrapScene, 3, 1, Qt.AlignLeft)
|
||
self.formatBox.addLayout(wrapSceneHide, 3, 2, Qt.AlignLeft)
|
||
|
||
# Section Heading
|
||
sectionHideTip = self._build.getLabel("headings.hideSection")
|
||
self.lblSection = QLabel(self._build.getLabel("headings.fmtSection"))
|
||
self.fmtSection = QLineEdit("")
|
||
self.fmtSection.setReadOnly(True)
|
||
self.btnSection = QToolButton()
|
||
self.btnSection.setIcon(SHARED.theme.getIcon("edit"))
|
||
self.btnSection.clicked.connect(lambda: self._editHeading(self.EDIT_SECTION))
|
||
self.hdeSection = QLabel(self.tr("Hide"))
|
||
self.hdeSection.setToolTip(sectionHideTip)
|
||
self.swtSection = NSwitch(self, width=2*iPx, height=iPx)
|
||
self.swtSection.setToolTip(sectionHideTip)
|
||
|
||
wrapSection = QHBoxLayout()
|
||
wrapSection.addWidget(self.fmtSection)
|
||
wrapSection.addWidget(self.btnSection)
|
||
wrapSection.setSpacing(bSp)
|
||
|
||
wrapSectionHide = QHBoxLayout()
|
||
wrapSectionHide.addWidget(self.hdeSection)
|
||
wrapSectionHide.addWidget(self.swtSection)
|
||
wrapSectionHide.setSpacing(bSp)
|
||
|
||
self.formatBox.addWidget(self.lblSection, 4, 0, Qt.AlignLeft)
|
||
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 = _HeadingSyntaxHighlighter(self.editTextBox.document())
|
||
|
||
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(nwHeadFmt.TITLE))
|
||
self.aInsChNum.triggered.connect(lambda: self._insertIntoForm(nwHeadFmt.CH_NUM))
|
||
self.aInsChWord.triggered.connect(lambda: self._insertIntoForm(nwHeadFmt.CH_WORD))
|
||
self.aInsChRomU.triggered.connect(lambda: self._insertIntoForm(nwHeadFmt.CH_ROMU))
|
||
self.aInsChRomL.triggered.connect(lambda: self._insertIntoForm(nwHeadFmt.CH_ROML))
|
||
self.aInsScNum.triggered.connect(lambda: self._insertIntoForm(nwHeadFmt.SC_NUM))
|
||
self.aInsScAbs.triggered.connect(lambda: self._insertIntoForm(nwHeadFmt.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) -> None:
|
||
"""Populate the widgets."""
|
||
self.fmtTitle.setText(self._build.getStr("headings.fmtTitle"))
|
||
self.fmtChapter.setText(self._build.getStr("headings.fmtChapter"))
|
||
self.fmtUnnumbered.setText(self._build.getStr("headings.fmtUnnumbered"))
|
||
self.fmtScene.setText(self._build.getStr("headings.fmtScene"))
|
||
self.fmtSection.setText(self._build.getStr("headings.fmtSection"))
|
||
self.swtScene.setChecked(self._build.getBool("headings.hideScene"))
|
||
self.swtSection.setChecked(self._build.getBool("headings.hideSection"))
|
||
return
|
||
|
||
def saveContent(self) -> None:
|
||
"""Save choices back into build object."""
|
||
self._build.setValue("headings.hideScene", self.swtScene.isChecked())
|
||
self._build.setValue("headings.hideSection", self.swtSection.isChecked())
|
||
return
|
||
|
||
##
|
||
# Internal Functions
|
||
##
|
||
|
||
def _insertIntoForm(self, text: str) -> None:
|
||
"""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) -> None:
|
||
"""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(nwHeadFmt.BR, "\n"))
|
||
self.lblEditForm.setText(self.tr("Editing: {0}").format(label))
|
||
|
||
return
|
||
|
||
##
|
||
# Private Slots
|
||
##
|
||
|
||
def _saveFormat(self) -> None:
|
||
"""Save the format from the edit text box."""
|
||
heading = self._editing
|
||
text = self.editTextBox.toPlainText().strip().replace("\n", nwHeadFmt.BR)
|
||
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
|
||
|
||
# END Class _HeadingsTab
|
||
|
||
|
||
class _HeadingSyntaxHighlighter(QSyntaxHighlighter):
|
||
|
||
def __init__(self, document: QTextDocument) -> None:
|
||
super().__init__(document)
|
||
self._fmtSymbol = QTextCharFormat()
|
||
self._fmtSymbol.setForeground(QColor(*SHARED.theme.colHead))
|
||
self._fmtFormat = QTextCharFormat()
|
||
self._fmtFormat.setForeground(QColor(*SHARED.theme.colEmph))
|
||
return
|
||
|
||
def highlightBlock(self, text: str) -> None:
|
||
"""Add syntax highlighting to the text block."""
|
||
for heading in nwHeadFmt.ALL:
|
||
pos = text.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 _HeadingSyntaxHighlighter
|
||
|
||
|
||
class _ContentTab(QWidget):
|
||
|
||
def __init__(self, buildMain: GuiBuildSettings, build: BuildSettings) -> None:
|
||
super().__init__(parent=buildMain)
|
||
|
||
self._build = build
|
||
|
||
iPx = SHARED.theme.baseIconSize
|
||
|
||
# Left Form
|
||
# =========
|
||
|
||
self.formLeft = NSimpleLayout()
|
||
self.formLeft.addGroupLabel(self._build.getLabel("text.grpContent"))
|
||
|
||
self.incSynopsis = NSwitch(self, width=2*iPx, height=iPx)
|
||
self.incComments = NSwitch(self, width=2*iPx, height=iPx)
|
||
self.incKeywords = NSwitch(self, width=2*iPx, height=iPx)
|
||
self.incBodyText = NSwitch(self, width=2*iPx, height=iPx)
|
||
|
||
self.formLeft.addRow(self._build.getLabel("text.includeSynopsis"), self.incSynopsis)
|
||
self.formLeft.addRow(self._build.getLabel("text.includeComments"), self.incComments)
|
||
self.formLeft.addRow(self._build.getLabel("text.includeKeywords"), self.incKeywords)
|
||
self.formLeft.addRow(self._build.getLabel("text.includeBodyText"), self.incBodyText)
|
||
|
||
# Right Form
|
||
# ==========
|
||
|
||
self.formRight = NSimpleLayout()
|
||
self.formRight.addGroupLabel(self._build.getLabel("text.grpInsert"))
|
||
|
||
self.addNoteHead = NSwitch(self, width=2*iPx, height=iPx)
|
||
|
||
self.formRight.addRow(self._build.getLabel("text.addNoteHeadings"), self.addNoteHead)
|
||
|
||
# Assemble GUI
|
||
# ============
|
||
|
||
self.outerBox = QHBoxLayout()
|
||
self.outerBox.addLayout(self.formLeft, 1)
|
||
self.outerBox.addLayout(self.formRight, 1)
|
||
self.outerBox.setContentsMargins(0, 0, 0, 0)
|
||
self.outerBox.setSpacing(CONFIG.pxInt(16))
|
||
|
||
self.setLayout(self.outerBox)
|
||
|
||
return
|
||
|
||
def loadContent(self) -> None:
|
||
"""Populate the widgets."""
|
||
self.incSynopsis.setChecked(self._build.getBool("text.includeSynopsis"))
|
||
self.incComments.setChecked(self._build.getBool("text.includeComments"))
|
||
self.incKeywords.setChecked(self._build.getBool("text.includeKeywords"))
|
||
self.incBodyText.setChecked(self._build.getBool("text.includeBodyText"))
|
||
self.addNoteHead.setChecked(self._build.getBool("text.addNoteHeadings"))
|
||
return
|
||
|
||
def saveContent(self) -> None:
|
||
"""Save choices back into build object."""
|
||
self._build.setValue("text.includeSynopsis", self.incSynopsis.isChecked())
|
||
self._build.setValue("text.includeComments", self.incComments.isChecked())
|
||
self._build.setValue("text.includeKeywords", self.incKeywords.isChecked())
|
||
self._build.setValue("text.includeBodyText", self.incBodyText.isChecked())
|
||
self._build.setValue("text.addNoteHeadings", self.addNoteHead.isChecked())
|
||
return
|
||
|
||
# END Class _ContentTab
|
||
|
||
|
||
class _FormatTab(QWidget):
|
||
|
||
def __init__(self, buildMain: GuiBuildSettings, build: BuildSettings) -> None:
|
||
super().__init__(parent=buildMain)
|
||
|
||
self.buildMain = buildMain
|
||
|
||
self._build = build
|
||
self._unitScale = 1.0
|
||
|
||
iPx = SHARED.theme.baseIconSize
|
||
spW = 6*SHARED.theme.textNWidth
|
||
dbW = 8*SHARED.theme.textNWidth
|
||
|
||
# Text Format Form
|
||
# ================
|
||
|
||
self.formFormat = NConfigLayout()
|
||
self.formFormat.addGroupLabel(self._build.getLabel("format.grpFormat"))
|
||
|
||
# Build Language
|
||
self.buildLang = QComboBox()
|
||
langauges = CONFIG.listLanguages(CONFIG.LANG_PROJ)
|
||
self.buildLang.addItem("[%s]" % self.tr("Not Set"), "None")
|
||
for langID, langName in langauges:
|
||
self.buildLang.addItem(langName, langID)
|
||
|
||
self.formFormat.addRow(self._build.getLabel("format.buildLang"), self.buildLang)
|
||
|
||
# Font Family
|
||
self.textFont = QLineEdit()
|
||
self.textFont.setReadOnly(True)
|
||
self.btnTextFont = QPushButton("...")
|
||
self.btnTextFont.setMaximumWidth(int(2.5*SHARED.theme.getTextWidth("...")))
|
||
self.btnTextFont.clicked.connect(self._selectFont)
|
||
self.formFormat.addRow(
|
||
self._build.getLabel("format.textFont"), self.textFont, button=self.btnTextFont
|
||
)
|
||
|
||
# Font Size
|
||
self.textSize = QSpinBox(self)
|
||
self.textSize.setMinimum(8)
|
||
self.textSize.setMaximum(60)
|
||
self.textSize.setSingleStep(1)
|
||
self.textSize.setMinimumWidth(spW)
|
||
self.formFormat.addRow(
|
||
self._build.getLabel("format.textSize"), self.textSize, unit="pt"
|
||
)
|
||
|
||
# Line Height
|
||
self.lineHeight = QDoubleSpinBox(self)
|
||
self.lineHeight.setFixedWidth(spW)
|
||
self.lineHeight.setMinimum(0.75)
|
||
self.lineHeight.setMaximum(3.0)
|
||
self.lineHeight.setSingleStep(0.05)
|
||
self.lineHeight.setDecimals(2)
|
||
self.formFormat.addRow(
|
||
self._build.getLabel("format.lineHeight"), self.lineHeight, unit="em"
|
||
)
|
||
|
||
# Text Options Form
|
||
# =================
|
||
|
||
self.formOptions = NSimpleLayout()
|
||
self.formOptions.addGroupLabel(self._build.getLabel("format.grpOptions"))
|
||
self.formOptions.setContentsMargins(0, 0, 0, 0)
|
||
|
||
self.justifyText = NSwitch(self, width=2*iPx, height=iPx)
|
||
self.stripUnicode = NSwitch(self, width=2*iPx, height=iPx)
|
||
self.replaceTabs = NSwitch(self, width=2*iPx, height=iPx)
|
||
|
||
self.formOptions.addRow(self._build.getLabel("format.justifyText"), self.justifyText)
|
||
self.formOptions.addRow(self._build.getLabel("format.stripUnicode"), self.stripUnicode)
|
||
self.formOptions.addRow(self._build.getLabel("format.replaceTabs"), self.replaceTabs)
|
||
|
||
# Page Layout Form
|
||
# ================
|
||
|
||
self.formLayout = NSimpleLayout()
|
||
self.formLayout.addGroupLabel(self._build.getLabel("format.grpPage"))
|
||
self.formLayout.setContentsMargins(0, 0, 0, 0)
|
||
|
||
self.pageUnit = QComboBox(self)
|
||
for key, name in nwLabels.UNIT_NAME.items():
|
||
self.pageUnit.addItem(trConst(name), key)
|
||
|
||
self.pageSize = QComboBox(self)
|
||
for key, name in nwLabels.PAPER_NAME.items():
|
||
self.pageSize.addItem(trConst(name), key)
|
||
|
||
self.pageWidth = QDoubleSpinBox(self)
|
||
self.pageWidth.setFixedWidth(dbW)
|
||
self.pageWidth.setMaximum(500.0)
|
||
self.pageWidth.valueChanged.connect(self._pageSizeValueChanged)
|
||
|
||
self.pageHeight = QDoubleSpinBox(self)
|
||
self.pageHeight.setFixedWidth(dbW)
|
||
self.pageHeight.setMaximum(500.0)
|
||
self.pageHeight.valueChanged.connect(self._pageSizeValueChanged)
|
||
|
||
self.topMargin = QDoubleSpinBox(self)
|
||
self.topMargin.setFixedWidth(dbW)
|
||
|
||
self.bottomMargin = QDoubleSpinBox(self)
|
||
self.bottomMargin.setFixedWidth(dbW)
|
||
|
||
self.leftMargin = QDoubleSpinBox(self)
|
||
self.leftMargin.setFixedWidth(dbW)
|
||
|
||
self.rightMargin = QDoubleSpinBox(self)
|
||
self.rightMargin.setFixedWidth(dbW)
|
||
|
||
self.formLayout.addRow(self._build.getLabel("format.pageUnit"), self.pageUnit)
|
||
self.formLayout.addRow(self._build.getLabel("format.pageSize"), self.pageSize)
|
||
self.formLayout.addRow(self._build.getLabel("format.pageWidth"), self.pageWidth)
|
||
self.formLayout.addRow(self._build.getLabel("format.pageHeight"), self.pageHeight)
|
||
self.formLayout.addRow(self._build.getLabel("format.topMargin"), self.topMargin)
|
||
self.formLayout.addRow(self._build.getLabel("format.bottomMargin"), self.bottomMargin)
|
||
self.formLayout.addRow(self._build.getLabel("format.leftMargin"), self.leftMargin)
|
||
self.formLayout.addRow(self._build.getLabel("format.rightMargin"), self.rightMargin)
|
||
|
||
# Assemble GUI
|
||
# ============
|
||
|
||
self.formLeft = QVBoxLayout()
|
||
self.formLeft.addLayout(self.formFormat)
|
||
self.formLeft.addLayout(self.formOptions)
|
||
self.formLeft.addStretch(1)
|
||
self.formLeft.setContentsMargins(0, 0, 0, 0)
|
||
self.formLeft.setSpacing(CONFIG.pxInt(8))
|
||
|
||
self.formRight = QVBoxLayout()
|
||
self.formRight.addLayout(self.formLayout)
|
||
self.formRight.addStretch(1)
|
||
self.formRight.setContentsMargins(0, 0, 0, 0)
|
||
self.formRight.setSpacing(CONFIG.pxInt(8))
|
||
|
||
self.outerBox = QHBoxLayout()
|
||
self.outerBox.addLayout(self.formLeft, 1)
|
||
self.outerBox.addLayout(self.formRight, 1)
|
||
self.outerBox.setContentsMargins(0, 0, 0, 0)
|
||
self.outerBox.setSpacing(CONFIG.pxInt(16))
|
||
|
||
self.setLayout(self.outerBox)
|
||
|
||
return
|
||
|
||
def loadContent(self) -> None:
|
||
"""Populate the widgets."""
|
||
langIdx = self.buildLang.findData(self._build.getStr("format.buildLang"))
|
||
if langIdx != -1:
|
||
self.buildLang.setCurrentIndex(langIdx)
|
||
|
||
textFont = self._build.getStr("format.textFont")
|
||
if not textFont:
|
||
textFont = str(CONFIG.textFont)
|
||
|
||
self.textFont.setText(textFont)
|
||
self.textSize.setValue(self._build.getInt("format.textSize"))
|
||
self.lineHeight.setValue(self._build.getFloat("format.lineHeight"))
|
||
|
||
self.justifyText.setChecked(self._build.getBool("format.justifyText"))
|
||
self.stripUnicode.setChecked(self._build.getBool("format.stripUnicode"))
|
||
self.replaceTabs.setChecked(self._build.getBool("format.replaceTabs"))
|
||
|
||
pageUnit = self._build.getStr("format.pageUnit")
|
||
index = self.pageUnit.findData(pageUnit)
|
||
if index >= 0:
|
||
self.pageUnit.setCurrentIndex(index)
|
||
self._unitScale = nwLabels.UNIT_SCALE.get(pageUnit, 1.0)
|
||
self._changeUnit(index)
|
||
|
||
self.pageWidth.setValue(self._build.getFloat("format.pageWidth"))
|
||
self.pageHeight.setValue(self._build.getFloat("format.pageHeight"))
|
||
self.topMargin.setValue(self._build.getFloat("format.topMargin"))
|
||
self.bottomMargin.setValue(self._build.getFloat("format.bottomMargin"))
|
||
self.leftMargin.setValue(self._build.getFloat("format.leftMargin"))
|
||
self.rightMargin.setValue(self._build.getFloat("format.rightMargin"))
|
||
|
||
pageSize = self._build.getStr("format.pageSize")
|
||
index = self.pageSize.findData(pageSize)
|
||
if index >= 0:
|
||
self.pageSize.setCurrentIndex(index)
|
||
self._changePageSize(index)
|
||
|
||
self.pageUnit.currentIndexChanged.connect(self._changeUnit)
|
||
self.pageSize.currentIndexChanged.connect(self._changePageSize)
|
||
|
||
return
|
||
|
||
def saveContent(self) -> None:
|
||
"""Save choices back into build object."""
|
||
self._build.setValue("format.buildLang", str(self.buildLang.currentData()))
|
||
self._build.setValue("format.textFont", self.textFont.text())
|
||
self._build.setValue("format.textSize", self.textSize.value())
|
||
self._build.setValue("format.lineHeight", self.lineHeight.value())
|
||
|
||
self._build.setValue("format.justifyText", self.justifyText.isChecked())
|
||
self._build.setValue("format.stripUnicode", self.stripUnicode.isChecked())
|
||
self._build.setValue("format.replaceTabs", self.replaceTabs.isChecked())
|
||
|
||
self._build.setValue("format.pageUnit", str(self.pageUnit.currentData()))
|
||
self._build.setValue("format.pageSize", str(self.pageSize.currentData()))
|
||
self._build.setValue("format.pageWidth", self.pageWidth.value())
|
||
self._build.setValue("format.pageHeight", self.pageHeight.value())
|
||
self._build.setValue("format.topMargin", self.topMargin.value())
|
||
self._build.setValue("format.bottomMargin", self.bottomMargin.value())
|
||
self._build.setValue("format.leftMargin", self.leftMargin.value())
|
||
self._build.setValue("format.rightMargin", self.rightMargin.value())
|
||
return
|
||
|
||
##
|
||
# Private Slots
|
||
##
|
||
|
||
@pyqtSlot()
|
||
def _selectFont(self) -> None:
|
||
"""Open the QFontDialog and set a font for the font style."""
|
||
currFont = QFont()
|
||
currFont.setFamily(self.textFont.text())
|
||
currFont.setPointSize(self.textSize.value())
|
||
theFont, theStatus = QFontDialog.getFont(currFont, self)
|
||
if theStatus:
|
||
self.textFont.setText(theFont.family())
|
||
self.textSize.setValue(theFont.pointSize())
|
||
return
|
||
|
||
@pyqtSlot(int)
|
||
def _changeUnit(self, index: int) -> None:
|
||
"""The current unit change, so recalculate sizes."""
|
||
newUnit = self.pageUnit.itemData(index)
|
||
newScale = nwLabels.UNIT_SCALE.get(newUnit, 1.0)
|
||
reScale = self._unitScale/newScale
|
||
|
||
pageWidth = self.pageWidth.value() * reScale
|
||
pageHeight = self.pageHeight.value() * reScale
|
||
topMargin = self.topMargin.value() * reScale
|
||
bottomMargin = self.bottomMargin.value() * reScale
|
||
leftMargin = self.leftMargin.value() * reScale
|
||
rightMargin = self.rightMargin.value() * reScale
|
||
|
||
isMM = newUnit == "mm"
|
||
nDec = 1 if isMM else 2
|
||
nStep = 1.0 if isMM else 0.1
|
||
pMax = 500.0 if isMM else 50.0
|
||
mMax = 150.0 if isMM else 15.0
|
||
|
||
self.pageWidth.blockSignals(True)
|
||
self.pageWidth.setDecimals(nDec)
|
||
self.pageWidth.setSingleStep(nStep)
|
||
self.pageWidth.setMaximum(pMax)
|
||
self.pageWidth.setValue(pageWidth)
|
||
self.pageWidth.blockSignals(False)
|
||
|
||
self.pageHeight.blockSignals(True)
|
||
self.pageHeight.setDecimals(nDec)
|
||
self.pageHeight.setSingleStep(nStep)
|
||
self.pageHeight.setMaximum(pMax)
|
||
self.pageHeight.setValue(pageHeight)
|
||
self.pageHeight.blockSignals(False)
|
||
|
||
self.topMargin.setDecimals(nDec)
|
||
self.topMargin.setSingleStep(nStep)
|
||
self.topMargin.setMaximum(mMax)
|
||
self.topMargin.setValue(topMargin)
|
||
|
||
self.bottomMargin.setDecimals(nDec)
|
||
self.bottomMargin.setSingleStep(nStep)
|
||
self.bottomMargin.setMaximum(mMax)
|
||
self.bottomMargin.setValue(bottomMargin)
|
||
|
||
self.leftMargin.setDecimals(nDec)
|
||
self.leftMargin.setSingleStep(nStep)
|
||
self.leftMargin.setMaximum(mMax)
|
||
self.leftMargin.setValue(leftMargin)
|
||
|
||
self.rightMargin.setDecimals(nDec)
|
||
self.rightMargin.setSingleStep(nStep)
|
||
self.rightMargin.setMaximum(mMax)
|
||
self.rightMargin.setValue(rightMargin)
|
||
|
||
self._unitScale = newScale
|
||
self._changePageSize(self.pageSize.currentIndex())
|
||
|
||
return
|
||
|
||
@pyqtSlot(int)
|
||
def _changePageSize(self, index: int) -> None:
|
||
"""The page size has changed."""
|
||
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:
|
||
self.pageWidth.blockSignals(True)
|
||
self.pageWidth.setValue(w/self._unitScale)
|
||
self.pageWidth.blockSignals(False)
|
||
self.pageHeight.blockSignals(True)
|
||
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
|
||
|
||
# END Class _FormatTab
|
||
|
||
|
||
class _OutputTab(QWidget):
|
||
|
||
def __init__(self, buildMain: GuiBuildSettings, build: BuildSettings) -> None:
|
||
super().__init__(parent=buildMain)
|
||
|
||
self._build = build
|
||
|
||
iPx = SHARED.theme.baseIconSize
|
||
|
||
# Left Form
|
||
# =========
|
||
|
||
self.formLeft = NSimpleLayout()
|
||
self.formLeft.addGroupLabel(self._build.getLabel("odt"))
|
||
|
||
self.odtAddColours = NSwitch(self, width=2*iPx, height=iPx)
|
||
|
||
self.formLeft.addRow(self._build.getLabel("odt.addColours"), self.odtAddColours)
|
||
|
||
# Right Form
|
||
# ==========
|
||
|
||
self.formRight = NSimpleLayout()
|
||
self.formRight.addGroupLabel(self._build.getLabel("html"))
|
||
|
||
self.htmlAddStyles = NSwitch(self, width=2*iPx, height=iPx)
|
||
|
||
self.formRight.addRow(self._build.getLabel("html.addStyles"), self.htmlAddStyles)
|
||
|
||
# Assemble GUI
|
||
# ============
|
||
|
||
self.outerBox = QHBoxLayout()
|
||
self.outerBox.addLayout(self.formLeft, 1)
|
||
self.outerBox.addLayout(self.formRight, 1)
|
||
self.outerBox.setContentsMargins(0, 0, 0, 0)
|
||
self.outerBox.setSpacing(CONFIG.pxInt(16))
|
||
|
||
self.setLayout(self.outerBox)
|
||
|
||
return
|
||
|
||
def loadContent(self) -> None:
|
||
"""Populate the widgets."""
|
||
self.odtAddColours.setChecked(self._build.getBool("odt.addColours"))
|
||
self.htmlAddStyles.setChecked(self._build.getBool("html.addStyles"))
|
||
return
|
||
|
||
def saveContent(self) -> None:
|
||
"""Save choices back into build object."""
|
||
self._build.setValue("odt.addColours", self.odtAddColours.isChecked())
|
||
self._build.setValue("html.addStyles", self.htmlAddStyles.isChecked())
|
||
return
|
||
|
||
# END Class _OutputTab
|