Merge branch 'dev' into project_handling
This commit is contained in:
@@ -42,9 +42,9 @@ __license__ = "GPLv3"
|
||||
__author__ = "Veronica Berglyd Olsen"
|
||||
__maintainer__ = "Veronica Berglyd Olsen"
|
||||
__email__ = "code@vkbo.net"
|
||||
__version__ = "2.3-alpha1"
|
||||
__hexversion__ = "0x020300a1"
|
||||
__date__ = "2023-12-17"
|
||||
__version__ = "2.3a2"
|
||||
__hexversion__ = "0x020300a2"
|
||||
__date__ = "2024-01-26"
|
||||
__status__ = "Stable"
|
||||
__domain__ = "novelwriter.io"
|
||||
|
||||
@@ -131,7 +131,7 @@ def main(sysArgs: list | None = None):
|
||||
elif inOpt == "--debug":
|
||||
CONFIG.isDebug = True
|
||||
logLevel = logging.DEBUG
|
||||
logFormat = "[{asctime:}] {filename:>17}:{lineno:<4d} {levelname:8} {message:}"
|
||||
logFormat = "[{asctime:}] {filename:>18}:{lineno:<4d} {levelname:8} {message:}"
|
||||
elif inOpt == "--style":
|
||||
qtStyle = inArg
|
||||
elif inOpt == "--config":
|
||||
|
||||
@@ -250,6 +250,11 @@ def formatTime(t: int) -> str:
|
||||
return "ERROR"
|
||||
|
||||
|
||||
def formatVersion(value: str) -> str:
|
||||
"""Format a version number into a more human readable form."""
|
||||
return value.lower().replace("a", " Alpha ").replace("b", " Beta ").replace("rc", " RC ")
|
||||
|
||||
|
||||
# =============================================================================================== #
|
||||
# String Functions
|
||||
# =============================================================================================== #
|
||||
|
||||
@@ -374,7 +374,6 @@ class ProjectBuilder:
|
||||
|
||||
project.data.setUuid(None)
|
||||
project.data.setName(projName)
|
||||
project.data.setTitle(projName)
|
||||
project.data.setAuthor(projAuthor)
|
||||
project.data.setLanguage(projLang)
|
||||
project.setDefaultStatusImport()
|
||||
@@ -383,7 +382,7 @@ class ProjectBuilder:
|
||||
# Add Root Folders
|
||||
hNovelRoot = project.newRoot(nwItemClass.NOVEL)
|
||||
hTitlePage = project.newFile(lblTitlePage, hNovelRoot)
|
||||
novelTitle = project.data.title if project.data.title else project.data.name
|
||||
novelTitle = project.data.name
|
||||
|
||||
titlePage = f"#! {novelTitle}\n\n"
|
||||
if project.data.author:
|
||||
|
||||
@@ -528,17 +528,20 @@ class NWIndex:
|
||||
yield f"{tHandle}:{sTitle}", tHandle, sTitle, hItem
|
||||
return
|
||||
|
||||
def getNovelWordCount(self, activeOnly: bool = True) -> int:
|
||||
"""Count the number of words in the novel project."""
|
||||
wCount = 0
|
||||
for _, _, hItem in self._itemIndex.iterNovelStructure(activeOnly=activeOnly):
|
||||
wCount += hItem.wordCount
|
||||
return wCount
|
||||
def getNovelWordCount(self, rootHandle: str | None = None, activeOnly: bool = True) -> int:
|
||||
"""Count the number of words in one or all novel roots."""
|
||||
return sum(hItem.wordCount for _, _, hItem in self._itemIndex.iterNovelStructure(
|
||||
rHandle=rootHandle, activeOnly=activeOnly
|
||||
))
|
||||
|
||||
def getNovelTitleCounts(self, activeOnly: bool = True) -> list[int]:
|
||||
"""Count the number of titles in the novel project."""
|
||||
def getNovelTitleCounts(
|
||||
self, rootHandle: str | None = None, activeOnly: bool = True
|
||||
) -> list[int]:
|
||||
"""Count the number of titles in one or all novel roots."""
|
||||
hCount = [0, 0, 0, 0, 0]
|
||||
for _, _, hItem in self._itemIndex.iterNovelStructure(activeOnly=activeOnly):
|
||||
for _, _, hItem in self._itemIndex.iterNovelStructure(
|
||||
rHandle=rootHandle, activeOnly=activeOnly
|
||||
):
|
||||
iLevel = nwHeaders.H_LEVEL.get(hItem.level, 0)
|
||||
hCount[iLevel] += 1
|
||||
return hCount
|
||||
|
||||
@@ -53,10 +53,6 @@ VALID_MAP: dict[str, set[str]] = {
|
||||
"GuiProjectSettings": {
|
||||
"winWidth", "winHeight", "replaceColW", "statusColW", "importColW",
|
||||
},
|
||||
"GuiProjectDetails": {
|
||||
"winWidth", "winHeight", "widthCol0", "widthCol1", "widthCol2",
|
||||
"widthCol3", "widthCol4", "wordsPerPage", "countFrom", "clearDouble",
|
||||
},
|
||||
"GuiWordList": {"winWidth", "winHeight"},
|
||||
"GuiNovelView": {"lastCol", "lastColSize"},
|
||||
"GuiBuildSettings": {
|
||||
@@ -71,7 +67,12 @@ VALID_MAP: dict[str, set[str]] = {
|
||||
},
|
||||
"GuiDocViewerPanel": {
|
||||
"colWidths", "hideInactive",
|
||||
}
|
||||
},
|
||||
"GuiNovelDetails": {
|
||||
"winWidth", "winHeight", "widthCol0", "widthCol1", "widthCol2",
|
||||
"widthCol3", "widthCol4", "wordsPerPage", "countFrom", "clearDouble",
|
||||
"novelRoot",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -53,7 +53,6 @@ class NWProjectData:
|
||||
# Project Meta
|
||||
self._uuid = ""
|
||||
self._name = ""
|
||||
self._title = ""
|
||||
self._author = ""
|
||||
self._saveCount = 0
|
||||
self._autoCount = 0
|
||||
@@ -102,11 +101,6 @@ class NWProjectData:
|
||||
"""Return the project name."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def title(self) -> str:
|
||||
"""Return the project title."""
|
||||
return self._title
|
||||
|
||||
@property
|
||||
def author(self) -> str:
|
||||
"""Return the project author."""
|
||||
@@ -228,16 +222,9 @@ class NWProjectData:
|
||||
self._project.setProjectChanged(True)
|
||||
return
|
||||
|
||||
def setTitle(self, value: str | None) -> None:
|
||||
"""Set a new novel title."""
|
||||
if value != self._title:
|
||||
self._title = simplified(str(value or ""))
|
||||
self._project.setProjectChanged(True)
|
||||
return
|
||||
|
||||
def setAuthor(self, value: str | None) -> None:
|
||||
"""Set the author value."""
|
||||
if value != self._title:
|
||||
if value != self._author:
|
||||
self._author = simplified(str(value or ""))
|
||||
self._project.setProjectChanged(True)
|
||||
return
|
||||
|
||||
@@ -46,7 +46,7 @@ if TYPE_CHECKING: # pragma: no cover
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
FILE_VERSION = "1.5" # The current project file format version
|
||||
FILE_REVISION = "1" # The current project file format revision
|
||||
FILE_REVISION = "2" # The current project file format revision
|
||||
HEX_VERSION = 0x0105
|
||||
|
||||
NUM_VERSION = {
|
||||
@@ -105,7 +105,8 @@ class ProjectXMLReader:
|
||||
the project or the content into their respective section nodes
|
||||
as attributes. The id attribute was also added to the project.
|
||||
|
||||
Rev 1: Drops the titleFormat section of settings.
|
||||
Rev 1: Drops the titleFormat node from settings. 2.1 Beta 1.
|
||||
Rev 2: Drops the title node from project. 2.3 Beta 1.
|
||||
"""
|
||||
|
||||
def __init__(self, path: str | Path) -> None:
|
||||
@@ -232,8 +233,6 @@ class ProjectXMLReader:
|
||||
for xItem in xSection:
|
||||
if xItem.tag == "name":
|
||||
data.setName(xItem.text)
|
||||
elif xItem.tag == "title":
|
||||
data.setTitle(xItem.text)
|
||||
elif xItem.tag == "author":
|
||||
data.setAuthor(xItem.text)
|
||||
else:
|
||||
@@ -505,7 +504,6 @@ class ProjectXMLWriter:
|
||||
|
||||
xProject = ET.SubElement(xRoot, "project", attrib=projAttr)
|
||||
self._packSingleValue(xProject, "name", data.name)
|
||||
self._packSingleValue(xProject, "title", data.title)
|
||||
self._packSingleValue(xProject, "author", data.author)
|
||||
|
||||
# Save Project Settings
|
||||
|
||||
@@ -338,7 +338,6 @@ class ToHtml(Tokenizer):
|
||||
data = {
|
||||
"meta": {
|
||||
"projectName": self._project.data.name,
|
||||
"novelTitle": self._project.data.title,
|
||||
"novelAuthor": self._project.data.author,
|
||||
"buildTime": int(timeStamp),
|
||||
"buildTimeStr": formatTimeStamp(timeStamp),
|
||||
|
||||
@@ -759,7 +759,6 @@ class Tokenizer(ABC):
|
||||
data = {
|
||||
"meta": {
|
||||
"projectName": self._project.data.name,
|
||||
"novelTitle": self._project.data.title,
|
||||
"novelAuthor": self._project.data.author,
|
||||
"buildTime": int(timeStamp),
|
||||
"buildTimeStr": formatTimeStamp(timeStamp),
|
||||
|
||||
@@ -283,9 +283,7 @@ class ToOdt(Tokenizer):
|
||||
# ===============
|
||||
|
||||
if self._headerText == "":
|
||||
theTitle = self._project.data.title or self._project.data.name
|
||||
theAuth = self._project.data.author
|
||||
self._headerText = f"{theTitle} / {theAuth} /"
|
||||
self._headerText = f"{self._project.data.name} / {self._project.data.author} /"
|
||||
|
||||
# Create Roots
|
||||
# ============
|
||||
@@ -373,7 +371,7 @@ class ToOdt(Tokenizer):
|
||||
|
||||
# Dublin Core Meta Data
|
||||
xMeta = ET.SubElement(self._xMeta, _mkTag("dc", "title"))
|
||||
xMeta.text = self._project.data.title or self._project.data.name
|
||||
xMeta.text = self._project.data.name
|
||||
|
||||
xMeta = ET.SubElement(self._xMeta, _mkTag("dc", "date"))
|
||||
xMeta.text = timeStamp
|
||||
|
||||
@@ -220,23 +220,25 @@ class GuiAbout(QDialog):
|
||||
|
||||
def _setStyleSheet(self) -> None:
|
||||
"""Set stylesheet for all browser tabs."""
|
||||
colHead = SHARED.theme.colHead
|
||||
colKey = SHARED.theme.colKey
|
||||
styleSheet = (
|
||||
"h1, h2, h3, h4 {{"
|
||||
" color: rgb({hColR},{hColG},{hColB});"
|
||||
" color: rgb({hColR}, {hColG}, {hColB});"
|
||||
"}}\n"
|
||||
"a {{"
|
||||
" color: rgb({hColR},{hColG},{hColB});"
|
||||
" color: rgb({hColR}, {hColG}, {hColB});"
|
||||
"}}\n"
|
||||
".alt {{"
|
||||
" color: rgb({kColR},{kColG},{kColB});"
|
||||
" color: rgb({kColR}, {kColG}, {kColB});"
|
||||
"}}\n"
|
||||
).format(
|
||||
hColR=SHARED.theme.colHead[0],
|
||||
hColG=SHARED.theme.colHead[1],
|
||||
hColB=SHARED.theme.colHead[2],
|
||||
kColR=SHARED.theme.colKey[0],
|
||||
kColG=SHARED.theme.colKey[1],
|
||||
kColB=SHARED.theme.colKey[2],
|
||||
hColR=colHead.red(),
|
||||
hColG=colHead.green(),
|
||||
hColB=colHead.blue(),
|
||||
kColR=colKey.red(),
|
||||
kColG=colKey.green(),
|
||||
kColB=colKey.blue(),
|
||||
)
|
||||
self.pageAbout.document().setDefaultStyleSheet(styleSheet)
|
||||
self.pageNotes.document().setDefaultStyleSheet(styleSheet)
|
||||
|
||||
@@ -35,7 +35,7 @@ from PyQt5.QtWidgets import (
|
||||
|
||||
from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.extensions.switch import NSwitch
|
||||
from novelwriter.extensions.configlayout import NHelpLabel
|
||||
from novelwriter.extensions.configlayout import NColourLabel
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -54,9 +54,10 @@ class GuiDocMerge(QDialog):
|
||||
self._data = {}
|
||||
|
||||
self.headLabel = QLabel("<b>{0}</b>".format(self.tr("Documents to Merge")))
|
||||
self.helpLabel = NHelpLabel(self.tr(
|
||||
"Drag and drop items to change the order, or uncheck to exclude."
|
||||
), SHARED.theme.helpText)
|
||||
self.helpLabel = NColourLabel(
|
||||
self.tr("Drag and drop items to change the order, or uncheck to exclude."),
|
||||
SHARED.theme.helpText, parent=self, wrap=True
|
||||
)
|
||||
|
||||
iPx = SHARED.theme.baseIconSize
|
||||
hSp = CONFIG.pxInt(12)
|
||||
|
||||
@@ -35,7 +35,7 @@ from PyQt5.QtWidgets import (
|
||||
|
||||
from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.extensions.switch import NSwitch
|
||||
from novelwriter.extensions.configlayout import NHelpLabel
|
||||
from novelwriter.extensions.configlayout import NColourLabel
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -58,9 +58,9 @@ class GuiDocSplit(QDialog):
|
||||
self.setWindowTitle(self.tr("Split Document"))
|
||||
|
||||
self.headLabel = QLabel("<b>{0}</b>".format(self.tr("Document Headers")))
|
||||
self.helpLabel = NHelpLabel(
|
||||
self.helpLabel = NColourLabel(
|
||||
self.tr("Select the maximum level to split into files."),
|
||||
SHARED.theme.helpText
|
||||
SHARED.theme.helpText, parent=self, wrap=True
|
||||
)
|
||||
|
||||
# Values
|
||||
|
||||
@@ -26,11 +26,11 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from PyQt5.QtGui import QCloseEvent, QColor, QFont, QKeyEvent, QKeySequence, QPalette
|
||||
from PyQt5.QtGui import QCloseEvent, QFont, QKeyEvent, QKeySequence
|
||||
from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot
|
||||
from PyQt5.QtWidgets import (
|
||||
QAbstractButton, QComboBox, QCompleter, QDialog, QDialogButtonBox,
|
||||
QDoubleSpinBox, QFileDialog, QFontDialog, QHBoxLayout, QLabel, QLineEdit,
|
||||
QDoubleSpinBox, QFileDialog, QFontDialog, QHBoxLayout, QLineEdit,
|
||||
QPushButton, QSpinBox, QToolButton, QVBoxLayout, QWidget, qApp
|
||||
)
|
||||
|
||||
@@ -38,7 +38,7 @@ from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.constants import nwConst, nwUnicode
|
||||
from novelwriter.dialogs.quotes import GuiQuoteSelect
|
||||
from novelwriter.extensions.switch import NSwitch
|
||||
from novelwriter.extensions.configlayout import NScrollableForm
|
||||
from novelwriter.extensions.configlayout import NColourLabel, NScrollableForm
|
||||
from novelwriter.extensions.pagedsidebar import NPagedSideBar
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -58,21 +58,10 @@ class GuiPreferences(QDialog):
|
||||
self.resize(*CONFIG.preferencesWinSize)
|
||||
|
||||
# Title
|
||||
font = self.font()
|
||||
font.setPointSizeF(1.25*SHARED.theme.fontPointSize)
|
||||
|
||||
palette = self.palette()
|
||||
palette.setColor(QPalette.ColorRole.WindowText, QColor(*SHARED.theme.helpText))
|
||||
|
||||
self.titleLabel = QLabel(self.tr("Preferences"), self)
|
||||
self.titleLabel.setFont(font)
|
||||
self.titleLabel.setPalette(palette)
|
||||
self.titleLabel.setIndent(CONFIG.pxInt(4))
|
||||
|
||||
# SideBar
|
||||
self.sidebar = NPagedSideBar(self)
|
||||
self.sidebar.setLabelColor(SHARED.theme.helpText)
|
||||
self.sidebar.buttonClicked.connect(self._sidebarClicked)
|
||||
self.titleLabel = NColourLabel(
|
||||
self.tr("Preferences"), SHARED.theme.helpText,
|
||||
parent=self, scale=NColourLabel.HEADER_SCALE, indent=CONFIG.pxInt(4)
|
||||
)
|
||||
|
||||
# Search Box
|
||||
self.searchText = QLineEdit(self)
|
||||
@@ -83,10 +72,10 @@ class GuiPreferences(QDialog):
|
||||
)
|
||||
self.searchAction.triggered.connect(self._gotoSearch)
|
||||
|
||||
self.searchBox = QHBoxLayout()
|
||||
self.searchBox.addWidget(self.titleLabel)
|
||||
self.searchBox.addStretch(1)
|
||||
self.searchBox.addWidget(self.searchText, 1)
|
||||
# SideBar
|
||||
self.sidebar = NPagedSideBar(self)
|
||||
self.sidebar.setLabelColor(SHARED.theme.helpText)
|
||||
self.sidebar.buttonClicked.connect(self._sidebarClicked)
|
||||
|
||||
# Form
|
||||
self.mainForm = NScrollableForm(self)
|
||||
@@ -101,6 +90,11 @@ class GuiPreferences(QDialog):
|
||||
self.buttonBox.clicked.connect(self._dialogButtonClicked)
|
||||
|
||||
# Assemble
|
||||
self.searchBox = QHBoxLayout()
|
||||
self.searchBox.addWidget(self.titleLabel)
|
||||
self.searchBox.addStretch(1)
|
||||
self.searchBox.addWidget(self.searchText, 1)
|
||||
|
||||
self.mainBox = QHBoxLayout()
|
||||
self.mainBox.addWidget(self.sidebar)
|
||||
self.mainBox.addWidget(self.mainForm)
|
||||
|
||||
@@ -1,518 +0,0 @@
|
||||
"""
|
||||
novelWriter – GUI Project Details
|
||||
=================================
|
||||
|
||||
File History:
|
||||
Created: 2021-01-03 [1.1rc1] GuiProjectDetails
|
||||
|
||||
This file is a part of novelWriter
|
||||
Copyright 2018–2024, 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 math
|
||||
import logging
|
||||
|
||||
from PyQt5.QtGui import QCloseEvent, QFont
|
||||
from PyQt5.QtCore import Qt, QSize, pyqtSlot
|
||||
from PyQt5.QtWidgets import (
|
||||
QAbstractItemView, QDialogButtonBox, QGridLayout, QHBoxLayout, QLabel,
|
||||
QLineEdit, QSpinBox, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget
|
||||
)
|
||||
|
||||
from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.common import formatTime, numberToRoman
|
||||
from novelwriter.constants import nwUnicode
|
||||
from novelwriter.extensions.switch import NSwitch
|
||||
from novelwriter.extensions.pageddialog import NPagedDialog
|
||||
from novelwriter.extensions.novelselector import NovelSelector
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GuiProjectDetails(NPagedDialog):
|
||||
|
||||
def __init__(self, parent: QWidget) -> None:
|
||||
super().__init__(parent=parent)
|
||||
|
||||
logger.debug("Create: GuiProjectDetails")
|
||||
self.setObjectName("GuiProjectDetails")
|
||||
|
||||
self.setWindowTitle(self.tr("Project Details"))
|
||||
|
||||
wW = CONFIG.pxInt(600)
|
||||
wH = CONFIG.pxInt(400)
|
||||
pOptions = SHARED.project.options
|
||||
|
||||
self.setMinimumWidth(wW)
|
||||
self.setMinimumHeight(wH)
|
||||
self.resize(
|
||||
CONFIG.pxInt(pOptions.getInt("GuiProjectDetails", "winWidth", wW)),
|
||||
CONFIG.pxInt(pOptions.getInt("GuiProjectDetails", "winHeight", wH))
|
||||
)
|
||||
|
||||
self.tabMain = GuiProjectDetailsMain(self)
|
||||
self.tabContents = GuiProjectDetailsContents(self)
|
||||
|
||||
self.addTab(self.tabMain, self.tr("Overview"))
|
||||
self.addTab(self.tabContents, self.tr("Contents"))
|
||||
|
||||
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Close)
|
||||
self.buttonBox.rejected.connect(self.close)
|
||||
self.rejected.connect(self.close)
|
||||
self.addControls(self.buttonBox)
|
||||
|
||||
logger.debug("Ready: GuiProjectDetails")
|
||||
|
||||
return
|
||||
|
||||
def __del__(self) -> None: # pragma: no cover
|
||||
logger.debug("Delete: GuiProjectDetails")
|
||||
return
|
||||
|
||||
def updateValues(self) -> None:
|
||||
"""Set all the values of the pages."""
|
||||
self.tabMain.updateValues()
|
||||
self.tabContents.updateValues()
|
||||
return
|
||||
|
||||
##
|
||||
# Events
|
||||
##
|
||||
|
||||
def closeEvent(self, event: QCloseEvent) -> None:
|
||||
"""Capture the close event and perform cleanup."""
|
||||
self._saveGuiSettings()
|
||||
event.accept()
|
||||
self.deleteLater()
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
##
|
||||
|
||||
def _saveGuiSettings(self) -> None:
|
||||
"""Save GUI settings."""
|
||||
winWidth = CONFIG.rpxInt(self.width())
|
||||
winHeight = CONFIG.rpxInt(self.height())
|
||||
|
||||
cColWidth = self.tabContents.getColumnSizes()
|
||||
widthCol0 = CONFIG.rpxInt(cColWidth[0])
|
||||
widthCol1 = CONFIG.rpxInt(cColWidth[1])
|
||||
widthCol2 = CONFIG.rpxInt(cColWidth[2])
|
||||
widthCol3 = CONFIG.rpxInt(cColWidth[3])
|
||||
widthCol4 = CONFIG.rpxInt(cColWidth[4])
|
||||
|
||||
wordsPerPage = self.tabContents.wpValue.value()
|
||||
countFrom = self.tabContents.poValue.value()
|
||||
clearDouble = self.tabContents.dblValue.isChecked()
|
||||
|
||||
logger.debug("Saving State: GuiProjectDetails")
|
||||
pOptions = SHARED.project.options
|
||||
pOptions.setValue("GuiProjectDetails", "winWidth", winWidth)
|
||||
pOptions.setValue("GuiProjectDetails", "winHeight", winHeight)
|
||||
pOptions.setValue("GuiProjectDetails", "widthCol0", widthCol0)
|
||||
pOptions.setValue("GuiProjectDetails", "widthCol1", widthCol1)
|
||||
pOptions.setValue("GuiProjectDetails", "widthCol2", widthCol2)
|
||||
pOptions.setValue("GuiProjectDetails", "widthCol3", widthCol3)
|
||||
pOptions.setValue("GuiProjectDetails", "widthCol4", widthCol4)
|
||||
pOptions.setValue("GuiProjectDetails", "wordsPerPage", wordsPerPage)
|
||||
pOptions.setValue("GuiProjectDetails", "countFrom", countFrom)
|
||||
pOptions.setValue("GuiProjectDetails", "clearDouble", clearDouble)
|
||||
|
||||
return
|
||||
|
||||
# END Class GuiProjectDetails
|
||||
|
||||
|
||||
class GuiProjectDetailsMain(QWidget):
|
||||
|
||||
def __init__(self, parent: QWidget) -> None:
|
||||
super().__init__(parent=parent)
|
||||
|
||||
fPx = SHARED.theme.fontPixelSize
|
||||
fPt = SHARED.theme.fontPointSize
|
||||
vPx = CONFIG.pxInt(4)
|
||||
hPx = CONFIG.pxInt(12)
|
||||
|
||||
# Header
|
||||
# ======
|
||||
|
||||
self.bookTitle = QLabel("")
|
||||
bookFont = self.bookTitle.font()
|
||||
bookFont.setPointSizeF(2.2*fPt)
|
||||
bookFont.setWeight(QFont.Bold)
|
||||
self.bookTitle.setFont(bookFont)
|
||||
self.bookTitle.setAlignment(Qt.AlignHCenter)
|
||||
self.bookTitle.setWordWrap(True)
|
||||
|
||||
self.projName = QLabel("")
|
||||
workFont = self.projName.font()
|
||||
workFont.setPointSizeF(0.8*fPt)
|
||||
workFont.setItalic(True)
|
||||
self.projName.setFont(workFont)
|
||||
self.projName.setAlignment(Qt.AlignHCenter)
|
||||
self.projName.setWordWrap(True)
|
||||
|
||||
self.bookAuthors = QLabel("")
|
||||
authFont = self.bookAuthors.font()
|
||||
authFont.setPointSizeF(1.2*fPt)
|
||||
self.bookAuthors.setFont(authFont)
|
||||
self.bookAuthors.setAlignment(Qt.AlignHCenter)
|
||||
self.bookAuthors.setWordWrap(True)
|
||||
|
||||
# Stats
|
||||
# =====
|
||||
|
||||
self.wordCountLbl = QLabel("<b>%s:</b>" % self.tr("Words"))
|
||||
self.wordCountVal = QLabel("")
|
||||
|
||||
self.chapCountLbl = QLabel("<b>%s:</b>" % self.tr("Chapters"))
|
||||
self.chapCountVal = QLabel("")
|
||||
|
||||
self.sceneCountLbl = QLabel("<b>%s:</b>" % self.tr("Scenes"))
|
||||
self.sceneCountVal = QLabel("")
|
||||
|
||||
self.revCountLbl = QLabel("<b>%s:</b>" % self.tr("Revisions"))
|
||||
self.revCountVal = QLabel("")
|
||||
|
||||
self.editTimeLbl = QLabel("<b>%s:</b>" % self.tr("Editing Time"))
|
||||
self.editTimeVal = QLabel("")
|
||||
|
||||
self.statsGrid = QGridLayout()
|
||||
self.statsGrid.addWidget(self.wordCountLbl, 0, 0, 1, 1, Qt.AlignRight)
|
||||
self.statsGrid.addWidget(self.wordCountVal, 0, 1, 1, 1, Qt.AlignLeft)
|
||||
self.statsGrid.addWidget(self.chapCountLbl, 1, 0, 1, 1, Qt.AlignRight)
|
||||
self.statsGrid.addWidget(self.chapCountVal, 1, 1, 1, 1, Qt.AlignLeft)
|
||||
self.statsGrid.addWidget(self.sceneCountLbl, 2, 0, 1, 1, Qt.AlignRight)
|
||||
self.statsGrid.addWidget(self.sceneCountVal, 2, 1, 1, 1, Qt.AlignLeft)
|
||||
self.statsGrid.addWidget(self.revCountLbl, 3, 0, 1, 1, Qt.AlignRight)
|
||||
self.statsGrid.addWidget(self.revCountVal, 3, 1, 1, 1, Qt.AlignLeft)
|
||||
self.statsGrid.addWidget(self.editTimeLbl, 4, 0, 1, 1, Qt.AlignRight)
|
||||
self.statsGrid.addWidget(self.editTimeVal, 4, 1, 1, 1, Qt.AlignLeft)
|
||||
self.statsGrid.setHorizontalSpacing(hPx)
|
||||
self.statsGrid.setVerticalSpacing(vPx)
|
||||
|
||||
# Meta
|
||||
# ====
|
||||
|
||||
self.projPathLbl = QLabel("<b>%s:</b>" % self.tr("Path"))
|
||||
self.projPathVal = QLineEdit()
|
||||
self.projPathVal.setReadOnly(True)
|
||||
|
||||
self.projPathBox = QHBoxLayout()
|
||||
self.projPathBox.addWidget(self.projPathLbl)
|
||||
self.projPathBox.addWidget(self.projPathVal)
|
||||
self.projPathBox.setSpacing(hPx)
|
||||
|
||||
# Assemble
|
||||
# ========
|
||||
|
||||
self.outerBox = QVBoxLayout()
|
||||
self.outerBox.addSpacing(fPx)
|
||||
self.outerBox.addWidget(self.bookTitle)
|
||||
self.outerBox.addWidget(self.projName)
|
||||
self.outerBox.addWidget(self.bookAuthors)
|
||||
self.outerBox.addSpacing(2*fPx)
|
||||
self.outerBox.addLayout(self.statsGrid)
|
||||
self.outerBox.addSpacing(fPx)
|
||||
self.outerBox.addStretch(1)
|
||||
self.outerBox.addLayout(self.projPathBox)
|
||||
|
||||
self.setLayout(self.outerBox)
|
||||
|
||||
return
|
||||
|
||||
def updateValues(self) -> None:
|
||||
"""Set all the values."""
|
||||
project = SHARED.project
|
||||
pIndex = project.index
|
||||
hCounts = pIndex.getNovelTitleCounts()
|
||||
nwCount = pIndex.getNovelWordCount()
|
||||
edTime = project.currentEditTime
|
||||
|
||||
self.bookTitle.setText(project.data.title or project.data.name)
|
||||
self.projName.setText(self.tr("Project: {0}").format(project.data.name))
|
||||
self.bookAuthors.setText(self.tr("By {0}").format(project.data.author))
|
||||
|
||||
self.wordCountVal.setText(f"{nwCount:n}")
|
||||
self.chapCountVal.setText(f"{hCounts[2]:n}")
|
||||
self.sceneCountVal.setText(f"{hCounts[3]:n}")
|
||||
self.revCountVal.setText(f"{project.data.saveCount:n}")
|
||||
self.editTimeVal.setText(formatTime(edTime))
|
||||
|
||||
self.projPathVal.setText(str(project.storage.storagePath))
|
||||
|
||||
return
|
||||
|
||||
# END Class GuiProjectDetailsMain
|
||||
|
||||
|
||||
class GuiProjectDetailsContents(QWidget):
|
||||
|
||||
C_TITLE = 0
|
||||
C_WORDS = 1
|
||||
C_PAGES = 2
|
||||
C_PAGE = 3
|
||||
C_PROG = 4
|
||||
|
||||
def __init__(self, parent: QWidget) -> None:
|
||||
super().__init__(parent=parent)
|
||||
|
||||
# Internal
|
||||
self._theToC = []
|
||||
self._currentRoot = None
|
||||
|
||||
iPx = SHARED.theme.baseIconSize
|
||||
hPx = CONFIG.pxInt(12)
|
||||
vPx = CONFIG.pxInt(4)
|
||||
pOptions = SHARED.project.options
|
||||
|
||||
# Header
|
||||
# ======
|
||||
|
||||
self.tocLabel = QLabel("<b>%s</b>" % self.tr("Table of Contents"))
|
||||
|
||||
self.novelValue = NovelSelector(self)
|
||||
self.novelValue.setMinimumWidth(CONFIG.pxInt(200))
|
||||
self.novelValue.novelSelectionChanged.connect(self._novelValueChanged)
|
||||
|
||||
self.headBox = QHBoxLayout()
|
||||
self.headBox.addWidget(self.tocLabel)
|
||||
self.headBox.addWidget(self.novelValue)
|
||||
|
||||
# Contents Tree
|
||||
# =============
|
||||
|
||||
self.tocTree = QTreeWidget()
|
||||
self.tocTree.setIconSize(QSize(iPx, iPx))
|
||||
self.tocTree.setIndentation(0)
|
||||
self.tocTree.setColumnCount(6)
|
||||
self.tocTree.setSelectionMode(QAbstractItemView.NoSelection)
|
||||
self.tocTree.setHeaderLabels([
|
||||
self.tr("Title"),
|
||||
self.tr("Words"),
|
||||
self.tr("Pages"),
|
||||
self.tr("Page"),
|
||||
self.tr("Progress"),
|
||||
""
|
||||
])
|
||||
|
||||
treeHeadItem = self.tocTree.headerItem()
|
||||
if treeHeadItem:
|
||||
treeHeadItem.setTextAlignment(self.C_WORDS, Qt.AlignRight)
|
||||
treeHeadItem.setTextAlignment(self.C_PAGES, Qt.AlignRight)
|
||||
treeHeadItem.setTextAlignment(self.C_PAGE, Qt.AlignRight)
|
||||
treeHeadItem.setTextAlignment(self.C_PROG, Qt.AlignRight)
|
||||
|
||||
treeHeader = self.tocTree.header()
|
||||
treeHeader.setStretchLastSection(True)
|
||||
treeHeader.setMinimumSectionSize(hPx)
|
||||
|
||||
wCol0 = CONFIG.pxInt(pOptions.getInt("GuiProjectDetails", "widthCol0", 200))
|
||||
wCol1 = CONFIG.pxInt(pOptions.getInt("GuiProjectDetails", "widthCol1", 60))
|
||||
wCol2 = CONFIG.pxInt(pOptions.getInt("GuiProjectDetails", "widthCol2", 60))
|
||||
wCol3 = CONFIG.pxInt(pOptions.getInt("GuiProjectDetails", "widthCol3", 60))
|
||||
wCol4 = CONFIG.pxInt(pOptions.getInt("GuiProjectDetails", "widthCol4", 90))
|
||||
|
||||
self.tocTree.setColumnWidth(0, wCol0)
|
||||
self.tocTree.setColumnWidth(1, wCol1)
|
||||
self.tocTree.setColumnWidth(2, wCol2)
|
||||
self.tocTree.setColumnWidth(3, wCol3)
|
||||
self.tocTree.setColumnWidth(4, wCol4)
|
||||
self.tocTree.setColumnWidth(5, hPx)
|
||||
|
||||
# Options
|
||||
# =======
|
||||
|
||||
wordsPerPage = pOptions.getInt("GuiProjectDetails", "wordsPerPage", 350)
|
||||
countFrom = pOptions.getInt("GuiProjectDetails", "countFrom", 1)
|
||||
clearDouble = pOptions.getBool("GuiProjectDetails", "clearDouble", True)
|
||||
|
||||
wordsHelp = (
|
||||
self.tr("Typical word count for a 5 by 8 inch book page with 11 pt font is 350.")
|
||||
)
|
||||
offsetHelp = (
|
||||
self.tr("Start counting page numbers from this page.")
|
||||
)
|
||||
dblHelp = (
|
||||
self.tr("Assume a new chapter or partition always start on an odd numbered page.")
|
||||
)
|
||||
|
||||
self.wpLabel = QLabel(self.tr("Words per page"))
|
||||
self.wpLabel.setToolTip(wordsHelp)
|
||||
|
||||
self.wpValue = QSpinBox()
|
||||
self.wpValue.setMinimum(10)
|
||||
self.wpValue.setMaximum(1000)
|
||||
self.wpValue.setSingleStep(10)
|
||||
self.wpValue.setValue(wordsPerPage)
|
||||
self.wpValue.setToolTip(wordsHelp)
|
||||
self.wpValue.valueChanged.connect(self._populateTree)
|
||||
|
||||
self.poLabel = QLabel(self.tr("Count pages from"))
|
||||
self.poLabel.setToolTip(offsetHelp)
|
||||
|
||||
self.poValue = QSpinBox()
|
||||
self.poValue.setMinimum(1)
|
||||
self.poValue.setMaximum(9999)
|
||||
self.poValue.setSingleStep(1)
|
||||
self.poValue.setValue(countFrom)
|
||||
self.poValue.setToolTip(offsetHelp)
|
||||
self.poValue.valueChanged.connect(self._populateTree)
|
||||
|
||||
self.dblLabel = QLabel(self.tr("Clear double pages"))
|
||||
self.dblLabel.setToolTip(dblHelp)
|
||||
|
||||
self.dblValue = NSwitch(self, 2*iPx, iPx)
|
||||
self.dblValue.setChecked(clearDouble)
|
||||
self.dblValue.setToolTip(dblHelp)
|
||||
self.dblValue.clicked.connect(self._populateTree)
|
||||
|
||||
self.optionsBox = QGridLayout()
|
||||
self.optionsBox.addWidget(self.wpLabel, 0, 0)
|
||||
self.optionsBox.addWidget(self.wpValue, 0, 1)
|
||||
self.optionsBox.addWidget(self.dblLabel, 0, 3)
|
||||
self.optionsBox.addWidget(self.dblValue, 0, 4)
|
||||
self.optionsBox.addWidget(self.poLabel, 1, 0)
|
||||
self.optionsBox.addWidget(self.poValue, 1, 1)
|
||||
self.optionsBox.setHorizontalSpacing(hPx)
|
||||
self.optionsBox.setVerticalSpacing(vPx)
|
||||
self.optionsBox.setColumnStretch(2, 1)
|
||||
|
||||
# Assemble
|
||||
# ========
|
||||
|
||||
self.outerBox = QVBoxLayout()
|
||||
self.outerBox.addLayout(self.headBox)
|
||||
self.outerBox.addWidget(self.tocTree)
|
||||
self.outerBox.addLayout(self.optionsBox)
|
||||
|
||||
self.setLayout(self.outerBox)
|
||||
|
||||
return
|
||||
|
||||
def getColumnSizes(self) -> list[int]:
|
||||
"""Return the column widths for the tree columns."""
|
||||
retVals = [
|
||||
self.tocTree.columnWidth(0),
|
||||
self.tocTree.columnWidth(1),
|
||||
self.tocTree.columnWidth(2),
|
||||
self.tocTree.columnWidth(3),
|
||||
self.tocTree.columnWidth(4),
|
||||
]
|
||||
return retVals
|
||||
|
||||
def updateValues(self) -> None:
|
||||
"""Populate the tree."""
|
||||
self._currentRoot = None
|
||||
self.novelValue.updateList()
|
||||
self.novelValue.setHandle(self.novelValue.firstHandle)
|
||||
self._prepareData(self.novelValue.firstHandle)
|
||||
self._populateTree()
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
##
|
||||
|
||||
def _prepareData(self, rootHandle: str | None) -> None:
|
||||
"""Extract the information from the project index."""
|
||||
logger.debug("Populating ToC from handle '%s'", rootHandle)
|
||||
self._theToC = SHARED.project.index.getTableOfContents(rootHandle, 2)
|
||||
self._theToC.append(("", 0, self.tr("END"), 0))
|
||||
return
|
||||
|
||||
##
|
||||
# Slots
|
||||
##
|
||||
|
||||
@pyqtSlot(str)
|
||||
def _novelValueChanged(self, tHandle: str) -> None:
|
||||
"""Refresh the tree with another root item."""
|
||||
if tHandle != self._currentRoot:
|
||||
self._prepareData(tHandle)
|
||||
self._populateTree()
|
||||
self._currentRoot = self.novelValue.handle
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _populateTree(self) -> None:
|
||||
"""Set the content of the chapter/page tree."""
|
||||
dblPages = self.dblValue.isChecked()
|
||||
wpPage = self.wpValue.value()
|
||||
fstPage = self.poValue.value() - 1
|
||||
|
||||
pTotal = 0
|
||||
tPages = 1
|
||||
|
||||
theList = []
|
||||
for _, tLevel, tTitle, wCount in self._theToC:
|
||||
pCount = math.ceil(wCount/wpPage)
|
||||
if dblPages:
|
||||
pCount += pCount%2
|
||||
|
||||
pTotal += pCount
|
||||
theList.append((tLevel, tTitle, wCount, pCount))
|
||||
|
||||
pMax = pTotal - fstPage
|
||||
|
||||
self.tocTree.clear()
|
||||
for tLevel, tTitle, wCount, pCount in theList:
|
||||
newItem = QTreeWidgetItem()
|
||||
|
||||
if tPages <= fstPage:
|
||||
progPage = numberToRoman(tPages, True)
|
||||
progText = ""
|
||||
else:
|
||||
cPage = tPages - fstPage
|
||||
pgProg = 100.0*(cPage - 1)/pMax if pMax > 0 else 0.0
|
||||
progPage = f"{cPage:n}"
|
||||
progText = f"{pgProg:.1f}{nwUnicode.U_THSP}%"
|
||||
|
||||
hDec = SHARED.theme.getHeaderDecoration(tLevel)
|
||||
if tTitle.strip() == "":
|
||||
tTitle = self.tr("Untitled")
|
||||
|
||||
newItem.setData(self.C_TITLE, Qt.DecorationRole, hDec)
|
||||
newItem.setText(self.C_TITLE, tTitle)
|
||||
newItem.setText(self.C_WORDS, f"{wCount:n}")
|
||||
newItem.setText(self.C_PAGES, f"{pCount:n}")
|
||||
newItem.setText(self.C_PAGE, progPage)
|
||||
newItem.setText(self.C_PROG, progText)
|
||||
|
||||
newItem.setTextAlignment(self.C_WORDS, Qt.AlignRight)
|
||||
newItem.setTextAlignment(self.C_PAGES, Qt.AlignRight)
|
||||
newItem.setTextAlignment(self.C_PAGE, Qt.AlignRight)
|
||||
newItem.setTextAlignment(self.C_PROG, Qt.AlignRight)
|
||||
|
||||
# Make pages and titles/partitions stand out
|
||||
if tLevel < 2:
|
||||
bFont = newItem.font(self.C_TITLE)
|
||||
if tLevel == 0:
|
||||
bFont.setItalic(True)
|
||||
else:
|
||||
bFont.setBold(True)
|
||||
bFont.setUnderline(True)
|
||||
newItem.setFont(self.C_TITLE, bFont)
|
||||
|
||||
tPages += pCount
|
||||
|
||||
self.tocTree.addTopLevelItem(newItem)
|
||||
|
||||
return
|
||||
|
||||
# END Class GuiProjectDetailsContents
|
||||
@@ -3,7 +3,8 @@ novelWriter – GUI Project Settings
|
||||
==================================
|
||||
|
||||
File History:
|
||||
Created: 2018-09-29 [0.0.1] GuiProjectSettings
|
||||
Created: 2018-09-29 [0.0.1] GuiProjectSettings
|
||||
Rewritten: 2024-01-26 [2.3b1] GuiProjectSettings
|
||||
|
||||
This file is a part of novelWriter
|
||||
Copyright 2018–2024, Veronica Berglyd Olsen
|
||||
@@ -25,75 +26,101 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from PyQt5.QtGui import QCloseEvent, QIcon, QPixmap, QColor
|
||||
from PyQt5.QtGui import QCloseEvent, QColor, QIcon, QPixmap
|
||||
from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot
|
||||
from PyQt5.QtWidgets import (
|
||||
QColorDialog, QComboBox, QDialogButtonBox, QHBoxLayout, QLabel, QLineEdit,
|
||||
QPushButton, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget, qApp
|
||||
QColorDialog, QComboBox, QDialog, QDialogButtonBox, QHBoxLayout, QLineEdit,
|
||||
QPushButton, QStackedWidget, QTreeWidget, QTreeWidgetItem, QVBoxLayout,
|
||||
QWidget, qApp
|
||||
)
|
||||
|
||||
from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.common import simplified
|
||||
from novelwriter.extensions.switch import NSwitch
|
||||
from novelwriter.extensions.pageddialog import NPagedDialog
|
||||
from novelwriter.extensions.configlayout import NConfigLayout
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from novelwriter.guimain import GuiMain
|
||||
from novelwriter.extensions.configlayout import NColourLabel, NFixedPage, NScrollableForm
|
||||
from novelwriter.extensions.pagedsidebar import NPagedSideBar
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GuiProjectSettings(NPagedDialog):
|
||||
class GuiProjectSettings(QDialog):
|
||||
|
||||
TAB_MAIN = 0
|
||||
TAB_STATUS = 1
|
||||
TAB_IMPORT = 2
|
||||
TAB_REPLACE = 3
|
||||
PAGE_SETTINGS = 0
|
||||
PAGE_STATUS = 1
|
||||
PAGE_IMPORT = 2
|
||||
PAGE_REPLACE = 3
|
||||
|
||||
newProjectSettingsReady = pyqtSignal()
|
||||
newProjectSettingsReady = pyqtSignal(bool)
|
||||
|
||||
def __init__(self, mainGui: GuiMain, focusTab: int = TAB_MAIN) -> None:
|
||||
super().__init__(parent=mainGui)
|
||||
def __init__(self, parent: QWidget, gotoPage: int = PAGE_SETTINGS) -> None:
|
||||
super().__init__(parent=parent)
|
||||
|
||||
logger.debug("Create: GuiProjectSettings")
|
||||
self.setObjectName("GuiProjectSettings")
|
||||
|
||||
self.mainGui = mainGui
|
||||
SHARED.project.countStatus()
|
||||
self.setWindowTitle(self.tr("Project Settings"))
|
||||
|
||||
wW = CONFIG.pxInt(570)
|
||||
wH = CONFIG.pxInt(375)
|
||||
pOptions = SHARED.project.options
|
||||
|
||||
self.setMinimumWidth(wW)
|
||||
self.setMinimumHeight(wH)
|
||||
options = SHARED.project.options
|
||||
self.setMinimumSize(CONFIG.pxInt(500), CONFIG.pxInt(400))
|
||||
self.resize(
|
||||
CONFIG.pxInt(pOptions.getInt("GuiProjectSettings", "winWidth", wW)),
|
||||
CONFIG.pxInt(pOptions.getInt("GuiProjectSettings", "winHeight", wH))
|
||||
CONFIG.pxInt(options.getInt("GuiProjectSettings", "winWidth", CONFIG.pxInt(650))),
|
||||
CONFIG.pxInt(options.getInt("GuiProjectSettings", "winHeight", CONFIG.pxInt(500)))
|
||||
)
|
||||
|
||||
self.tabMain = GuiProjectEditMain(self)
|
||||
self.tabStatus = GuiProjectEditStatus(self, True)
|
||||
self.tabImport = GuiProjectEditStatus(self, False)
|
||||
self.tabReplace = GuiProjectEditReplace(self)
|
||||
# Title
|
||||
self.titleLabel = NColourLabel(
|
||||
self.tr("Project Settings"), SHARED.theme.helpText,
|
||||
parent=self, scale=NColourLabel.HEADER_SCALE, indent=CONFIG.pxInt(4)
|
||||
)
|
||||
|
||||
self.addTab(self.tabMain, self.tr("Settings"))
|
||||
self.addTab(self.tabStatus, self.tr("Status"))
|
||||
self.addTab(self.tabImport, self.tr("Importance"))
|
||||
self.addTab(self.tabReplace, self.tr("Auto-Replace"))
|
||||
# SideBar
|
||||
self.sidebar = NPagedSideBar(self)
|
||||
self.sidebar.setLabelColor(SHARED.theme.helpText)
|
||||
self.sidebar.addButton(self.tr("Settings"), self.PAGE_SETTINGS)
|
||||
self.sidebar.addButton(self.tr("Status"), self.PAGE_STATUS)
|
||||
self.sidebar.addButton(self.tr("Importance"), self.PAGE_IMPORT)
|
||||
self.sidebar.addButton(self.tr("Auto-Replace"), self.PAGE_REPLACE)
|
||||
self.sidebar.setSelected(gotoPage)
|
||||
self.sidebar.buttonClicked.connect(self._sidebarClicked)
|
||||
|
||||
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
||||
# Buttons
|
||||
self.buttonBox = QDialogButtonBox(
|
||||
QDialogButtonBox.StandardButton.Save | QDialogButtonBox.StandardButton.Cancel
|
||||
)
|
||||
self.buttonBox.accepted.connect(self._doSave)
|
||||
self.buttonBox.rejected.connect(self.close)
|
||||
self.rejected.connect(self.close)
|
||||
self.addControls(self.buttonBox)
|
||||
|
||||
# Focus Tab
|
||||
self._focusTab(focusTab)
|
||||
# Content
|
||||
SHARED.project.countStatus()
|
||||
|
||||
self.settingsPage = _SettingsPage(self)
|
||||
self.statusPage = _StatusPage(self, True)
|
||||
self.importPage = _StatusPage(self, False)
|
||||
self.replacePage = _ReplacePage(self)
|
||||
|
||||
self.mainStack = QStackedWidget(self)
|
||||
self.mainStack.addWidget(self.settingsPage)
|
||||
self.mainStack.addWidget(self.statusPage)
|
||||
self.mainStack.addWidget(self.importPage)
|
||||
self.mainStack.addWidget(self.replacePage)
|
||||
|
||||
# Assemble
|
||||
self.topBox = QHBoxLayout()
|
||||
self.topBox.addWidget(self.titleLabel)
|
||||
self.topBox.addStretch(1)
|
||||
|
||||
self.mainBox = QHBoxLayout()
|
||||
self.mainBox.addWidget(self.sidebar)
|
||||
self.mainBox.addWidget(self.mainStack)
|
||||
self.mainBox.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
self.outerBox = QVBoxLayout()
|
||||
self.outerBox.addLayout(self.topBox)
|
||||
self.outerBox.addLayout(self.mainBox)
|
||||
self.outerBox.addWidget(self.buttonBox)
|
||||
self.outerBox.setSpacing(CONFIG.pxInt(8))
|
||||
|
||||
self.setLayout(self.outerBox)
|
||||
self.setSizeGripEnabled(True)
|
||||
|
||||
logger.debug("Ready: GuiProjectSettings")
|
||||
|
||||
@@ -103,9 +130,13 @@ class GuiProjectSettings(NPagedDialog):
|
||||
logger.debug("Delete: GuiProjectSettings")
|
||||
return
|
||||
|
||||
##
|
||||
# Events
|
||||
##
|
||||
|
||||
def closeEvent(self, event: QCloseEvent) -> None:
|
||||
"""Capture the close event and perform cleanup."""
|
||||
self._saveGuiSettings()
|
||||
"""Capture the user closing the window and save settings."""
|
||||
self._saveSettings()
|
||||
event.accept()
|
||||
self.deleteLater()
|
||||
return
|
||||
@@ -114,40 +145,52 @@ class GuiProjectSettings(NPagedDialog):
|
||||
# Private Slots
|
||||
##
|
||||
|
||||
@pyqtSlot(int)
|
||||
def _sidebarClicked(self, pageId: int) -> None:
|
||||
"""Process a user request to switch page."""
|
||||
if pageId == self.PAGE_SETTINGS:
|
||||
self.mainStack.setCurrentWidget(self.settingsPage)
|
||||
elif pageId == self.PAGE_STATUS:
|
||||
self.mainStack.setCurrentWidget(self.statusPage)
|
||||
elif pageId == self.PAGE_IMPORT:
|
||||
self.mainStack.setCurrentWidget(self.importPage)
|
||||
elif pageId == self.PAGE_REPLACE:
|
||||
self.mainStack.setCurrentWidget(self.replacePage)
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _doSave(self) -> None:
|
||||
"""Save settings and close dialog."""
|
||||
project = SHARED.project
|
||||
projName = self.tabMain.editName.text()
|
||||
bookTitle = self.tabMain.editTitle.text()
|
||||
bookAuthor = self.tabMain.editAuthor.text()
|
||||
projLang = self.tabMain.projLang.currentData()
|
||||
spellLang = self.tabMain.spellLang.currentData()
|
||||
doBackup = not self.tabMain.doBackup.isChecked()
|
||||
projName = self.settingsPage.projName.text()
|
||||
projAuthor = self.settingsPage.projAuthor.text()
|
||||
projLang = self.settingsPage.projLang.currentData()
|
||||
spellLang = self.settingsPage.spellLang.currentData()
|
||||
doBackup = not self.settingsPage.doBackup.isChecked()
|
||||
|
||||
project.data.setName(projName)
|
||||
project.data.setTitle(bookTitle)
|
||||
project.data.setAuthor(bookAuthor)
|
||||
project.data.setAuthor(projAuthor)
|
||||
project.data.setDoBackup(doBackup)
|
||||
project.data.setSpellLang(spellLang)
|
||||
project.setProjectLang(projLang)
|
||||
|
||||
if self.tabStatus.colChanged:
|
||||
newList, delList = self.tabStatus.getNewList()
|
||||
rebuildTrees = False
|
||||
|
||||
if self.statusPage.wasChanged:
|
||||
newList, delList = self.statusPage.getNewList()
|
||||
project.setStatusColours(newList, delList)
|
||||
rebuildTrees = True
|
||||
|
||||
if self.tabImport.colChanged:
|
||||
newList, delList = self.tabImport.getNewList()
|
||||
if self.importPage.wasChanged:
|
||||
newList, delList = self.importPage.getNewList()
|
||||
project.setImportColours(newList, delList)
|
||||
rebuildTrees = True
|
||||
|
||||
if self.tabStatus.colChanged or self.tabImport.colChanged:
|
||||
self.mainGui.rebuildTrees()
|
||||
|
||||
if self.tabReplace.arChanged:
|
||||
newList = self.tabReplace.getNewList()
|
||||
if self.replacePage.wasChanged:
|
||||
newList = self.replacePage.getNewList()
|
||||
project.data.setAutoReplace(newList)
|
||||
|
||||
self.newProjectSettingsReady.emit()
|
||||
self.newProjectSettingsReady.emit(rebuildTrees)
|
||||
qApp.processEvents()
|
||||
self.close()
|
||||
|
||||
@@ -157,134 +200,103 @@ class GuiProjectSettings(NPagedDialog):
|
||||
# Internal Functions
|
||||
##
|
||||
|
||||
def _focusTab(self, tab: int) -> None:
|
||||
"""Change which is the focused tab."""
|
||||
if tab == self.TAB_MAIN:
|
||||
self.setCurrentWidget(self.tabMain)
|
||||
elif tab == self.TAB_STATUS:
|
||||
self.setCurrentWidget(self.tabStatus)
|
||||
elif tab == self.TAB_IMPORT:
|
||||
self.setCurrentWidget(self.tabImport)
|
||||
elif tab == self.TAB_REPLACE:
|
||||
self.setCurrentWidget(self.tabReplace)
|
||||
return
|
||||
|
||||
def _saveGuiSettings(self) -> None:
|
||||
def _saveSettings(self) -> None:
|
||||
"""Save GUI settings."""
|
||||
winWidth = CONFIG.rpxInt(self.width())
|
||||
winHeight = CONFIG.rpxInt(self.height())
|
||||
replaceColW = CONFIG.rpxInt(self.tabReplace.listBox.columnWidth(0))
|
||||
statusColW = CONFIG.rpxInt(self.tabStatus.listBox.columnWidth(0))
|
||||
importColW = CONFIG.rpxInt(self.tabImport.listBox.columnWidth(0))
|
||||
statusColW = CONFIG.rpxInt(self.statusPage.columnWidth())
|
||||
importColW = CONFIG.rpxInt(self.importPage.columnWidth())
|
||||
replaceColW = CONFIG.rpxInt(self.replacePage.columnWidth())
|
||||
|
||||
logger.debug("Saving State: GuiProjectSettings")
|
||||
pOptions = SHARED.project.options
|
||||
pOptions.setValue("GuiProjectSettings", "winWidth", winWidth)
|
||||
pOptions.setValue("GuiProjectSettings", "winHeight", winHeight)
|
||||
pOptions.setValue("GuiProjectSettings", "replaceColW", replaceColW)
|
||||
pOptions.setValue("GuiProjectSettings", "statusColW", statusColW)
|
||||
pOptions.setValue("GuiProjectSettings", "importColW", importColW)
|
||||
options = SHARED.project.options
|
||||
options.setValue("GuiProjectSettings", "winWidth", winWidth)
|
||||
options.setValue("GuiProjectSettings", "winHeight", winHeight)
|
||||
options.setValue("GuiProjectSettings", "statusColW", statusColW)
|
||||
options.setValue("GuiProjectSettings", "importColW", importColW)
|
||||
options.setValue("GuiProjectSettings", "replaceColW", replaceColW)
|
||||
|
||||
return
|
||||
|
||||
# END Class GuiProjectSettings
|
||||
|
||||
|
||||
class GuiProjectEditMain(QWidget):
|
||||
class _SettingsPage(NScrollableForm):
|
||||
|
||||
def __init__(self, parent: QWidget) -> None:
|
||||
super().__init__(parent=parent)
|
||||
|
||||
# The Form
|
||||
self.mainForm = NConfigLayout()
|
||||
self.mainForm.setHelpTextStyle(SHARED.theme.helpText)
|
||||
self.setLayout(self.mainForm)
|
||||
xW = CONFIG.pxInt(200)
|
||||
data = SHARED.project.data
|
||||
self.setHelpTextStyle(SHARED.theme.helpText)
|
||||
self.setRowIndent(0)
|
||||
|
||||
self.mainForm.addGroupLabel(self.tr("Project Settings"))
|
||||
|
||||
xW = CONFIG.pxInt(250)
|
||||
pData = SHARED.project.data
|
||||
|
||||
self.editName = QLineEdit()
|
||||
self.editName.setMaxLength(200)
|
||||
self.editName.setMaximumWidth(xW)
|
||||
self.editName.setText(pData.name)
|
||||
self.mainForm.addRow(
|
||||
self.tr("Project name"),
|
||||
self.editName,
|
||||
self.tr("Should be set only once.")
|
||||
# Project Name
|
||||
self.projName = QLineEdit(self)
|
||||
self.projName.setMaxLength(200)
|
||||
self.projName.setMinimumWidth(xW)
|
||||
self.projName.setText(data.name)
|
||||
self.addRow(
|
||||
self.tr("Project name"), self.projName,
|
||||
self.tr("Changing this will affect the backup path."),
|
||||
stretch=(3, 2)
|
||||
)
|
||||
|
||||
self.editTitle = QLineEdit()
|
||||
self.editTitle.setMaxLength(200)
|
||||
self.editTitle.setMaximumWidth(xW)
|
||||
self.editTitle.setText(pData.title)
|
||||
self.mainForm.addRow(
|
||||
self.tr("Novel title"),
|
||||
self.editTitle,
|
||||
self.tr("Change whenever you want!")
|
||||
)
|
||||
|
||||
self.editAuthor = QLineEdit()
|
||||
self.editAuthor.setMaxLength(200)
|
||||
self.editAuthor.setMaximumWidth(xW)
|
||||
self.editAuthor.setText(pData.author)
|
||||
self.mainForm.addRow(
|
||||
self.tr("Author(s)"),
|
||||
self.editAuthor,
|
||||
self.tr("Change whenever you want!")
|
||||
# Project Author
|
||||
self.projAuthor = QLineEdit(self)
|
||||
self.projAuthor.setMaxLength(200)
|
||||
self.projAuthor.setMinimumWidth(xW)
|
||||
self.projAuthor.setText(data.author)
|
||||
self.addRow(
|
||||
self.tr("Author(s)"), self.projAuthor,
|
||||
self.tr("Only used when building the manuscript."),
|
||||
stretch=(3, 2)
|
||||
)
|
||||
|
||||
# Project Language
|
||||
self.projLang = QComboBox(self)
|
||||
self.projLang.setMaximumWidth(xW)
|
||||
self.projLang.setMinimumWidth(xW)
|
||||
for tag, language in CONFIG.listLanguages(CONFIG.LANG_PROJ):
|
||||
self.projLang.addItem(language, tag)
|
||||
self.mainForm.addRow(
|
||||
self.tr("Project language"),
|
||||
self.projLang,
|
||||
self.tr("Used when building the manuscript.")
|
||||
self.addRow(
|
||||
self.tr("Project language"), self.projLang,
|
||||
self.tr("Only used when building the manuscript."),
|
||||
stretch=(3, 2)
|
||||
)
|
||||
if (idx := self.projLang.findData(data.language)) != -1:
|
||||
self.projLang.setCurrentIndex(idx)
|
||||
|
||||
langIdx = 0
|
||||
if pData.language is not None:
|
||||
langIdx = self.projLang.findData(pData.language)
|
||||
if langIdx == -1:
|
||||
langIdx = self.projLang.findData("en_GB")
|
||||
if langIdx != -1:
|
||||
self.projLang.setCurrentIndex(langIdx)
|
||||
|
||||
# Spell Check Language
|
||||
self.spellLang = QComboBox(self)
|
||||
self.spellLang.setMaximumWidth(xW)
|
||||
self.spellLang.setMinimumWidth(xW)
|
||||
self.spellLang.addItem(self.tr("Default"), "None")
|
||||
if CONFIG.hasEnchant:
|
||||
for tag, language in SHARED.spelling.listDictionaries():
|
||||
self.spellLang.addItem(language, tag)
|
||||
self.mainForm.addRow(
|
||||
self.tr("Spell check language"),
|
||||
self.spellLang,
|
||||
self.tr("Overrides main preferences.")
|
||||
self.addRow(
|
||||
self.tr("Spell check language"), self.spellLang,
|
||||
self.tr("Overrides main preferences."),
|
||||
stretch=(3, 2)
|
||||
)
|
||||
if (idx := self.spellLang.findData(data.spellLang)) != -1:
|
||||
self.spellLang.setCurrentIndex(idx)
|
||||
|
||||
langIdx = 0
|
||||
if pData.spellLang is not None:
|
||||
langIdx = self.spellLang.findData(pData.spellLang)
|
||||
if langIdx != -1:
|
||||
self.spellLang.setCurrentIndex(langIdx)
|
||||
|
||||
# Backup on Close
|
||||
self.doBackup = NSwitch(self)
|
||||
self.doBackup.setChecked(not pData.doBackup)
|
||||
self.mainForm.addRow(
|
||||
self.tr("No backup on close"),
|
||||
self.doBackup,
|
||||
self.doBackup.setChecked(not data.doBackup)
|
||||
self.addRow(
|
||||
self.tr("Disable backup on close"), self.doBackup,
|
||||
self.tr("Overrides main preferences.")
|
||||
)
|
||||
|
||||
self.finalise()
|
||||
|
||||
return
|
||||
|
||||
# END Class GuiProjectEditMain
|
||||
# END Class _SettingsPage
|
||||
|
||||
|
||||
class GuiProjectEditStatus(QWidget):
|
||||
class _StatusPage(NFixedPage):
|
||||
|
||||
COL_LABEL = 0
|
||||
COL_USAGE = 1
|
||||
@@ -297,56 +309,54 @@ class GuiProjectEditStatus(QWidget):
|
||||
super().__init__(parent=parent)
|
||||
|
||||
if isStatus:
|
||||
self.theStatus = SHARED.project.data.itemStatus
|
||||
pageLabel = self.tr("Novel File Status Levels")
|
||||
status = SHARED.project.data.itemStatus
|
||||
pageLabel = self.tr("Novel Document Status Levels")
|
||||
colSetting = "statusColW"
|
||||
else:
|
||||
self.theStatus = SHARED.project.data.itemImport
|
||||
pageLabel = self.tr("Note File Importance Levels")
|
||||
status = SHARED.project.data.itemImport
|
||||
pageLabel = self.tr("Project Note Importance Levels")
|
||||
colSetting = "importColW"
|
||||
|
||||
wCol0 = CONFIG.pxInt(
|
||||
SHARED.project.options.getInt("GuiProjectSettings", colSetting, 130)
|
||||
)
|
||||
|
||||
self.colDeleted = []
|
||||
self.colChanged = False
|
||||
self.selColour = QColor(100, 100, 100)
|
||||
self._changed = False
|
||||
self._colDeleted = []
|
||||
self._selColour = QColor(100, 100, 100)
|
||||
|
||||
self.iPx = SHARED.theme.baseIconSize
|
||||
|
||||
# The List
|
||||
# ========
|
||||
# Title
|
||||
self.pageTitle = NColourLabel(
|
||||
pageLabel, SHARED.theme.helpText, parent=self,
|
||||
scale=NColourLabel.HEADER_SCALE
|
||||
)
|
||||
|
||||
self.listBox = QTreeWidget()
|
||||
self.listBox.setHeaderLabels([
|
||||
self.tr("Label"), self.tr("Usage"),
|
||||
])
|
||||
# List Box
|
||||
self.listBox = QTreeWidget(self)
|
||||
self.listBox.setHeaderLabels([self.tr("Label"), self.tr("Usage")])
|
||||
self.listBox.itemSelectionChanged.connect(self._selectedItem)
|
||||
self.listBox.setColumnWidth(self.COL_LABEL, wCol0)
|
||||
self.listBox.setIndentation(0)
|
||||
|
||||
for key, entry in self.theStatus.items():
|
||||
for key, entry in status.items():
|
||||
self._addItem(key, entry["name"], entry["cols"], entry["count"])
|
||||
|
||||
# List Controls
|
||||
# =============
|
||||
|
||||
self.addButton = QPushButton(SHARED.theme.getIcon("add"), "")
|
||||
self.addButton = QPushButton(SHARED.theme.getIcon("add"), "", self)
|
||||
self.addButton.clicked.connect(self._newItem)
|
||||
|
||||
self.delButton = QPushButton(SHARED.theme.getIcon("remove"), "")
|
||||
self.delButton = QPushButton(SHARED.theme.getIcon("remove"), "", self)
|
||||
self.delButton.clicked.connect(self._delItem)
|
||||
|
||||
self.upButton = QPushButton(SHARED.theme.getIcon("up"), "")
|
||||
self.upButton = QPushButton(SHARED.theme.getIcon("up"), "", self)
|
||||
self.upButton.clicked.connect(lambda: self._moveItem(-1))
|
||||
|
||||
self.dnButton = QPushButton(SHARED.theme.getIcon("down"), "")
|
||||
self.dnButton = QPushButton(SHARED.theme.getIcon("down"), "", self)
|
||||
self.dnButton.clicked.connect(lambda: self._moveItem(1))
|
||||
|
||||
# Edit Form
|
||||
# =========
|
||||
|
||||
self.editName = QLineEdit()
|
||||
self.editName.setMaxLength(40)
|
||||
self.editName.setPlaceholderText(self.tr("Select item to edit"))
|
||||
@@ -364,8 +374,6 @@ class GuiProjectEditStatus(QWidget):
|
||||
self.saveButton.clicked.connect(self._saveItem)
|
||||
|
||||
# Assemble
|
||||
# ========
|
||||
|
||||
self.listControls = QVBoxLayout()
|
||||
self.listControls.addWidget(self.addButton)
|
||||
self.listControls.addWidget(self.delButton)
|
||||
@@ -387,16 +395,25 @@ class GuiProjectEditStatus(QWidget):
|
||||
self.innerBox.addLayout(self.listControls)
|
||||
|
||||
self.outerBox = QVBoxLayout()
|
||||
self.outerBox.addWidget(QLabel("<b>%s</b>" % pageLabel))
|
||||
self.outerBox.addWidget(self.pageTitle)
|
||||
self.outerBox.addLayout(self.innerBox)
|
||||
|
||||
self.setLayout(self.outerBox)
|
||||
self.setCentralLayout(self.outerBox)
|
||||
|
||||
return
|
||||
|
||||
@property
|
||||
def wasChanged(self) -> bool:
|
||||
"""The user changed these settings."""
|
||||
return self._changed
|
||||
|
||||
##
|
||||
# Methods
|
||||
##
|
||||
|
||||
def getNewList(self) -> tuple[list, list]:
|
||||
"""Return list of entries."""
|
||||
if self.colChanged:
|
||||
if self._changed:
|
||||
newList = []
|
||||
for n in range(self.listBox.topLevelItemCount()):
|
||||
item = self.listBox.topLevelItem(n)
|
||||
@@ -406,10 +423,13 @@ class GuiProjectEditStatus(QWidget):
|
||||
"name": item.text(self.COL_LABEL),
|
||||
"cols": item.data(self.COL_LABEL, self.COL_ROLE),
|
||||
})
|
||||
return newList, self.colDeleted
|
||||
|
||||
return newList, self._colDeleted
|
||||
return [], []
|
||||
|
||||
def columnWidth(self) -> int:
|
||||
"""Return the size of the header column."""
|
||||
return self.listBox.columnWidth(0)
|
||||
|
||||
##
|
||||
# Private Slots
|
||||
##
|
||||
@@ -417,12 +437,12 @@ class GuiProjectEditStatus(QWidget):
|
||||
@pyqtSlot()
|
||||
def _selectColour(self) -> None:
|
||||
"""Open a dialog to select the status icon colour."""
|
||||
if self.selColour is not None:
|
||||
if self._selColour is not None:
|
||||
newCol = QColorDialog.getColor(
|
||||
self.selColour, self, self.tr("Select Colour")
|
||||
self._selColour, self, self.tr("Select Colour")
|
||||
)
|
||||
if newCol.isValid():
|
||||
self.selColour = newCol
|
||||
self._selColour = newCol
|
||||
pixmap = QPixmap(self.iPx, self.iPx)
|
||||
pixmap.fill(newCol)
|
||||
self.colButton.setIcon(QIcon(pixmap))
|
||||
@@ -433,7 +453,7 @@ class GuiProjectEditStatus(QWidget):
|
||||
def _newItem(self) -> None:
|
||||
"""Create a new status item."""
|
||||
self._addItem(None, self.tr("New Item"), (100, 100, 100), 0)
|
||||
self.colChanged = True
|
||||
self._changed = True
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
@@ -446,8 +466,8 @@ class GuiProjectEditStatus(QWidget):
|
||||
SHARED.error(self.tr("Cannot delete a status item that is in use."))
|
||||
else:
|
||||
self.listBox.takeTopLevelItem(iRow)
|
||||
self.colDeleted.append(selItem.data(self.COL_LABEL, self.KEY_ROLE))
|
||||
self.colChanged = True
|
||||
self._colDeleted.append(selItem.data(self.COL_LABEL, self.KEY_ROLE))
|
||||
self._changed = True
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
@@ -458,9 +478,9 @@ class GuiProjectEditStatus(QWidget):
|
||||
selItem.setText(self.COL_LABEL, simplified(self.editName.text()))
|
||||
selItem.setIcon(self.COL_LABEL, self.colButton.icon())
|
||||
selItem.setData(self.COL_LABEL, self.COL_ROLE, (
|
||||
self.selColour.red(), self.selColour.green(), self.selColour.blue()
|
||||
self._selColour.red(), self._selColour.green(), self._selColour.blue()
|
||||
))
|
||||
self.colChanged = True
|
||||
self._changed = True
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
@@ -474,7 +494,7 @@ class GuiProjectEditStatus(QWidget):
|
||||
name = selItem.text(self.COL_LABEL)
|
||||
pixmap = QPixmap(self.iPx, self.iPx)
|
||||
pixmap.fill(QColor(*cols))
|
||||
self.selColour = QColor(*cols)
|
||||
self._selColour = QColor(*cols)
|
||||
self.editName.setText(name)
|
||||
self.colButton.setIcon(QIcon(pixmap))
|
||||
self.editName.selectAll()
|
||||
@@ -485,7 +505,7 @@ class GuiProjectEditStatus(QWidget):
|
||||
else:
|
||||
pixmap = QPixmap(self.iPx, self.iPx)
|
||||
pixmap.fill(QColor(100, 100, 100))
|
||||
self.selColour = QColor(100, 100, 100)
|
||||
self._selColour = QColor(100, 100, 100)
|
||||
self.editName.setText("")
|
||||
self.colButton.setIcon(QIcon(pixmap))
|
||||
self.editName.setEnabled(False)
|
||||
@@ -498,16 +518,16 @@ class GuiProjectEditStatus(QWidget):
|
||||
##
|
||||
|
||||
def _addItem(self, key: str | None, name: str,
|
||||
cols: tuple[int, int, int], count: int) -> None:
|
||||
colour: tuple[int, int, int], count: int) -> None:
|
||||
"""Add a status item to the list."""
|
||||
pixmap = QPixmap(self.iPx, self.iPx)
|
||||
pixmap.fill(QColor(*cols))
|
||||
pixmap.fill(QColor(*colour))
|
||||
|
||||
item = QTreeWidgetItem()
|
||||
item.setText(self.COL_LABEL, name)
|
||||
item.setIcon(self.COL_LABEL, QIcon(pixmap))
|
||||
item.setData(self.COL_LABEL, self.KEY_ROLE, key)
|
||||
item.setData(self.COL_LABEL, self.COL_ROLE, cols)
|
||||
item.setData(self.COL_LABEL, self.COL_ROLE, colour)
|
||||
item.setData(self.COL_LABEL, self.NUM_ROLE, count)
|
||||
item.setText(self.COL_USAGE, self._usageString(count))
|
||||
|
||||
@@ -533,30 +553,29 @@ class GuiProjectEditStatus(QWidget):
|
||||
|
||||
if cItem is not None:
|
||||
cItem.setSelected(True)
|
||||
self.colChanged = True
|
||||
self._changed = True
|
||||
|
||||
return
|
||||
|
||||
def _getSelectedItem(self) -> QTreeWidgetItem | None:
|
||||
"""Get the currently selected item."""
|
||||
selItem = self.listBox.selectedItems()
|
||||
if len(selItem) > 0:
|
||||
return selItem[0]
|
||||
if items := self.listBox.selectedItems():
|
||||
return items[0]
|
||||
return None
|
||||
|
||||
def _usageString(self, nUse: int) -> str:
|
||||
def _usageString(self, count: int) -> str:
|
||||
"""Generate usage string."""
|
||||
if nUse == 0:
|
||||
if count == 0:
|
||||
return self.tr("Not in use")
|
||||
elif nUse == 1:
|
||||
elif count == 1:
|
||||
return self.tr("Used once")
|
||||
else:
|
||||
return self.tr("Used by {0} items").format(nUse)
|
||||
return self.tr("Used by {0} items").format(count)
|
||||
|
||||
# END Class GuiProjectEditStatus
|
||||
# END Class _StatusPage
|
||||
|
||||
|
||||
class GuiProjectEditReplace(QWidget):
|
||||
class _ReplacePage(NFixedPage):
|
||||
|
||||
COL_KEY = 0
|
||||
COL_REPL = 1
|
||||
@@ -564,24 +583,24 @@ class GuiProjectEditReplace(QWidget):
|
||||
def __init__(self, parent: QWidget) -> None:
|
||||
super().__init__(parent=parent)
|
||||
|
||||
self.arChanged = False
|
||||
self._changed = False
|
||||
|
||||
wCol0 = CONFIG.pxInt(
|
||||
SHARED.project.options.getInt("GuiProjectSettings", "replaceColW", 130)
|
||||
)
|
||||
pageLabel = self.tr("Text Replace List for Preview and Export")
|
||||
|
||||
# Title
|
||||
self.pageTitle = NColourLabel(
|
||||
self.tr("Text Auto-Replace for Preview and Build"),
|
||||
SHARED.theme.helpText, parent=self, scale=NColourLabel.HEADER_SCALE
|
||||
)
|
||||
|
||||
# List Box
|
||||
# ========
|
||||
|
||||
self.listBox = QTreeWidget()
|
||||
self.listBox.setHeaderLabels([
|
||||
self.tr("Keyword"),
|
||||
self.tr("Replace With"),
|
||||
])
|
||||
self.listBox.itemSelectionChanged.connect(self._selectedItem)
|
||||
self.listBox.setHeaderLabels([self.tr("Keyword"), self.tr("Replace With")])
|
||||
self.listBox.setColumnWidth(self.COL_KEY, wCol0)
|
||||
self.listBox.setIndentation(0)
|
||||
self.listBox.itemSelectionChanged.connect(self._selectedItem)
|
||||
|
||||
for aKey, aVal in SHARED.project.data.autoReplace.items():
|
||||
newItem = QTreeWidgetItem(["<%s>" % aKey, aVal])
|
||||
@@ -591,8 +610,6 @@ class GuiProjectEditReplace(QWidget):
|
||||
self.listBox.setSortingEnabled(True)
|
||||
|
||||
# List Controls
|
||||
# =============
|
||||
|
||||
self.addButton = QPushButton(SHARED.theme.getIcon("add"), "")
|
||||
self.addButton.clicked.connect(self._addEntry)
|
||||
|
||||
@@ -600,8 +617,6 @@ class GuiProjectEditReplace(QWidget):
|
||||
self.delButton.clicked.connect(self._delEntry)
|
||||
|
||||
# Edit Form
|
||||
# =========
|
||||
|
||||
self.editKey = QLineEdit()
|
||||
self.editKey.setPlaceholderText(self.tr("Select item to edit"))
|
||||
self.editKey.setEnabled(False)
|
||||
@@ -615,8 +630,6 @@ class GuiProjectEditReplace(QWidget):
|
||||
self.saveButton.clicked.connect(self._saveEntry)
|
||||
|
||||
# Assemble
|
||||
# ========
|
||||
|
||||
self.listControls = QVBoxLayout()
|
||||
self.listControls.addWidget(self.addButton)
|
||||
self.listControls.addWidget(self.delButton)
|
||||
@@ -636,50 +649,61 @@ class GuiProjectEditReplace(QWidget):
|
||||
self.innerBox.addLayout(self.listControls)
|
||||
|
||||
self.outerBox = QVBoxLayout()
|
||||
self.outerBox.addWidget(QLabel("<b>%s</b>" % pageLabel))
|
||||
self.outerBox.addWidget(self.pageTitle)
|
||||
self.outerBox.addLayout(self.innerBox)
|
||||
|
||||
self.setLayout(self.outerBox)
|
||||
self.setCentralLayout(self.outerBox)
|
||||
|
||||
return
|
||||
|
||||
@property
|
||||
def wasChanged(self) -> bool:
|
||||
"""The user changed these settings."""
|
||||
return self._changed
|
||||
|
||||
##
|
||||
# Methods
|
||||
##
|
||||
|
||||
def getNewList(self) -> dict:
|
||||
"""Extract the list from the widget."""
|
||||
new = {}
|
||||
for n in range(self.listBox.topLevelItemCount()):
|
||||
tItem = self.listBox.topLevelItem(n)
|
||||
if tItem is not None:
|
||||
if tItem := self.listBox.topLevelItem(n):
|
||||
aKey = self._stripNotAllowed(tItem.text(0))
|
||||
aVal = tItem.text(1)
|
||||
if len(aKey) > 0:
|
||||
new[aKey] = aVal
|
||||
return new
|
||||
|
||||
def columnWidth(self) -> int:
|
||||
"""Return the size of the header column."""
|
||||
return self.listBox.columnWidth(0)
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
# Private Slots
|
||||
##
|
||||
|
||||
def _selectedItem(self) -> bool:
|
||||
@pyqtSlot()
|
||||
def _selectedItem(self) -> None:
|
||||
"""Extract the details from the selected item and populate the
|
||||
edit form.
|
||||
"""
|
||||
selItem = self._getSelectedItem()
|
||||
if selItem is None:
|
||||
return False
|
||||
editKey = self._stripNotAllowed(selItem.text(0))
|
||||
editVal = selItem.text(1)
|
||||
self.editKey.setText(editKey)
|
||||
self.editValue.setText(editVal)
|
||||
self.editKey.setEnabled(True)
|
||||
self.editValue.setEnabled(True)
|
||||
self.editKey.selectAll()
|
||||
self.editKey.setFocus()
|
||||
return True
|
||||
if selItem := self._getSelectedItem():
|
||||
editKey = self._stripNotAllowed(selItem.text(0))
|
||||
editVal = selItem.text(1)
|
||||
self.editKey.setText(editKey)
|
||||
self.editValue.setText(editVal)
|
||||
self.editKey.setEnabled(True)
|
||||
self.editValue.setEnabled(True)
|
||||
self.editKey.selectAll()
|
||||
self.editKey.setFocus()
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _saveEntry(self) -> None:
|
||||
"""Save the form data into the list widget."""
|
||||
selItem = self._getSelectedItem()
|
||||
if selItem:
|
||||
if selItem := self._getSelectedItem():
|
||||
newKey = self.editKey.text()
|
||||
newVal = self.editValue.text()
|
||||
saveKey = self._stripNotAllowed(newKey)
|
||||
@@ -691,38 +715,36 @@ class GuiProjectEditReplace(QWidget):
|
||||
self.editKey.setEnabled(False)
|
||||
self.editValue.setEnabled(False)
|
||||
self.listBox.clearSelection()
|
||||
self.arChanged = True
|
||||
self._changed = True
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _addEntry(self) -> None:
|
||||
"""Add a new list entry."""
|
||||
saveKey = "<keyword%d>" % (self.listBox.topLevelItemCount() + 1)
|
||||
newVal = ""
|
||||
newItem = QTreeWidgetItem([saveKey, newVal])
|
||||
self.listBox.addTopLevelItem(newItem)
|
||||
self.listBox.addTopLevelItem(QTreeWidgetItem([saveKey, ""]))
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _delEntry(self) -> None:
|
||||
"""Delete the selected entry."""
|
||||
selItem = self._getSelectedItem()
|
||||
if selItem:
|
||||
if selItem := self._getSelectedItem():
|
||||
self.listBox.takeTopLevelItem(self.listBox.indexOfTopLevelItem(selItem))
|
||||
self.arChanged = True
|
||||
self._changed = True
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
##
|
||||
|
||||
def _getSelectedItem(self) -> QTreeWidgetItem | None:
|
||||
"""Extract the currently selected item."""
|
||||
selItem = self.listBox.selectedItems()
|
||||
if len(selItem) == 0:
|
||||
return None
|
||||
return selItem[0]
|
||||
if items := self.listBox.selectedItems():
|
||||
return items[0]
|
||||
return None
|
||||
|
||||
def _stripNotAllowed(self, key: str) -> str:
|
||||
"""Clean up the replace key string."""
|
||||
result = ""
|
||||
for c in key:
|
||||
if c.isalnum():
|
||||
result += c
|
||||
return result
|
||||
return "".join(c for c in key if c.isalnum())
|
||||
|
||||
# END Class GuiProjectEditReplace
|
||||
# END Class _ReplacePage
|
||||
@@ -3,9 +3,11 @@ novelWriter – Custom Widget: Config Layout
|
||||
==========================================
|
||||
|
||||
File History:
|
||||
Created: 2020-05-03 [0.4.5] NConfigLayout, NHelpLabel
|
||||
Created: 2020-05-03 [0.4.5] NConfigLayout, NColourLabel
|
||||
Created: 2023-05-23 [2.1b1] NSimpleLayout
|
||||
Created: 2024-01-08 [2.3b1] NScrollableForm
|
||||
Created: 2024-01-26 [2.3b1] NScrollablePage
|
||||
Created: 2024-01-26 [2.3b1] NFixedPage
|
||||
|
||||
This file is a part of novelWriter
|
||||
Copyright 2018–2024, Veronica Berglyd Olsen
|
||||
@@ -28,27 +30,74 @@ from __future__ import annotations
|
||||
from PyQt5.QtGui import QColor, QPalette
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtWidgets import (
|
||||
QAbstractButton, QGridLayout, QHBoxLayout, QLabel, QLineEdit, QScrollArea, QSizePolicy,
|
||||
QVBoxLayout, QWidget
|
||||
QAbstractButton, QFrame, QGridLayout, QHBoxLayout, QLabel, QLayout,
|
||||
QScrollArea, QSizePolicy, QVBoxLayout, QWidget
|
||||
)
|
||||
|
||||
from novelwriter import CONFIG
|
||||
|
||||
FONT_SCALE = 0.9
|
||||
DEFAULT_SCALE = 0.9
|
||||
RIGHT_TOP = Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignTop
|
||||
LEFT_TOP = Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop
|
||||
|
||||
|
||||
class NFixedPage(QFrame):
|
||||
"""Extension: Fixed Page Widget
|
||||
|
||||
A custom widget that holds a layout. This is just a wrapper around a
|
||||
QFrame that sets the same frame style as the other Page widgets.
|
||||
"""
|
||||
|
||||
def __init__(self, parent: QWidget) -> None:
|
||||
super().__init__(parent=parent)
|
||||
self.setFrameShadow(QFrame.Shadow.Sunken)
|
||||
self.setFrameShape(QFrame.Shape.StyledPanel)
|
||||
self.setCentralLayout = self.setLayout
|
||||
return
|
||||
|
||||
# END Class NFixedPage
|
||||
|
||||
|
||||
class NScrollablePage(QScrollArea):
|
||||
"""Extension: Scrollable Page Widget
|
||||
|
||||
A custom widget that holds a layout within a scrollable area.
|
||||
"""
|
||||
|
||||
def __init__(self, parent: QWidget) -> None:
|
||||
super().__init__(parent=parent)
|
||||
self._widget = QWidget(self)
|
||||
self.setWidget(self._widget)
|
||||
self.setWidgetResizable(True)
|
||||
self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
|
||||
self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
|
||||
self.setFrameShadow(QFrame.Shadow.Sunken)
|
||||
self.setFrameShape(QFrame.Shape.StyledPanel)
|
||||
return
|
||||
|
||||
def setCentralLayout(self, layout: QLayout) -> None:
|
||||
"""Set the central layout of the scroll page."""
|
||||
self._widget.setLayout(layout)
|
||||
return
|
||||
|
||||
# END Class NScrollablePage
|
||||
|
||||
|
||||
class NScrollableForm(QScrollArea):
|
||||
"""Extension: Scrollable Form Widget
|
||||
|
||||
A custom widget that creates a form within a scrollable area.
|
||||
"""
|
||||
|
||||
def __init__(self, parent: QWidget) -> None:
|
||||
super().__init__(parent=parent)
|
||||
self._helpCol = QColor(0, 0, 0)
|
||||
self._fontScale = FONT_SCALE
|
||||
self._fontScale = DEFAULT_SCALE
|
||||
self._first = True
|
||||
self._indent = CONFIG.pxInt(12)
|
||||
|
||||
self._sections: dict[int, QLabel] = {}
|
||||
self._editable: dict[str, NHelpLabel] = {}
|
||||
self._editable: dict[str, NColourLabel] = {}
|
||||
self._index: dict[str, QWidget] = {}
|
||||
|
||||
self._layout = QVBoxLayout()
|
||||
@@ -61,6 +110,8 @@ class NScrollableForm(QScrollArea):
|
||||
self.setWidgetResizable(True)
|
||||
self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
|
||||
self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
|
||||
self.setFrameShadow(QFrame.Shadow.Sunken)
|
||||
self.setFrameShape(QFrame.Shape.StyledPanel)
|
||||
|
||||
return
|
||||
|
||||
@@ -76,9 +127,9 @@ class NScrollableForm(QScrollArea):
|
||||
# Setters
|
||||
##
|
||||
|
||||
def setHelpTextStyle(self, color: QColor | list | tuple, scale: float = FONT_SCALE) -> None:
|
||||
def setHelpTextStyle(self, color: QColor, scale: float = DEFAULT_SCALE) -> None:
|
||||
"""Set the text color for the help text."""
|
||||
self._helpCol = color if isinstance(color, QColor) else QColor(*color)
|
||||
self._helpCol = color
|
||||
self._fontScale = scale
|
||||
return
|
||||
|
||||
@@ -88,6 +139,11 @@ class NScrollableForm(QScrollArea):
|
||||
qHelp.setText(text)
|
||||
return
|
||||
|
||||
def setRowIndent(self, indent: int) -> None:
|
||||
"""Set the indentation of each row."""
|
||||
self._indent = max(indent, 0)
|
||||
return
|
||||
|
||||
##
|
||||
# Methods
|
||||
##
|
||||
@@ -119,37 +175,43 @@ class NScrollableForm(QScrollArea):
|
||||
return
|
||||
|
||||
def addRow(self, label: str, widget: QWidget, helpText: str = "", unit: str | None = None,
|
||||
button: QWidget | None = None, editable: str | None = None) -> None:
|
||||
button: QWidget | None = None, editable: str | None = None,
|
||||
stretch: tuple[int, int] = (0, 0)) -> None:
|
||||
"""Add a label and a widget as a new row of the form."""
|
||||
row = QHBoxLayout()
|
||||
row.setSpacing(CONFIG.pxInt(4))
|
||||
row.setSpacing(CONFIG.pxInt(12))
|
||||
|
||||
mPx = CONFIG.pxInt(12)
|
||||
qLabel = QLabel(label, self)
|
||||
qLabel.setIndent(mPx)
|
||||
qLabel.setIndent(self._indent)
|
||||
qLabel.setBuddy(widget)
|
||||
|
||||
if helpText:
|
||||
qHelp = NHelpLabel(str(helpText), self._helpCol, self._fontScale)
|
||||
qHelp.setIndent(mPx)
|
||||
qHelp = NColourLabel(
|
||||
str(helpText), self._helpCol, parent=self,
|
||||
scale=self._fontScale, wrap=True, indent=self._indent
|
||||
)
|
||||
labelBox = QVBoxLayout()
|
||||
labelBox.addWidget(qLabel)
|
||||
labelBox.addWidget(qHelp)
|
||||
labelBox.setSpacing(0)
|
||||
labelBox.addStretch(1)
|
||||
row.addLayout(labelBox)
|
||||
row.addLayout(labelBox, stretch[0])
|
||||
if editable:
|
||||
self._editable[editable] = qHelp
|
||||
else:
|
||||
row.addWidget(qLabel)
|
||||
|
||||
row.addSpacing(mPx)
|
||||
row.addWidget(widget)
|
||||
row.addWidget(qLabel, stretch[0])
|
||||
|
||||
if isinstance(unit, str):
|
||||
row.addWidget(QLabel(unit, self))
|
||||
box = QHBoxLayout()
|
||||
box.addWidget(widget)
|
||||
box.addWidget(QLabel(unit, self))
|
||||
row.addLayout(box, stretch[1])
|
||||
elif isinstance(button, QAbstractButton):
|
||||
row.addWidget(button)
|
||||
box = QHBoxLayout()
|
||||
box.addWidget(widget)
|
||||
box.addWidget(button)
|
||||
row.addLayout(box, stretch[1])
|
||||
else:
|
||||
row.addWidget(widget, stretch[1])
|
||||
|
||||
self._layout.addLayout(row)
|
||||
self._index[label.strip()] = widget
|
||||
@@ -173,7 +235,7 @@ class NConfigLayout(QGridLayout):
|
||||
|
||||
self._nextRow = 0
|
||||
self._helpCol = QColor(0, 0, 0)
|
||||
self._fontScale = FONT_SCALE
|
||||
self._fontScale = DEFAULT_SCALE
|
||||
self._itemMap = {}
|
||||
|
||||
wSp = CONFIG.pxInt(8)
|
||||
@@ -183,24 +245,6 @@ class NConfigLayout(QGridLayout):
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Getters and Setters
|
||||
##
|
||||
|
||||
def setHelpTextStyle(self, color: QColor | list | tuple, scale: float = FONT_SCALE) -> None:
|
||||
"""Set the text color for the help text."""
|
||||
self._helpCol = color if isinstance(color, QColor) else QColor(*color)
|
||||
self._fontScale = scale
|
||||
return
|
||||
|
||||
def setHelpText(self, row: int, text: str) -> None:
|
||||
"""Set the text for the help label."""
|
||||
if row in self._itemMap:
|
||||
qHelp = self._itemMap[row][1]
|
||||
if isinstance(qHelp, NHelpLabel):
|
||||
qHelp.setText(text)
|
||||
return
|
||||
|
||||
##
|
||||
# Class Methods
|
||||
##
|
||||
@@ -216,8 +260,8 @@ class NConfigLayout(QGridLayout):
|
||||
self._nextRow += 1
|
||||
return
|
||||
|
||||
def addRow(self, label: str, widget: QWidget, helpText: str | None = None,
|
||||
unit: str | None = None, button: QWidget | None = None) -> int:
|
||||
def addRow(self, label: str, widget: QWidget, unit: str | None = None,
|
||||
button: QWidget | None = None) -> int:
|
||||
"""Add a label and a widget as a new row of the grid."""
|
||||
wSp = CONFIG.pxInt(8)
|
||||
qLabel = QLabel(label)
|
||||
@@ -225,17 +269,7 @@ class NConfigLayout(QGridLayout):
|
||||
qLabel.setBuddy(widget)
|
||||
|
||||
qHelp = None
|
||||
if helpText is not None:
|
||||
qHelp = NHelpLabel(str(helpText), self._helpCol, self._fontScale)
|
||||
qHelp.setIndent(wSp)
|
||||
labelBox = QVBoxLayout()
|
||||
labelBox.addWidget(qLabel)
|
||||
labelBox.addWidget(qHelp)
|
||||
labelBox.setSpacing(0)
|
||||
labelBox.addStretch(1)
|
||||
self.addLayout(labelBox, self._nextRow, 0, 1, 1, LEFT_TOP)
|
||||
else:
|
||||
self.addWidget(qLabel, self._nextRow, 0, 1, 1, LEFT_TOP)
|
||||
self.addWidget(qLabel, self._nextRow, 0, 1, 1, LEFT_TOP)
|
||||
|
||||
if isinstance(unit, str):
|
||||
controlBox = QHBoxLayout()
|
||||
@@ -252,12 +286,7 @@ class NConfigLayout(QGridLayout):
|
||||
self.addLayout(controlBox, self._nextRow, 1, 1, 1, RIGHT_TOP)
|
||||
|
||||
else:
|
||||
if isinstance(widget, QLineEdit):
|
||||
qLayout = QHBoxLayout()
|
||||
qLayout.addWidget(widget)
|
||||
self.addLayout(qLayout, self._nextRow, 1, 1, 1, RIGHT_TOP)
|
||||
else:
|
||||
self.addWidget(widget, self._nextRow, 1, 1, 1, RIGHT_TOP)
|
||||
self.addWidget(widget, self._nextRow, 1, 1, 1, RIGHT_TOP)
|
||||
|
||||
self.setRowStretch(self._nextRow, 0)
|
||||
self.setRowStretch(self._nextRow+1, 1)
|
||||
@@ -306,17 +335,9 @@ class NSimpleLayout(QGridLayout):
|
||||
wSp = CONFIG.pxInt(8)
|
||||
qLabel = QLabel(label)
|
||||
qLabel.setIndent(wSp)
|
||||
self.addWidget(qLabel, self._nextRow, 0, 1, 1, LEFT_TOP)
|
||||
|
||||
if isinstance(widget, QLineEdit):
|
||||
qLayout = QHBoxLayout()
|
||||
qLayout.addWidget(widget)
|
||||
self.addLayout(qLayout, self._nextRow, 1, 1, 1, RIGHT_TOP)
|
||||
else:
|
||||
self.addWidget(widget, self._nextRow, 1, 1, 1, RIGHT_TOP)
|
||||
|
||||
qLabel.setBuddy(widget)
|
||||
|
||||
self.addWidget(qLabel, self._nextRow, 0, 1, 1, LEFT_TOP)
|
||||
self.addWidget(widget, self._nextRow, 1, 1, 1, RIGHT_TOP)
|
||||
self.setRowStretch(self._nextRow, 0)
|
||||
self.setRowStretch(self._nextRow+1, 1)
|
||||
self._nextRow += 1
|
||||
@@ -326,25 +347,33 @@ class NSimpleLayout(QGridLayout):
|
||||
# END Class NSimpleLayout
|
||||
|
||||
|
||||
class NHelpLabel(QLabel):
|
||||
class NColourLabel(QLabel):
|
||||
"""Extension: A Coloured Label
|
||||
|
||||
def __init__(self, text: str, color: QColor | list | tuple,
|
||||
fontSize: float = FONT_SCALE) -> None:
|
||||
super().__init__(text)
|
||||
A custom widget that draws a label in a specific colour, and
|
||||
optionally at a specific size, and word wrapped.
|
||||
"""
|
||||
|
||||
qCol = color if isinstance(color, QColor) else QColor(*color)
|
||||
HELP_SCALE = DEFAULT_SCALE
|
||||
HEADER_SCALE = 1.25
|
||||
|
||||
lblCol = self.palette()
|
||||
lblCol.setColor(QPalette.WindowText, qCol)
|
||||
self.setPalette(lblCol)
|
||||
def __init__(self, text: str, color: QColor, parent: QWidget | None = None,
|
||||
scale: float = HELP_SCALE, wrap: bool = False, indent: int = 0) -> None:
|
||||
super().__init__(text, parent=parent)
|
||||
|
||||
lblFont = self.font()
|
||||
lblFont.setPointSizeF(fontSize*lblFont.pointSizeF())
|
||||
self.setFont(lblFont)
|
||||
font = self.font()
|
||||
font.setPointSizeF(scale*font.pointSizeF())
|
||||
colour = self.palette()
|
||||
colour.setColor(QPalette.WindowText, color)
|
||||
|
||||
self.setWordWrap(True)
|
||||
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||
self.setPalette(colour)
|
||||
self.setFont(font)
|
||||
self.setIndent(indent)
|
||||
|
||||
if wrap:
|
||||
self.setWordWrap(True)
|
||||
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||
|
||||
return
|
||||
|
||||
# END Class NHelpLabel
|
||||
# END Class NColourLabel
|
||||
|
||||
@@ -3,7 +3,7 @@ novelWriter – Custom Widget: Novel Selector
|
||||
===========================================
|
||||
|
||||
File History:
|
||||
Created: 2022-11-17 [2.0]
|
||||
Created: 2022-11-17 [2.0] NovelSelector
|
||||
|
||||
This file is a part of novelWriter
|
||||
Copyright 2018–2024, Veronica Berglyd Olsen
|
||||
@@ -43,6 +43,8 @@ class NovelSelector(QComboBox):
|
||||
super().__init__(parent=parent)
|
||||
self._blockSignal = False
|
||||
self._firstHandle = None
|
||||
self._includeAll = False
|
||||
self._listFormat = None
|
||||
self.currentIndexChanged.connect(self._indexChanged)
|
||||
return
|
||||
|
||||
@@ -64,17 +66,29 @@ class NovelSelector(QComboBox):
|
||||
|
||||
def setHandle(self, tHandle: str | None, blockSignal: bool = True) -> None:
|
||||
"""Set the currently selected handle."""
|
||||
self._blockSignal = blockSignal
|
||||
if tHandle is None:
|
||||
index = self.count() - 1
|
||||
else:
|
||||
index = self.findData(tHandle)
|
||||
if index >= 0:
|
||||
if (index := self.findData(tHandle) if tHandle else (self.count() - 1)) >= 0:
|
||||
self._blockSignal = blockSignal
|
||||
self.setCurrentIndex(index)
|
||||
self._blockSignal = False
|
||||
self._blockSignal = False
|
||||
return
|
||||
|
||||
def updateList(self, includeAll: bool = False, prefix: str | None = None) -> None:
|
||||
def setIncludeAll(self, value: bool) -> None:
|
||||
"""Set flag to add an "All Novel Folders" option."""
|
||||
self._includeAll = value
|
||||
return
|
||||
|
||||
def setListFormat(self, value: str | None) -> None:
|
||||
"""Set a format string for the list entries."""
|
||||
if value is None or "{0}" in value:
|
||||
self._listFormat = value
|
||||
return
|
||||
|
||||
##
|
||||
# Public Slots
|
||||
##
|
||||
|
||||
@pyqtSlot()
|
||||
def refreshNovelList(self) -> None:
|
||||
"""Rebuild the list of novel items."""
|
||||
self._blockSignal = True
|
||||
self._firstHandle = None
|
||||
@@ -83,8 +97,8 @@ class NovelSelector(QComboBox):
|
||||
icon = SHARED.theme.getIcon(nwLabels.CLASS_ICON[nwItemClass.NOVEL])
|
||||
handle = self.currentData()
|
||||
for tHandle, nwItem in SHARED.project.tree.iterRoots(nwItemClass.NOVEL):
|
||||
if prefix:
|
||||
name = prefix.format(nwItem.itemName)
|
||||
if self._listFormat:
|
||||
name = self._listFormat.format(nwItem.itemName)
|
||||
self.addItem(name, tHandle)
|
||||
else:
|
||||
name = nwItem.itemName
|
||||
@@ -92,7 +106,7 @@ class NovelSelector(QComboBox):
|
||||
if self._firstHandle is None:
|
||||
self._firstHandle = tHandle
|
||||
|
||||
if includeAll:
|
||||
if self._includeAll:
|
||||
self.insertSeparator(self.count())
|
||||
self.addItem(icon, self.tr("All Novel Folders"), "")
|
||||
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
"""
|
||||
novelWriter – Custom Widget: Paged Dialog
|
||||
=========================================
|
||||
|
||||
File History:
|
||||
Created: 2020-05-17 [0.5.1]
|
||||
|
||||
This file is a part of novelWriter
|
||||
Copyright 2018–2024, 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
|
||||
|
||||
from PyQt5.QtGui import QPaintEvent
|
||||
from PyQt5.QtCore import QRect, QPoint, QSize
|
||||
from PyQt5.QtWidgets import (
|
||||
QDialog, QHBoxLayout, QStyle, QStyleOptionTab, QStylePainter, QTabBar,
|
||||
QTabWidget, QVBoxLayout, QWidget
|
||||
)
|
||||
|
||||
from novelwriter import CONFIG
|
||||
|
||||
|
||||
class NPagedDialog(QDialog):
|
||||
|
||||
def __init__(self, parent: QWidget) -> None:
|
||||
super().__init__(parent=parent)
|
||||
|
||||
self._tabBar = NVerticalTabBar(self)
|
||||
self._tabBar.setExpanding(False)
|
||||
|
||||
self._tabBox = QTabWidget(self)
|
||||
self._tabBox.setTabBar(self._tabBar)
|
||||
self._tabBox.setTabPosition(QTabWidget.West)
|
||||
|
||||
self._buttonBox = QHBoxLayout()
|
||||
|
||||
self._outerBox = QVBoxLayout()
|
||||
self._outerBox.addWidget(self._tabBox)
|
||||
self._outerBox.addLayout(self._buttonBox)
|
||||
|
||||
# Default Margins
|
||||
thisStyle = self.style()
|
||||
mL = thisStyle.pixelMetric(QStyle.PM_LayoutLeftMargin)
|
||||
mR = thisStyle.pixelMetric(QStyle.PM_LayoutRightMargin)
|
||||
mT = thisStyle.pixelMetric(QStyle.PM_LayoutLeftMargin)
|
||||
mB = thisStyle.pixelMetric(QStyle.PM_LayoutBottomMargin)
|
||||
|
||||
# Set Margins
|
||||
self.setContentsMargins(0, 0, 0, 0)
|
||||
self._outerBox.setContentsMargins(0, 0, 0, mB)
|
||||
self._buttonBox.setContentsMargins(mL, 0, mR, 0)
|
||||
self._outerBox.setSpacing(mT)
|
||||
|
||||
self.setLayout(self._outerBox)
|
||||
|
||||
return
|
||||
|
||||
def addTab(self, widget: QWidget, label: str) -> None:
|
||||
"""Forward the adding of tabs to the QTabWidget."""
|
||||
self._tabBox.addTab(widget, label)
|
||||
return
|
||||
|
||||
def addControls(self, buttonBar: QWidget) -> None:
|
||||
"""Add a button bar to the dialog."""
|
||||
self._buttonBox.addWidget(buttonBar)
|
||||
return
|
||||
|
||||
def setCurrentWidget(self, widget: QWidget) -> None:
|
||||
"""Forward the changing of tab to the QTabWidget."""
|
||||
self._tabBox.setCurrentWidget(widget)
|
||||
return
|
||||
|
||||
# END Class NPagedDialog
|
||||
|
||||
|
||||
class NVerticalTabBar(QTabBar):
|
||||
|
||||
def __init__(self, parent: QWidget) -> None:
|
||||
super().__init__(parent=parent)
|
||||
self._mW = CONFIG.pxInt(150)
|
||||
return
|
||||
|
||||
def tabSizeHint(self, index: int) -> QSize:
|
||||
"""Return a transposed size hint for the rotated bar."""
|
||||
tSize = super().tabSizeHint(index)
|
||||
tSize.transpose()
|
||||
tSize.setWidth(min(tSize.width(), self._mW))
|
||||
return tSize
|
||||
|
||||
def paintEvent(self, event: QPaintEvent) -> None:
|
||||
"""Custom implementation of the label painter that rotates the
|
||||
label 90 degrees.
|
||||
"""
|
||||
pObj = QStylePainter(self)
|
||||
oObj = QStyleOptionTab()
|
||||
|
||||
for i in range(self.count()):
|
||||
self.initStyleOption(oObj, i)
|
||||
pObj.drawControl(QStyle.CE_TabBarTabShape, oObj)
|
||||
pObj.save()
|
||||
|
||||
oSize = oObj.rect.size()
|
||||
oSize.transpose()
|
||||
oRect = QRect(QPoint(), oSize)
|
||||
oRect.moveCenter(oObj.rect.center())
|
||||
oObj.rect = oRect
|
||||
|
||||
oCenter = self.tabRect(i).center()
|
||||
pObj.translate(oCenter)
|
||||
pObj.rotate(90)
|
||||
pObj.translate(-oCenter)
|
||||
pObj.drawControl(QStyle.CE_TabBarTabLabel, oObj)
|
||||
pObj.restore()
|
||||
|
||||
return
|
||||
|
||||
# END Class NVerticalTabBar
|
||||
@@ -45,10 +45,9 @@ class NPagedSideBar(QToolBar):
|
||||
def __init__(self, parent: QWidget) -> None:
|
||||
super().__init__(parent=parent)
|
||||
|
||||
self._buttons = []
|
||||
self._actions = []
|
||||
self._labelCol = None
|
||||
self._spacerHeight = self.fontMetrics().height() // 2
|
||||
self._buttons: dict[int, _NPagedToolButton] = {}
|
||||
|
||||
self._group = QButtonGroup(self)
|
||||
self._group.setExclusive(True)
|
||||
@@ -67,9 +66,9 @@ class NPagedSideBar(QToolBar):
|
||||
"""Return a specific button."""
|
||||
return self._buttons[buttonId]
|
||||
|
||||
def setLabelColor(self, color: list | QColor) -> None:
|
||||
def setLabelColor(self, color: QColor) -> None:
|
||||
"""Set the text color for the labels."""
|
||||
self._labelCol = color if isinstance(color, QColor) else QColor(*color)
|
||||
self._labelCol = color
|
||||
return
|
||||
|
||||
def addSeparator(self) -> None:
|
||||
@@ -95,8 +94,7 @@ class NPagedSideBar(QToolBar):
|
||||
action = self.insertWidget(self._stretchAction, button)
|
||||
self._group.addButton(button, id=buttonId)
|
||||
|
||||
self._buttons.append(button)
|
||||
self._actions.append(action)
|
||||
self._buttons[buttonId] = button
|
||||
|
||||
return action
|
||||
|
||||
|
||||
@@ -276,14 +276,14 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
def updateSyntaxColours(self) -> None:
|
||||
"""Update the syntax highlighting theme."""
|
||||
mainPalette = self.palette()
|
||||
mainPalette.setColor(QPalette.ColorRole.Window, QColor(*SHARED.theme.colBack))
|
||||
mainPalette.setColor(QPalette.ColorRole.Base, QColor(*SHARED.theme.colBack))
|
||||
mainPalette.setColor(QPalette.ColorRole.Text, QColor(*SHARED.theme.colText))
|
||||
mainPalette.setColor(QPalette.ColorRole.Window, SHARED.theme.colBack)
|
||||
mainPalette.setColor(QPalette.ColorRole.Base, SHARED.theme.colBack)
|
||||
mainPalette.setColor(QPalette.ColorRole.Text, SHARED.theme.colText)
|
||||
self.setPalette(mainPalette)
|
||||
|
||||
docPalette = self.viewport().palette()
|
||||
docPalette.setColor(QPalette.ColorRole.Base, QColor(*SHARED.theme.colBack))
|
||||
docPalette.setColor(QPalette.ColorRole.Text, QColor(*SHARED.theme.colText))
|
||||
docPalette.setColor(QPalette.ColorRole.Base, SHARED.theme.colBack)
|
||||
docPalette.setColor(QPalette.ColorRole.Text, SHARED.theme.colText)
|
||||
self.viewport().setPalette(docPalette)
|
||||
|
||||
self.docHeader.matchColours()
|
||||
@@ -2326,9 +2326,9 @@ class GuiDocToolBar(QWidget):
|
||||
def updateTheme(self) -> None:
|
||||
"""Initialise GUI elements that depend on specific settings."""
|
||||
palette = QPalette()
|
||||
palette.setColor(QPalette.ColorRole.Window, QColor(*SHARED.theme.colBack))
|
||||
palette.setColor(QPalette.ColorRole.WindowText, QColor(*SHARED.theme.colText))
|
||||
palette.setColor(QPalette.ColorRole.Text, QColor(*SHARED.theme.colText))
|
||||
palette.setColor(QPalette.ColorRole.Window, SHARED.theme.colBack)
|
||||
palette.setColor(QPalette.ColorRole.WindowText, SHARED.theme.colText)
|
||||
palette.setColor(QPalette.ColorRole.Text, SHARED.theme.colText)
|
||||
self.setPalette(palette)
|
||||
|
||||
self.tbBoldMD.setIcon(SHARED.theme.getIcon("fmt_bold-md"))
|
||||
@@ -2866,10 +2866,11 @@ class GuiDocEditHeader(QWidget):
|
||||
self.minmaxButton.setIcon(SHARED.theme.getIcon("maximise"))
|
||||
self.closeButton.setIcon(SHARED.theme.getIcon("close"))
|
||||
|
||||
colText = SHARED.theme.colText
|
||||
buttonStyle = (
|
||||
"QToolButton {{border: none; background: transparent;}} "
|
||||
"QToolButton:hover {{border: none; background: rgba({0},{1},{2},0.2);}}"
|
||||
).format(*SHARED.theme.colText)
|
||||
"QToolButton:hover {{border: none; background: rgba({0}, {1}, {2}, 0.2);}}"
|
||||
).format(colText.red(), colText.green(), colText.blue())
|
||||
|
||||
self.tbButton.setStyleSheet(buttonStyle)
|
||||
self.searchButton.setStyleSheet(buttonStyle)
|
||||
@@ -2885,9 +2886,9 @@ class GuiDocEditHeader(QWidget):
|
||||
theme rather than the main GUI.
|
||||
"""
|
||||
palette = QPalette()
|
||||
palette.setColor(QPalette.ColorRole.Window, QColor(*SHARED.theme.colBack))
|
||||
palette.setColor(QPalette.ColorRole.WindowText, QColor(*SHARED.theme.colText))
|
||||
palette.setColor(QPalette.ColorRole.Text, QColor(*SHARED.theme.colText))
|
||||
palette.setColor(QPalette.ColorRole.Window, SHARED.theme.colBack)
|
||||
palette.setColor(QPalette.ColorRole.WindowText, SHARED.theme.colText)
|
||||
palette.setColor(QPalette.ColorRole.Text, SHARED.theme.colText)
|
||||
|
||||
self.setPalette(palette)
|
||||
self.itemTitle.setPalette(palette)
|
||||
@@ -3093,9 +3094,9 @@ class GuiDocEditFooter(QWidget):
|
||||
theme rather than the main GUI.
|
||||
"""
|
||||
palette = QPalette()
|
||||
palette.setColor(QPalette.ColorRole.Window, QColor(*SHARED.theme.colBack))
|
||||
palette.setColor(QPalette.ColorRole.WindowText, QColor(*SHARED.theme.colText))
|
||||
palette.setColor(QPalette.ColorRole.Text, QColor(*SHARED.theme.colText))
|
||||
palette.setColor(QPalette.ColorRole.Window, SHARED.theme.colBack)
|
||||
palette.setColor(QPalette.ColorRole.WindowText, SHARED.theme.colText)
|
||||
palette.setColor(QPalette.ColorRole.Text, SHARED.theme.colText)
|
||||
|
||||
self.setPalette(palette)
|
||||
self.statusText.setPalette(palette)
|
||||
|
||||
@@ -48,6 +48,8 @@ SPELLRX.setPatternOptions(QRegularExpression.UseUnicodePropertiesOption)
|
||||
|
||||
class GuiDocHighlighter(QSyntaxHighlighter):
|
||||
|
||||
__slots__ = ("_tItem", "_tHandle", "_spellCheck", "_spellErr", "_hRules", "_hStyles")
|
||||
|
||||
BLOCK_NONE = 0
|
||||
BLOCK_TEXT = 1
|
||||
BLOCK_META = 2
|
||||
@@ -58,29 +60,14 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
||||
|
||||
logger.debug("Create: GuiDocHighlighter")
|
||||
|
||||
self._tItem = None
|
||||
self._tHandle = None
|
||||
self._tItem = None
|
||||
self._tHandle = None
|
||||
self._spellCheck = False
|
||||
self._spellErr = QTextCharFormat()
|
||||
|
||||
self._hRules: list[tuple[str, dict]] = []
|
||||
self._hStyles: dict[str, QTextCharFormat] = {}
|
||||
|
||||
self._colHead = QColor(0, 0, 0)
|
||||
self._colHeadH = QColor(0, 0, 0)
|
||||
self._colEmph = QColor(0, 0, 0)
|
||||
self._colDialN = QColor(0, 0, 0)
|
||||
self._colDialD = QColor(0, 0, 0)
|
||||
self._colDialS = QColor(0, 0, 0)
|
||||
self._colHidden = QColor(0, 0, 0)
|
||||
self._colCode = QColor(0, 0, 0)
|
||||
self._colKey = QColor(0, 0, 0)
|
||||
self._colVal = QColor(0, 0, 0)
|
||||
self._colSpell = QColor(0, 0, 0)
|
||||
self._colError = QColor(0, 0, 0)
|
||||
self._colRepTag = QColor(0, 0, 0)
|
||||
self._colMod = QColor(0, 0, 0)
|
||||
self._colBreak = QColor(0, 0, 0)
|
||||
|
||||
self.initHighlighter()
|
||||
|
||||
logger.debug("Ready: GuiDocHighlighter")
|
||||
@@ -93,54 +80,42 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
||||
"""
|
||||
logger.debug("Setting up highlighting rules")
|
||||
|
||||
self._colHead = QColor(*SHARED.theme.colHead)
|
||||
self._colHeadH = QColor(*SHARED.theme.colHeadH)
|
||||
self._colDialN = QColor(*SHARED.theme.colDialN)
|
||||
self._colDialD = QColor(*SHARED.theme.colDialD)
|
||||
self._colDialS = QColor(*SHARED.theme.colDialS)
|
||||
self._colHidden = QColor(*SHARED.theme.colHidden)
|
||||
self._colCode = QColor(*SHARED.theme.colCode)
|
||||
self._colKey = QColor(*SHARED.theme.colKey)
|
||||
self._colVal = QColor(*SHARED.theme.colVal)
|
||||
self._colSpell = QColor(*SHARED.theme.colSpell)
|
||||
self._colError = QColor(*SHARED.theme.colError)
|
||||
self._colRepTag = QColor(*SHARED.theme.colRepTag)
|
||||
self._colMod = QColor(*SHARED.theme.colMod)
|
||||
self._colBreak = QColor(*SHARED.theme.colEmph)
|
||||
self._colBreak.setAlpha(64)
|
||||
|
||||
self._colEmph = None
|
||||
if CONFIG.highlightEmph:
|
||||
self._colEmph = QColor(*SHARED.theme.colEmph)
|
||||
colEmph = SHARED.theme.colEmph if CONFIG.highlightEmph else None
|
||||
colBreak = QColor(SHARED.theme.colEmph)
|
||||
colBreak.setAlpha(64)
|
||||
|
||||
self._hRules = []
|
||||
self._hStyles = {
|
||||
"header1": self._makeFormat(self._colHead, "bold", 1.8),
|
||||
"header2": self._makeFormat(self._colHead, "bold", 1.6),
|
||||
"header3": self._makeFormat(self._colHead, "bold", 1.4),
|
||||
"header4": self._makeFormat(self._colHead, "bold", 1.2),
|
||||
"header1h": self._makeFormat(self._colHeadH, "bold", 1.8),
|
||||
"header2h": self._makeFormat(self._colHeadH, "bold", 1.6),
|
||||
"header3h": self._makeFormat(self._colHeadH, "bold", 1.4),
|
||||
"header4h": self._makeFormat(self._colHeadH, "bold", 1.2),
|
||||
"bold": self._makeFormat(self._colEmph, "bold"),
|
||||
"italic": self._makeFormat(self._colEmph, "italic"),
|
||||
"strike": self._makeFormat(self._colHidden, "strike"),
|
||||
"mspaces": self._makeFormat(self._colError, "errline"),
|
||||
"nobreak": self._makeFormat(self._colBreak, "background"),
|
||||
"dialogue1": self._makeFormat(self._colDialN),
|
||||
"dialogue2": self._makeFormat(self._colDialD),
|
||||
"dialogue3": self._makeFormat(self._colDialS),
|
||||
"replace": self._makeFormat(self._colRepTag),
|
||||
"hidden": self._makeFormat(self._colHidden),
|
||||
"code": self._makeFormat(self._colCode),
|
||||
"keyword": self._makeFormat(self._colKey),
|
||||
"modifier": self._makeFormat(self._colMod),
|
||||
"value": self._makeFormat(self._colVal, "underline"),
|
||||
"codevalue": self._makeFormat(self._colVal),
|
||||
"header1": self._makeFormat(SHARED.theme.colHead, "bold", 1.8),
|
||||
"header2": self._makeFormat(SHARED.theme.colHead, "bold", 1.6),
|
||||
"header3": self._makeFormat(SHARED.theme.colHead, "bold", 1.4),
|
||||
"header4": self._makeFormat(SHARED.theme.colHead, "bold", 1.2),
|
||||
"header1h": self._makeFormat(SHARED.theme.colHeadH, "bold", 1.8),
|
||||
"header2h": self._makeFormat(SHARED.theme.colHeadH, "bold", 1.6),
|
||||
"header3h": self._makeFormat(SHARED.theme.colHeadH, "bold", 1.4),
|
||||
"header4h": self._makeFormat(SHARED.theme.colHeadH, "bold", 1.2),
|
||||
"bold": self._makeFormat(colEmph, "bold"),
|
||||
"italic": self._makeFormat(colEmph, "italic"),
|
||||
"strike": self._makeFormat(SHARED.theme.colHidden, "strike"),
|
||||
"mspaces": self._makeFormat(SHARED.theme.colError, "errline"),
|
||||
"nobreak": self._makeFormat(colBreak, "background"),
|
||||
"dialogue1": self._makeFormat(SHARED.theme.colDialN),
|
||||
"dialogue2": self._makeFormat(SHARED.theme.colDialD),
|
||||
"dialogue3": self._makeFormat(SHARED.theme.colDialS),
|
||||
"replace": self._makeFormat(SHARED.theme.colRepTag),
|
||||
"hidden": self._makeFormat(SHARED.theme.colHidden),
|
||||
"code": self._makeFormat(SHARED.theme.colCode),
|
||||
"keyword": self._makeFormat(SHARED.theme.colKey),
|
||||
"modifier": self._makeFormat(SHARED.theme.colMod),
|
||||
"value": self._makeFormat(SHARED.theme.colVal, "underline"),
|
||||
"codevalue": self._makeFormat(SHARED.theme.colVal),
|
||||
"codeinval": self._makeFormat(None, "errline"),
|
||||
}
|
||||
|
||||
self._hRules = []
|
||||
# Cache Spell Error Format
|
||||
self._spellErr = QTextCharFormat()
|
||||
self._spellErr.setUnderlineColor(SHARED.theme.colSpell)
|
||||
self._spellErr.setUnderlineStyle(QTextCharFormat.SpellCheckUnderline)
|
||||
|
||||
# Multiple or Trailing Spaces
|
||||
if CONFIG.showMultiSpaces:
|
||||
@@ -305,12 +280,12 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
||||
self.setCurrentBlockState(self.BLOCK_META)
|
||||
if self._tItem:
|
||||
pIndex = SHARED.project.index
|
||||
isValid, theBits, thePos = pIndex.scanThis(text)
|
||||
isGood = pIndex.checkThese(theBits, self._tItem)
|
||||
isValid, bits, pos = pIndex.scanThis(text)
|
||||
isGood = pIndex.checkThese(bits, self._tItem)
|
||||
if isValid:
|
||||
for n, theBit in enumerate(theBits):
|
||||
xPos = thePos[n]
|
||||
xLen = len(theBit)
|
||||
for n, bit in enumerate(bits):
|
||||
xPos = pos[n]
|
||||
xLen = len(bit)
|
||||
if isGood[n]:
|
||||
if n == 0:
|
||||
self.setFormat(xPos, xLen, self._hStyles["keyword"])
|
||||
@@ -318,8 +293,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
||||
self.setFormat(xPos, xLen, self._hStyles["value"])
|
||||
else:
|
||||
kwFmt = self.format(xPos)
|
||||
kwFmt.setUnderlineColor(self._colError)
|
||||
kwFmt.setUnderlineStyle(QTextCharFormat.SpellCheckUnderline)
|
||||
kwFmt.merge(self._hStyles["codeinval"])
|
||||
self.setFormat(xPos, xLen, kwFmt)
|
||||
|
||||
# We never want to run the spell checker on keyword/values,
|
||||
@@ -402,8 +376,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
||||
for xPos, xLen in data.spellCheck(text):
|
||||
for x in range(xPos, xPos+xLen):
|
||||
spFmt = self.format(x)
|
||||
spFmt.setUnderlineColor(self._colSpell)
|
||||
spFmt.setUnderlineStyle(QTextCharFormat.SpellCheckUnderline)
|
||||
spFmt.merge(self._spellErr)
|
||||
self.setFormat(x, 1, spFmt)
|
||||
|
||||
return
|
||||
@@ -431,7 +404,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
||||
if "strike" in styles:
|
||||
charFormat.setFontStrikeOut(True)
|
||||
if "errline" in styles:
|
||||
charFormat.setUnderlineColor(self._colError)
|
||||
charFormat.setUnderlineColor(SHARED.theme.colError)
|
||||
charFormat.setUnderlineStyle(QTextCharFormat.SpellCheckUnderline)
|
||||
if "underline" in styles:
|
||||
charFormat.setFontUnderline(True)
|
||||
|
||||
@@ -33,7 +33,7 @@ from typing import TYPE_CHECKING
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QPoint, QSize, Qt, QUrl
|
||||
from PyQt5.QtGui import (
|
||||
QColor, QCursor, QFont, QMouseEvent, QPalette, QResizeEvent, QTextCursor,
|
||||
QCursor, QFont, QMouseEvent, QPalette, QResizeEvent, QTextCursor,
|
||||
QTextOption
|
||||
)
|
||||
from PyQt5.QtWidgets import (
|
||||
@@ -149,14 +149,14 @@ class GuiDocViewer(QTextBrowser):
|
||||
|
||||
# Set the widget colours to match syntax theme
|
||||
mainPalette = self.palette()
|
||||
mainPalette.setColor(QPalette.ColorRole.Window, QColor(*SHARED.theme.colBack))
|
||||
mainPalette.setColor(QPalette.ColorRole.Base, QColor(*SHARED.theme.colBack))
|
||||
mainPalette.setColor(QPalette.ColorRole.Text, QColor(*SHARED.theme.colText))
|
||||
mainPalette.setColor(QPalette.ColorRole.Window, SHARED.theme.colBack)
|
||||
mainPalette.setColor(QPalette.ColorRole.Base, SHARED.theme.colBack)
|
||||
mainPalette.setColor(QPalette.ColorRole.Text, SHARED.theme.colText)
|
||||
self.setPalette(mainPalette)
|
||||
|
||||
docPalette = self.viewport().palette()
|
||||
docPalette.setColor(QPalette.ColorRole.Base, QColor(*SHARED.theme.colBack))
|
||||
docPalette.setColor(QPalette.ColorRole.Text, QColor(*SHARED.theme.colText))
|
||||
docPalette.setColor(QPalette.ColorRole.Base, SHARED.theme.colBack)
|
||||
docPalette.setColor(QPalette.ColorRole.Text, SHARED.theme.colText)
|
||||
self.viewport().setPalette(docPalette)
|
||||
|
||||
self.docHeader.matchColours()
|
||||
@@ -463,7 +463,13 @@ class GuiDocViewer(QTextBrowser):
|
||||
"""Generate an appropriate style sheet for the document viewer,
|
||||
based on the current syntax highlighter theme,
|
||||
"""
|
||||
pTheme = SHARED.theme
|
||||
colText = SHARED.theme.colText
|
||||
colHead = SHARED.theme.colHead
|
||||
colVal = SHARED.theme.colVal
|
||||
colEmph = SHARED.theme.colEmph
|
||||
colKey = SHARED.theme.colKey
|
||||
colHidden = SHARED.theme.colHidden
|
||||
colMod = SHARED.theme.colMod
|
||||
styleSheet = (
|
||||
"body {{"
|
||||
" color: rgb({tColR}, {tColG}, {tColB});"
|
||||
@@ -490,27 +496,27 @@ class GuiDocViewer(QTextBrowser):
|
||||
" text-align: center;"
|
||||
"}}\n"
|
||||
).format(
|
||||
tColR=pTheme.colText[0],
|
||||
tColG=pTheme.colText[1],
|
||||
tColB=pTheme.colText[2],
|
||||
hColR=pTheme.colHead[0],
|
||||
hColG=pTheme.colHead[1],
|
||||
hColB=pTheme.colHead[2],
|
||||
aColR=pTheme.colVal[0],
|
||||
aColG=pTheme.colVal[1],
|
||||
aColB=pTheme.colVal[2],
|
||||
eColR=pTheme.colEmph[0],
|
||||
eColG=pTheme.colEmph[1],
|
||||
eColB=pTheme.colEmph[2],
|
||||
kColR=pTheme.colKey[0],
|
||||
kColG=pTheme.colKey[1],
|
||||
kColB=pTheme.colKey[2],
|
||||
cColR=pTheme.colHidden[0],
|
||||
cColG=pTheme.colHidden[1],
|
||||
cColB=pTheme.colHidden[2],
|
||||
mColR=pTheme.colMod[0],
|
||||
mColG=pTheme.colMod[1],
|
||||
mColB=pTheme.colMod[2],
|
||||
tColR=colText.red(),
|
||||
tColG=colText.green(),
|
||||
tColB=colText.blue(),
|
||||
hColR=colHead.red(),
|
||||
hColG=colHead.green(),
|
||||
hColB=colHead.blue(),
|
||||
aColR=colVal.red(),
|
||||
aColG=colVal.green(),
|
||||
aColB=colVal.blue(),
|
||||
eColR=colEmph.red(),
|
||||
eColG=colEmph.green(),
|
||||
eColB=colEmph.blue(),
|
||||
kColR=colKey.red(),
|
||||
kColG=colKey.green(),
|
||||
kColB=colKey.blue(),
|
||||
cColR=colHidden.red(),
|
||||
cColG=colHidden.green(),
|
||||
cColB=colHidden.blue(),
|
||||
mColR=colMod.red(),
|
||||
mColG=colMod.green(),
|
||||
mColB=colMod.blue(),
|
||||
)
|
||||
self.document().setDefaultStyleSheet(styleSheet)
|
||||
|
||||
@@ -744,10 +750,11 @@ class GuiDocViewHeader(QWidget):
|
||||
self.refreshButton.setIcon(SHARED.theme.getIcon("refresh"))
|
||||
self.closeButton.setIcon(SHARED.theme.getIcon("close"))
|
||||
|
||||
colText = SHARED.theme.colText
|
||||
buttonStyle = (
|
||||
"QToolButton {{border: none; background: transparent;}} "
|
||||
"QToolButton:hover {{border: none; background: rgba({0},{1},{2},0.2);}}"
|
||||
).format(*SHARED.theme.colText)
|
||||
"QToolButton:hover {{border: none; background: rgba({0}, {1}, {2}, 0.2);}}"
|
||||
).format(colText.red(), colText.green(), colText.blue())
|
||||
|
||||
self.backButton.setStyleSheet(buttonStyle)
|
||||
self.forwardButton.setStyleSheet(buttonStyle)
|
||||
@@ -763,9 +770,9 @@ class GuiDocViewHeader(QWidget):
|
||||
theme rather than the main GUI.
|
||||
"""
|
||||
palette = QPalette()
|
||||
palette.setColor(QPalette.ColorRole.Window, QColor(*SHARED.theme.colBack))
|
||||
palette.setColor(QPalette.ColorRole.WindowText, QColor(*SHARED.theme.colText))
|
||||
palette.setColor(QPalette.ColorRole.Text, QColor(*SHARED.theme.colText))
|
||||
palette.setColor(QPalette.ColorRole.Window, SHARED.theme.colBack)
|
||||
palette.setColor(QPalette.ColorRole.WindowText, SHARED.theme.colText)
|
||||
palette.setColor(QPalette.ColorRole.Text, SHARED.theme.colText)
|
||||
self.setPalette(palette)
|
||||
self.docTitle.setPalette(palette)
|
||||
return
|
||||
@@ -937,11 +944,11 @@ class GuiDocViewFooter(QWidget):
|
||||
self.showComments.setIcon(bulletIcon)
|
||||
self.showSynopsis.setIcon(bulletIcon)
|
||||
|
||||
# StyleSheets
|
||||
colText = SHARED.theme.colText
|
||||
buttonStyle = (
|
||||
"QToolButton {{border: none; background: transparent;}} "
|
||||
"QToolButton:hover {{border: none; background: rgba({0},{1},{2},0.2);}}"
|
||||
).format(*SHARED.theme.colText)
|
||||
"QToolButton:hover {{border: none; background: rgba({0}, {1}, {2}, 0.2);}}"
|
||||
).format(colText.red(), colText.green(), colText.blue())
|
||||
|
||||
self.showHide.setStyleSheet(buttonStyle)
|
||||
self.showComments.setStyleSheet(buttonStyle)
|
||||
@@ -956,9 +963,9 @@ class GuiDocViewFooter(QWidget):
|
||||
theme rather than the main GUI.
|
||||
"""
|
||||
palette = QPalette()
|
||||
palette.setColor(QPalette.ColorRole.Window, QColor(*SHARED.theme.colBack))
|
||||
palette.setColor(QPalette.ColorRole.WindowText, QColor(*SHARED.theme.colText))
|
||||
palette.setColor(QPalette.ColorRole.Text, QColor(*SHARED.theme.colText))
|
||||
palette.setColor(QPalette.ColorRole.Window, SHARED.theme.colBack)
|
||||
palette.setColor(QPalette.ColorRole.WindowText, SHARED.theme.colText)
|
||||
palette.setColor(QPalette.ColorRole.Text, SHARED.theme.colText)
|
||||
self.setPalette(palette)
|
||||
return
|
||||
|
||||
|
||||
@@ -155,10 +155,10 @@ class GuiMainMenu(QMenuBar):
|
||||
self.aProjectSettings.setShortcut("Ctrl+Shift+,")
|
||||
self.aProjectSettings.triggered.connect(self.mainGui.showProjectSettingsDialog)
|
||||
|
||||
# Project > Project Details
|
||||
self.aProjectDetails = self.projMenu.addAction(self.tr("Project Details"))
|
||||
self.aProjectDetails.setShortcut("Shift+F6")
|
||||
self.aProjectDetails.triggered.connect(self.mainGui.showProjectDetailsDialog)
|
||||
# Project > Novel Details
|
||||
self.aNovelDetails = self.projMenu.addAction(self.tr("Novel Details"))
|
||||
self.aNovelDetails.setShortcut("Shift+F6")
|
||||
self.aNovelDetails.triggered.connect(self.mainGui.showNovelDetailsDialog)
|
||||
|
||||
# Project > Separator
|
||||
self.projMenu.addSeparator()
|
||||
|
||||
@@ -207,9 +207,10 @@ class GuiNovelToolBar(QWidget):
|
||||
# Novel Selector
|
||||
selFont = self.font()
|
||||
selFont.setWeight(QFont.Weight.Bold)
|
||||
self.novelPrefix = self.tr("Outline of {0}")
|
||||
|
||||
self.novelValue = NovelSelector(self)
|
||||
self.novelValue.setFont(selFont)
|
||||
self.novelValue.setListFormat(self.tr("Outline of {0}"))
|
||||
self.novelValue.setMinimumWidth(CONFIG.pxInt(150))
|
||||
self.novelValue.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
||||
self.novelValue.novelSelectionChanged.connect(self.setCurrentRoot)
|
||||
@@ -294,7 +295,7 @@ class GuiNovelToolBar(QWidget):
|
||||
"QComboBox {border-style: none; padding-left: 0;} "
|
||||
"QComboBox::drop-down {border-style: none}"
|
||||
)
|
||||
self.novelValue.updateList(prefix=self.novelPrefix)
|
||||
self.novelValue.refreshNovelList()
|
||||
self.tbNovel.setVisible(self.novelValue.count() > 1)
|
||||
|
||||
return
|
||||
@@ -307,7 +308,7 @@ class GuiNovelToolBar(QWidget):
|
||||
|
||||
def buildNovelRootMenu(self) -> None:
|
||||
"""Build the novel root menu."""
|
||||
self.novelValue.updateList(prefix=self.novelPrefix)
|
||||
self.novelValue.refreshNovelList()
|
||||
self.tbNovel.setVisible(self.novelValue.count() > 1)
|
||||
return
|
||||
|
||||
|
||||
@@ -220,6 +220,7 @@ class GuiOutlineToolBar(QToolBar):
|
||||
self.novelLabel.setContentsMargins(0, 0, mPx, 0)
|
||||
|
||||
self.novelValue = NovelSelector(self)
|
||||
self.novelValue.setIncludeAll(True)
|
||||
self.novelValue.setMinimumWidth(CONFIG.pxInt(200))
|
||||
self.novelValue.novelSelectionChanged.connect(self._novelValueChanged)
|
||||
|
||||
@@ -258,7 +259,7 @@ class GuiOutlineToolBar(QToolBar):
|
||||
def updateTheme(self) -> None:
|
||||
"""Update theme elements."""
|
||||
self.setStyleSheet("QToolBar {border: 0px;}")
|
||||
self.novelValue.updateList(includeAll=True)
|
||||
self.novelValue.refreshNovelList()
|
||||
self.aRefresh.setIcon(SHARED.theme.getIcon("refresh"))
|
||||
self.tbColumns.setIcon(SHARED.theme.getIcon("menu"))
|
||||
self.tbColumns.setStyleSheet("QToolButton::menu-indicator {image: none;}")
|
||||
@@ -266,7 +267,7 @@ class GuiOutlineToolBar(QToolBar):
|
||||
|
||||
def populateNovelList(self) -> None:
|
||||
"""Reload the content of the novel list."""
|
||||
self.novelValue.updateList(includeAll=True)
|
||||
self.novelValue.refreshNovelList()
|
||||
return
|
||||
|
||||
def setCurrentRoot(self, rootHandle: str | None) -> None:
|
||||
|
||||
@@ -48,7 +48,7 @@ from novelwriter.core.coretools import DocDuplicator, DocMerger, DocSplitter
|
||||
from novelwriter.dialogs.docmerge import GuiDocMerge
|
||||
from novelwriter.dialogs.docsplit import GuiDocSplit
|
||||
from novelwriter.dialogs.editlabel import GuiEditLabel
|
||||
from novelwriter.dialogs.projsettings import GuiProjectSettings
|
||||
from novelwriter.dialogs.projectsettings import GuiProjectSettings
|
||||
from novelwriter.enum import (
|
||||
nwDocMode, nwItemType, nwItemClass, nwItemLayout, nwWidget
|
||||
)
|
||||
@@ -1748,7 +1748,7 @@ class _TreeContextMenu(QMenu):
|
||||
menu.addSeparator()
|
||||
action = menu.addAction(self.tr("Manage Labels ..."))
|
||||
action.triggered.connect(
|
||||
lambda: self.projView.projectSettingsRequest.emit(GuiProjectSettings.TAB_STATUS)
|
||||
lambda: self.projView.projectSettingsRequest.emit(GuiProjectSettings.PAGE_STATUS)
|
||||
)
|
||||
else:
|
||||
menu = self.addMenu(self.tr("Set Importance to ..."))
|
||||
@@ -1765,7 +1765,7 @@ class _TreeContextMenu(QMenu):
|
||||
menu.addSeparator()
|
||||
action = menu.addAction(self.tr("Manage Labels ..."))
|
||||
action.triggered.connect(
|
||||
lambda: self.projView.projectSettingsRequest.emit(GuiProjectSettings.TAB_IMPORT)
|
||||
lambda: self.projView.projectSettingsRequest.emit(GuiProjectSettings.PAGE_IMPORT)
|
||||
)
|
||||
return
|
||||
|
||||
|
||||
@@ -79,9 +79,9 @@ class GuiSideBar(QWidget):
|
||||
self.tbBuild.clicked.connect(self.mainGui.showBuildManuscriptDialog)
|
||||
|
||||
self.tbDetails = QToolButton(self)
|
||||
self.tbDetails.setToolTip("{0} [Shift+F6]".format(self.tr("Project Details")))
|
||||
self.tbDetails.setToolTip("{0} [Shift+F6]".format(self.tr("Novel Details")))
|
||||
self.tbDetails.setIconSize(iconSize)
|
||||
self.tbDetails.clicked.connect(self.mainGui.showProjectDetailsDialog)
|
||||
self.tbDetails.clicked.connect(self.mainGui.showNovelDetailsDialog)
|
||||
|
||||
self.tbStats = QToolButton(self)
|
||||
self.tbStats.setToolTip("{0} [F6]".format(self.tr("Writing Statistics")))
|
||||
|
||||
@@ -29,7 +29,6 @@ from time import time
|
||||
from typing import TYPE_CHECKING, Literal
|
||||
from datetime import datetime
|
||||
|
||||
from PyQt5.QtGui import QColor
|
||||
from PyQt5.QtCore import pyqtSlot, QLocale
|
||||
from PyQt5.QtWidgets import qApp, QStatusBar, QLabel
|
||||
|
||||
@@ -55,9 +54,9 @@ class GuiMainStatus(QStatusBar):
|
||||
self._userIdle = False
|
||||
self._debugInfo = False
|
||||
|
||||
colNone = QColor(*SHARED.theme.statNone)
|
||||
colSaved = QColor(*SHARED.theme.statSaved)
|
||||
colUnsaved = QColor(*SHARED.theme.statUnsaved)
|
||||
colNone = SHARED.theme.statNone
|
||||
colSaved = SHARED.theme.statSaved
|
||||
colUnsaved = SHARED.theme.statUnsaved
|
||||
|
||||
iPx = SHARED.theme.baseIconSize
|
||||
|
||||
|
||||
+26
-41
@@ -70,10 +70,10 @@ class GuiTheme:
|
||||
self.isLightTheme = True
|
||||
|
||||
# GUI
|
||||
self.statNone = [120, 120, 120]
|
||||
self.statUnsaved = [200, 15, 39]
|
||||
self.statSaved = [2, 133, 37]
|
||||
self.helpText = [0, 0, 0]
|
||||
self.statNone = QColor(120, 120, 120)
|
||||
self.statUnsaved = QColor(200, 15, 39)
|
||||
self.statSaved = QColor(2, 133, 37)
|
||||
self.helpText = QColor(0, 0, 0)
|
||||
|
||||
# Loaded Syntax Settings
|
||||
# ======================
|
||||
@@ -88,23 +88,23 @@ class GuiTheme:
|
||||
self.syntaxLicenseUrl = ""
|
||||
|
||||
# Colours
|
||||
self.colBack = [255, 255, 255]
|
||||
self.colText = [0, 0, 0]
|
||||
self.colLink = [0, 0, 0]
|
||||
self.colHead = [0, 0, 0]
|
||||
self.colHeadH = [0, 0, 0]
|
||||
self.colEmph = [0, 0, 0]
|
||||
self.colDialN = [0, 0, 0]
|
||||
self.colDialD = [0, 0, 0]
|
||||
self.colDialS = [0, 0, 0]
|
||||
self.colHidden = [0, 0, 0]
|
||||
self.colCode = [0, 0, 0]
|
||||
self.colKey = [0, 0, 0]
|
||||
self.colVal = [0, 0, 0]
|
||||
self.colSpell = [0, 0, 0]
|
||||
self.colError = [0, 0, 0]
|
||||
self.colRepTag = [0, 0, 0]
|
||||
self.colMod = [0, 0, 0]
|
||||
self.colBack = QColor(255, 255, 255)
|
||||
self.colText = QColor(0, 0, 0)
|
||||
self.colLink = QColor(0, 0, 0)
|
||||
self.colHead = QColor(0, 0, 0)
|
||||
self.colHeadH = QColor(0, 0, 0)
|
||||
self.colEmph = QColor(0, 0, 0)
|
||||
self.colDialN = QColor(0, 0, 0)
|
||||
self.colDialD = QColor(0, 0, 0)
|
||||
self.colDialS = QColor(0, 0, 0)
|
||||
self.colHidden = QColor(0, 0, 0)
|
||||
self.colCode = QColor(0, 0, 0)
|
||||
self.colKey = QColor(0, 0, 0)
|
||||
self.colVal = QColor(0, 0, 0)
|
||||
self.colSpell = QColor(0, 0, 0)
|
||||
self.colError = QColor(0, 0, 0)
|
||||
self.colRepTag = QColor(0, 0, 0)
|
||||
self.colMod = QColor(0, 0, 0)
|
||||
|
||||
# Class Setup
|
||||
# ===========
|
||||
@@ -256,12 +256,12 @@ class GuiTheme:
|
||||
backLNess = backCol.lightnessF()
|
||||
textLNess = textCol.lightnessF()
|
||||
self.isLightTheme = backLNess > textLNess
|
||||
if self.helpText == [0, 0, 0]:
|
||||
if self.helpText == QColor(0, 0, 0):
|
||||
if self.isLightTheme:
|
||||
helpLCol = textLNess + 0.35*(backLNess - textLNess)
|
||||
else:
|
||||
helpLCol = backLNess + 0.65*(textLNess - backLNess)
|
||||
self.helpText = [int(255*helpLCol)]*3
|
||||
self.helpText = QColor.fromHsl(0, 0, int(255*helpLCol))
|
||||
|
||||
# Icons
|
||||
defaultIcons = "typicons_light" if backLNess >= 0.5 else "typicons_dark"
|
||||
@@ -398,29 +398,14 @@ class GuiTheme:
|
||||
|
||||
return True
|
||||
|
||||
def _parseColour(self, parser: NWConfigParser, section: str, name: str) -> list[int]:
|
||||
def _parseColour(self, parser: NWConfigParser, section: str, name: str) -> QColor:
|
||||
"""Parse a colour value from a config string."""
|
||||
if parser.has_option(section, name):
|
||||
values = parser.get(section, name).split(",")
|
||||
result = []
|
||||
try:
|
||||
result.append(minmax(int(values[0]), 0, 255))
|
||||
result.append(minmax(int(values[1]), 0, 255))
|
||||
result.append(minmax(int(values[2]), 0, 255))
|
||||
except Exception:
|
||||
logger.error("Could not load theme colours for '%s' from config file", name)
|
||||
result = [0, 0, 0]
|
||||
else:
|
||||
logger.warning("Could not find theme colours for '%s' in config file", name)
|
||||
result = [0, 0, 0]
|
||||
return result
|
||||
return QColor(*parser.rdIntList(section, name, [0, 0, 0, 255]))
|
||||
|
||||
def _setPalette(self, parser: NWConfigParser, section: str,
|
||||
name: str, value: QPalette.ColorRole) -> None:
|
||||
"""Set a palette colour value from a config string."""
|
||||
self._guiPalette.setColor(
|
||||
value, QColor(*self._parseColour(parser, section, name))
|
||||
)
|
||||
self._guiPalette.setColor(value, self._parseColour(parser, section, name))
|
||||
return
|
||||
|
||||
# End Class GuiTheme
|
||||
|
||||
+11
-16
@@ -53,11 +53,11 @@ from novelwriter.dialogs.about import GuiAbout
|
||||
from novelwriter.dialogs.updates import GuiUpdates
|
||||
from novelwriter.dialogs.wordlist import GuiWordList
|
||||
from novelwriter.dialogs.preferences import GuiPreferences
|
||||
from novelwriter.dialogs.projdetails import GuiProjectDetails
|
||||
from novelwriter.dialogs.projsettings import GuiProjectSettings
|
||||
from novelwriter.dialogs.projectsettings import GuiProjectSettings
|
||||
from novelwriter.tools.welcome import GuiWelcome
|
||||
from novelwriter.tools.manuscript import GuiManuscript
|
||||
from novelwriter.tools.dictionaries import GuiDictionaries
|
||||
from novelwriter.tools.noveldetails import GuiNovelDetails
|
||||
from novelwriter.tools.writingstats import GuiWritingStats
|
||||
|
||||
from novelwriter.enum import (
|
||||
@@ -79,13 +79,6 @@ class GuiMain(QMainWindow):
|
||||
function. Also, the project instance and theme instance are created
|
||||
here. These should be passed around to all other objects who need
|
||||
them and new instances of them should generally not be created.
|
||||
|
||||
* All other GUI classes that depend on any components from the
|
||||
main GUI should be passed a reference to the instance of this
|
||||
class.
|
||||
* All non-GUI classes can be passed a reference to the NWProject
|
||||
instance if the Main GUI is not needed (which it generally
|
||||
shouldn't need).
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
@@ -816,19 +809,19 @@ class GuiMain(QMainWindow):
|
||||
|
||||
@pyqtSlot()
|
||||
@pyqtSlot(int)
|
||||
def showProjectSettingsDialog(self, focusTab: int = GuiProjectSettings.TAB_MAIN) -> None:
|
||||
def showProjectSettingsDialog(self, focusTab: int = GuiProjectSettings.PAGE_SETTINGS) -> None:
|
||||
"""Open the project settings dialog."""
|
||||
if SHARED.hasProject:
|
||||
dialog = GuiProjectSettings(self, focusTab=focusTab)
|
||||
dialog = GuiProjectSettings(self, gotoPage=focusTab)
|
||||
dialog.newProjectSettingsReady.connect(self._processProjectSettingsChanges)
|
||||
dialog.exec_()
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def showProjectDetailsDialog(self) -> None:
|
||||
"""Open the project details dialog."""
|
||||
def showNovelDetailsDialog(self) -> None:
|
||||
"""Open the novel details dialog."""
|
||||
if SHARED.hasProject:
|
||||
dialog = GuiProjectDetails(self)
|
||||
dialog = GuiNovelDetails(self)
|
||||
dialog.setModal(True)
|
||||
dialog.show()
|
||||
dialog.raise_()
|
||||
@@ -1118,13 +1111,15 @@ class GuiMain(QMainWindow):
|
||||
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _processProjectSettingsChanges(self) -> None:
|
||||
@pyqtSlot(bool)
|
||||
def _processProjectSettingsChanges(self, rebuildTrees: bool) -> None:
|
||||
"""Refresh data dependent on project settings."""
|
||||
logger.debug("Applying new project settings")
|
||||
SHARED.updateSpellCheckLanguage()
|
||||
self.itemDetails.refreshDetails()
|
||||
self._updateWindowTitle(SHARED.project.data.name)
|
||||
if rebuildTrees:
|
||||
self.rebuildTrees()
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
|
||||
@@ -27,9 +27,7 @@ import logging
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from PyQt5.QtGui import (
|
||||
QColor, QFont, QIcon, QSyntaxHighlighter, QTextCharFormat, QTextDocument
|
||||
)
|
||||
from PyQt5.QtGui import QFont, QIcon, QSyntaxHighlighter, QTextCharFormat, QTextDocument
|
||||
from PyQt5.QtCore import QEvent, QSize, Qt, pyqtSignal, pyqtSlot
|
||||
from PyQt5.QtWidgets import (
|
||||
QAbstractButton, QAbstractItemView, QComboBox, QDialog, QDialogButtonBox,
|
||||
@@ -862,9 +860,9 @@ class _HeadingSyntaxHighlighter(QSyntaxHighlighter):
|
||||
def __init__(self, document: QTextDocument) -> None:
|
||||
super().__init__(document)
|
||||
self._fmtSymbol = QTextCharFormat()
|
||||
self._fmtSymbol.setForeground(QColor(*SHARED.theme.colHead))
|
||||
self._fmtSymbol.setForeground(SHARED.theme.colHead)
|
||||
self._fmtFormat = QTextCharFormat()
|
||||
self._fmtFormat.setForeground(QColor(*SHARED.theme.colEmph))
|
||||
self._fmtFormat.setForeground(SHARED.theme.colEmph)
|
||||
return
|
||||
|
||||
def highlightBlock(self, text: str) -> None:
|
||||
|
||||
@@ -0,0 +1,525 @@
|
||||
"""
|
||||
novelWriter – GUI Novel Info
|
||||
============================
|
||||
|
||||
File History:
|
||||
Created: 2024-01-18 [2.3b1] GuiNovelDetails
|
||||
|
||||
This file is a part of novelWriter
|
||||
Copyright 2018–2024, 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 math
|
||||
import logging
|
||||
|
||||
from PyQt5.QtGui import QCloseEvent
|
||||
from PyQt5.QtCore import QSize, Qt, pyqtSlot
|
||||
from PyQt5.QtWidgets import (
|
||||
QAbstractItemView, QDialog, QDialogButtonBox, QFormLayout, QGridLayout,
|
||||
QHBoxLayout, QLabel, QSpinBox, QStackedWidget, QTreeWidget,
|
||||
QTreeWidgetItem, QVBoxLayout, QWidget
|
||||
)
|
||||
|
||||
from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.common import formatTime, numberToRoman
|
||||
from novelwriter.constants import nwUnicode
|
||||
from novelwriter.extensions.switch import NSwitch
|
||||
from novelwriter.extensions.configlayout import NColourLabel, NFixedPage, NScrollablePage
|
||||
from novelwriter.extensions.pagedsidebar import NPagedSideBar
|
||||
from novelwriter.extensions.novelselector import NovelSelector
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GuiNovelDetails(QDialog):
|
||||
|
||||
PAGE_OVERVIEW = 1
|
||||
PAGE_CONTENTS = 2
|
||||
|
||||
def __init__(self, parent: QWidget) -> None:
|
||||
super().__init__(parent=parent)
|
||||
|
||||
logger.debug("Create: GuiNovelDetails")
|
||||
self.setObjectName("GuiNovelDetails")
|
||||
self.setWindowTitle(self.tr("Novel Details"))
|
||||
|
||||
options = SHARED.project.options
|
||||
self.setMinimumSize(CONFIG.pxInt(500), CONFIG.pxInt(400))
|
||||
self.resize(
|
||||
CONFIG.pxInt(options.getInt("GuiNovelDetails", "winWidth", CONFIG.pxInt(650))),
|
||||
CONFIG.pxInt(options.getInt("GuiNovelDetails", "winHeight", CONFIG.pxInt(500)))
|
||||
)
|
||||
|
||||
# Title
|
||||
self.titleLabel = NColourLabel(
|
||||
self.tr("Novel Details"), SHARED.theme.helpText,
|
||||
parent=self, scale=NColourLabel.HEADER_SCALE, indent=CONFIG.pxInt(4)
|
||||
)
|
||||
|
||||
# Novel Selector
|
||||
self.novelSelector = NovelSelector(self)
|
||||
self.novelSelector.refreshNovelList()
|
||||
self.novelSelector.setHandle(
|
||||
options.getString("GuiNovelDetails", "novelRoot", self.novelSelector.firstHandle or "")
|
||||
)
|
||||
|
||||
# SideBar
|
||||
self.sidebar = NPagedSideBar(self)
|
||||
self.sidebar.setLabelColor(SHARED.theme.helpText)
|
||||
self.sidebar.addButton(self.tr("Overview"), self.PAGE_OVERVIEW)
|
||||
self.sidebar.addButton(self.tr("Contents"), self.PAGE_CONTENTS)
|
||||
self.sidebar.setSelected(self.PAGE_OVERVIEW)
|
||||
self.sidebar.buttonClicked.connect(self._sidebarClicked)
|
||||
|
||||
# Content
|
||||
self.overviewPage = _OverviewPage(self)
|
||||
self.contentsPage = _ContentsPage(self)
|
||||
|
||||
self.mainStack = QStackedWidget(self)
|
||||
self.mainStack.addWidget(self.overviewPage)
|
||||
self.mainStack.addWidget(self.contentsPage)
|
||||
|
||||
# Buttons
|
||||
self.buttonBox = QDialogButtonBox(QDialogButtonBox.StandardButton.Close)
|
||||
self.buttonBox.rejected.connect(self.close)
|
||||
|
||||
# Assemble
|
||||
self.topBox = QHBoxLayout()
|
||||
self.topBox.addWidget(self.titleLabel)
|
||||
self.topBox.addStretch(1)
|
||||
self.topBox.addWidget(self.novelSelector, 1)
|
||||
|
||||
self.mainBox = QHBoxLayout()
|
||||
self.mainBox.addWidget(self.sidebar)
|
||||
self.mainBox.addWidget(self.mainStack)
|
||||
self.mainBox.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
self.outerBox = QVBoxLayout()
|
||||
self.outerBox.addLayout(self.topBox)
|
||||
self.outerBox.addLayout(self.mainBox)
|
||||
self.outerBox.addWidget(self.buttonBox)
|
||||
self.outerBox.setSpacing(CONFIG.pxInt(8))
|
||||
|
||||
self.setLayout(self.outerBox)
|
||||
self.setSizeGripEnabled(True)
|
||||
|
||||
# Connect Signals
|
||||
self.novelSelector.novelSelectionChanged.connect(self.overviewPage.novelValueChanged)
|
||||
self.novelSelector.novelSelectionChanged.connect(self.contentsPage.novelValueChanged)
|
||||
|
||||
logger.debug("Ready: GuiNovelDetails")
|
||||
|
||||
return
|
||||
|
||||
def __del__(self) -> None: # pragma: no cover
|
||||
logger.debug("Delete: GuiNovelDetails")
|
||||
return
|
||||
|
||||
##
|
||||
# Methods
|
||||
##
|
||||
|
||||
def updateValues(self) -> None:
|
||||
"""Load the dialogs initial values."""
|
||||
self.overviewPage.updateProjectData()
|
||||
self.overviewPage.novelValueChanged(self.novelSelector.handle)
|
||||
self.contentsPage.novelValueChanged(self.novelSelector.handle)
|
||||
return
|
||||
|
||||
##
|
||||
# Events
|
||||
##
|
||||
|
||||
def closeEvent(self, event: QCloseEvent) -> None:
|
||||
"""Capture the user closing the window and save settings."""
|
||||
self._saveSettings()
|
||||
event.accept()
|
||||
self.deleteLater()
|
||||
return
|
||||
|
||||
##
|
||||
# Private Slots
|
||||
##
|
||||
|
||||
@pyqtSlot(int)
|
||||
def _sidebarClicked(self, pageId: int) -> None:
|
||||
"""Process a user request to switch page."""
|
||||
if pageId == self.PAGE_OVERVIEW:
|
||||
self.mainStack.setCurrentWidget(self.overviewPage)
|
||||
elif pageId == self.PAGE_CONTENTS:
|
||||
self.mainStack.setCurrentWidget(self.contentsPage)
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
##
|
||||
|
||||
def _saveSettings(self) -> None:
|
||||
"""Save the user GUI settings."""
|
||||
winWidth = CONFIG.rpxInt(self.width())
|
||||
winHeight = CONFIG.rpxInt(self.height())
|
||||
novelRoot = self.novelSelector.handle
|
||||
|
||||
logger.debug("Saving State: GuiNovelDetails")
|
||||
options = SHARED.project.options
|
||||
options.setValue("GuiNovelDetails", "winWidth", winWidth)
|
||||
options.setValue("GuiNovelDetails", "winHeight", winHeight)
|
||||
options.setValue("GuiNovelDetails", "novelRoot", novelRoot)
|
||||
|
||||
self.contentsPage.saveSettings()
|
||||
|
||||
return
|
||||
|
||||
# END Class GuiNovelDetails
|
||||
|
||||
|
||||
class _OverviewPage(NScrollablePage):
|
||||
|
||||
def __init__(self, parent: QWidget) -> None:
|
||||
super().__init__(parent=parent)
|
||||
|
||||
mPx = CONFIG.pxInt(8)
|
||||
sPx = CONFIG.pxInt(16)
|
||||
hPx = CONFIG.pxInt(24)
|
||||
vPx = CONFIG.pxInt(4)
|
||||
|
||||
# Project Info
|
||||
self.projLabel = NColourLabel(
|
||||
self.tr("Project"), SHARED.theme.helpText,
|
||||
parent=self, scale=NColourLabel.HEADER_SCALE
|
||||
)
|
||||
|
||||
self.projName = QLabel("", self)
|
||||
self.projWords = QLabel("", self)
|
||||
self.projNovels = QLabel("", self)
|
||||
self.projNotes = QLabel("", self)
|
||||
self.projRevisions = QLabel("", self)
|
||||
self.projEditTime = QLabel("", self)
|
||||
|
||||
self.projForm = QFormLayout()
|
||||
self.projForm.addRow("<b>{0}</b>".format(self.tr("Name")), self.projName)
|
||||
self.projForm.addRow("<b>{0}</b>".format(self.tr("Revisions")), self.projRevisions)
|
||||
self.projForm.addRow("<b>{0}</b>".format(self.tr("Editing Time")), self.projEditTime)
|
||||
self.projForm.addRow("<b>{0}</b>".format(self.tr("Word Count")), self.projWords)
|
||||
self.projForm.addRow("<b>\u2026 {0}</b>".format(self.tr("In Novels")), self.projNovels)
|
||||
self.projForm.addRow("<b>\u2026 {0}</b>".format(self.tr("In Notes ")), self.projNotes)
|
||||
self.projForm.setContentsMargins(mPx, 0, 0, 0)
|
||||
self.projForm.setHorizontalSpacing(hPx)
|
||||
self.projForm.setVerticalSpacing(vPx)
|
||||
|
||||
# Novel Info
|
||||
self.novelLabel = NColourLabel(
|
||||
self.tr("Selected Novel"), SHARED.theme.helpText,
|
||||
parent=self, scale=NColourLabel.HEADER_SCALE
|
||||
)
|
||||
|
||||
self.novelName = QLabel("", self)
|
||||
self.novelWords = QLabel("", self)
|
||||
self.novelChapters = QLabel("", self)
|
||||
self.novelScenes = QLabel("", self)
|
||||
|
||||
self.novelForm = QFormLayout()
|
||||
self.novelForm.addRow("<b>{0}</b>".format(self.tr("Name")), self.novelName)
|
||||
self.novelForm.addRow("<b>{0}</b>".format(self.tr("Word Count")), self.novelWords)
|
||||
self.novelForm.addRow("<b>{0}</b>".format(self.tr("Chapters")), self.novelChapters)
|
||||
self.novelForm.addRow("<b>{0}</b>".format(self.tr("Scenes")), self.novelScenes)
|
||||
self.novelForm.setContentsMargins(mPx, 0, 0, 0)
|
||||
self.novelForm.setHorizontalSpacing(hPx)
|
||||
self.novelForm.setVerticalSpacing(vPx)
|
||||
|
||||
# Assemble
|
||||
self.outerBox = QVBoxLayout()
|
||||
self.outerBox.addWidget(self.projLabel)
|
||||
self.outerBox.addLayout(self.projForm)
|
||||
self.outerBox.addWidget(self.novelLabel)
|
||||
self.outerBox.addLayout(self.novelForm)
|
||||
self.outerBox.setSpacing(sPx)
|
||||
self.outerBox.addStretch(1)
|
||||
|
||||
self.setCentralLayout(self.outerBox)
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Methods
|
||||
##
|
||||
|
||||
def updateProjectData(self) -> None:
|
||||
"""Load information about the project."""
|
||||
project = SHARED.project
|
||||
project.updateWordCounts()
|
||||
wcNovel, wcNotes = project.data.currCounts
|
||||
|
||||
self.projName.setText(project.data.name)
|
||||
self.projRevisions.setText(f"{project.data.saveCount:n}")
|
||||
self.projEditTime.setText(formatTime(project.currentEditTime))
|
||||
self.projWords.setText(f"{wcNovel + wcNotes:n}")
|
||||
self.projNovels.setText(f"{wcNovel:n}")
|
||||
self.projNotes.setText(f"{wcNotes:n}")
|
||||
return
|
||||
|
||||
##
|
||||
# Public Slots
|
||||
##
|
||||
|
||||
@pyqtSlot(str)
|
||||
def novelValueChanged(self, tHandle: str) -> None:
|
||||
"""Refresh the data for the selected novel."""
|
||||
project = SHARED.project
|
||||
if nwItem := project.tree[tHandle]:
|
||||
self.novelName.setText(nwItem.itemName)
|
||||
|
||||
nwCount = project.index.getNovelWordCount(rootHandle=tHandle)
|
||||
self.novelWords.setText(f"{nwCount:n}")
|
||||
|
||||
hCounts = project.index.getNovelTitleCounts(rootHandle=tHandle)
|
||||
self.novelChapters.setText(f"{hCounts[2]:n}")
|
||||
self.novelScenes.setText(f"{hCounts[3]:n}")
|
||||
|
||||
return
|
||||
|
||||
# END Class _OverviewPage
|
||||
|
||||
|
||||
class _ContentsPage(NFixedPage):
|
||||
|
||||
C_TITLE = 0
|
||||
C_WORDS = 1
|
||||
C_PAGES = 2
|
||||
C_PAGE = 3
|
||||
C_PROG = 4
|
||||
|
||||
def __init__(self, parent: QWidget) -> None:
|
||||
super().__init__(parent=parent)
|
||||
|
||||
self._data = []
|
||||
self._currentRoot = None
|
||||
|
||||
iPx = SHARED.theme.baseIconSize
|
||||
hPx = CONFIG.pxInt(12)
|
||||
vPx = CONFIG.pxInt(4)
|
||||
options = SHARED.project.options
|
||||
|
||||
# Title
|
||||
self.contentLabel = NColourLabel(
|
||||
self.tr("Table of Contents"), SHARED.theme.helpText,
|
||||
parent=self, scale=NColourLabel.HEADER_SCALE
|
||||
)
|
||||
|
||||
# Contents Tree
|
||||
self.tocTree = QTreeWidget(self)
|
||||
self.tocTree.setIconSize(QSize(iPx, iPx))
|
||||
self.tocTree.setIndentation(0)
|
||||
self.tocTree.setColumnCount(6)
|
||||
self.tocTree.setSelectionMode(QAbstractItemView.SelectionMode.NoSelection)
|
||||
self.tocTree.setHeaderLabels([
|
||||
self.tr("Title"),
|
||||
self.tr("Words"),
|
||||
self.tr("Pages"),
|
||||
self.tr("Page"),
|
||||
self.tr("Progress"),
|
||||
"",
|
||||
])
|
||||
|
||||
treeHeadItem = self.tocTree.headerItem()
|
||||
if treeHeadItem:
|
||||
treeHeadItem.setTextAlignment(self.C_WORDS, Qt.AlignmentFlag.AlignRight)
|
||||
treeHeadItem.setTextAlignment(self.C_PAGES, Qt.AlignmentFlag.AlignRight)
|
||||
treeHeadItem.setTextAlignment(self.C_PAGE, Qt.AlignmentFlag.AlignRight)
|
||||
treeHeadItem.setTextAlignment(self.C_PROG, Qt.AlignmentFlag.AlignRight)
|
||||
|
||||
treeHeader = self.tocTree.header()
|
||||
treeHeader.setStretchLastSection(True)
|
||||
treeHeader.setMinimumSectionSize(hPx)
|
||||
|
||||
wCol0 = CONFIG.pxInt(options.getInt("GuiNovelDetails", "widthCol0", 200))
|
||||
wCol1 = CONFIG.pxInt(options.getInt("GuiNovelDetails", "widthCol1", 60))
|
||||
wCol2 = CONFIG.pxInt(options.getInt("GuiNovelDetails", "widthCol2", 60))
|
||||
wCol3 = CONFIG.pxInt(options.getInt("GuiNovelDetails", "widthCol3", 60))
|
||||
wCol4 = CONFIG.pxInt(options.getInt("GuiNovelDetails", "widthCol4", 90))
|
||||
|
||||
self.tocTree.setColumnWidth(0, wCol0)
|
||||
self.tocTree.setColumnWidth(1, wCol1)
|
||||
self.tocTree.setColumnWidth(2, wCol2)
|
||||
self.tocTree.setColumnWidth(3, wCol3)
|
||||
self.tocTree.setColumnWidth(4, wCol4)
|
||||
self.tocTree.setColumnWidth(5, hPx)
|
||||
|
||||
# Options
|
||||
wordsPerPage = options.getInt("GuiNovelDetails", "wordsPerPage", 350)
|
||||
countFrom = options.getInt("GuiNovelDetails", "countFrom", 1)
|
||||
clearDouble = options.getBool("GuiNovelDetails", "clearDouble", True)
|
||||
|
||||
self.wpLabel = QLabel(self.tr("Words per page"))
|
||||
|
||||
self.wpValue = QSpinBox(self)
|
||||
self.wpValue.setMinimum(10)
|
||||
self.wpValue.setMaximum(1000)
|
||||
self.wpValue.setSingleStep(10)
|
||||
self.wpValue.setValue(wordsPerPage)
|
||||
self.wpValue.valueChanged.connect(self._populateTree)
|
||||
|
||||
self.poLabel = QLabel(self.tr("First page offset"))
|
||||
|
||||
self.poValue = QSpinBox(self)
|
||||
self.poValue.setMinimum(1)
|
||||
self.poValue.setMaximum(9999)
|
||||
self.poValue.setSingleStep(1)
|
||||
self.poValue.setValue(countFrom)
|
||||
self.poValue.valueChanged.connect(self._populateTree)
|
||||
|
||||
self.dblLabel = QLabel(self.tr("Chapters on odd pages"))
|
||||
|
||||
self.dblValue = NSwitch(self, 2*iPx, iPx)
|
||||
self.dblValue.setChecked(clearDouble)
|
||||
self.dblValue.clicked.connect(self._populateTree)
|
||||
|
||||
self.optionsBox = QGridLayout()
|
||||
self.optionsBox.addWidget(self.wpLabel, 0, 0)
|
||||
self.optionsBox.addWidget(self.wpValue, 0, 1)
|
||||
self.optionsBox.addWidget(self.dblLabel, 0, 3)
|
||||
self.optionsBox.addWidget(self.dblValue, 0, 4)
|
||||
self.optionsBox.addWidget(self.poLabel, 1, 0)
|
||||
self.optionsBox.addWidget(self.poValue, 1, 1)
|
||||
self.optionsBox.setHorizontalSpacing(hPx)
|
||||
self.optionsBox.setVerticalSpacing(vPx)
|
||||
self.optionsBox.setColumnStretch(2, 1)
|
||||
|
||||
# Assemble
|
||||
self.outerBox = QVBoxLayout()
|
||||
self.outerBox.addWidget(self.contentLabel)
|
||||
self.outerBox.addWidget(self.tocTree)
|
||||
self.outerBox.addLayout(self.optionsBox)
|
||||
|
||||
self.setCentralLayout(self.outerBox)
|
||||
|
||||
return
|
||||
|
||||
def saveSettings(self) -> None:
|
||||
"""Save the user GUI settings."""
|
||||
widthCol0 = CONFIG.rpxInt(self.tocTree.columnWidth(0))
|
||||
widthCol1 = CONFIG.rpxInt(self.tocTree.columnWidth(1))
|
||||
widthCol2 = CONFIG.rpxInt(self.tocTree.columnWidth(2))
|
||||
widthCol3 = CONFIG.rpxInt(self.tocTree.columnWidth(3))
|
||||
widthCol4 = CONFIG.rpxInt(self.tocTree.columnWidth(4))
|
||||
|
||||
options = SHARED.project.options
|
||||
options.setValue("GuiNovelDetails", "widthCol0", widthCol0)
|
||||
options.setValue("GuiNovelDetails", "widthCol1", widthCol1)
|
||||
options.setValue("GuiNovelDetails", "widthCol2", widthCol2)
|
||||
options.setValue("GuiNovelDetails", "widthCol3", widthCol3)
|
||||
options.setValue("GuiNovelDetails", "widthCol4", widthCol4)
|
||||
options.setValue("GuiNovelDetails", "wordsPerPage", self.wpValue.value())
|
||||
options.setValue("GuiNovelDetails", "countFrom", self.poValue.value())
|
||||
options.setValue("GuiNovelDetails", "clearDouble", self.dblValue.isChecked())
|
||||
return
|
||||
|
||||
##
|
||||
# Public Slots
|
||||
##
|
||||
|
||||
@pyqtSlot(str)
|
||||
def novelValueChanged(self, tHandle: str) -> None:
|
||||
"""Refresh the tree with another root item."""
|
||||
if tHandle != self._currentRoot:
|
||||
self._prepareData(tHandle)
|
||||
self._populateTree()
|
||||
self._currentRoot = tHandle
|
||||
return
|
||||
|
||||
##
|
||||
# Private Slots
|
||||
##
|
||||
|
||||
@pyqtSlot()
|
||||
def _populateTree(self) -> None:
|
||||
"""Set the content of the chapter/page tree."""
|
||||
dblPages = self.dblValue.isChecked()
|
||||
wpPage = self.wpValue.value()
|
||||
fstPage = self.poValue.value() - 1
|
||||
|
||||
pTotal = 0
|
||||
tPages = 1
|
||||
|
||||
theList = []
|
||||
for _, tLevel, tTitle, wCount in self._data:
|
||||
pCount = math.ceil(wCount/wpPage)
|
||||
if dblPages:
|
||||
pCount += pCount%2
|
||||
|
||||
pTotal += pCount
|
||||
theList.append((tLevel, tTitle, wCount, pCount))
|
||||
|
||||
pMax = pTotal - fstPage
|
||||
|
||||
self.tocTree.clear()
|
||||
for tLevel, tTitle, wCount, pCount in theList:
|
||||
newItem = QTreeWidgetItem()
|
||||
|
||||
if tPages <= fstPage:
|
||||
progPage = numberToRoman(tPages, True)
|
||||
progText = ""
|
||||
else:
|
||||
cPage = tPages - fstPage
|
||||
pgProg = 100.0*(cPage - 1)/pMax if pMax > 0 else 0.0
|
||||
progPage = f"{cPage:n}"
|
||||
progText = f"{pgProg:.1f}{nwUnicode.U_THSP}%"
|
||||
|
||||
hDec = SHARED.theme.getHeaderDecoration(tLevel)
|
||||
if tTitle.strip() == "":
|
||||
tTitle = self.tr("Untitled")
|
||||
|
||||
newItem.setData(self.C_TITLE, Qt.DecorationRole, hDec)
|
||||
newItem.setText(self.C_TITLE, tTitle)
|
||||
newItem.setText(self.C_WORDS, f"{wCount:n}")
|
||||
newItem.setText(self.C_PAGES, f"{pCount:n}")
|
||||
newItem.setText(self.C_PAGE, progPage)
|
||||
newItem.setText(self.C_PROG, progText)
|
||||
|
||||
newItem.setTextAlignment(self.C_WORDS, Qt.AlignRight)
|
||||
newItem.setTextAlignment(self.C_PAGES, Qt.AlignRight)
|
||||
newItem.setTextAlignment(self.C_PAGE, Qt.AlignRight)
|
||||
newItem.setTextAlignment(self.C_PROG, Qt.AlignRight)
|
||||
|
||||
# Make pages and titles/partitions stand out
|
||||
if tLevel < 2:
|
||||
bFont = newItem.font(self.C_TITLE)
|
||||
if tLevel == 0:
|
||||
bFont.setItalic(True)
|
||||
else:
|
||||
bFont.setBold(True)
|
||||
bFont.setUnderline(True)
|
||||
newItem.setFont(self.C_TITLE, bFont)
|
||||
|
||||
tPages += pCount
|
||||
|
||||
self.tocTree.addTopLevelItem(newItem)
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
##
|
||||
|
||||
def _prepareData(self, rootHandle: str | None) -> None:
|
||||
"""Extract the information from the project index."""
|
||||
logger.debug("Populating ToC from handle '%s'", rootHandle)
|
||||
self._data = SHARED.project.index.getTableOfContents(rootHandle, 2)
|
||||
self._data.append(("", 0, self.tr("END"), 0))
|
||||
return
|
||||
|
||||
# END Class _ContentsPage
|
||||
@@ -25,7 +25,6 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
|
||||
@@ -43,14 +42,11 @@ from PyQt5.QtWidgets import (
|
||||
|
||||
from novelwriter import CONFIG, SHARED, __version__, __date__
|
||||
from novelwriter.enum import nwItemClass
|
||||
from novelwriter.common import formatInt, makeFileNameSafe
|
||||
from novelwriter.common import formatInt, formatVersion, makeFileNameSafe
|
||||
from novelwriter.constants import nwUnicode
|
||||
from novelwriter.core.coretools import ProjectBuilder
|
||||
from novelwriter.extensions.switch import NSwitch
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from novelwriter.guimain import GuiMain
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -58,8 +54,8 @@ class GuiWelcome(QDialog):
|
||||
|
||||
openProjectRequest = pyqtSignal(Path)
|
||||
|
||||
def __init__(self, mainGui: GuiMain) -> None:
|
||||
super().__init__(parent=mainGui)
|
||||
def __init__(self, parent: QWidget) -> None:
|
||||
super().__init__(parent=parent)
|
||||
|
||||
logger.debug("Create: GuiWelcome")
|
||||
self.setObjectName("GuiWelcome")
|
||||
@@ -91,7 +87,8 @@ class GuiWelcome(QDialog):
|
||||
self.nwLabel.setPixmap(self.nwImage)
|
||||
|
||||
self.nwInfo = QLabel(self.tr("Version {0} {1} Released on {2}").format(
|
||||
__version__, nwUnicode.U_ENDASH, datetime.strptime(__date__, "%Y-%m-%d").strftime("%x")
|
||||
formatVersion(__version__), nwUnicode.U_ENDASH,
|
||||
datetime.strptime(__date__, "%Y-%m-%d").strftime("%x")
|
||||
))
|
||||
|
||||
self.tabOpen = _OpenProjectPage(self)
|
||||
@@ -343,7 +340,7 @@ class _ProjectListItem(QStyledItemDelegate):
|
||||
|
||||
self._dFont = qApp.font()
|
||||
self._dFont.setPointSizeF(fPt)
|
||||
self._dPen = QPen(QColor(*SHARED.theme.helpText))
|
||||
self._dPen = QPen(SHARED.theme.helpText)
|
||||
|
||||
self._icon = SHARED.theme.getPixmap("proj_nwx", (iPx, iPx))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user