Merge branch 'theme_fixes' of https://github.com/HeyMyian/novelWriter into theme_fixes
This commit is contained in:
+1
-1
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
Configuration file for the Sphinx documentation builder.
|
||||
Documentation: http://www.sphinx-doc.org/en/master/config
|
||||
"""
|
||||
""" # noqa
|
||||
|
||||
# -- Imports -----------------------------------------------------------------
|
||||
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ not yet have a qtbase_xx.qm file shipped with Qt.
|
||||
|
||||
If a qtbase_xx.qm file already exists, do not add a translation for the
|
||||
entries generated from this file.
|
||||
"""
|
||||
""" # noqa
|
||||
|
||||
from PyQt6.QtCore import QT_TRANSLATE_NOOP
|
||||
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
"""
|
||||
novelWriter – Start Script
|
||||
==========================
|
||||
"""
|
||||
""" # noqa
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import getopt
|
||||
|
||||
+14
-17
@@ -20,7 +20,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
@@ -135,7 +135,7 @@ def checkPath(value: Any, default: Path) -> Path:
|
||||
|
||||
def isHandle(value: Any) -> TypeGuard[str]:
|
||||
"""Check if a string is a valid novelWriter handle.
|
||||
Note: This is case sensitive. Must be lower case!
|
||||
Note: This is case sensitive. Must be lower case.
|
||||
"""
|
||||
if not isinstance(value, str):
|
||||
return False
|
||||
@@ -219,7 +219,7 @@ def firstFloat(*args: Any) -> float:
|
||||
##
|
||||
|
||||
def formatInt(value: int) -> str:
|
||||
"""Formats an integer with k, M, G etc."""
|
||||
"""Format an integer with k, M, G etc."""
|
||||
if not isinstance(value, int):
|
||||
return "ERR"
|
||||
|
||||
@@ -464,21 +464,21 @@ def fontMatcher(font: QFont) -> QFont:
|
||||
|
||||
|
||||
def qtLambda(func: Callable, *args: Any, **kwargs: Any) -> Callable:
|
||||
"""A replacement for Python lambdas that works for Qt slots."""
|
||||
"""A replacement for Python lambdas that works for Qt slots.""" # noqa: D401
|
||||
def wrapper(*a_: Any) -> None:
|
||||
func(*args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
|
||||
def qtAddAction(parent: QWidget, label: str) -> QAction:
|
||||
"""Helper to add action to widget and always return the action."""
|
||||
"""Helper to add action to widget and always return the action.""" # noqa: D401
|
||||
action = QAction(label, parent)
|
||||
parent.addAction(action)
|
||||
return action
|
||||
|
||||
|
||||
def qtAddMenu(parent: QMenuBar | QMenu, label: str) -> QMenu:
|
||||
"""Helper to add menu to menu and always return the menu."""
|
||||
"""Helper to add menu to menu and always return the menu.""" # noqa: D401
|
||||
menu = QMenu(label, parent)
|
||||
parent.addMenu(menu)
|
||||
return menu
|
||||
@@ -487,7 +487,6 @@ def qtAddMenu(parent: QMenuBar | QMenu, label: str) -> QMenu:
|
||||
def encodeMimeHandles(mimeData: QMimeData, handles: list[str]) -> None:
|
||||
"""Encode handles into a mime data object."""
|
||||
mimeData.setData(nwConst.MIME_HANDLE, b"|".join(h.encode() for h in handles))
|
||||
return
|
||||
|
||||
|
||||
def decodeMimeHandles(mimeData: QMimeData) -> list[str]:
|
||||
@@ -536,7 +535,7 @@ def jsonEncode(data: dict | list | tuple, n: int = 0, nmax: int = 0) -> str:
|
||||
elif first in ("{", "["):
|
||||
n += 1
|
||||
indent = "\n"+" "*n
|
||||
if n > nmax and nmax > 0:
|
||||
if n > nmax > 0:
|
||||
buffer.append(chunk)
|
||||
else:
|
||||
buffer.append(chunk[0] + indent + chunk[1:])
|
||||
@@ -544,13 +543,13 @@ def jsonEncode(data: dict | list | tuple, n: int = 0, nmax: int = 0) -> str:
|
||||
elif first in ("}", "]"):
|
||||
n -= 1
|
||||
indent = "\n"+" "*n
|
||||
if n >= nmax and nmax > 0:
|
||||
if n >= nmax > 0:
|
||||
buffer.append(chunk)
|
||||
else:
|
||||
buffer.append(indent + chunk)
|
||||
|
||||
elif first == ",":
|
||||
if n > nmax and nmax > 0:
|
||||
if n > nmax > 0:
|
||||
buffer.append(chunk)
|
||||
else:
|
||||
buffer.append(chunk[0] + indent + chunk[1:].lstrip())
|
||||
@@ -568,7 +567,7 @@ def jsonEncode(data: dict | list | tuple, n: int = 0, nmax: int = 0) -> str:
|
||||
def xmlIndent(xml: ET.Element | ET.ElementTree) -> None:
|
||||
"""A modified version of the XML indent function in the standard
|
||||
library. It behaves more closely to how the one from lxml does.
|
||||
"""
|
||||
""" # noqa: D401
|
||||
tree = xml.getroot() if isinstance(xml, ET.ElementTree) else xml
|
||||
if not isinstance(tree, ET.Element):
|
||||
return
|
||||
@@ -598,8 +597,6 @@ def xmlIndent(xml: ET.Element | ET.ElementTree) -> None:
|
||||
if last is not None:
|
||||
last.tail = indentations[level]
|
||||
|
||||
return
|
||||
|
||||
if len(tree):
|
||||
indentChildren(tree, 0)
|
||||
tree.tail = "\n"
|
||||
@@ -614,7 +611,7 @@ def xmlElement(
|
||||
attrib: dict | None = None,
|
||||
tail: str | None = None,
|
||||
) -> ET.Element:
|
||||
"""A custom implementation of Element with more arguments."""
|
||||
"""A custom implementation of Element with more arguments.""" # noqa: D401
|
||||
xSub = ET.Element(tag, attrib=attrib or {})
|
||||
if text is not None:
|
||||
if isinstance(text, bool):
|
||||
@@ -634,7 +631,7 @@ def xmlSubElem(
|
||||
attrib: dict | None = None,
|
||||
tail: str | None = None,
|
||||
) -> ET.Element:
|
||||
"""A custom implementation of SubElement with more arguments."""
|
||||
"""A custom implementation of SubElement with more arguments.""" # noqa: D401
|
||||
xSub = ET.SubElement(parent, tag, attrib=attrib or {})
|
||||
if text is not None:
|
||||
if isinstance(text, bool):
|
||||
@@ -665,6 +662,7 @@ def readTextFile(path: str | Path) -> str:
|
||||
|
||||
def makeFileNameSafe(text: str) -> str:
|
||||
"""Return a filename-safe string.
|
||||
|
||||
See: https://unicode.org/reports/tr15/#Norm_Forms
|
||||
"""
|
||||
text = unicodedata.normalize("NFKC", text).strip()
|
||||
@@ -697,7 +695,7 @@ _T_Enum = TypeVar("_T_Enum", bound=Enum)
|
||||
|
||||
|
||||
class NWConfigParser(ConfigParser):
|
||||
"""Common: Adapted Config Parser
|
||||
"""Common: Adapted Config Parser.
|
||||
|
||||
This is a subclass of the standard config parser that adds type safe
|
||||
helper functions, and support for lists. It also turns off
|
||||
@@ -706,7 +704,6 @@ class NWConfigParser(ConfigParser):
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__(interpolation=None)
|
||||
return
|
||||
|
||||
def rdStr(self, section: str, option: str, default: str) -> str:
|
||||
"""Read string value."""
|
||||
|
||||
+11
-26
@@ -22,7 +22,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
@@ -63,6 +63,13 @@ DEF_TREECOL = "theme"
|
||||
|
||||
|
||||
class Config:
|
||||
"""User Config.
|
||||
|
||||
The main user config. The state of the config is stored in the
|
||||
novelwriter.conf file between sessions. Most of the settings can be
|
||||
modified by the user in the Preferences dialog, but some just record
|
||||
various states of the GUI.
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
"_appPath", "_appRoot", "_backPath", "_backupPath", "_confPath", "_dLocale", "_dShortDate",
|
||||
@@ -300,8 +307,6 @@ class Config:
|
||||
# Packages
|
||||
self.hasEnchant = False # The pyenchant package
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Properties
|
||||
##
|
||||
@@ -350,7 +355,6 @@ class Config:
|
||||
def setLastAuthor(self, value: str) -> None:
|
||||
"""Set tle last used author name."""
|
||||
self._lastAuthor = simplified(value)
|
||||
return
|
||||
|
||||
def setMainWinSize(self, width: int, height: int) -> None:
|
||||
"""Set the size of the main window, but only if the change is
|
||||
@@ -362,17 +366,14 @@ class Config:
|
||||
self.mainWinSize[0] = width
|
||||
if abs(self.mainWinSize[1] - height) > 5:
|
||||
self.mainWinSize[1] = height
|
||||
return
|
||||
|
||||
def setWelcomeWinSize(self, width: int, height: int) -> None:
|
||||
"""Set the size of the Preferences dialog window."""
|
||||
self.welcomeWinSize = [width, height]
|
||||
return
|
||||
|
||||
def setPreferencesWinSize(self, width: int, height: int) -> None:
|
||||
"""Set the size of the Preferences dialog window."""
|
||||
self.prefsWinSize = [width, height]
|
||||
return
|
||||
|
||||
def setLastPath(self, key: str, path: str | Path) -> None:
|
||||
"""Set the last used path. Only the folder is saved, so if the
|
||||
@@ -384,12 +385,10 @@ class Config:
|
||||
path = path.parent
|
||||
if path.is_dir():
|
||||
self._recentPaths.setPath(key, path)
|
||||
return
|
||||
|
||||
def setBackupPath(self, path: Path | str) -> None:
|
||||
"""Set the current backup path."""
|
||||
self._backupPath = checkPath(path, self._backPath)
|
||||
return
|
||||
|
||||
def setGuiFont(self, value: QFont | str | None) -> None:
|
||||
"""Update the GUI's font style from settings."""
|
||||
@@ -410,7 +409,6 @@ class Config:
|
||||
self.guiFont = fontMatcher(font)
|
||||
logger.debug("Main font set to: %s", describeFont(font))
|
||||
QApplication.setFont(self.guiFont)
|
||||
return
|
||||
|
||||
def setTextFont(self, value: QFont | str | None) -> None:
|
||||
"""Set the text font if it exists. If it doesn't, or is None,
|
||||
@@ -436,14 +434,13 @@ class Config:
|
||||
font = QFontDatabase.systemFont(QFontDatabase.SystemFont.GeneralFont)
|
||||
self.textFont = fontMatcher(font)
|
||||
logger.debug("Text font set to: %s", describeFont(self.textFont))
|
||||
return
|
||||
|
||||
##
|
||||
# Methods
|
||||
##
|
||||
|
||||
def homePath(self) -> Path:
|
||||
"""The user's home folder."""
|
||||
"""Return the user's home folder."""
|
||||
return self._homePath
|
||||
|
||||
def dataPath(self, target: str | None = None) -> Path:
|
||||
@@ -525,7 +522,6 @@ class Config:
|
||||
"""Send a message to the splash screen."""
|
||||
if self._splash:
|
||||
self._splash.showStatus(message)
|
||||
return
|
||||
|
||||
##
|
||||
# Config Actions
|
||||
@@ -568,8 +564,6 @@ class Config:
|
||||
|
||||
logger.debug("Config instance initialised")
|
||||
|
||||
return
|
||||
|
||||
def initLocalisation(self, nwApp: QApplication) -> None:
|
||||
"""Initialise the localisation of the GUI."""
|
||||
self.splashMessage("Loading localisation ...")
|
||||
@@ -597,8 +591,6 @@ class Config:
|
||||
nwApp.installTranslator(qTrans)
|
||||
self._qtTrans[lngFile] = qTrans
|
||||
|
||||
return
|
||||
|
||||
def loadConfig(self, splash: NSplashScreen | None = None) -> bool:
|
||||
"""Load preferences from file and replace default settings."""
|
||||
self._splash = splash
|
||||
@@ -872,7 +864,6 @@ class Config:
|
||||
def finishStartup(self) -> None:
|
||||
"""Call after startup is complete."""
|
||||
self._splash = None
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
@@ -894,7 +885,6 @@ class Config:
|
||||
else:
|
||||
self.hasEnchant = True
|
||||
logger.debug("Checking package 'pyenchant': OK")
|
||||
return
|
||||
|
||||
def _prepareFont(self, font: QFont, kind: str) -> None:
|
||||
"""Check Unicode availability in font. This also initialises any
|
||||
@@ -905,16 +895,15 @@ class Config:
|
||||
for char in nwUnicode.UI_SYMBOLS:
|
||||
if not metrics.inFont(char): # type: ignore
|
||||
logger.warning("No glyph U+%04x in font", ord(char)) # pragma: no cover
|
||||
return
|
||||
|
||||
|
||||
class RecentProjects:
|
||||
"""A record of recently opened projects."""
|
||||
|
||||
def __init__(self, config: Config) -> None:
|
||||
self._conf = config
|
||||
self._data: dict[str, dict[str, str | int]] = {}
|
||||
self._map: dict[str, str] = {}
|
||||
return
|
||||
|
||||
def loadCache(self) -> bool:
|
||||
"""Load the cache file for recent projects."""
|
||||
@@ -976,14 +965,12 @@ class RecentProjects:
|
||||
self.saveCache()
|
||||
except Exception:
|
||||
pass
|
||||
return
|
||||
|
||||
def remove(self, path: str | Path) -> None:
|
||||
"""Try to remove a path from the recent projects cache."""
|
||||
if self._data.pop(str(path), None) is not None:
|
||||
logger.debug("Removed recent: %s", path)
|
||||
self.saveCache()
|
||||
return
|
||||
|
||||
def _setEntry(
|
||||
self, puuid: str, path: str, title: str, words: int, chars: int, saved: int
|
||||
@@ -998,24 +985,22 @@ class RecentProjects:
|
||||
}
|
||||
if puuid:
|
||||
self._map[puuid] = path
|
||||
return
|
||||
|
||||
|
||||
class RecentPaths:
|
||||
"""A record of recently used file paths."""
|
||||
|
||||
KEYS: Final[list[str]] = ["default", "project", "import", "outline", "stats"]
|
||||
|
||||
def __init__(self, config: Config) -> None:
|
||||
self._conf = config
|
||||
self._data = {}
|
||||
return
|
||||
|
||||
def setPath(self, key: str, path: Path | str) -> None:
|
||||
"""Set a path for a given key, and save the cache."""
|
||||
if key in self.KEYS:
|
||||
self._data[key] = str(path)
|
||||
self.saveCache()
|
||||
return
|
||||
|
||||
def getPath(self, key: str) -> str | None:
|
||||
"""Get a path for a given key, or return None."""
|
||||
|
||||
@@ -20,7 +20,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Final
|
||||
@@ -34,16 +34,17 @@ from novelwriter.enum import (
|
||||
|
||||
|
||||
def trConst(text: str) -> str:
|
||||
"""Wrapper function for locally translating constants."""
|
||||
"""Translate a constant."""
|
||||
return QCoreApplication.translate("Constant", text)
|
||||
|
||||
|
||||
def trStats(text: str) -> str:
|
||||
"""Wrapper function for locally translating stats constants."""
|
||||
"""Translate a stats constants."""
|
||||
return QCoreApplication.translate("Stats", text)
|
||||
|
||||
|
||||
class nwConst:
|
||||
"""Various Constants."""
|
||||
|
||||
# Date and Time Formats
|
||||
FMT_TSTAMP = "%Y-%m-%d %H:%M:%S" # Default format
|
||||
@@ -70,6 +71,7 @@ class nwConst:
|
||||
|
||||
|
||||
class nwRegEx:
|
||||
"""Common RegExes."""
|
||||
|
||||
URL = r"https?://(?:www\.|(?!www))[\w/()@:%_\+-.~#?&=]+"
|
||||
WORDS = r"\b[^\s\-\+\/–—\[\]:]+\b"
|
||||
@@ -83,6 +85,7 @@ class nwRegEx:
|
||||
|
||||
|
||||
class nwShortcode:
|
||||
"""Document ShortCodes."""
|
||||
|
||||
BOLD_O = "[b]"
|
||||
BOLD_C = "[/b]"
|
||||
@@ -112,6 +115,7 @@ class nwShortcode:
|
||||
|
||||
|
||||
class nwStyles:
|
||||
"""Style Settings for Headings."""
|
||||
|
||||
H_VALID = ("H0", "H1", "H2", "H3", "H4")
|
||||
H_LEVEL: Final[dict[str, int]] = {"H0": 0, "H1": 1, "H2": 2, "H3": 3, "H4": 4}
|
||||
@@ -143,6 +147,7 @@ class nwStyles:
|
||||
|
||||
|
||||
class nwFiles:
|
||||
"""novelWriter Files."""
|
||||
|
||||
# Config Files
|
||||
CONF_FILE = "novelwriter.conf"
|
||||
@@ -163,6 +168,7 @@ class nwFiles:
|
||||
|
||||
|
||||
class nwKeyWords:
|
||||
"""Meta Data KeyWord Constants."""
|
||||
|
||||
TAG_KEY = "@tag"
|
||||
POV_KEY = "@pov"
|
||||
@@ -210,6 +216,7 @@ class nwKeyWords:
|
||||
|
||||
|
||||
class nwLists:
|
||||
"""Various Lists."""
|
||||
|
||||
USER_CLASSES: Final[list[nwItemClass]] = [
|
||||
nwItemClass.CHARACTER,
|
||||
@@ -223,6 +230,7 @@ class nwLists:
|
||||
|
||||
|
||||
class nwStats:
|
||||
"""Text Statistics."""
|
||||
|
||||
CHARS = "allChars"
|
||||
CHARS_TEXT = "textChars"
|
||||
@@ -246,6 +254,7 @@ class nwStats:
|
||||
|
||||
|
||||
class nwLabels:
|
||||
"""Various Common GUI Labels."""
|
||||
|
||||
CLASS_NAME: Final[dict[nwItemClass, str]] = {
|
||||
nwItemClass.NO_CLASS: QT_TRANSLATE_NOOP("Constant", "None"),
|
||||
@@ -472,6 +481,7 @@ class nwLabels:
|
||||
|
||||
|
||||
class nwHeadFmt:
|
||||
"""Manuscript Header Formats."""
|
||||
|
||||
BR = "{BR}"
|
||||
TITLE = "{Title}"
|
||||
@@ -498,8 +508,10 @@ class nwHeadFmt:
|
||||
|
||||
class nwQuotes:
|
||||
"""Allowed quotation marks.
|
||||
|
||||
Source: https://en.wikipedia.org/wiki/Quotation_mark
|
||||
"""
|
||||
|
||||
SYMBOLS: Final[dict[str, str]] = {
|
||||
"\u0027": QT_TRANSLATE_NOOP("Constant", "Straight single quotation mark"),
|
||||
"\u0022": QT_TRANSLATE_NOOP("Constant", "Straight double quotation mark"),
|
||||
@@ -541,6 +553,7 @@ class nwQuotes:
|
||||
|
||||
class nwUnicode:
|
||||
"""Supported unicode character constants and their HTML equivalents."""
|
||||
|
||||
# Unicode Constants
|
||||
# =================
|
||||
|
||||
@@ -672,6 +685,7 @@ class nwUnicode:
|
||||
|
||||
|
||||
class nwHtmlUnicode:
|
||||
"""Unicode to HTML Map."""
|
||||
|
||||
U_TO_H: Final[dict[str, str]] = {
|
||||
# Quotes
|
||||
|
||||
@@ -21,7 +21,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
@@ -211,7 +211,7 @@ class FilterMode(Enum):
|
||||
|
||||
|
||||
class BuildSettings:
|
||||
"""Core: Build Settings Class
|
||||
"""Core: Build Settings Class.
|
||||
|
||||
This class manages the build settings for a Manuscript build job.
|
||||
The settings can be packed/unpacked to/from a dictionary for JSON.
|
||||
@@ -229,7 +229,6 @@ class BuildSettings:
|
||||
self._included = set()
|
||||
self._settings = {k: v[1] for k, v in SETTINGS_TEMPLATE.items()}
|
||||
self._changed = False
|
||||
return
|
||||
|
||||
@classmethod
|
||||
def fromDict(cls, data: dict) -> BuildSettings:
|
||||
@@ -315,7 +314,6 @@ class BuildSettings:
|
||||
def setName(self, name: str) -> None:
|
||||
"""Set the build setting display name."""
|
||||
self._name = str(name)
|
||||
return
|
||||
|
||||
def setBuildID(self, value: str | uuid.UUID) -> None:
|
||||
"""Set a UUID build ID."""
|
||||
@@ -324,13 +322,11 @@ class BuildSettings:
|
||||
self._uuid = str(uuid.uuid4())
|
||||
elif value != self._uuid:
|
||||
self._uuid = value
|
||||
return
|
||||
|
||||
def setOrder(self, value: int) -> None:
|
||||
"""Set the build order."""
|
||||
if isinstance(value, int):
|
||||
self._order = value
|
||||
return
|
||||
|
||||
def setLastBuildPath(self, path: Path | str | None) -> None:
|
||||
"""Set the last used build path."""
|
||||
@@ -341,41 +337,35 @@ class BuildSettings:
|
||||
else:
|
||||
self._path = CONFIG.homePath()
|
||||
self._changed = True
|
||||
return
|
||||
|
||||
def setLastBuildName(self, name: str) -> None:
|
||||
"""Set the last used build name."""
|
||||
self._build = str(name).strip()
|
||||
self._changed = True
|
||||
return
|
||||
|
||||
def setLastFormat(self, value: nwBuildFmt) -> None:
|
||||
"""Set the last used build format."""
|
||||
if isinstance(value, nwBuildFmt):
|
||||
self._format = value
|
||||
self._changed = True
|
||||
return
|
||||
|
||||
def setFiltered(self, tHandle: str) -> None:
|
||||
"""Set an item as filtered."""
|
||||
self._excluded.discard(tHandle)
|
||||
self._included.discard(tHandle)
|
||||
self._changed = True
|
||||
return
|
||||
|
||||
def setIncluded(self, tHandle: str) -> None:
|
||||
"""Set an item as explicitly included."""
|
||||
self._excluded.discard(tHandle)
|
||||
self._included.add(tHandle)
|
||||
self._changed = True
|
||||
return
|
||||
|
||||
def setExcluded(self, tHandle: str) -> None:
|
||||
"""Set an item as explicitly excluded."""
|
||||
self._excluded.add(tHandle)
|
||||
self._included.discard(tHandle)
|
||||
self._changed = True
|
||||
return
|
||||
|
||||
def setAllowRoot(self, tHandle: str, state: bool) -> None:
|
||||
"""Set a specific root folder as allowed or not."""
|
||||
@@ -385,14 +375,12 @@ class BuildSettings:
|
||||
elif state is False:
|
||||
self._skipRoot.add(tHandle)
|
||||
self._changed = True
|
||||
return
|
||||
|
||||
def setValue(self, key: str, value: T_BuildValue) -> None:
|
||||
"""Set a specific value for a build setting."""
|
||||
if (d := SETTINGS_TEMPLATE.get(key)) and len(d) == 2 and isinstance(value, d[0]):
|
||||
self._changed |= (value != self._settings[key])
|
||||
self._settings[key] = value
|
||||
return
|
||||
|
||||
##
|
||||
# Methods
|
||||
@@ -463,7 +451,6 @@ class BuildSettings:
|
||||
called when the changes have been safely saved or passed on.
|
||||
"""
|
||||
self._changed = False
|
||||
return
|
||||
|
||||
def pack(self) -> dict:
|
||||
"""Pack all content into a JSON compatible dictionary."""
|
||||
@@ -516,8 +503,6 @@ class BuildSettings:
|
||||
|
||||
self._changed = False
|
||||
|
||||
return
|
||||
|
||||
@classmethod
|
||||
def duplicate(cls, source: BuildSettings) -> BuildSettings:
|
||||
"""Make a copy of another build."""
|
||||
@@ -529,7 +514,7 @@ class BuildSettings:
|
||||
|
||||
|
||||
class BuildCollection:
|
||||
"""Core: Build Collection Class
|
||||
"""Core: Build Collection Class.
|
||||
|
||||
This object holds all the build setting objects defined by the given
|
||||
project. The build settings are saved as a single JSON file in the
|
||||
@@ -542,7 +527,6 @@ class BuildCollection:
|
||||
self._defaultBuild = ""
|
||||
self._builds: dict[str, BuildSettings] = {}
|
||||
self._loadCollection()
|
||||
return
|
||||
|
||||
def __len__(self) -> int:
|
||||
"""Return the number of builds."""
|
||||
@@ -581,21 +565,18 @@ class BuildCollection:
|
||||
build.setOrder(i)
|
||||
self._lastBuild = lastBuild
|
||||
self._saveCollection()
|
||||
return
|
||||
|
||||
def setDefaultBuild(self, buildID: str) -> None:
|
||||
"""Set the default build id."""
|
||||
if buildID != self._defaultBuild:
|
||||
self._defaultBuild = buildID
|
||||
self._saveCollection()
|
||||
return
|
||||
|
||||
def setBuild(self, build: BuildSettings) -> None:
|
||||
"""Set build settings data in the collection."""
|
||||
if isinstance(build, BuildSettings):
|
||||
self._builds[build.buildID] = build
|
||||
self._saveCollection()
|
||||
return
|
||||
|
||||
##
|
||||
# Methods
|
||||
@@ -605,7 +586,6 @@ class BuildCollection:
|
||||
"""Remove a build from the collection."""
|
||||
self._builds.pop(buildID, None)
|
||||
self._saveCollection()
|
||||
return
|
||||
|
||||
def builds(self) -> Iterable[tuple[str, str]]:
|
||||
"""Iterate over all available builds."""
|
||||
|
||||
@@ -23,7 +23,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
@@ -52,7 +52,9 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DocMerger:
|
||||
"""Document tool for merging a set of documents into a single new
|
||||
"""Tool: Merge Documents.
|
||||
|
||||
Document tool for merging a set of documents into a single new
|
||||
document. The parameters are defined by the user using the
|
||||
GuiDocMerge dialog.
|
||||
"""
|
||||
@@ -62,7 +64,6 @@ class DocMerger:
|
||||
self._error = ""
|
||||
self._target = None
|
||||
self._text = []
|
||||
return
|
||||
|
||||
@property
|
||||
def targetHandle(self) -> str | None:
|
||||
@@ -85,7 +86,6 @@ class DocMerger:
|
||||
"""
|
||||
self._target = self._project.tree[tHandle]
|
||||
self._text = []
|
||||
return
|
||||
|
||||
def newTargetDoc(self, sHandle: str, label: str) -> None:
|
||||
"""Create a brand new target document based on a source handle
|
||||
@@ -101,7 +101,6 @@ class DocMerger:
|
||||
nwItem.notifyToRefresh()
|
||||
self._target = nwItem
|
||||
self._text = []
|
||||
return
|
||||
|
||||
def appendText(self, sHandle: str, addComment: bool, cmtPrefix: str) -> None:
|
||||
"""Append text from an existing document to the text buffer."""
|
||||
@@ -112,7 +111,6 @@ class DocMerger:
|
||||
status, _ = item.getImportStatus()
|
||||
text = f"% {cmtPrefix} {info}: {item.itemName} [{status}]\n\n{text}"
|
||||
self._text.append(text)
|
||||
return
|
||||
|
||||
def writeTargetDoc(self) -> bool:
|
||||
"""Write the accumulated text into the designated target
|
||||
@@ -158,10 +156,7 @@ class DocSplitter:
|
||||
self._srcHandle = sHandle
|
||||
self._srcItem = srcItem
|
||||
|
||||
return
|
||||
|
||||
def __len__(self) -> int:
|
||||
"""The length of the split job."""
|
||||
return len(self._rawData)
|
||||
|
||||
##
|
||||
@@ -178,7 +173,6 @@ class DocSplitter:
|
||||
"""
|
||||
self._parHandle = pHandle
|
||||
self._inFolder = False
|
||||
return
|
||||
|
||||
def newParentFolder(self, pHandle: str, folderLabel: str) -> None:
|
||||
"""Create a new folder that will be the top level parent item
|
||||
@@ -192,7 +186,6 @@ class DocSplitter:
|
||||
nwItem.notifyToRefresh()
|
||||
self._parHandle = nHandle
|
||||
self._inFolder = True
|
||||
return
|
||||
|
||||
def splitDocument(self, splitData: list, splitText: list[str]) -> None:
|
||||
"""Loop through the split data record and perform the split job
|
||||
@@ -204,12 +197,9 @@ class DocSplitter:
|
||||
chunk = buffer[lineNo:]
|
||||
buffer = buffer[:lineNo]
|
||||
self._rawData.insert(0, (chunk, hLevel, hLabel))
|
||||
return
|
||||
|
||||
def writeDocuments(self, docHierarchy: bool) -> Iterable[bool]:
|
||||
"""An iterator that will write each document in the buffer, and
|
||||
return its new handle, parent handle, and sibling handle.
|
||||
"""
|
||||
"""Write each document in the buffer and yield if successful."""
|
||||
if self._srcHandle and self._srcItem and self._parHandle:
|
||||
pHandle = self._parHandle
|
||||
hHandle = [self._parHandle, None, None, None, None]
|
||||
@@ -260,7 +250,6 @@ class DocDuplicator:
|
||||
|
||||
def __init__(self, project: NWProject) -> None:
|
||||
self._project = project
|
||||
return
|
||||
|
||||
##
|
||||
# Methods
|
||||
@@ -293,13 +282,16 @@ class DocDuplicator:
|
||||
|
||||
|
||||
class DocSearch:
|
||||
"""Tool: Search Documents.
|
||||
|
||||
A global document search class.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._regEx = re.compile(r"")
|
||||
self._opts = re.IGNORECASE
|
||||
self._words = False
|
||||
self._escape = True
|
||||
return
|
||||
|
||||
##
|
||||
# Methods
|
||||
@@ -308,22 +300,19 @@ class DocSearch:
|
||||
def setCaseSensitive(self, state: bool) -> None:
|
||||
"""Set the case sensitive search flag."""
|
||||
self._opts = 0 if state else re.IGNORECASE
|
||||
return
|
||||
|
||||
def setWholeWords(self, state: bool) -> None:
|
||||
"""Set the whole words search flag."""
|
||||
self._words = state
|
||||
return
|
||||
|
||||
def setUserRegEx(self, state: bool) -> None:
|
||||
"""Set the escape flag to the opposite state."""
|
||||
self._escape = not state
|
||||
return
|
||||
|
||||
def iterSearch(
|
||||
self, project: NWProject, search: str
|
||||
) -> Iterable[tuple[NWItem, list[tuple[int, int, str]], bool]]:
|
||||
"""Iteratively search through documents in a project."""
|
||||
"""Iterate through documents in a project and apply search."""
|
||||
self._regEx = re.compile(self._buildPattern(search), self._opts)
|
||||
logger.debug("Searching with pattern '%s'", self._regEx.pattern)
|
||||
storage = project.storage
|
||||
@@ -376,7 +365,6 @@ class ProjectBuilder:
|
||||
def __init__(self) -> None:
|
||||
self._path = None
|
||||
self.tr = partial(QCoreApplication.translate, "ProjectBuilder")
|
||||
return
|
||||
|
||||
@property
|
||||
def projPath(self) -> Path | None:
|
||||
@@ -620,4 +608,3 @@ class ProjectBuilder:
|
||||
project.index.rebuild()
|
||||
project.saveProject()
|
||||
project.closeProject()
|
||||
return
|
||||
|
||||
@@ -20,7 +20,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
@@ -53,7 +53,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NWBuildDocument:
|
||||
"""Core: Manuscript Document Build Class
|
||||
"""Core: Manuscript Document Build Class.
|
||||
|
||||
This is the core tool that assembles a project and outputs a
|
||||
manuscript, based on a build definition object (BuildSettings).
|
||||
@@ -72,7 +72,6 @@ class NWBuildDocument:
|
||||
self._cache = None
|
||||
self._count = False
|
||||
self._outline = False
|
||||
return
|
||||
|
||||
##
|
||||
# Properties
|
||||
@@ -106,7 +105,6 @@ class NWBuildDocument:
|
||||
def addDocument(self, tHandle: str) -> None:
|
||||
"""Add a document to the build queue manually."""
|
||||
self._queue.append(tHandle)
|
||||
return
|
||||
|
||||
def queueAll(self) -> None:
|
||||
"""Queue all document as defined by the build settings."""
|
||||
@@ -115,7 +113,6 @@ class NWBuildDocument:
|
||||
for item in self._project.tree:
|
||||
if filtered.get(item.itemHandle, False):
|
||||
self._queue.append(item.itemHandle)
|
||||
return
|
||||
|
||||
def iterBuildPreview(self, newPage: bool) -> Iterable[tuple[int, bool]]:
|
||||
"""Build a preview QTextDocument."""
|
||||
@@ -131,7 +128,7 @@ class NWBuildDocument:
|
||||
return
|
||||
|
||||
def iterBuildDocument(self, path: Path, bFormat: nwBuildFmt) -> Iterable[tuple[int, bool]]:
|
||||
"""Wrapper for builders based on format."""
|
||||
"""Select a builder based on format."""
|
||||
self._error = None
|
||||
self._cache = None
|
||||
|
||||
@@ -341,12 +338,10 @@ class NWBuildDocument:
|
||||
scale*self._build.getFloat("format.rightMargin"),
|
||||
)
|
||||
|
||||
filtered = self._build.buildItemFilter(
|
||||
return self._build.buildItemFilter(
|
||||
self._project, withRoots=self._build.getBool("text.addNoteHeadings")
|
||||
)
|
||||
|
||||
return filtered
|
||||
|
||||
def _doBuild(self, bldObj: Tokenizer, tHandle: str, convert: bool = True) -> bool:
|
||||
"""Build a single document and add it to the build object."""
|
||||
tItem = self._project.tree[tHandle]
|
||||
|
||||
@@ -20,7 +20,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
@@ -42,7 +42,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NWDocument:
|
||||
"""Core: Document Class
|
||||
"""Core: Document Class.
|
||||
|
||||
A Class wrapping a single novelWriter document file. It represents
|
||||
a project item of nwItemType FILE. The file is not guaranteed to
|
||||
@@ -68,8 +68,6 @@ class NWDocument:
|
||||
if self._handle is not None:
|
||||
self._item = self._project.tree[tHandle]
|
||||
|
||||
return
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<NWDocument handle={self._handle}>"
|
||||
|
||||
@@ -357,5 +355,3 @@ class NWDocument:
|
||||
|
||||
else:
|
||||
logger.debug("Unknown meta data: '%s'", metaLine.strip())
|
||||
|
||||
return
|
||||
|
||||
@@ -22,7 +22,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
@@ -56,7 +56,7 @@ KEY_SOURCE = "0123456789bcdfghjklmnpqrstvwxz"
|
||||
|
||||
|
||||
class Index:
|
||||
"""Core: Project Index
|
||||
"""Core: Project Index.
|
||||
|
||||
This class holds the entire index for a given project. The index
|
||||
contains the data that isn't stored in the project items themselves.
|
||||
@@ -99,8 +99,6 @@ class Index:
|
||||
self._indexChange = 0.0
|
||||
self._rootChange = {}
|
||||
|
||||
return
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<Index project='{self._project.data.name}'>"
|
||||
|
||||
@@ -129,7 +127,6 @@ class Index:
|
||||
def setNovelModelExtraColumn(self, extra: nwNovelExtra) -> None:
|
||||
"""Set the data content type of the novel model extra column."""
|
||||
self._novelExtra = extra
|
||||
return
|
||||
|
||||
##
|
||||
# Public Methods
|
||||
@@ -142,7 +139,6 @@ class Index:
|
||||
self._indexChange = 0.0
|
||||
self._rootChange = {}
|
||||
SHARED.emitIndexCleared(self._project)
|
||||
return
|
||||
|
||||
def rebuild(self) -> None:
|
||||
"""Rebuild the entire index from scratch."""
|
||||
@@ -158,7 +154,6 @@ class Index:
|
||||
for tHandle in self._novelModels:
|
||||
self.refreshNovelModel(tHandle)
|
||||
SHARED.clearMainProgress()
|
||||
return
|
||||
|
||||
def deleteHandle(self, tHandle: str) -> None:
|
||||
"""Delete all entries of a given document handle."""
|
||||
@@ -168,7 +163,6 @@ class Index:
|
||||
del self._tagsIndex[tTag]
|
||||
del self._itemIndex[tHandle]
|
||||
SHARED.emitIndexChangedTags(self._project, [], delTags)
|
||||
return
|
||||
|
||||
def reIndexHandle(self, tHandle: str | None) -> None:
|
||||
"""Put a file back into the index. This is used when files are
|
||||
@@ -178,7 +172,6 @@ class Index:
|
||||
if tHandle and self._project.tree.checkType(tHandle, nwItemType.FILE):
|
||||
logger.debug("Re-indexing item '%s'", tHandle)
|
||||
self.scanText(tHandle, self._project.storage.getDocumentText(tHandle))
|
||||
return
|
||||
|
||||
def refreshHandle(self, tHandle: str) -> None:
|
||||
"""Update the class for all tags of a handle."""
|
||||
@@ -188,7 +181,6 @@ class Index:
|
||||
self.deleteHandle(tHandle)
|
||||
else:
|
||||
self._tagsIndex.updateClass(tHandle, item.itemClass.name)
|
||||
return
|
||||
|
||||
def indexChangedSince(self, checkTime: int | float) -> bool:
|
||||
"""Check if the index has changed since a given time."""
|
||||
@@ -211,7 +203,6 @@ class Index:
|
||||
model.setExtraColumn(self._novelExtra)
|
||||
self._appendSubTreeToModel(tHandle, model)
|
||||
model.endResetModel()
|
||||
return
|
||||
|
||||
def updateNovelModelData(self, nwItem: NWItem) -> bool:
|
||||
"""Refresh a novel model."""
|
||||
@@ -428,8 +419,6 @@ class Index:
|
||||
if updated or deleted:
|
||||
SHARED.emitIndexChangedTags(self._project, updated, deleted)
|
||||
|
||||
return
|
||||
|
||||
def _scanInactive(self, nwItem: NWItem, text: str) -> None:
|
||||
"""Scan an inactive document for meta data."""
|
||||
for line in text.splitlines():
|
||||
@@ -438,7 +427,6 @@ class Index:
|
||||
if hDepth != "H0":
|
||||
nwItem.setMainHeading(hDepth)
|
||||
break
|
||||
return
|
||||
|
||||
def _splitHeading(self, line: str) -> tuple[str, str]:
|
||||
"""Split a heading into its heading level and text value."""
|
||||
@@ -462,7 +450,6 @@ class Index:
|
||||
"""Count text stats and save the counts to the index."""
|
||||
cC, wC, pC = standardCounter(text)
|
||||
self._itemIndex.setHeadingCounts(tHandle, sTitle, cC, wC, pC)
|
||||
return
|
||||
|
||||
def _indexKeyword(
|
||||
self, tHandle: str, line: str, sTitle: str, itemClass: nwItemClass, tags: dict[str, bool]
|
||||
@@ -498,7 +485,6 @@ class Index:
|
||||
model.setExtraColumn(self._novelExtra)
|
||||
self._appendSubTreeToModel(tHandle, model)
|
||||
self._novelModels[tHandle] = model
|
||||
return
|
||||
|
||||
def _appendSubTreeToModel(self, tHandle: str, model: NovelModel) -> None:
|
||||
"""Append all active novel documents to a novel model."""
|
||||
@@ -509,7 +495,6 @@ class Index:
|
||||
and node.item.isActive
|
||||
):
|
||||
model.append(node)
|
||||
return
|
||||
|
||||
##
|
||||
# Check @ Lines
|
||||
@@ -683,15 +668,13 @@ class Index:
|
||||
"words": hItem.wordCount,
|
||||
}
|
||||
|
||||
result = [(
|
||||
return [(
|
||||
tKey,
|
||||
tData[tKey]["level"],
|
||||
tData[tKey]["title"],
|
||||
tData[tKey]["words"]
|
||||
) for tKey in tOrder]
|
||||
|
||||
return result
|
||||
|
||||
def getCounts(self, tHandle: str, sTitle: str | None = None) -> tuple[int, int, int]:
|
||||
"""Return the counts for a file, or a section of a file,
|
||||
starting at title sTitle if it is provided.
|
||||
@@ -796,7 +779,7 @@ class Index:
|
||||
# =====================
|
||||
|
||||
class TagsIndex:
|
||||
"""Core: Tags Index Wrapper Class
|
||||
"""Core: Tags Index Wrapper Class.
|
||||
|
||||
A wrapper class that holds the reverse lookup tags index. This is
|
||||
just a simple wrapper around a single dictionary to keep tighter
|
||||
@@ -807,14 +790,12 @@ class TagsIndex:
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._tags: dict[str, dict[str, str]] = {}
|
||||
return
|
||||
|
||||
def __contains__(self, tagKey: str) -> bool:
|
||||
return tagKey.lower() in self._tags
|
||||
|
||||
def __delitem__(self, tagKey: str) -> None:
|
||||
self._tags.pop(tagKey.lower(), None)
|
||||
return
|
||||
|
||||
def __getitem__(self, tagKey: str) -> dict | None:
|
||||
return self._tags.get(tagKey.lower(), None)
|
||||
@@ -826,7 +807,6 @@ class TagsIndex:
|
||||
def clear(self) -> None:
|
||||
"""Clear the index."""
|
||||
self._tags = {}
|
||||
return
|
||||
|
||||
def items(self) -> ItemsView:
|
||||
"""Return a dictionary view of all tags."""
|
||||
@@ -842,7 +822,6 @@ class TagsIndex:
|
||||
"heading": sTitle,
|
||||
"class": className,
|
||||
}
|
||||
return
|
||||
|
||||
def tagName(self, tagKey: str, default: str = "") -> str:
|
||||
"""Get the name of a given tag."""
|
||||
@@ -882,7 +861,6 @@ class TagsIndex:
|
||||
for entry in self._tags.values():
|
||||
if entry.get("handle") == tHandle:
|
||||
entry["class"] = className
|
||||
return
|
||||
|
||||
##
|
||||
# Pack/Unpack
|
||||
@@ -925,11 +903,9 @@ class TagsIndex:
|
||||
|
||||
self.add(name, display, handle, heading, className)
|
||||
|
||||
return
|
||||
|
||||
|
||||
class IndexCache:
|
||||
"""Core: Item Index Lookup Data Class
|
||||
"""Core: Item Index Lookup Data Class.
|
||||
|
||||
A small data class passed between all objects of the Item Index
|
||||
which provides lookup capabilities and caching for shared data.
|
||||
@@ -941,14 +917,13 @@ class IndexCache:
|
||||
self.tags: TagsIndex = tagsIndex
|
||||
self.story: set[str] = set()
|
||||
self.note: set[str] = set()
|
||||
return
|
||||
|
||||
|
||||
# The Item Index Objects
|
||||
# ======================
|
||||
|
||||
class ItemIndex:
|
||||
"""Core: Item Index Wrapper Class
|
||||
"""Core: Item Index Wrapper Class.
|
||||
|
||||
A wrapper object holding the indexed items. This is a wrapper
|
||||
class around a single storage dictionary with a set of utility
|
||||
@@ -963,14 +938,12 @@ class ItemIndex:
|
||||
self._project = project
|
||||
self._cache = IndexCache(tagsIndex)
|
||||
self._items: dict[str, IndexNode] = {}
|
||||
return
|
||||
|
||||
def __contains__(self, tHandle: str) -> bool:
|
||||
return tHandle in self._items
|
||||
|
||||
def __delitem__(self, tHandle: str) -> None:
|
||||
self._items.pop(tHandle, None)
|
||||
return
|
||||
|
||||
def __getitem__(self, tHandle: str) -> IndexNode | None:
|
||||
return self._items.get(tHandle, None)
|
||||
@@ -982,14 +955,12 @@ class ItemIndex:
|
||||
def clear(self) -> None:
|
||||
"""Clear the index."""
|
||||
self._items = {}
|
||||
return
|
||||
|
||||
def add(self, tHandle: str, nwItem: NWItem) -> None:
|
||||
"""Add a new item to the index. This will overwrite the item if
|
||||
it already exists.
|
||||
"""
|
||||
self._items[tHandle] = IndexNode(self._cache, tHandle, nwItem)
|
||||
return
|
||||
|
||||
def allStoryKeys(self) -> set[str]:
|
||||
"""Return all story structure keys."""
|
||||
@@ -1064,7 +1035,6 @@ class ItemIndex:
|
||||
"""
|
||||
if tHandle in self._items:
|
||||
self._items[tHandle].setHeadingCounts(sTitle, cC, wC, pC)
|
||||
return
|
||||
|
||||
def setHeadingComment(
|
||||
self, tHandle: str, sTitle: str,
|
||||
@@ -1073,25 +1043,21 @@ class ItemIndex:
|
||||
"""Set a story comment for a heading on a given item."""
|
||||
if tHandle in self._items:
|
||||
self._items[tHandle].setHeadingComment(sTitle, comment, key, text)
|
||||
return
|
||||
|
||||
def setHeadingTag(self, tHandle: str, sTitle: str, tagKey: str) -> None:
|
||||
"""Set the main tag for a heading on a given item."""
|
||||
if tHandle in self._items:
|
||||
self._items[tHandle].setHeadingTag(sTitle, tagKey)
|
||||
return
|
||||
|
||||
def addHeadingRef(self, tHandle: str, sTitle: str, tagKeys: list[str], refType: str) -> None:
|
||||
"""Set the reference tags for a heading on a given item."""
|
||||
if tHandle in self._items:
|
||||
self._items[tHandle].addHeadingRef(sTitle, tagKeys, refType)
|
||||
return
|
||||
|
||||
def addNoteKey(self, tHandle: str, style: T_NoteTypes, key: str) -> None:
|
||||
"""Set notes key for a given item."""
|
||||
if tHandle in self._items:
|
||||
self._items[tHandle].addNoteKey(style, key)
|
||||
return
|
||||
|
||||
def genNewNoteKey(self, tHandle: str, style: T_NoteTypes) -> str:
|
||||
"""Set notes key for a given item."""
|
||||
@@ -1131,5 +1097,3 @@ class ItemIndex:
|
||||
tItem = IndexNode(self._cache, tHandle, nwItem)
|
||||
tItem.unpackData(tData)
|
||||
self._items[tHandle] = tItem
|
||||
|
||||
return
|
||||
|
||||
@@ -23,7 +23,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
@@ -50,7 +50,7 @@ NOTE_TYPES: list[T_NoteTypes] = ["footnotes", "comments"]
|
||||
|
||||
|
||||
class IndexNode:
|
||||
"""Core: Single Index Item Node Class
|
||||
"""Core: Single Index Item Node Class.
|
||||
|
||||
This object represents the index data of a project item (NWItem).
|
||||
It holds a record of all the headings in the text, and the meta data
|
||||
@@ -68,7 +68,6 @@ class IndexNode:
|
||||
self._headings: dict[str, IndexHeading] = {TT_NONE: IndexHeading(self._cache, TT_NONE)}
|
||||
self._notes: dict[str, set[str]] = {}
|
||||
self._count = 0
|
||||
return
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<IndexNode handle='{self._handle}'>"
|
||||
@@ -107,39 +106,33 @@ class IndexNode:
|
||||
if TT_NONE in self._headings:
|
||||
self._headings.pop(TT_NONE)
|
||||
self._headings[tHeading.key] = tHeading
|
||||
return
|
||||
|
||||
def setHeadingCounts(self, sTitle: str, cCount: int, wCount: int, pCount: int) -> None:
|
||||
"""Set the character, word and paragraph count of a heading."""
|
||||
if sTitle in self._headings:
|
||||
self._headings[sTitle].setCounts([cCount, wCount, pCount])
|
||||
return
|
||||
|
||||
def setHeadingComment(self, sTitle: str, comment: nwComment, key: str, text: str) -> None:
|
||||
"""Set the comment text of a heading."""
|
||||
if sTitle in self._headings:
|
||||
self._headings[sTitle].setComment(comment.name, key, text)
|
||||
return
|
||||
|
||||
def setHeadingTag(self, sTitle: str, tag: str) -> None:
|
||||
"""Set the tag of a heading."""
|
||||
if sTitle in self._headings:
|
||||
self._headings[sTitle].setTag(tag)
|
||||
return
|
||||
|
||||
def addHeadingRef(self, sTitle: str, tags: list[str], keyword: str) -> None:
|
||||
"""Add a reference key and all its types to a heading."""
|
||||
if sTitle in self._headings:
|
||||
for tag in tags:
|
||||
self._headings[sTitle].addReference(tag, keyword)
|
||||
return
|
||||
|
||||
def addNoteKey(self, style: T_NoteTypes, key: str) -> None:
|
||||
"""Add a note key to the index."""
|
||||
if style not in self._notes:
|
||||
self._notes[style] = set()
|
||||
self._notes[style].add(key)
|
||||
return
|
||||
|
||||
##
|
||||
# Data Methods
|
||||
@@ -195,11 +188,10 @@ class IndexNode:
|
||||
self._notes[style] = set(keys)
|
||||
else:
|
||||
raise KeyError("Index node contains an invalid key")
|
||||
return
|
||||
|
||||
|
||||
class IndexHeading:
|
||||
"""Core: Single Index Heading Class
|
||||
"""Core: Single Index Heading Class.
|
||||
|
||||
This object represents a section of text in a project item
|
||||
associated with a single (valid) heading. It holds a separate record
|
||||
@@ -224,7 +216,6 @@ class IndexHeading:
|
||||
self._tag = ""
|
||||
self._refs: dict[str, set[str]] = {}
|
||||
self._comments: dict[str, str] = {}
|
||||
return
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<IndexHeading key='{self._key}'>"
|
||||
@@ -289,12 +280,10 @@ class IndexHeading:
|
||||
"""Set the level of the heading if it's a valid value."""
|
||||
if level in nwStyles.H_VALID:
|
||||
self._level = level
|
||||
return
|
||||
|
||||
def setLine(self, line: int) -> None:
|
||||
"""Set the line number of a heading."""
|
||||
self._line = max(0, checkInt(line, 0))
|
||||
return
|
||||
|
||||
def setCounts(self, counts: Sequence[int]) -> None:
|
||||
"""Set the character, word and paragraph count. Make sure the
|
||||
@@ -306,7 +295,6 @@ class IndexHeading:
|
||||
max(0, checkInt(counts[1], 0)),
|
||||
max(0, checkInt(counts[2], 0)),
|
||||
)
|
||||
return
|
||||
|
||||
def setComment(self, comment: str, key: str, text: str) -> None:
|
||||
"""Set the text for a comment and make sure it is a string."""
|
||||
@@ -319,12 +307,10 @@ class IndexHeading:
|
||||
case "note" if key:
|
||||
self._cache.note.add(key)
|
||||
self._comments[f"note.{key}"] = str(text)
|
||||
return
|
||||
|
||||
def setTag(self, tag: str) -> None:
|
||||
"""Set the tag for references, and make sure it is a string."""
|
||||
self._tag = str(tag).lower()
|
||||
return
|
||||
|
||||
def addReference(self, tag: str, keyword: str) -> None:
|
||||
"""Add a record of a reference tag, and what keyword types it is
|
||||
@@ -335,7 +321,6 @@ class IndexHeading:
|
||||
if tag not in self._refs:
|
||||
self._refs[tag] = set()
|
||||
self._refs[tag].add(keyword)
|
||||
return
|
||||
|
||||
##
|
||||
# Getters
|
||||
@@ -409,4 +394,3 @@ class IndexHeading:
|
||||
self.setComment(comment, compact(kind), str(entry))
|
||||
else:
|
||||
raise KeyError("Unknown key in heading entry")
|
||||
return
|
||||
|
||||
@@ -20,7 +20,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
@@ -44,7 +44,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NWItem:
|
||||
"""Core: Item Data Class
|
||||
"""Core: Item Data Class.
|
||||
|
||||
This class holds all the project information about a project item.
|
||||
Each item must be associated with a project and have a valid handle.
|
||||
@@ -84,16 +84,14 @@ class NWItem:
|
||||
self._wordInit = 0 # Initial character count
|
||||
self._charInit = 0 # Initial word count
|
||||
|
||||
return
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<NWItem handle={self._handle}, parent={self._parent}, name='{self._name}'>"
|
||||
|
||||
def __bool__(self) -> bool:
|
||||
"""The truthiness of the class. The handle used to be initiated
|
||||
to None, but this is no longer the case. It should always
|
||||
evaluate to True since 2.1-beta1, although unpack and the NWTree
|
||||
class can leave it as an empty string.
|
||||
"""Check the truthiness of the class. The handle used to be
|
||||
initiated to None, but this is no longer the case. It should
|
||||
always evaluate to True since 2.1-beta1, although unpack and the
|
||||
NWTree class can leave it as an empty string.
|
||||
"""
|
||||
return bool(self._handle)
|
||||
|
||||
@@ -206,15 +204,13 @@ class NWItem:
|
||||
meta["cursorPos"] = str(self._cursorPos)
|
||||
name["active"] = yesNo(self._active)
|
||||
|
||||
data = {
|
||||
return {
|
||||
"name": str(self._name),
|
||||
"itemAttr": item,
|
||||
"metaAttr": meta,
|
||||
"nameAttr": name,
|
||||
}
|
||||
|
||||
return data
|
||||
|
||||
def unpack(self, data: dict) -> bool:
|
||||
"""Set the values from a data dictionary."""
|
||||
item = data.get("itemAttr", {})
|
||||
@@ -298,13 +294,11 @@ class NWItem:
|
||||
def notifyToRefresh(self) -> None:
|
||||
"""Notify GUI that item info needs to be refreshed."""
|
||||
self._project.tree.refreshItems([self._handle])
|
||||
return
|
||||
|
||||
def notifyNovelStructureChange(self) -> None:
|
||||
"""Notify that the structure of a novel has changed."""
|
||||
if self._root and self._class == nwItemClass.NOVEL:
|
||||
self._project.tree.novelStructureChanged(self._root)
|
||||
return
|
||||
|
||||
##
|
||||
# Lookup Methods
|
||||
@@ -457,8 +451,6 @@ class NWItem:
|
||||
if self._import is None:
|
||||
self.setImport("New") # This forces a default value lookup
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Set Item Values
|
||||
##
|
||||
@@ -469,7 +461,6 @@ class NWItem:
|
||||
self._name = simplified(name)
|
||||
else:
|
||||
self._name = ""
|
||||
return
|
||||
|
||||
def setParent(self, handle: Any) -> None:
|
||||
"""Set the parent handle, and ensure it is valid."""
|
||||
@@ -479,7 +470,6 @@ class NWItem:
|
||||
self._parent = handle
|
||||
else:
|
||||
self._parent = None
|
||||
return
|
||||
|
||||
def setRoot(self, handle: Any) -> None:
|
||||
"""Set the root handle, and ensure it is valid."""
|
||||
@@ -489,7 +479,6 @@ class NWItem:
|
||||
self._root = handle
|
||||
else:
|
||||
self._root = None
|
||||
return
|
||||
|
||||
def setOrder(self, order: Any) -> None:
|
||||
"""Set the item order, and ensure that it is valid. This value
|
||||
@@ -497,7 +486,6 @@ class NWItem:
|
||||
the moment.
|
||||
"""
|
||||
self._order = checkInt(order, 0)
|
||||
return
|
||||
|
||||
def setType(self, value: Any) -> None:
|
||||
"""Set the item type from either a proper nwItemType, or set it
|
||||
@@ -510,7 +498,6 @@ class NWItem:
|
||||
else:
|
||||
logger.error("Unrecognised item type '%s'", value)
|
||||
self._type = nwItemType.NO_TYPE
|
||||
return
|
||||
|
||||
def setClass(self, value: Any) -> None:
|
||||
"""Set the item class from either a proper nwItemClass, or set
|
||||
@@ -523,7 +510,6 @@ class NWItem:
|
||||
else:
|
||||
logger.error("Unrecognised item class '%s'", value)
|
||||
self._class = nwItemClass.NO_CLASS
|
||||
return
|
||||
|
||||
def setLayout(self, value: Any) -> None:
|
||||
"""Set the item layout from either a proper nwItemLayout, or set
|
||||
@@ -536,21 +522,18 @@ class NWItem:
|
||||
else:
|
||||
logger.error("Unrecognised item layout '%s'", value)
|
||||
self._layout = nwItemLayout.NO_LAYOUT
|
||||
return
|
||||
|
||||
def setStatus(self, value: Any) -> None:
|
||||
"""Set the item status by looking it up in the valid status
|
||||
items of the current project.
|
||||
"""
|
||||
self._status = self._project.data.itemStatus.check(value)
|
||||
return
|
||||
|
||||
def setImport(self, value: Any) -> None:
|
||||
"""Set the item importance by looking it up in the valid import
|
||||
items of the current project.
|
||||
"""
|
||||
self._import = self._project.data.itemImport.check(value)
|
||||
return
|
||||
|
||||
def setActive(self, state: Any) -> None:
|
||||
"""Set the active flag."""
|
||||
@@ -558,7 +541,6 @@ class NWItem:
|
||||
self._active = state
|
||||
else:
|
||||
self._active = False
|
||||
return
|
||||
|
||||
def setExpanded(self, state: Any) -> None:
|
||||
"""Set the expanded status of an item in the project tree."""
|
||||
@@ -566,7 +548,6 @@ class NWItem:
|
||||
self._expanded = state
|
||||
else:
|
||||
self._expanded = False
|
||||
return
|
||||
|
||||
##
|
||||
# Set Document Meta Data
|
||||
@@ -576,7 +557,6 @@ class NWItem:
|
||||
"""Set the main heading level."""
|
||||
if value in nwStyles.H_LEVEL:
|
||||
self._heading = value
|
||||
return
|
||||
|
||||
def setCharCount(self, count: Any) -> None:
|
||||
"""Set the character count, and ensure that it is an integer."""
|
||||
@@ -584,7 +564,6 @@ class NWItem:
|
||||
self._charCount = max(0, count)
|
||||
else:
|
||||
self._charCount = 0
|
||||
return
|
||||
|
||||
def setWordCount(self, count: Any) -> None:
|
||||
"""Set the word count, and ensure that it is an integer."""
|
||||
@@ -592,7 +571,6 @@ class NWItem:
|
||||
self._wordCount = max(0, count)
|
||||
else:
|
||||
self._wordCount = 0
|
||||
return
|
||||
|
||||
def setParaCount(self, count: Any) -> None:
|
||||
"""Set the paragraph count, and ensure that it is an integer."""
|
||||
@@ -600,7 +578,6 @@ class NWItem:
|
||||
self._paraCount = max(0, count)
|
||||
else:
|
||||
self._paraCount = 0
|
||||
return
|
||||
|
||||
def setCursorPos(self, position: Any) -> None:
|
||||
"""Set the cursor position, and ensure that it is an integer."""
|
||||
@@ -608,4 +585,3 @@ class NWItem:
|
||||
self._cursorPos = max(0, position)
|
||||
else:
|
||||
self._cursorPos = 0
|
||||
return
|
||||
|
||||
@@ -21,7 +21,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
@@ -66,7 +66,7 @@ T_NodeData = str | QIcon | QFont | Qt.AlignmentFlag | None
|
||||
|
||||
|
||||
class ProjectNode:
|
||||
"""Core: Project Model Node Class
|
||||
"""Core: Project Model Node Class.
|
||||
|
||||
The project tree structure is saved as nodes in a tree, starting
|
||||
from a root node. This class makes up these nodes.
|
||||
@@ -103,7 +103,6 @@ class ProjectNode:
|
||||
self._count = 0
|
||||
self.refresh()
|
||||
self.updateCount()
|
||||
return
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
@@ -114,7 +113,7 @@ class ProjectNode:
|
||||
)
|
||||
|
||||
def __bool__(self) -> bool:
|
||||
"""A node should always evaluate to True."""
|
||||
# A node should always evaluate to True.
|
||||
return True
|
||||
|
||||
##
|
||||
@@ -162,15 +161,12 @@ class ProjectNode:
|
||||
self._cache[C_STATUS_TIP] = sText
|
||||
self._cache[C_STATUS_ACCESS] = sText
|
||||
|
||||
return
|
||||
|
||||
def updateCount(self, propagate: bool = True) -> None:
|
||||
"""Update counts, and propagate upwards in the tree."""
|
||||
self._count = self._item.mainCount + sum(c._count for c in self._children) # noqa: SLF001
|
||||
self._cache[C_COUNT_TEXT] = f"{self._count:n}"
|
||||
if propagate and (parent := self._parent):
|
||||
parent.updateCount()
|
||||
return
|
||||
|
||||
##
|
||||
# Data Access
|
||||
@@ -223,7 +219,6 @@ class ProjectNode:
|
||||
self._children.append(child)
|
||||
self._refreshChildrenPos()
|
||||
self._item.notifyNovelStructureChange()
|
||||
return
|
||||
|
||||
def takeChild(self, pos: int) -> ProjectNode | None:
|
||||
"""Remove a child item and return it."""
|
||||
@@ -243,7 +238,6 @@ class ProjectNode:
|
||||
self._children.insert(target, node)
|
||||
self._refreshChildrenPos()
|
||||
self._item.notifyNovelStructureChange()
|
||||
return
|
||||
|
||||
def setExpanded(self, state: bool) -> None:
|
||||
"""Set the node's expanded state."""
|
||||
@@ -251,7 +245,6 @@ class ProjectNode:
|
||||
self._item.setExpanded(True)
|
||||
else:
|
||||
self._item.setExpanded(False)
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
@@ -262,14 +255,12 @@ class ProjectNode:
|
||||
for node in self._children:
|
||||
children.append(node)
|
||||
node._recursiveAppendChildren(children) # noqa: SLF001
|
||||
return
|
||||
|
||||
def _refreshChildrenPos(self) -> None:
|
||||
"""Update the row value on all children."""
|
||||
for n, child in enumerate(self._children):
|
||||
child._row = n # noqa: SLF001
|
||||
child.item.setOrder(n)
|
||||
return
|
||||
|
||||
def _updateRelationships(self, child: ProjectNode) -> None:
|
||||
"""Update a child item's relationships."""
|
||||
@@ -282,11 +273,10 @@ class ProjectNode:
|
||||
child.item.setParent(None)
|
||||
child.item.setRoot(child.item.itemHandle)
|
||||
child.item.setClassDefaults(child.item.itemClass)
|
||||
return
|
||||
|
||||
|
||||
class ProjectModel(QAbstractItemModel):
|
||||
"""Core: Project Model Class
|
||||
"""Core: Project Model Class.
|
||||
|
||||
This class provides the interface for the tree widget used on the
|
||||
GUI. It implements the QModelIndex based interface required, adds
|
||||
@@ -302,11 +292,9 @@ class ProjectModel(QAbstractItemModel):
|
||||
self._root = ProjectNode(NWItem(tree.project, INV_ROOT))
|
||||
self._root.item.setName("Invisible Root")
|
||||
logger.debug("Ready: ProjectModel")
|
||||
return
|
||||
|
||||
def __del__(self) -> None: # pragma: no cover
|
||||
logger.debug("Delete: ProjectModel")
|
||||
return
|
||||
|
||||
##
|
||||
# Properties
|
||||
@@ -363,7 +351,7 @@ class ProjectModel(QAbstractItemModel):
|
||||
##
|
||||
|
||||
def supportedDropActions(self) -> Qt.DropAction:
|
||||
"""Return supported drop actions"""
|
||||
"""Return supported drop actions."""
|
||||
return Qt.DropAction.MoveAction
|
||||
|
||||
def mimeTypes(self) -> list[str]:
|
||||
@@ -445,7 +433,6 @@ class ProjectModel(QAbstractItemModel):
|
||||
self.beginInsertRows(parent, row, row)
|
||||
node.addChild(child, row)
|
||||
self.endInsertRows()
|
||||
return
|
||||
|
||||
def removeChild(self, parent: QModelIndex, pos: int) -> ProjectNode | None:
|
||||
"""Remove a node from the model and return it."""
|
||||
@@ -469,7 +456,6 @@ class ProjectModel(QAbstractItemModel):
|
||||
self.beginMoveRows(index.parent(), pos, pos, index.parent(), end)
|
||||
parent.moveChild(pos, new)
|
||||
self.endMoveRows()
|
||||
return
|
||||
|
||||
def multiMove(self, indices: list[QModelIndex], target: QModelIndex, pos: int = -1) -> None:
|
||||
"""Move multiple items to a new location."""
|
||||
@@ -497,7 +483,6 @@ class ProjectModel(QAbstractItemModel):
|
||||
node._updateRelationships(child) # noqa: SLF001
|
||||
child.item.notifyToRefresh()
|
||||
node.item.notifyToRefresh()
|
||||
return
|
||||
|
||||
##
|
||||
# Other Methods
|
||||
@@ -506,7 +491,6 @@ class ProjectModel(QAbstractItemModel):
|
||||
def clear(self) -> None:
|
||||
"""Clear the project model."""
|
||||
self._root.children.clear()
|
||||
return
|
||||
|
||||
def allExpanded(self) -> list[QModelIndex]:
|
||||
"""Return a list of all expanded items."""
|
||||
|
||||
@@ -20,7 +20,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
@@ -54,6 +54,7 @@ T_NodeData = str | QIcon | QPixmap | Qt.AlignmentFlag | None
|
||||
|
||||
|
||||
class NovelModel(QAbstractTableModel):
|
||||
"""Core: Novel Model CLass."""
|
||||
|
||||
__slots__ = ("_columns", "_extraKey", "_extraLabel", "_more", "_rows")
|
||||
|
||||
@@ -64,11 +65,9 @@ class NovelModel(QAbstractTableModel):
|
||||
self._columns = 3
|
||||
self._extraKey = ""
|
||||
self._extraLabel = ""
|
||||
return
|
||||
|
||||
def __del__(self) -> None: # pragma: no cover
|
||||
logger.debug("Delete: NovelModel")
|
||||
return
|
||||
|
||||
##
|
||||
# Properties
|
||||
@@ -102,7 +101,6 @@ class NovelModel(QAbstractTableModel):
|
||||
self._columns = 4
|
||||
self._extraKey = nwKeyWords.PLOT_KEY
|
||||
self._extraLabel = trConst(nwLabels.KEY_NAME[nwKeyWords.PLOT_KEY])
|
||||
return
|
||||
|
||||
##
|
||||
# Model Interface
|
||||
@@ -147,7 +145,6 @@ class NovelModel(QAbstractTableModel):
|
||||
def clear(self) -> None:
|
||||
"""Clear the model."""
|
||||
self._rows.clear()
|
||||
return
|
||||
|
||||
def append(self, node: IndexNode) -> None:
|
||||
"""Append a node to the model."""
|
||||
@@ -155,7 +152,6 @@ class NovelModel(QAbstractTableModel):
|
||||
for key, head in node.items():
|
||||
if key != "T0000":
|
||||
self._rows.append(self._generateEntry(handle, key, head))
|
||||
return
|
||||
|
||||
def refresh(self, node: IndexNode) -> bool:
|
||||
"""Refresh an index node."""
|
||||
|
||||
@@ -21,7 +21,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
@@ -80,7 +80,7 @@ VALID_MAP: dict[str, set[str]] = {
|
||||
|
||||
|
||||
class OptionState:
|
||||
"""Core: GUI Options Storage
|
||||
"""Core: GUI Options Storage.
|
||||
|
||||
A class for storing the state of the GUI. The data is stored per
|
||||
project. Settings that should be project-independent are stored in
|
||||
@@ -90,7 +90,6 @@ class OptionState:
|
||||
def __init__(self, project: NWProject) -> None:
|
||||
self._project = project
|
||||
self._state = {}
|
||||
return
|
||||
|
||||
##
|
||||
# Load and Save Cache
|
||||
|
||||
@@ -20,7 +20,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
@@ -57,6 +57,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NWProjectState(Enum):
|
||||
"""The state of the loaded project."""
|
||||
|
||||
UNKNOWN = 0
|
||||
LOCKED = 1
|
||||
@@ -65,6 +66,11 @@ class NWProjectState(Enum):
|
||||
|
||||
|
||||
class NWProject:
|
||||
"""Core: novelWriter Project Class.
|
||||
|
||||
This class is the parent class of the project, and holds instances
|
||||
of project data, the project tree, and the project index.
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
"_changed", "_data", "_index", "_langData", "_options", "_session",
|
||||
@@ -92,17 +98,13 @@ class NWProject:
|
||||
|
||||
logger.debug("Ready: NWProject")
|
||||
|
||||
return
|
||||
|
||||
def __del__(self) -> None: # pragma: no cover
|
||||
logger.debug("Delete: NWProject")
|
||||
return
|
||||
|
||||
def clear(self) -> None:
|
||||
"""Clear the project."""
|
||||
self._tree.clear()
|
||||
self._index.clear()
|
||||
return
|
||||
|
||||
##
|
||||
# Properties
|
||||
@@ -263,7 +265,6 @@ class NWProject:
|
||||
if rHandle and (tHandle := SHARED.project.newFile(tag.title(), rHandle)):
|
||||
self.writeNewFile(tHandle, 1, False, f"@tag: {tag}\n\n")
|
||||
self._tree.refreshItems([tHandle])
|
||||
return
|
||||
|
||||
##
|
||||
# Project Methods
|
||||
@@ -441,7 +442,6 @@ class NWProject:
|
||||
self._tree.writeToCFile()
|
||||
self._session.appendSession(idleTime)
|
||||
self._storage.closeSession()
|
||||
return
|
||||
|
||||
def backupProject(self, doNotify: bool) -> bool:
|
||||
"""Create a zip file of the entire project."""
|
||||
@@ -499,7 +499,6 @@ class NWProject:
|
||||
self._data.itemImport.add(None, self.tr("Minor"), "purple", "BLOCK_2", 0)
|
||||
self._data.itemImport.add(None, self.tr("Major"), "purple", "BLOCK_3", 0)
|
||||
self._data.itemImport.add(None, self.tr("Main"), "purple", "BLOCK_4", 0)
|
||||
return
|
||||
|
||||
def setProjectLang(self, language: str | None) -> None:
|
||||
"""Set the project-specific language."""
|
||||
@@ -508,7 +507,6 @@ class NWProject:
|
||||
self._data.setLanguage(language)
|
||||
self._loadProjectLocalisation()
|
||||
self.setProjectChanged(True)
|
||||
return
|
||||
|
||||
def setProjectChanged(self, status: bool) -> bool:
|
||||
"""Toggle the project changed flag, and propagate the
|
||||
@@ -527,7 +525,6 @@ class NWProject:
|
||||
"""Update the total word and character count values."""
|
||||
wNovel, wNotes, cNovel, cNotes = self._tree.sumCounts()
|
||||
self._data.setCurrCounts(wNovel=wNovel, wNotes=wNotes, cNovel=cNovel, cNotes=cNotes)
|
||||
return
|
||||
|
||||
def countStatus(self) -> None:
|
||||
"""Count how many times the various status flags are used in the
|
||||
@@ -541,7 +538,6 @@ class NWProject:
|
||||
self._data.itemStatus.increment(nwItem.itemStatus)
|
||||
else:
|
||||
self._data.itemImport.increment(nwItem.itemImport)
|
||||
return
|
||||
|
||||
def updateStatus(self, kind: T_StatusKind, update: T_UpdateEntry) -> None:
|
||||
"""Update status or import entries."""
|
||||
@@ -553,13 +549,11 @@ class NWProject:
|
||||
self._data.itemImport.update(update)
|
||||
SHARED.emitStatusLabelsChanged(self, kind)
|
||||
self._tree.refreshAllItems()
|
||||
return
|
||||
|
||||
def updateTheme(self) -> None:
|
||||
"""Update theme elements."""
|
||||
self._data.itemStatus.refreshIcons()
|
||||
self._data.itemImport.refreshIcons()
|
||||
return
|
||||
|
||||
def localLookup(self, word: str | int) -> str:
|
||||
"""Look up a word or number in the translation map for the
|
||||
|
||||
@@ -20,7 +20,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
@@ -41,7 +41,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NWProjectData:
|
||||
"""Core: Project Data Class
|
||||
"""Core: Project Data Class.
|
||||
|
||||
The class holds all project data from the main XML file, aside from
|
||||
the list of project items.
|
||||
@@ -86,8 +86,6 @@ class NWProjectData:
|
||||
self._status = NWStatus(NWStatus.STATUS)
|
||||
self._import = NWStatus(NWStatus.IMPORT)
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Properties
|
||||
##
|
||||
@@ -191,13 +189,11 @@ class NWProjectData:
|
||||
"""Increment the save count by one."""
|
||||
self._saveCount += 1
|
||||
self._project.setProjectChanged(True)
|
||||
return
|
||||
|
||||
def incAutoCount(self) -> None:
|
||||
"""Increment the auto save count by one."""
|
||||
self._autoCount += 1
|
||||
self._project.setProjectChanged(True)
|
||||
return
|
||||
|
||||
##
|
||||
# Getters
|
||||
@@ -219,67 +215,57 @@ class NWProjectData:
|
||||
elif value != self._uuid:
|
||||
self._uuid = value
|
||||
self._project.setProjectChanged(True)
|
||||
return
|
||||
|
||||
def setName(self, value: str | None) -> None:
|
||||
"""Set a new project name."""
|
||||
if value != self._name:
|
||||
self._name = simplified(str(value or ""))
|
||||
self._project.setProjectChanged(True)
|
||||
return
|
||||
|
||||
def setAuthor(self, value: str | None) -> None:
|
||||
"""Set the author value."""
|
||||
if value != self._author:
|
||||
self._author = simplified(str(value or ""))
|
||||
self._project.setProjectChanged(True)
|
||||
return
|
||||
|
||||
def setSaveCount(self, value: Any) -> None:
|
||||
"""Set the save count from last session."""
|
||||
self._saveCount = checkInt(value, 0)
|
||||
self._project.setProjectChanged(True)
|
||||
return
|
||||
|
||||
def setAutoCount(self, value: Any) -> None:
|
||||
"""Set the auto save count from last session."""
|
||||
self._autoCount = checkInt(value, 0)
|
||||
self._project.setProjectChanged(True)
|
||||
return
|
||||
|
||||
def setEditTime(self, value: Any) -> None:
|
||||
"""Set the edit time from last session."""
|
||||
self._editTime = checkInt(value, 0)
|
||||
self._project.setProjectChanged(True)
|
||||
return
|
||||
|
||||
def setDoBackup(self, value: Any) -> None:
|
||||
"""Set the do write backup flag."""
|
||||
if value != self._doBackup:
|
||||
self._doBackup = checkBool(value, False)
|
||||
self._project.setProjectChanged(True)
|
||||
return
|
||||
|
||||
def setLanguage(self, value: str | None) -> None:
|
||||
"""Set the project language."""
|
||||
if value != self._language:
|
||||
self._language = checkStringNone(value, None)
|
||||
self._project.setProjectChanged(True)
|
||||
return
|
||||
|
||||
def setSpellCheck(self, value: Any) -> None:
|
||||
"""Set the spell check flag."""
|
||||
if value != self._spellCheck:
|
||||
self._spellCheck = checkBool(value, False)
|
||||
self._project.setProjectChanged(True)
|
||||
return
|
||||
|
||||
def setSpellLang(self, value: str | None) -> None:
|
||||
"""Set the spell check language."""
|
||||
if value != self._spellLang:
|
||||
self._spellLang = checkStringNone(value, None)
|
||||
self._project.setProjectChanged(True)
|
||||
return
|
||||
|
||||
def setLastHandle(self, value: str | None, component: str) -> None:
|
||||
"""Set a last used handle into the handle registry for a given
|
||||
@@ -288,7 +274,6 @@ class NWProjectData:
|
||||
if isinstance(component, str):
|
||||
self._lastHandle[component] = checkStringNone(value, None)
|
||||
self._project.setProjectChanged(True)
|
||||
return
|
||||
|
||||
def setLastHandles(self, value: dict) -> None:
|
||||
"""Set the full last handles dictionary to a new set of values.
|
||||
@@ -299,7 +284,6 @@ class NWProjectData:
|
||||
if key in self._lastHandle:
|
||||
self._lastHandle[key] = str(entry) if isHandle(entry) else None
|
||||
self._project.setProjectChanged(True)
|
||||
return
|
||||
|
||||
def setInitCounts(
|
||||
self, wNovel: Any = None, wNotes: Any = None, cNovel: Any = None, cNotes: Any = None
|
||||
@@ -321,7 +305,6 @@ class NWProjectData:
|
||||
count = checkInt(cNotes, 0)
|
||||
self._initCounts[3] = count
|
||||
self._currCounts[3] = count
|
||||
return
|
||||
|
||||
def setCurrCounts(
|
||||
self, wNovel: Any = None, wNotes: Any = None, cNovel: Any = None, cNotes: Any = None
|
||||
@@ -335,7 +318,6 @@ class NWProjectData:
|
||||
self._currCounts[2] = checkInt(cNovel, 0)
|
||||
if cNotes is not None:
|
||||
self._currCounts[3] = checkInt(cNotes, 0)
|
||||
return
|
||||
|
||||
def setAutoReplace(self, value: dict) -> None:
|
||||
"""Set the auto-replace dictionary."""
|
||||
@@ -345,4 +327,3 @@ class NWProjectData:
|
||||
if isinstance(entry, str):
|
||||
self._autoReplace[key] = simplified(entry)
|
||||
self._project.setProjectChanged(True)
|
||||
return
|
||||
|
||||
@@ -22,7 +22,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
@@ -72,7 +72,7 @@ class XMLReadState(Enum):
|
||||
|
||||
|
||||
class ProjectXMLReader:
|
||||
"""Core: Project XML Reader
|
||||
"""Core: Project XML Reader.
|
||||
|
||||
All data is read into a NWProjectData instance, which must be
|
||||
provided.
|
||||
@@ -124,7 +124,6 @@ class ProjectXMLReader:
|
||||
self._appVersion = ""
|
||||
self._hexVersion = 0x0
|
||||
self._timeStamp = ""
|
||||
return
|
||||
|
||||
##
|
||||
# Properties
|
||||
@@ -254,8 +253,6 @@ class ProjectXMLReader:
|
||||
elif xItem.tag == "editTime": # Moved to attribute in 1.5
|
||||
data.setEditTime(xItem.text)
|
||||
|
||||
return
|
||||
|
||||
def _parseProjectSettings(self, xSection: ET.Element, data: NWProjectData) -> None:
|
||||
"""Parse the settings section of the XML file."""
|
||||
logger.debug("Parsing <settings> section")
|
||||
@@ -294,8 +291,6 @@ class ProjectXMLReader:
|
||||
elif xItem.tag == "notesWordCount": # Moved to content attribute in 1.5
|
||||
data.setInitCounts(wNotes=xItem.text)
|
||||
|
||||
return
|
||||
|
||||
def _parseProjectContent(
|
||||
self, xSection: ET.Element, data: NWProjectData, content: list
|
||||
) -> None:
|
||||
@@ -356,8 +351,6 @@ class ProjectXMLReader:
|
||||
"nameAttr": name,
|
||||
})
|
||||
|
||||
return
|
||||
|
||||
def _parseProjectContentLegacy(
|
||||
self, xSection: ET.Element, data: NWProjectData, content: list
|
||||
) -> None:
|
||||
@@ -434,8 +427,6 @@ class ProjectXMLReader:
|
||||
"nameAttr": name,
|
||||
})
|
||||
|
||||
return
|
||||
|
||||
def _parseStatusImport(self, xItem: ET.Element, sObject: NWStatus) -> None:
|
||||
"""Parse a status or importance entry."""
|
||||
for xEntry in xItem:
|
||||
@@ -450,7 +441,6 @@ class ProjectXMLReader:
|
||||
if color is None:
|
||||
color = f"{red}, {green}, {blue}"
|
||||
sObject.add(key, xEntry.text or "", color, shape, count)
|
||||
return
|
||||
|
||||
def _parseDictKeyText(self, xItem: ET.Element) -> dict:
|
||||
"""Parse a dictionary stored with key as an attribute and the
|
||||
@@ -470,7 +460,7 @@ class ProjectXMLReader:
|
||||
|
||||
|
||||
class ProjectXMLWriter:
|
||||
"""Core: Project XML Writer
|
||||
"""Core: Project XML Writer.
|
||||
|
||||
The project writer class will only write a file according to the
|
||||
very latest spec.
|
||||
@@ -479,7 +469,6 @@ class ProjectXMLWriter:
|
||||
def __init__(self, path: str | Path) -> None:
|
||||
self._path = Path(path)
|
||||
self._error = None
|
||||
return
|
||||
|
||||
##
|
||||
# Properties
|
||||
@@ -580,7 +569,6 @@ class ProjectXMLWriter:
|
||||
"""Pack a single value into an XML element."""
|
||||
xItem = ET.SubElement(xParent, name, attrib=attrib or {})
|
||||
xItem.text = str(value) or ""
|
||||
return
|
||||
|
||||
def _packDictKeyValue(self, xParent: ET.Element, name: str, data: dict) -> None:
|
||||
"""Pack the entries of a dictionary into an XML element."""
|
||||
@@ -589,4 +577,3 @@ class ProjectXMLWriter:
|
||||
if len(key) > 0:
|
||||
xEntry = ET.SubElement(xItem, "entry", attrib={"key": key})
|
||||
xEntry.text = str(value) or ""
|
||||
return
|
||||
|
||||
@@ -20,7 +20,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
@@ -43,7 +43,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NWSessionLog:
|
||||
"""Core: Session JSON Lines Log File
|
||||
"""Core: Session JSON Lines Log File.
|
||||
|
||||
The class that wraps the session log file, which is in JSON Lines
|
||||
format. That is, one JSON object per line.
|
||||
@@ -52,7 +52,6 @@ class NWSessionLog:
|
||||
def __init__(self, project: NWProject) -> None:
|
||||
self._project = project
|
||||
self._start = 0.0
|
||||
return
|
||||
|
||||
##
|
||||
# Properties
|
||||
@@ -70,7 +69,6 @@ class NWSessionLog:
|
||||
def startSession(self) -> None:
|
||||
"""Start the writing session."""
|
||||
self._start = time()
|
||||
return
|
||||
|
||||
def appendSession(self, idleTime: float) -> bool:
|
||||
"""Append session statistics to the sessions log file."""
|
||||
|
||||
@@ -21,7 +21,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
@@ -44,7 +44,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NWSpellEnchant:
|
||||
"""Core: Enchant Spell Checking Wrapper
|
||||
"""Core: Enchant Spell Checking Wrapper.
|
||||
|
||||
This is a rapper class for Enchant to keep the API consistent
|
||||
between spell check tools.
|
||||
@@ -57,11 +57,9 @@ class NWSpellEnchant:
|
||||
self._language = None
|
||||
self._broker = None
|
||||
logger.debug("Ready: NWSpellEnchant")
|
||||
return
|
||||
|
||||
def __del__(self) -> None: # pragma: no cover
|
||||
logger.debug("Delete: NWSpellEnchant")
|
||||
return
|
||||
|
||||
##
|
||||
# Properties
|
||||
@@ -106,21 +104,19 @@ class NWSpellEnchant:
|
||||
for word in self._userDict:
|
||||
self._enchant.add_to_session(word)
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Methods
|
||||
##
|
||||
|
||||
def checkWord(self, word: str) -> bool:
|
||||
"""Wrapper function for pyenchant."""
|
||||
"""Forward check to pyenchant."""
|
||||
try:
|
||||
return bool(self._enchant.check(word))
|
||||
except Exception:
|
||||
return True
|
||||
|
||||
def suggestWords(self, word: str) -> list[str]:
|
||||
"""Wrapper function for pyenchant."""
|
||||
"""Ask pyenchant for suggestions."""
|
||||
try:
|
||||
return self._enchant.suggest(word)
|
||||
except Exception:
|
||||
@@ -172,24 +168,29 @@ class FakeEnchant:
|
||||
self.tag = ""
|
||||
self.provider = FakeProvider()
|
||||
|
||||
return
|
||||
|
||||
def check(self, word: str) -> bool:
|
||||
"""Return True for all words."""
|
||||
return True
|
||||
|
||||
def suggest(self, word: str) -> list[str]:
|
||||
"""Return an empty suggestion list."""
|
||||
return []
|
||||
|
||||
def add_to_session(self, word: str) -> None:
|
||||
"""Do nothing."""
|
||||
return
|
||||
|
||||
|
||||
class UserDictionary:
|
||||
"""Core: User Word Dictionary.
|
||||
|
||||
This class holds all the user's own words for spell checking
|
||||
purposes. The dictionary is per-project.
|
||||
"""
|
||||
|
||||
def __init__(self, project: NWProject) -> None:
|
||||
self._project = project
|
||||
self._words = set()
|
||||
return
|
||||
|
||||
def __contains__(self, word: str) -> bool:
|
||||
return word in self._words
|
||||
@@ -219,7 +220,6 @@ class UserDictionary:
|
||||
except Exception:
|
||||
logger.error("Failed to load user dictionary")
|
||||
logException()
|
||||
return
|
||||
|
||||
def save(self) -> None:
|
||||
"""Save the user's dictionary."""
|
||||
@@ -232,4 +232,3 @@ class UserDictionary:
|
||||
except Exception:
|
||||
logger.error("Failed to save user dictionary")
|
||||
logException()
|
||||
return
|
||||
|
||||
@@ -21,7 +21,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
@@ -49,6 +49,7 @@ CUSTOM_COL = "custom"
|
||||
|
||||
@dataclasses.dataclass
|
||||
class StatusEntry:
|
||||
"""DataClass: Status Label Values."""
|
||||
|
||||
name: str
|
||||
color: QColor
|
||||
@@ -73,6 +74,7 @@ T_StatusKind = Literal["s", "i"]
|
||||
|
||||
|
||||
class NWStatus:
|
||||
"""Core: Status/Importance Label Class."""
|
||||
|
||||
STATUS = "s"
|
||||
IMPORT = "i"
|
||||
@@ -84,7 +86,6 @@ class NWStatus:
|
||||
self._default = None
|
||||
self._prefix = prefix[:1]
|
||||
self._height = SHARED.theme.baseIconHeight
|
||||
return
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self._store)
|
||||
@@ -133,8 +134,6 @@ class NWStatus:
|
||||
if self._default not in self._store:
|
||||
self._default = next(iter(self._store)) if self._store else None
|
||||
|
||||
return
|
||||
|
||||
def check(self, value: str) -> str:
|
||||
"""Check the key against the stored status names."""
|
||||
if self._isKey(value) and value in self._store:
|
||||
@@ -147,13 +146,11 @@ class NWStatus:
|
||||
"""Clear the counts of references to the status entries."""
|
||||
for entry in self._store.values():
|
||||
entry.count = 0
|
||||
return
|
||||
|
||||
def increment(self, key: str | None) -> None:
|
||||
"""Increment the counter for a given entry."""
|
||||
if key and key in self._store:
|
||||
self._store[key].count += 1
|
||||
return
|
||||
|
||||
def pack(self) -> Iterable[tuple[str, dict]]:
|
||||
"""Pack the status entries into a dictionary."""
|
||||
@@ -195,7 +192,6 @@ class NWStatus:
|
||||
if entry.theme != CUSTOM_COL:
|
||||
entry.color = SHARED.theme.parseColor(entry.theme)
|
||||
entry.icon = NWStatus.createIcon(self._height, entry.color, entry.shape)
|
||||
return
|
||||
|
||||
@staticmethod
|
||||
def createIcon(height: int, color: QColor, shape: nwStatusShape) -> QIcon:
|
||||
@@ -252,7 +248,6 @@ class _ShapeCache:
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._cache: dict[nwStatusShape, QPainterPath] = {}
|
||||
return
|
||||
|
||||
def getShape(self, shape: nwStatusShape) -> QPainterPath:
|
||||
"""Return a painter shape for an icon."""
|
||||
|
||||
@@ -20,7 +20,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
@@ -47,6 +47,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NWStorageOpen(Enum):
|
||||
"""The status of a storage location."""
|
||||
|
||||
UNKOWN = 0
|
||||
NOT_FOUND = 1
|
||||
@@ -56,6 +57,7 @@ class NWStorageOpen(Enum):
|
||||
|
||||
|
||||
class NWStorageCreate(Enum):
|
||||
"""The status of a new storage location."""
|
||||
|
||||
NOT_EMPTY = 0
|
||||
OS_ERROR = 1
|
||||
@@ -63,7 +65,7 @@ class NWStorageCreate(Enum):
|
||||
|
||||
|
||||
class NWStorage:
|
||||
"""Core: Project Storage Class
|
||||
"""Core: Project Storage Class.
|
||||
|
||||
The class that handles all paths related to the project storage.
|
||||
"""
|
||||
@@ -81,7 +83,6 @@ class NWStorage:
|
||||
self._openMode = self.MODE_INACTIVE
|
||||
self._ready = False
|
||||
self._exception = None
|
||||
return
|
||||
|
||||
def clear(self) -> None:
|
||||
"""Reset internal variables."""
|
||||
@@ -90,7 +91,6 @@ class NWStorage:
|
||||
self._lockFilePath = None
|
||||
self._openMode = self.MODE_INACTIVE
|
||||
self._ready = False
|
||||
return
|
||||
|
||||
##
|
||||
# Properties
|
||||
@@ -252,13 +252,11 @@ class NWStorage:
|
||||
"""Lock the session when the project is successfully opened."""
|
||||
if self._ready:
|
||||
self._writeLockFile()
|
||||
return
|
||||
|
||||
def closeSession(self) -> None:
|
||||
"""Run tasks related to closing the session."""
|
||||
self._clearLockFile()
|
||||
self.clear()
|
||||
return
|
||||
|
||||
##
|
||||
# Content Access Methods
|
||||
@@ -394,7 +392,7 @@ class NWStorage:
|
||||
|
||||
|
||||
class _LegacyStorage:
|
||||
"""Core: Legacy Storage Converter Utils
|
||||
"""Core: Legacy Storage Converter Utils.
|
||||
|
||||
A class with various functions to convert old file formats and
|
||||
file/folder layouts to the current project format.
|
||||
@@ -402,7 +400,6 @@ class _LegacyStorage:
|
||||
|
||||
def __init__(self, project: NWProject) -> None:
|
||||
self._project = project
|
||||
return
|
||||
|
||||
def legacyDataFolder(self, path: Path, child: Path) -> None:
|
||||
"""Handle the content of a legacy data folder from a version 1.0
|
||||
@@ -484,8 +481,6 @@ class _LegacyStorage:
|
||||
except Exception as exc:
|
||||
logger.warning("Failed to delete: %s", item, exc_info=exc)
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
##
|
||||
|
||||
@@ -21,7 +21,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
@@ -50,7 +50,7 @@ MAX_DEPTH = 999 # Cap of tree traversing for loops (recursion limit)
|
||||
|
||||
|
||||
class NWTree:
|
||||
"""Core: Project Tree Data Class
|
||||
"""Core: Project Tree Data Class.
|
||||
|
||||
Only one instance of this class should exist in the project class.
|
||||
This class holds all the project items of the project as instances
|
||||
@@ -71,18 +71,16 @@ class NWTree:
|
||||
self._trash = None
|
||||
self._ready = False
|
||||
logger.debug("Ready: NWTree")
|
||||
return
|
||||
|
||||
def __del__(self) -> None: # pragma: no cover
|
||||
logger.debug("Delete: NWTree")
|
||||
return
|
||||
|
||||
def __len__(self) -> int:
|
||||
"""The number of items in the project."""
|
||||
"""Return the number of items in the project."""
|
||||
return len(self._items)
|
||||
|
||||
def __bool__(self) -> bool:
|
||||
"""True if there are any items in the project."""
|
||||
"""Return True if there are any items in the project."""
|
||||
return bool(self._items)
|
||||
|
||||
def __getitem__(self, tHandle: str | None) -> NWItem | None:
|
||||
@@ -95,7 +93,7 @@ class NWTree:
|
||||
return None
|
||||
|
||||
def __contains__(self, tHandle: str) -> bool:
|
||||
"""Checks if a handle exists in the tree."""
|
||||
"""Check if a handle exists in the tree."""
|
||||
return tHandle in self._items
|
||||
|
||||
def __iter__(self) -> Iterator[NWItem]:
|
||||
@@ -142,7 +140,6 @@ class NWTree:
|
||||
self._trash = None
|
||||
oldModel.deleteLater()
|
||||
del oldModel
|
||||
return
|
||||
|
||||
def add(self, item: NWItem, pos: int = -1) -> bool:
|
||||
"""Add a project item into the project tree."""
|
||||
@@ -260,8 +257,6 @@ class NWTree:
|
||||
self._model.endInsertRows()
|
||||
self._model.layoutChanged.emit()
|
||||
|
||||
return
|
||||
|
||||
def pickParent(self, sNode: ProjectNode, hLevel: int, isNote: bool) -> tuple[str | None, int]:
|
||||
"""Pick an appropriate parent handle for adding a new item."""
|
||||
if sNode.item.isFolderType() or sNode.item.isRootType():
|
||||
@@ -299,7 +294,6 @@ class NWTree:
|
||||
indexE = self._model.indexFromNode(node, 3)
|
||||
self._model.dataChanged.emit(indexS, indexE)
|
||||
self._itemChange(node.item, nwChange.UPDATE)
|
||||
return
|
||||
|
||||
def refreshAllItems(self) -> None:
|
||||
"""Refresh all items in the tree."""
|
||||
@@ -309,13 +303,11 @@ class NWTree:
|
||||
self._model.root.refresh()
|
||||
self._model.root.updateCount(propagate=False)
|
||||
self._model.layoutChanged.emit()
|
||||
return
|
||||
|
||||
def novelStructureChanged(self, tHandle: str) -> None:
|
||||
"""Emit a novel structure change signal."""
|
||||
if self._ready:
|
||||
SHARED.novelStructureChanged.emit(tHandle)
|
||||
return
|
||||
|
||||
def checkConsistency(self, prefix: str) -> tuple[int, int]:
|
||||
"""Check the project tree consistency. Also check the content
|
||||
@@ -496,7 +488,6 @@ class NWTree:
|
||||
SHARED.emitProjectItemChanged(self._project, tHandle, change)
|
||||
if item.isRootType():
|
||||
SHARED.emitRootFolderChanged(self._project, tHandle, change)
|
||||
return
|
||||
|
||||
def _getTrashNode(self) -> ProjectNode | None:
|
||||
"""Get the trash node. If it doesn't exist, create it."""
|
||||
|
||||
@@ -20,7 +20,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
@@ -45,6 +45,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GuiAbout(NDialog):
|
||||
"""GUI: About novelWriter Dialog."""
|
||||
|
||||
def __init__(self, parent: QWidget) -> None:
|
||||
super().__init__(parent=parent)
|
||||
@@ -109,11 +110,8 @@ class GuiAbout(NDialog):
|
||||
|
||||
logger.debug("Ready: GuiAbout")
|
||||
|
||||
return
|
||||
|
||||
def __del__(self) -> None: # pragma: no cover
|
||||
logger.debug("Delete: GuiAbout")
|
||||
return
|
||||
|
||||
##
|
||||
# Events
|
||||
@@ -123,7 +121,6 @@ class GuiAbout(NDialog):
|
||||
"""Capture the close event and perform cleanup."""
|
||||
event.accept()
|
||||
self.softDelete()
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
@@ -135,7 +132,6 @@ class GuiAbout(NDialog):
|
||||
self.txtCredits.setHtml(html)
|
||||
else:
|
||||
self.txtCredits.setHtml("Error loading credits text ...")
|
||||
return
|
||||
|
||||
def _setStyleSheet(self) -> None:
|
||||
"""Set stylesheet text document."""
|
||||
@@ -143,4 +139,3 @@ class GuiAbout(NDialog):
|
||||
self.txtCredits.setStyleSheet(
|
||||
f"QTextBrowser {{border: none; background: {baseCol};}} "
|
||||
)
|
||||
return
|
||||
|
||||
@@ -21,7 +21,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
@@ -42,6 +42,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GuiDocMerge(NDialog):
|
||||
"""GUI: Document Merge Tool."""
|
||||
|
||||
D_HANDLE = QtUserRole
|
||||
|
||||
@@ -110,11 +111,8 @@ class GuiDocMerge(NDialog):
|
||||
|
||||
logger.debug("Ready: GuiDocMerge")
|
||||
|
||||
return
|
||||
|
||||
def __del__(self) -> None: # pragma: no cover
|
||||
logger.debug("Delete: GuiDocMerge")
|
||||
return
|
||||
|
||||
def data(self) -> dict:
|
||||
"""Return the user's choices."""
|
||||
@@ -150,7 +148,6 @@ class GuiDocMerge(NDialog):
|
||||
if sHandle := self._data.get("sHandle"):
|
||||
itemList = self._data.get("origItems", [])
|
||||
self._loadContent(sHandle, itemList)
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
@@ -170,4 +167,3 @@ class GuiDocMerge(NDialog):
|
||||
item.setData(self.D_HANDLE, tHandle)
|
||||
item.setCheckState(Qt.CheckState.Checked)
|
||||
self.listBox.addItem(item)
|
||||
return
|
||||
|
||||
@@ -21,7 +21,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
@@ -42,6 +42,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GuiDocSplit(NDialog):
|
||||
"""GUI: Document Split Tool."""
|
||||
|
||||
LINE_ROLE = QtUserRole
|
||||
LEVEL_ROLE = QtUserRole + 1
|
||||
@@ -139,11 +140,8 @@ class GuiDocSplit(NDialog):
|
||||
|
||||
logger.debug("Ready: GuiDocSplit")
|
||||
|
||||
return
|
||||
|
||||
def __del__(self) -> None: # pragma: no cover
|
||||
logger.debug("Delete: GuiDocSplit")
|
||||
return
|
||||
|
||||
def data(self) -> tuple[dict, list[str]]:
|
||||
"""Return the user's choices. Also save the users options for
|
||||
@@ -197,7 +195,6 @@ class GuiDocSplit(NDialog):
|
||||
"""Reload the content of the list box."""
|
||||
if sHandle := self._data.get("sHandle"):
|
||||
self._loadContent(sHandle)
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
|
||||
@@ -20,7 +20,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
@@ -34,6 +34,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GuiEditLabel(NDialog):
|
||||
"""GUI: Edit Item Label Dialog."""
|
||||
|
||||
def __init__(self, parent: QWidget, text: str = "") -> None:
|
||||
super().__init__(parent=parent)
|
||||
@@ -72,11 +73,8 @@ class GuiEditLabel(NDialog):
|
||||
|
||||
logger.debug("Ready: GuiEditLabel")
|
||||
|
||||
return
|
||||
|
||||
def __del__(self) -> None: # pragma: no cover
|
||||
logger.debug("Delete: GuiEditLabel")
|
||||
return
|
||||
|
||||
@property
|
||||
def itemLabel(self) -> str:
|
||||
|
||||
@@ -21,7 +21,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
@@ -50,6 +50,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GuiPreferences(NDialog):
|
||||
"""GUI: Preferences Dialog."""
|
||||
|
||||
newPreferencesReady = pyqtSignal(bool, bool, bool, bool)
|
||||
|
||||
@@ -125,11 +126,8 @@ class GuiPreferences(NDialog):
|
||||
|
||||
logger.debug("Ready: GuiPreferences")
|
||||
|
||||
return
|
||||
|
||||
def __del__(self) -> None: # pragma: no cover
|
||||
logger.debug("Delete: GuiPreferences")
|
||||
return
|
||||
|
||||
def buildForm(self) -> None:
|
||||
"""Build the settings form."""
|
||||
@@ -856,8 +854,6 @@ class GuiPreferences(NDialog):
|
||||
self.mainForm.finalise()
|
||||
self.sidebar.setSelected(1)
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Events
|
||||
##
|
||||
@@ -868,7 +864,6 @@ class GuiPreferences(NDialog):
|
||||
self._saveWindowSize()
|
||||
event.accept()
|
||||
self.softDelete()
|
||||
return
|
||||
|
||||
def keyPressEvent(self, event: QKeyEvent) -> None:
|
||||
"""Overload keyPressEvent and only accept escape. The main
|
||||
@@ -878,7 +873,6 @@ class GuiPreferences(NDialog):
|
||||
if event.matches(QKeySequence.StandardKey.Cancel):
|
||||
self.close()
|
||||
event.ignore()
|
||||
return
|
||||
|
||||
##
|
||||
# Private Slots
|
||||
@@ -888,13 +882,11 @@ class GuiPreferences(NDialog):
|
||||
def _sidebarClicked(self, section: int) -> None:
|
||||
"""Process a user request to switch page."""
|
||||
self.mainForm.scrollToSection(section)
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _gotoSearch(self) -> None:
|
||||
"""Go to the setting indicated by the search text."""
|
||||
self.mainForm.scrollToLabel(self.searchText.text().strip())
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _selectGuiFont(self) -> None:
|
||||
@@ -904,7 +896,6 @@ class GuiPreferences(NDialog):
|
||||
self.guiFont.setText(describeFont(font))
|
||||
self.guiFont.setCursorPosition(0)
|
||||
self._guiFont = font
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _selectTextFont(self) -> None:
|
||||
@@ -914,7 +905,6 @@ class GuiPreferences(NDialog):
|
||||
self.textFont.setText(describeFont(font))
|
||||
self.textFont.setCursorPosition(0)
|
||||
self._textFont = font
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _backupFolder(self) -> None:
|
||||
@@ -925,13 +915,11 @@ class GuiPreferences(NDialog):
|
||||
):
|
||||
self.backupPath = path
|
||||
self.mainForm.setHelpText("backupPath", self.tr("Path: {0}").format(path))
|
||||
return
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def _toggledBackupOnClose(self, state: bool) -> None:
|
||||
"""Toggle switch that depends on the backup on close switch."""
|
||||
self.askBeforeBackup.setEnabled(state)
|
||||
return
|
||||
|
||||
@pyqtSlot(str)
|
||||
def _insertDialogLineSymbol(self, symbol: str) -> None:
|
||||
@@ -939,7 +927,6 @@ class GuiPreferences(NDialog):
|
||||
current = self.dialogLine.text()
|
||||
values = processDialogSymbols(f"{current} {symbol}")
|
||||
self.dialogLine.setText(" ".join(values))
|
||||
return
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def _toggleAutoReplaceMain(self, state: bool) -> None:
|
||||
@@ -949,7 +936,6 @@ class GuiPreferences(NDialog):
|
||||
self.doReplaceDash.setEnabled(state)
|
||||
self.doReplaceDots.setEnabled(state)
|
||||
self.fmtPadThin.setEnabled(state)
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _changeSingleQuoteOpen(self) -> None:
|
||||
@@ -957,7 +943,6 @@ class GuiPreferences(NDialog):
|
||||
quote, status = GuiQuoteSelect.getQuote(self, current=self.fmtSQuoteOpen.text())
|
||||
if status:
|
||||
self.fmtSQuoteOpen.setText(quote)
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _changeSingleQuoteClose(self) -> None:
|
||||
@@ -965,7 +950,6 @@ class GuiPreferences(NDialog):
|
||||
quote, status = GuiQuoteSelect.getQuote(self, current=self.fmtSQuoteClose.text())
|
||||
if status:
|
||||
self.fmtSQuoteClose.setText(quote)
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _changeDoubleQuoteOpen(self) -> None:
|
||||
@@ -973,7 +957,6 @@ class GuiPreferences(NDialog):
|
||||
quote, status = GuiQuoteSelect.getQuote(self, current=self.fmtDQuoteOpen.text())
|
||||
if status:
|
||||
self.fmtDQuoteOpen.setText(quote)
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _changeDoubleQuoteClose(self) -> None:
|
||||
@@ -981,7 +964,6 @@ class GuiPreferences(NDialog):
|
||||
quote, status = GuiQuoteSelect.getQuote(self, current=self.fmtDQuoteClose.text())
|
||||
if status:
|
||||
self.fmtDQuoteClose.setText(quote)
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
@@ -990,7 +972,6 @@ class GuiPreferences(NDialog):
|
||||
def _saveWindowSize(self) -> None:
|
||||
"""Save the dialog window size."""
|
||||
CONFIG.setPreferencesWinSize(self.width(), self.height())
|
||||
return
|
||||
|
||||
def _doSave(self) -> None:
|
||||
"""Save the values set in the form."""
|
||||
@@ -1134,5 +1115,3 @@ class GuiPreferences(NDialog):
|
||||
self.newPreferencesReady.emit(needsRestart, refreshTree, updateTheme, updateSyntax)
|
||||
|
||||
self.close()
|
||||
|
||||
return
|
||||
|
||||
@@ -21,7 +21,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import csv
|
||||
@@ -55,6 +55,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GuiProjectSettings(NDialog):
|
||||
"""GUI: Project Settings DIalog."""
|
||||
|
||||
PAGE_SETTINGS = 0
|
||||
PAGE_STATUS = 1
|
||||
@@ -137,11 +138,8 @@ class GuiProjectSettings(NDialog):
|
||||
|
||||
logger.debug("Ready: GuiProjectSettings")
|
||||
|
||||
return
|
||||
|
||||
def __del__(self) -> None: # pragma: no cover
|
||||
logger.debug("Delete: GuiProjectSettings")
|
||||
return
|
||||
|
||||
##
|
||||
# Events
|
||||
@@ -152,7 +150,6 @@ class GuiProjectSettings(NDialog):
|
||||
self._saveSettings()
|
||||
event.accept()
|
||||
self.softDelete()
|
||||
return
|
||||
|
||||
##
|
||||
# Private Slots
|
||||
@@ -169,7 +166,6 @@ class GuiProjectSettings(NDialog):
|
||||
self.mainStack.setCurrentWidget(self.importPage)
|
||||
elif pageId == self.PAGE_REPLACE:
|
||||
self.mainStack.setCurrentWidget(self.replacePage)
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _doSave(self) -> None:
|
||||
@@ -203,8 +199,6 @@ class GuiProjectSettings(NDialog):
|
||||
QApplication.processEvents()
|
||||
self.close()
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
##
|
||||
@@ -223,8 +217,6 @@ class GuiProjectSettings(NDialog):
|
||||
options.setValue("GuiProjectSettings", "importColW", importColW)
|
||||
options.setValue("GuiProjectSettings", "replaceColW", replaceColW)
|
||||
|
||||
return
|
||||
|
||||
|
||||
class _SettingsPage(NScrollableForm):
|
||||
|
||||
@@ -295,8 +287,6 @@ class _SettingsPage(NScrollableForm):
|
||||
|
||||
self.finalise()
|
||||
|
||||
return
|
||||
|
||||
|
||||
class _StatusPage(NFixedPage):
|
||||
|
||||
@@ -478,8 +468,6 @@ class _StatusPage(NFixedPage):
|
||||
self.setCentralLayout(self.outerBox)
|
||||
self._setButtonIcons()
|
||||
|
||||
return
|
||||
|
||||
@property
|
||||
def changed(self) -> bool:
|
||||
"""The user changed these settings."""
|
||||
@@ -518,7 +506,6 @@ class _StatusPage(NFixedPage):
|
||||
entry.name = name
|
||||
item.setText(self.C_LABEL, name)
|
||||
self._changed = True
|
||||
return
|
||||
|
||||
@pyqtSlot(int)
|
||||
def _onThemeSelect(self, index: int) -> None:
|
||||
@@ -526,7 +513,6 @@ class _StatusPage(NFixedPage):
|
||||
self._theme = str(self.iconColor.currentData())
|
||||
self._setButtonIcons()
|
||||
self._updateIcon()
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _onColorSelect(self) -> None:
|
||||
@@ -536,7 +522,6 @@ class _StatusPage(NFixedPage):
|
||||
self._theme = CUSTOM_COL
|
||||
self._setButtonIcons()
|
||||
self._updateIcon()
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _onItemCreate(self) -> None:
|
||||
@@ -547,7 +532,6 @@ class _StatusPage(NFixedPage):
|
||||
theme = str(self.iconColor.currentData())
|
||||
self._addItem(None, StatusEntry(self.tr("New Item"), color, theme, shape, icon, 0))
|
||||
self._changed = True
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _onItemDelete(self) -> None:
|
||||
@@ -560,7 +544,6 @@ class _StatusPage(NFixedPage):
|
||||
else:
|
||||
self.listBox.takeTopLevelItem(iRow)
|
||||
self._changed = True
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _onSelectionChanged(self) -> None:
|
||||
@@ -593,7 +576,6 @@ class _StatusPage(NFixedPage):
|
||||
self.iconColor.setEnabled(False)
|
||||
self.colorButton.setEnabled(False)
|
||||
self.shapeButton.setEnabled(False)
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _importLabels(self) -> None:
|
||||
@@ -630,7 +612,6 @@ class _StatusPage(NFixedPage):
|
||||
writer.writerow([entry.shape.name, entry.color.name(), entry.name])
|
||||
except Exception as exc:
|
||||
SHARED.error("Could not write file.", exc=exc)
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
@@ -641,7 +622,6 @@ class _StatusPage(NFixedPage):
|
||||
self._shape = shape
|
||||
self._setButtonIcons()
|
||||
self._updateIcon()
|
||||
return
|
||||
|
||||
def _updateIcon(self) -> None:
|
||||
"""Apply changes made to a status icon."""
|
||||
@@ -654,7 +634,6 @@ class _StatusPage(NFixedPage):
|
||||
entry.icon = icon
|
||||
item.setIcon(self.C_LABEL, icon)
|
||||
self._changed = True
|
||||
return
|
||||
|
||||
def _addItem(self, key: str | None, entry: StatusEntry) -> None:
|
||||
"""Add a status item to the list."""
|
||||
@@ -665,7 +644,6 @@ class _StatusPage(NFixedPage):
|
||||
item.setData(self.C_DATA, self.D_KEY, key)
|
||||
item.setData(self.C_DATA, self.D_ENTRY, entry)
|
||||
self.listBox.addTopLevelItem(item)
|
||||
return
|
||||
|
||||
def _moveItem(self, step: int) -> None:
|
||||
"""Move and item up or down step."""
|
||||
@@ -678,7 +656,6 @@ class _StatusPage(NFixedPage):
|
||||
self.listBox.clearSelection()
|
||||
cItem.setSelected(True)
|
||||
self._changed = True
|
||||
return
|
||||
|
||||
def _getSelectedItem(self) -> QTreeWidgetItem | None:
|
||||
"""Get the currently selected item."""
|
||||
@@ -701,7 +678,6 @@ class _StatusPage(NFixedPage):
|
||||
self.iconColor.setCurrentData(self._theme, CUSTOM_COL)
|
||||
self.colorButton.setIcon(icon)
|
||||
self.shapeButton.setIcon(self._icons[self._shape])
|
||||
return
|
||||
|
||||
def _pickColor(self) -> QColor:
|
||||
"""Get the correct colour value based on selections."""
|
||||
@@ -789,8 +765,6 @@ class _ReplacePage(NFixedPage):
|
||||
|
||||
self.setCentralLayout(self.outerBox)
|
||||
|
||||
return
|
||||
|
||||
@property
|
||||
def changed(self) -> bool:
|
||||
"""The user changed these settings."""
|
||||
@@ -823,7 +797,6 @@ class _ReplacePage(NFixedPage):
|
||||
if (item := self._getSelectedItem()) and (key := self._stripKey(text)):
|
||||
item.setText(self.C_KEY, f"<{key}>")
|
||||
self._changed = True
|
||||
return
|
||||
|
||||
@pyqtSlot(str)
|
||||
def _onValueEdit(self, text: str) -> None:
|
||||
@@ -831,7 +804,6 @@ class _ReplacePage(NFixedPage):
|
||||
if item := self._getSelectedItem():
|
||||
item.setText(self.C_REPL, text)
|
||||
self._changed = True
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _onSelectionChanged(self) -> None:
|
||||
@@ -850,14 +822,12 @@ class _ReplacePage(NFixedPage):
|
||||
self.editValue.setText("")
|
||||
self.editKey.setEnabled(False)
|
||||
self.editValue.setEnabled(False)
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _onEntryCreated(self) -> None:
|
||||
"""Add a new list entry."""
|
||||
key = f"<keyword{self.listBox.topLevelItemCount() + 1:d}>"
|
||||
self.listBox.addTopLevelItem(QTreeWidgetItem([key, ""]))
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _onEntryDeleted(self) -> None:
|
||||
@@ -865,7 +835,6 @@ class _ReplacePage(NFixedPage):
|
||||
if item := self._getSelectedItem():
|
||||
self.listBox.takeTopLevelItem(self.listBox.indexOfTopLevelItem(item))
|
||||
self._changed = True
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
|
||||
@@ -20,7 +20,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
@@ -43,6 +43,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GuiQuoteSelect(NDialog):
|
||||
"""GUI: Quote Selector Dialog."""
|
||||
|
||||
_selected = ""
|
||||
|
||||
@@ -108,11 +109,8 @@ class GuiQuoteSelect(NDialog):
|
||||
|
||||
logger.debug("Ready: GuiQuoteSelect")
|
||||
|
||||
return
|
||||
|
||||
def __del__(self) -> None: # pragma: no cover
|
||||
logger.debug("Delete: GuiQuoteSelect")
|
||||
return
|
||||
|
||||
@property
|
||||
def selectedQuote(self) -> str:
|
||||
@@ -140,4 +138,3 @@ class GuiQuoteSelect(NDialog):
|
||||
quote = items[0].data(self.D_KEY)
|
||||
self.previewLabel.setText(quote)
|
||||
self._selected = quote
|
||||
return
|
||||
|
||||
@@ -20,7 +20,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
@@ -48,6 +48,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GuiWordList(NDialog):
|
||||
"""GUI: User Dictionary Edit Tool."""
|
||||
|
||||
newWordListReady = pyqtSignal()
|
||||
|
||||
@@ -128,11 +129,8 @@ class GuiWordList(NDialog):
|
||||
|
||||
logger.debug("Ready: GuiWordList")
|
||||
|
||||
return
|
||||
|
||||
def __del__(self) -> None: # pragma: no cover
|
||||
logger.debug("Delete: GuiWordList")
|
||||
return
|
||||
|
||||
##
|
||||
# Events
|
||||
@@ -143,7 +141,6 @@ class GuiWordList(NDialog):
|
||||
self._saveGuiSettings()
|
||||
event.accept()
|
||||
self.softDelete()
|
||||
return
|
||||
|
||||
##
|
||||
# Private Slots
|
||||
@@ -159,14 +156,12 @@ class GuiWordList(NDialog):
|
||||
if items := self.listBox.findItems(word, Qt.MatchFlag.MatchExactly):
|
||||
self.listBox.setCurrentItem(items[0])
|
||||
self.listBox.scrollToItem(items[0], QAbstractItemView.ScrollHint.PositionAtCenter)
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _doDelete(self) -> None:
|
||||
"""Delete the selected items."""
|
||||
for item in self.listBox.selectedItems():
|
||||
self.listBox.takeItem(self.listBox.row(item))
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _doSave(self) -> None:
|
||||
@@ -178,7 +173,6 @@ class GuiWordList(NDialog):
|
||||
self.newWordListReady.emit()
|
||||
QApplication.processEvents()
|
||||
self.close()
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _importWords(self) -> None:
|
||||
@@ -213,7 +207,6 @@ class GuiWordList(NDialog):
|
||||
fo.write("\n".join(self._listWords()))
|
||||
except Exception as exc:
|
||||
SHARED.error("Could not write file.", exc=exc)
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
@@ -226,7 +219,6 @@ class GuiWordList(NDialog):
|
||||
self.listBox.clear()
|
||||
for word in userDict:
|
||||
self.listBox.addItem(word)
|
||||
return
|
||||
|
||||
def _saveGuiSettings(self) -> None:
|
||||
"""Save GUI settings."""
|
||||
@@ -234,14 +226,12 @@ class GuiWordList(NDialog):
|
||||
pOptions = SHARED.project.options
|
||||
pOptions.setValue("GuiWordList", "winWidth", self.width())
|
||||
pOptions.setValue("GuiWordList", "winHeight", self.height())
|
||||
return
|
||||
|
||||
def _addWord(self, word: str) -> None:
|
||||
"""Add a single word to the list."""
|
||||
if word and not self.listBox.findItems(word, Qt.MatchFlag.MatchExactly):
|
||||
self.listBox.addItem(word)
|
||||
self._changed = True
|
||||
return
|
||||
|
||||
def _listWords(self) -> list[str]:
|
||||
"""List all words in the list box."""
|
||||
|
||||
+16
-1
@@ -20,12 +20,13 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class nwItemType(Enum):
|
||||
"""Enum: Project Item Types."""
|
||||
|
||||
NO_TYPE = 0
|
||||
ROOT = 1
|
||||
@@ -34,6 +35,7 @@ class nwItemType(Enum):
|
||||
|
||||
|
||||
class nwItemClass(Enum):
|
||||
"""Enum: Project Item Classes."""
|
||||
|
||||
NO_CLASS = 0
|
||||
NOVEL = 1
|
||||
@@ -50,6 +52,7 @@ class nwItemClass(Enum):
|
||||
|
||||
|
||||
class nwItemLayout(Enum):
|
||||
"""A project item's layout."""
|
||||
|
||||
NO_LAYOUT = 0
|
||||
DOCUMENT = 1
|
||||
@@ -57,6 +60,7 @@ class nwItemLayout(Enum):
|
||||
|
||||
|
||||
class nwComment(Enum):
|
||||
"""Types of text comments."""
|
||||
|
||||
PLAIN = 0
|
||||
IGNORE = 1
|
||||
@@ -69,6 +73,7 @@ class nwComment(Enum):
|
||||
|
||||
|
||||
class nwChange(Enum):
|
||||
"""Change request modes."""
|
||||
|
||||
CREATE = 0
|
||||
UPDATE = 1
|
||||
@@ -76,12 +81,14 @@ class nwChange(Enum):
|
||||
|
||||
|
||||
class nwDocMode(Enum):
|
||||
"""Document open modes."""
|
||||
|
||||
VIEW = 0
|
||||
EDIT = 1
|
||||
|
||||
|
||||
class nwDocAction(Enum):
|
||||
"""Document actions."""
|
||||
|
||||
NO_ACTION = 0
|
||||
UNDO = 1
|
||||
@@ -125,6 +132,7 @@ class nwDocAction(Enum):
|
||||
|
||||
|
||||
class nwDocInsert(Enum):
|
||||
"""Document insert actions."""
|
||||
|
||||
NO_INSERT = 0
|
||||
QUOTE_LS = 1
|
||||
@@ -142,6 +150,7 @@ class nwDocInsert(Enum):
|
||||
|
||||
|
||||
class nwView(Enum):
|
||||
"""Main GUI view modes."""
|
||||
|
||||
EDITOR = 0
|
||||
PROJECT = 1
|
||||
@@ -151,6 +160,7 @@ class nwView(Enum):
|
||||
|
||||
|
||||
class nwFocus(Enum):
|
||||
"""Main GUI panel focus."""
|
||||
|
||||
TREE = 1
|
||||
DOCUMENT = 2
|
||||
@@ -158,6 +168,7 @@ class nwFocus(Enum):
|
||||
|
||||
|
||||
class nwTheme(Enum):
|
||||
"""GUI theme colour modes."""
|
||||
|
||||
AUTO = 0
|
||||
LIGHT = 1
|
||||
@@ -165,6 +176,7 @@ class nwTheme(Enum):
|
||||
|
||||
|
||||
class nwOutline(Enum):
|
||||
"""Enum: Project Outline Columns."""
|
||||
|
||||
TITLE = 0
|
||||
LEVEL = 1
|
||||
@@ -189,6 +201,7 @@ class nwOutline(Enum):
|
||||
|
||||
|
||||
class nwNovelExtra(Enum):
|
||||
"""Enum: Novel View Extra Columns."""
|
||||
|
||||
HIDDEN = 0
|
||||
POV = 1
|
||||
@@ -197,6 +210,7 @@ class nwNovelExtra(Enum):
|
||||
|
||||
|
||||
class nwBuildFmt(Enum):
|
||||
"""Enum: Manuscript Document Formats."""
|
||||
|
||||
ODT = 0
|
||||
FODT = 1
|
||||
@@ -211,6 +225,7 @@ class nwBuildFmt(Enum):
|
||||
|
||||
|
||||
class nwStatusShape(Enum):
|
||||
"""Enum: Status/Importance Icon Shapes."""
|
||||
|
||||
SQUARE = 0
|
||||
TRIANGLE = 1
|
||||
|
||||
+5
-10
@@ -20,7 +20,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
@@ -47,7 +47,6 @@ def logException() -> None:
|
||||
exType, exValue, _ = sys.exc_info()
|
||||
if exType is not None:
|
||||
logger.error(f"{exType.__name__}: {exValue!s}", stacklevel=2)
|
||||
return
|
||||
|
||||
|
||||
def formatException(exc: BaseException) -> str:
|
||||
@@ -58,6 +57,7 @@ def formatException(exc: BaseException) -> str:
|
||||
|
||||
|
||||
class NWErrorMessage(QDialog):
|
||||
"""GUI: Error Dialog."""
|
||||
|
||||
def __init__(self, parent: QWidget) -> None:
|
||||
super().__init__(parent=parent)
|
||||
@@ -110,8 +110,6 @@ class NWErrorMessage(QDialog):
|
||||
self.setSizeGripEnabled(True)
|
||||
self.resize(800, 400)
|
||||
|
||||
return
|
||||
|
||||
def setMessage(self, exType: type, exValue: BaseException, exTrace: TracebackType) -> None:
|
||||
"""Generate a message and append session data, error info and
|
||||
error traceback.
|
||||
@@ -143,7 +141,7 @@ class NWErrorMessage(QDialog):
|
||||
enchantVersion = "Unknown"
|
||||
|
||||
try:
|
||||
txtTrace = "\n".join(format_tb(exTrace))
|
||||
trace = "\n".join(format_tb(exTrace))
|
||||
self.msgBody.setPlainText(
|
||||
"Environment:\n"
|
||||
f"novelWriter Version: {__version__}\n"
|
||||
@@ -152,13 +150,11 @@ class NWErrorMessage(QDialog):
|
||||
f"Qt: {QT_VERSION_STR}, PyQt: {PYQT_VERSION_STR}\n"
|
||||
f"enchant: {enchantVersion}\n\n"
|
||||
f"{exType.__name__}:\n{exValue!s}\n\n"
|
||||
f"Traceback:\n{txtTrace}\n"
|
||||
f"Traceback:\n{trace}\n"
|
||||
)
|
||||
except Exception:
|
||||
self.msgBody.setPlainText("Failed to generate error report ...")
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Slots
|
||||
##
|
||||
@@ -167,11 +163,10 @@ class NWErrorMessage(QDialog):
|
||||
def _doClose(self) -> None:
|
||||
"""Close the dialog."""
|
||||
self.close()
|
||||
return
|
||||
|
||||
|
||||
def exceptionHandler(exType: type, exValue: BaseException, exTrace: TracebackType) -> None:
|
||||
"""Function to catch unhandled global exceptions."""
|
||||
"""Catch unhandled global exceptions."""
|
||||
from traceback import print_tb
|
||||
|
||||
from PyQt6.QtWidgets import QApplication
|
||||
|
||||
@@ -24,7 +24,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
from PyQt6.QtGui import QColor, QFont, QPalette, QPixmap
|
||||
@@ -39,7 +39,7 @@ DEFAULT_SCALE = 0.9
|
||||
|
||||
|
||||
class NFixedPage(QFrame):
|
||||
"""Extension: Fixed Page Widget
|
||||
"""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.
|
||||
@@ -49,23 +49,20 @@ class NFixedPage(QFrame):
|
||||
super().__init__(parent=parent)
|
||||
self.setFrameShadow(QFrame.Shadow.Sunken)
|
||||
self.setFrameShape(QFrame.Shape.StyledPanel)
|
||||
return
|
||||
|
||||
def setCentralLayout(self, layout: QLayout) -> None:
|
||||
"""Set a layout as the central object."""
|
||||
self.setLayout(layout)
|
||||
return
|
||||
|
||||
def setCentralWidget(self, widget: QWidget) -> None:
|
||||
"""Set a layout as the central object."""
|
||||
layout = QHBoxLayout()
|
||||
layout.addWidget(widget)
|
||||
self.setLayout(layout)
|
||||
return
|
||||
|
||||
|
||||
class NScrollablePage(QScrollArea):
|
||||
"""Extension: Scrollable Page Widget
|
||||
"""Extension: Scrollable Page Widget.
|
||||
|
||||
A custom widget that holds a layout within a scrollable area.
|
||||
"""
|
||||
@@ -79,16 +76,14 @@ class NScrollablePage(QScrollArea):
|
||||
self.setVerticalScrollBarPolicy(QtScrollAsNeeded)
|
||||
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
|
||||
|
||||
|
||||
class NScrollableForm(QScrollArea):
|
||||
"""Extension: Scrollable Form Widget
|
||||
"""Extension: Scrollable Form Widget.
|
||||
|
||||
A custom widget that creates a form within a scrollable area.
|
||||
"""
|
||||
@@ -117,8 +112,6 @@ class NScrollableForm(QScrollArea):
|
||||
self.setFrameShadow(QFrame.Shadow.Sunken)
|
||||
self.setFrameShape(QFrame.Shape.StyledPanel)
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Properties
|
||||
##
|
||||
@@ -135,18 +128,15 @@ class NScrollableForm(QScrollArea):
|
||||
"""Set the text color for the help text."""
|
||||
self._helpCol = color
|
||||
self._fontScale = scale
|
||||
return
|
||||
|
||||
def setHelpText(self, key: str, text: str) -> None:
|
||||
"""Set the text for the help label."""
|
||||
if qHelp := self._editable.get(key):
|
||||
qHelp.setText(text)
|
||||
return
|
||||
|
||||
def setRowIndent(self, indent: int) -> None:
|
||||
"""Set the indentation of each row."""
|
||||
self._indent = max(indent, 0)
|
||||
return
|
||||
|
||||
##
|
||||
# Methods
|
||||
@@ -158,7 +148,6 @@ class NScrollableForm(QScrollArea):
|
||||
yPos = self._sections[identifier].pos().y() - 8
|
||||
if vBar := self.verticalScrollBar():
|
||||
vBar.setValue(yPos)
|
||||
return
|
||||
|
||||
def scrollToLabel(self, label: str) -> None:
|
||||
"""Scroll to the requested label."""
|
||||
@@ -166,7 +155,6 @@ class NScrollableForm(QScrollArea):
|
||||
yPos = self._index[label].pos().y() - 8
|
||||
if vBar := self.verticalScrollBar():
|
||||
vBar.setValue(yPos)
|
||||
return
|
||||
|
||||
def addGroupLabel(self, label: str, identifier: int | None = None) -> None:
|
||||
"""Add a text label to separate groups of settings."""
|
||||
@@ -178,7 +166,6 @@ class NScrollableForm(QScrollArea):
|
||||
self._first = False
|
||||
if identifier is not None:
|
||||
self._sections[identifier] = qLabel
|
||||
return
|
||||
|
||||
def addRow(
|
||||
self,
|
||||
@@ -252,17 +239,14 @@ class NScrollableForm(QScrollArea):
|
||||
self._index[label.strip()] = qWidget
|
||||
qLabel.setAccessibleName(text)
|
||||
|
||||
return
|
||||
|
||||
def finalise(self) -> None:
|
||||
"""Finalise the layout when the form is built."""
|
||||
self._layout.addSpacing(20)
|
||||
self._layout.addStretch(1)
|
||||
return
|
||||
|
||||
|
||||
class NColorLabel(QLabel):
|
||||
"""Extension: A Coloured Label
|
||||
"""Extension: A Coloured Label.
|
||||
|
||||
A custom widget that draws a label in a specific colour, and
|
||||
optionally at a specific size, and word wrapped.
|
||||
@@ -298,21 +282,17 @@ class NColorLabel(QLabel):
|
||||
self.setWordWrap(wrap)
|
||||
self.setColorState(True)
|
||||
|
||||
return
|
||||
|
||||
def setTextColors(self, *, color: QColor | None = None, faded: QColor | None = None) -> None:
|
||||
"""Set or update the text colours."""
|
||||
self._color = color or self._color
|
||||
self._faded = faded or self._faded
|
||||
self._refeshTextColor()
|
||||
return
|
||||
|
||||
def setColorState(self, state: bool) -> None:
|
||||
"""Change the colour state."""
|
||||
if self._state is not state:
|
||||
self._state = state
|
||||
self._refeshTextColor()
|
||||
return
|
||||
|
||||
def _refeshTextColor(self) -> None:
|
||||
"""Refresh the colour of the text on the label."""
|
||||
@@ -322,11 +302,10 @@ class NColorLabel(QLabel):
|
||||
self._color if self._state else self._faded,
|
||||
)
|
||||
self.setPalette(palette)
|
||||
return
|
||||
|
||||
|
||||
class NWrappedWidgetBox(QHBoxLayout):
|
||||
"""Extension: A Text-Wrapped Widget Box
|
||||
"""Extension: A Text-Wrapped Widget Box.
|
||||
|
||||
A custom layout box where a widget is wrapped in text labels on
|
||||
either side within a layout box. The widget is inserted at the {0}
|
||||
@@ -341,4 +320,3 @@ class NWrappedWidgetBox(QHBoxLayout):
|
||||
self.addWidget(widget)
|
||||
if after:
|
||||
self.addWidget(QLabel(after.lstrip()))
|
||||
return
|
||||
|
||||
@@ -21,7 +21,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
@@ -34,7 +34,7 @@ if TYPE_CHECKING:
|
||||
|
||||
|
||||
class WheelEventFilter(QObject):
|
||||
"""Extensions: Wheel Event Filter
|
||||
"""Extensions: Wheel Event Filter.
|
||||
|
||||
An event filter that filters mouse wheel events for a widget and
|
||||
forward them to the root widget. This solves the lack of mouse wheel
|
||||
@@ -50,7 +50,6 @@ class WheelEventFilter(QObject):
|
||||
super().__init__(parent=parent)
|
||||
self._parent = parent
|
||||
self._locked = False
|
||||
return
|
||||
|
||||
def eventFilter(self, obj: QObject, event: QEvent) -> bool:
|
||||
"""Filter events of type QWheelEvent and forward them to the
|
||||
@@ -69,6 +68,7 @@ class WheelEventFilter(QObject):
|
||||
|
||||
|
||||
class StatusTipFilter(QObject):
|
||||
"""Filter: Remove StatusBar ToolTips."""
|
||||
|
||||
def eventFilter(self, obj: QObject, event: QEvent) -> bool:
|
||||
"""Filter out status tip events on menus."""
|
||||
|
||||
@@ -24,7 +24,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
@@ -47,6 +47,7 @@ if TYPE_CHECKING:
|
||||
|
||||
|
||||
class NDialog(QDialog):
|
||||
"""Custom: Modified QDialog."""
|
||||
|
||||
def softDelete(self) -> None:
|
||||
"""Since calling deleteLater is sometimes not safe from Python,
|
||||
@@ -55,53 +56,54 @@ class NDialog(QDialog):
|
||||
so that it gets garbage collected when it runs out of scope.
|
||||
"""
|
||||
self.setParent(None) # type: ignore
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def reject(self) -> None:
|
||||
"""Overload the reject slot and also call close."""
|
||||
super().reject()
|
||||
self.close()
|
||||
return
|
||||
|
||||
|
||||
class NToolDialog(NDialog):
|
||||
"""Custom: Modified QDialog for Tools."""
|
||||
|
||||
def __init__(self, parent: GuiMain) -> None:
|
||||
super().__init__(parent=parent)
|
||||
self.setModal(False)
|
||||
if CONFIG.osDarwin:
|
||||
self.setWindowFlag(Qt.WindowType.Tool)
|
||||
return
|
||||
|
||||
def activateDialog(self) -> None:
|
||||
"""Helper function to activate dialog on various systems."""
|
||||
"""Activate dialog on various operating systems."""
|
||||
self.show()
|
||||
if CONFIG.osWindows:
|
||||
self.activateWindow()
|
||||
self.raise_()
|
||||
QApplication.processEvents()
|
||||
return
|
||||
|
||||
|
||||
class NNonBlockingDialog(NDialog):
|
||||
"""Custom: Modified Non-Blocking QDialog."""
|
||||
|
||||
def __init__(self, parent: QWidget | None = None) -> None:
|
||||
super().__init__(parent=parent)
|
||||
self.setModal(True)
|
||||
return
|
||||
|
||||
def activateDialog(self) -> None:
|
||||
"""Helper function to activate dialog on various systems."""
|
||||
"""Activate dialog on various operating systems."""
|
||||
self.show()
|
||||
if CONFIG.osWindows:
|
||||
self.activateWindow()
|
||||
self.raise_()
|
||||
QApplication.processEvents()
|
||||
return
|
||||
|
||||
|
||||
class NTreeView(QTreeView):
|
||||
"""Custom: Modified QTreeView.
|
||||
|
||||
The main purpose is to provide the middleClicked signal that matches
|
||||
clicked and doubleCLicked.
|
||||
"""
|
||||
|
||||
middleClicked = pyqtSignal(QModelIndex)
|
||||
|
||||
@@ -116,6 +118,12 @@ class NTreeView(QTreeView):
|
||||
|
||||
|
||||
class NComboBox(QComboBox):
|
||||
"""Custom: Modified QComboBox.
|
||||
|
||||
The main purpose is to provide a combo box that doesn't scroll when
|
||||
the mousewheel is active on it while scrolling through a scrollable
|
||||
window of many widgets.
|
||||
"""
|
||||
|
||||
def __init__(self, parent: QWidget | None = None, maxItems: int = 15) -> None:
|
||||
super().__init__(parent=parent)
|
||||
@@ -126,7 +134,30 @@ class NComboBox(QComboBox):
|
||||
# and allows for scrolling of long lists of items
|
||||
self.setStyleSheet("QComboBox {combobox-popup: 0;}")
|
||||
|
||||
return
|
||||
def wheelEvent(self, event: QWheelEvent) -> None:
|
||||
"""Only capture the mouse wheel if the widget has focus."""
|
||||
if self.hasFocus():
|
||||
super().wheelEvent(event)
|
||||
else:
|
||||
event.ignore()
|
||||
|
||||
def setCurrentData(self, data: str | int | Enum, default: str | int | Enum) -> None:
|
||||
"""Set the current index from data, with a fallback."""
|
||||
idx = self.findData(data)
|
||||
self.setCurrentIndex(self.findData(default) if idx < 0 else idx)
|
||||
|
||||
|
||||
class NSpinBox(QSpinBox):
|
||||
"""Custom: Modified QSpinBox.
|
||||
|
||||
The main purpose is to provide a spin box that doesn't scroll when
|
||||
the mousewheel is active on it while scrolling through a scrollable
|
||||
window of many widgets.
|
||||
"""
|
||||
|
||||
def __init__(self, parent: QWidget | None = None) -> None:
|
||||
super().__init__(parent=parent)
|
||||
self.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
|
||||
|
||||
def wheelEvent(self, event: QWheelEvent) -> None:
|
||||
"""Only capture the mouse wheel if the widget has focus."""
|
||||
@@ -134,31 +165,15 @@ class NComboBox(QComboBox):
|
||||
super().wheelEvent(event)
|
||||
else:
|
||||
event.ignore()
|
||||
return
|
||||
|
||||
def setCurrentData(self, data: str | int | Enum, default: str | int | Enum) -> None:
|
||||
"""Set the current index from data, with a fallback."""
|
||||
idx = self.findData(data)
|
||||
self.setCurrentIndex(self.findData(default) if idx < 0 else idx)
|
||||
return
|
||||
|
||||
|
||||
class NSpinBox(QSpinBox):
|
||||
|
||||
def __init__(self, parent: QWidget | None = None) -> None:
|
||||
super().__init__(parent=parent)
|
||||
self.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
|
||||
return
|
||||
|
||||
def wheelEvent(self, event: QWheelEvent) -> None:
|
||||
if self.hasFocus():
|
||||
super().wheelEvent(event)
|
||||
else:
|
||||
event.ignore()
|
||||
return
|
||||
|
||||
|
||||
class NDoubleSpinBox(QDoubleSpinBox):
|
||||
"""Custom: Modified QDoubleSpinBox.
|
||||
|
||||
The main purpose is to provide a float spin box that doesn't scroll
|
||||
when the mousewheel is active on it while scrolling through a
|
||||
scrollable window of many widgets.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -175,17 +190,20 @@ class NDoubleSpinBox(QDoubleSpinBox):
|
||||
self.setMaximum(maxVal)
|
||||
self.setSingleStep(step)
|
||||
self.setDecimals(prec)
|
||||
return
|
||||
|
||||
def wheelEvent(self, event: QWheelEvent) -> None:
|
||||
"""Only capture the mouse wheel if the widget has focus."""
|
||||
if self.hasFocus():
|
||||
super().wheelEvent(event)
|
||||
else:
|
||||
event.ignore()
|
||||
return
|
||||
|
||||
|
||||
class NIconToolButton(QToolButton):
|
||||
"""Custom: Modified QToolButton.
|
||||
|
||||
A quicker way to create a tool button using the app theme.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, parent: QWidget, iconSize: QSize,
|
||||
@@ -197,15 +215,17 @@ class NIconToolButton(QToolButton):
|
||||
self.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
|
||||
if icon:
|
||||
self.setThemeIcon(icon, color)
|
||||
return
|
||||
|
||||
def setThemeIcon(self, iconKey: str, color: str | None = None) -> None:
|
||||
"""Set an icon from the current theme."""
|
||||
self.setIcon(SHARED.theme.getIcon(iconKey, color))
|
||||
return
|
||||
|
||||
|
||||
class NIconToggleButton(QToolButton):
|
||||
"""Custom: Modified QToolButton.
|
||||
|
||||
A quicker way to create a toggle button using the app theme.
|
||||
"""
|
||||
|
||||
def __init__(self, parent: QWidget, iconSize: QSize, icon: str | None = None) -> None:
|
||||
super().__init__(parent=parent)
|
||||
@@ -216,16 +236,15 @@ class NIconToggleButton(QToolButton):
|
||||
self.setStyleSheet("border: none; background: transparent;")
|
||||
if icon:
|
||||
self.setThemeIcon(icon)
|
||||
return
|
||||
|
||||
def setThemeIcon(self, iconKey: str) -> None:
|
||||
"""Set an icon from the current theme."""
|
||||
iconSize = self.iconSize()
|
||||
self.setIcon(SHARED.theme.getToggleIcon(iconKey, (iconSize.width(), iconSize.height())))
|
||||
return
|
||||
|
||||
|
||||
class NClickableLabel(QLabel):
|
||||
"""Custom: Clickable QLabel."""
|
||||
|
||||
mouseClicked = pyqtSignal()
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
@@ -42,6 +42,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NovelSelector(QComboBox):
|
||||
"""Custom: Novel Root Folder Selector."""
|
||||
|
||||
novelSelectionChanged = pyqtSignal(str)
|
||||
|
||||
@@ -53,7 +54,6 @@ class NovelSelector(QComboBox):
|
||||
self._listFormat = None
|
||||
self.currentIndexChanged.connect(self._indexChanged)
|
||||
self.updateTheme()
|
||||
return
|
||||
|
||||
##
|
||||
# Properties
|
||||
@@ -80,18 +80,15 @@ class NovelSelector(QComboBox):
|
||||
self._blockSignal = blockSignal
|
||||
self.setCurrentIndex(index)
|
||||
self._blockSignal = False
|
||||
return
|
||||
|
||||
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
|
||||
|
||||
def updateTheme(self) -> None:
|
||||
"""Update theme colours."""
|
||||
@@ -99,7 +96,6 @@ class NovelSelector(QComboBox):
|
||||
palette.setBrush(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Text, palette.text())
|
||||
self.setPalette(palette)
|
||||
self.refreshNovelList()
|
||||
return
|
||||
|
||||
##
|
||||
# Public Slots
|
||||
@@ -133,8 +129,6 @@ class NovelSelector(QComboBox):
|
||||
self.setEnabled(self.count() > 1)
|
||||
self._blockSignal = False
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Private Slots
|
||||
##
|
||||
@@ -144,4 +138,3 @@ class NovelSelector(QComboBox):
|
||||
"""Re-emit the change of selection signal, unless blocked."""
|
||||
if not self._blockSignal:
|
||||
self.novelSelectionChanged.emit(self.currentData())
|
||||
return
|
||||
|
||||
@@ -22,7 +22,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
from PyQt6.QtCore import QPoint, QRectF, QSize, Qt, pyqtSignal, pyqtSlot
|
||||
@@ -39,7 +39,7 @@ from novelwriter.types import (
|
||||
|
||||
|
||||
class NPagedSideBar(QToolBar):
|
||||
"""Extensions: Paged Side Bar
|
||||
"""Extensions: Paged Side Bar.
|
||||
|
||||
A side bar widget that holds buttons that mimic tabs. It is designed
|
||||
to be used in combination with a QStackedWidget for options panels.
|
||||
@@ -65,8 +65,6 @@ class NPagedSideBar(QToolBar):
|
||||
stretch.setSizePolicy(QtSizeExpanding, QtSizeExpanding)
|
||||
self._stretchAction = self.addWidget(stretch)
|
||||
|
||||
return
|
||||
|
||||
def button(self, buttonId: int) -> _PagedToolButton:
|
||||
"""Return a specific button."""
|
||||
return self._buttons[buttonId]
|
||||
@@ -74,14 +72,12 @@ class NPagedSideBar(QToolBar):
|
||||
def setLabelColor(self, color: QColor) -> None:
|
||||
"""Set the text color for the labels."""
|
||||
self._labelCol = color
|
||||
return
|
||||
|
||||
def addLabel(self, text: str) -> None:
|
||||
"""Add a new label to the toolbar."""
|
||||
label = _NPagedToolLabel(self, self._labelCol)
|
||||
label.setText(text)
|
||||
self.insertWidget(self._stretchAction, label)
|
||||
return
|
||||
|
||||
def addButton(self, text: str, buttonId: int = -1) -> None:
|
||||
"""Add a new button to the toolbar."""
|
||||
@@ -90,13 +86,11 @@ class NPagedSideBar(QToolBar):
|
||||
self.insertWidget(self._stretchAction, button)
|
||||
self._group.addButton(button, id=buttonId)
|
||||
self._buttons[buttonId] = button
|
||||
return
|
||||
|
||||
def setSelected(self, buttonId: int) -> None:
|
||||
"""Set the selected button."""
|
||||
if button := self._group.button(buttonId):
|
||||
button.setChecked(True)
|
||||
return
|
||||
|
||||
##
|
||||
# Private Slots
|
||||
@@ -104,11 +98,10 @@ class NPagedSideBar(QToolBar):
|
||||
|
||||
@pyqtSlot("QAbstractButton*")
|
||||
def _buttonClicked(self, button: QAbstractButton) -> None:
|
||||
"""A button was clicked in the group, emit its id."""
|
||||
"""Handle a button click in the group and emit its id."""
|
||||
buttonId = self._group.id(button)
|
||||
if buttonId != -1:
|
||||
self.buttonClicked.emit(buttonId)
|
||||
return
|
||||
|
||||
|
||||
class _PagedToolButton(QToolButton):
|
||||
@@ -127,8 +120,6 @@ class _PagedToolButton(QToolButton):
|
||||
self._aH = 2*fH//7
|
||||
self.setFixedHeight(self._bH)
|
||||
|
||||
return
|
||||
|
||||
def sizeHint(self) -> QSize:
|
||||
"""Return a size hint that includes the arrow."""
|
||||
return super().sizeHint() + QSize(4*self._aH, 0)
|
||||
@@ -180,8 +171,6 @@ class _PagedToolButton(QToolButton):
|
||||
]))
|
||||
painter.end()
|
||||
|
||||
return
|
||||
|
||||
|
||||
class _NPagedToolLabel(QLabel):
|
||||
|
||||
@@ -199,8 +188,6 @@ class _NPagedToolLabel(QLabel):
|
||||
|
||||
self._textCol = textColor or self.palette().text().color()
|
||||
|
||||
return
|
||||
|
||||
def paintEvent(self, event: QPaintEvent) -> None:
|
||||
"""Overload the paint event to draw a simple, left aligned text
|
||||
label that matches the button style.
|
||||
@@ -215,5 +202,3 @@ class _NPagedToolLabel(QLabel):
|
||||
painter.setOpacity(1.0)
|
||||
painter.drawText(QRectF(4, self._tM, tW, tH), QtAlignLeft, self.text())
|
||||
painter.end()
|
||||
|
||||
return
|
||||
|
||||
@@ -21,7 +21,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
from math import ceil
|
||||
@@ -37,7 +37,7 @@ from novelwriter.types import (
|
||||
|
||||
|
||||
class NProgressCircle(QProgressBar):
|
||||
"""Extension: Circular Progress Widget
|
||||
"""Extension: Circular Progress Widget.
|
||||
|
||||
A custom widget that paints a circular progress indicator instead of
|
||||
a straight bar. It is also possible to set custom text for iṫ.
|
||||
@@ -64,7 +64,6 @@ class NProgressCircle(QProgressBar):
|
||||
self.setSizePolicy(QtSizeFixed, QtSizeFixed)
|
||||
self.setFixedWidth(size)
|
||||
self.setFixedHeight(size)
|
||||
return
|
||||
|
||||
def setColors(
|
||||
self, back: QColor | None = None, track: QColor | None = None,
|
||||
@@ -80,16 +79,14 @@ class NProgressCircle(QProgressBar):
|
||||
self._bPen = QPen(QBrush(track), self._point, QtSolidLine, QtRoundCap)
|
||||
if isinstance(text, QColor):
|
||||
self._tColor = text
|
||||
return
|
||||
|
||||
def setCentreText(self, text: str | None) -> None:
|
||||
"""Replace the progress text with a custom string."""
|
||||
self._text = text
|
||||
self.setValue(self.value()) # Triggers a redraw
|
||||
return
|
||||
|
||||
def paintEvent(self, event: QPaintEvent) -> None:
|
||||
"""Custom painter for the progress bar."""
|
||||
"""Paint the progress bar."""
|
||||
progress = 100.0*self.value()/self.maximum()
|
||||
angle = ceil(16*3.6*progress)
|
||||
painter = QPainter(self)
|
||||
@@ -103,21 +100,19 @@ class NProgressCircle(QProgressBar):
|
||||
painter.drawArc(self._cRect, 90*16, -angle)
|
||||
painter.setPen(self._tColor)
|
||||
painter.drawText(self._cRect, QtAlignCenter, self._text or f"{progress:.1f} %")
|
||||
return
|
||||
|
||||
|
||||
class NProgressSimple(QProgressBar):
|
||||
"""Extension: Simple Progress Widget
|
||||
"""Extension: Simple Progress Widget.
|
||||
|
||||
A custom widget that paints a plain bar with no other styling.
|
||||
"""
|
||||
|
||||
def __init__(self, parent: QWidget) -> None:
|
||||
super().__init__(parent=parent)
|
||||
return
|
||||
|
||||
def paintEvent(self, event: QPaintEvent) -> None:
|
||||
"""Custom painter for the progress bar."""
|
||||
"""Paint the progress bar."""
|
||||
if (value := self.value()) > 0:
|
||||
progress = ceil(self.width()*float(value)/self.maximum())
|
||||
painter = QPainter(self)
|
||||
@@ -125,4 +120,3 @@ class NProgressSimple(QProgressBar):
|
||||
painter.setPen(self.palette().highlight().color())
|
||||
painter.setBrush(self.palette().highlight())
|
||||
painter.drawRect(0, 0, progress, self.height())
|
||||
return
|
||||
|
||||
@@ -20,7 +20,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
@@ -34,6 +34,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class StatusLED(QAbstractButton):
|
||||
"""Custom: LED Style Indicator."""
|
||||
|
||||
__slots__ = ("_color", "_negative", "_neutral", "_postitve", "_state")
|
||||
|
||||
@@ -46,7 +47,6 @@ class StatusLED(QAbstractButton):
|
||||
self._state = None
|
||||
self.setFixedWidth(sW)
|
||||
self.setFixedHeight(sH)
|
||||
return
|
||||
|
||||
@property
|
||||
def state(self) -> bool | None:
|
||||
@@ -59,7 +59,6 @@ class StatusLED(QAbstractButton):
|
||||
self._postitve = positive
|
||||
self._negative = negative
|
||||
self.setState(self._state)
|
||||
return
|
||||
|
||||
def setState(self, state: bool | None) -> None:
|
||||
"""Set the colour state."""
|
||||
@@ -71,7 +70,6 @@ class StatusLED(QAbstractButton):
|
||||
self._color = self._neutral
|
||||
self._state = state
|
||||
self.update()
|
||||
return
|
||||
|
||||
def paintEvent(self, event: QPaintEvent) -> None:
|
||||
"""Draw the LED."""
|
||||
@@ -82,4 +80,3 @@ class StatusLED(QAbstractButton):
|
||||
painter.setOpacity(1.0)
|
||||
painter.drawEllipse(1, 1, self.width() - 2, self.height() - 2)
|
||||
painter.end()
|
||||
return
|
||||
|
||||
@@ -20,7 +20,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
from PyQt6.QtCore import QPropertyAnimation, Qt, pyqtProperty, pyqtSlot # pyright: ignore
|
||||
@@ -32,6 +32,7 @@ from novelwriter.types import QtNoPen, QtPaintAntiAlias, QtSizeFixed
|
||||
|
||||
|
||||
class NSwitch(QAbstractButton):
|
||||
"""Custom: Toggle Switch."""
|
||||
|
||||
__slots__ = ("_cOff", "_cOn", "_offset", "_rH", "_rR", "_xH", "_xR", "_xW")
|
||||
|
||||
@@ -55,8 +56,6 @@ class NSwitch(QAbstractButton):
|
||||
|
||||
self.clicked.connect(self._onClick)
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Properties
|
||||
##
|
||||
@@ -69,7 +68,6 @@ class NSwitch(QAbstractButton):
|
||||
def offset(self, offset: int) -> None:
|
||||
self._offset = offset
|
||||
self.update()
|
||||
return
|
||||
|
||||
##
|
||||
# Getters and Setters
|
||||
@@ -79,7 +77,6 @@ class NSwitch(QAbstractButton):
|
||||
"""Overload setChecked to also alter the offset."""
|
||||
super().setChecked(checked)
|
||||
self._offset = (self._xW - self._xR) if checked else self._xR
|
||||
return
|
||||
|
||||
##
|
||||
# Events
|
||||
@@ -89,7 +86,6 @@ class NSwitch(QAbstractButton):
|
||||
"""Overload resize to ensure correct offset."""
|
||||
super().resizeEvent(event)
|
||||
self._offset = (self._xW - self._xR) if self.isChecked() else self._xR
|
||||
return
|
||||
|
||||
def paintEvent(self, event: QPaintEvent) -> None:
|
||||
"""Drawing the switch itself."""
|
||||
@@ -109,13 +105,10 @@ class NSwitch(QAbstractButton):
|
||||
|
||||
painter.end()
|
||||
|
||||
return
|
||||
|
||||
def enterEvent(self, event: QEnterEvent) -> None:
|
||||
"""Change the cursor when hovering the button."""
|
||||
self.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
super().enterEvent(event)
|
||||
return
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def _onClick(self, checked: bool) -> None:
|
||||
@@ -125,4 +118,3 @@ class NSwitch(QAbstractButton):
|
||||
anim.setStartValue(self._offset)
|
||||
anim.setEndValue((self._xW - self._xR) if checked else self._xR)
|
||||
anim.start()
|
||||
return
|
||||
|
||||
@@ -20,7 +20,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
@@ -39,7 +39,7 @@ if TYPE_CHECKING:
|
||||
|
||||
|
||||
class NSwitchBox(QScrollArea):
|
||||
"""Extension: Switch Box Widget
|
||||
"""Extension: Switch Box Widget.
|
||||
|
||||
A widget that can hold a list of switches with labels and optional
|
||||
icons. The switch toggles emits a common signal with a switch key.
|
||||
@@ -55,7 +55,6 @@ class NSwitchBox(QScrollArea):
|
||||
self._sIcon = baseSize
|
||||
self._widgets = []
|
||||
self.clear()
|
||||
return
|
||||
|
||||
def clear(self) -> None:
|
||||
"""Rebuild the content of the core widget."""
|
||||
@@ -72,8 +71,6 @@ class NSwitchBox(QScrollArea):
|
||||
self.setWidgetResizable(True)
|
||||
self.setWidget(self._widget)
|
||||
|
||||
return
|
||||
|
||||
def addLabel(self, text: str) -> None:
|
||||
"""Add a header label to the content box."""
|
||||
label = QLabel(text, self)
|
||||
@@ -83,7 +80,6 @@ class NSwitchBox(QScrollArea):
|
||||
self._content.addWidget(label, self._index, 0, 1, 3, QtAlignLeft)
|
||||
self._widgets.append(label)
|
||||
self._bumpIndex()
|
||||
return
|
||||
|
||||
def addItem(self, qIcon: QIcon, text: str, identifier: str, default: bool = False) -> None:
|
||||
"""Add an item to the content box."""
|
||||
@@ -104,8 +100,6 @@ class NSwitchBox(QScrollArea):
|
||||
self._widgets.append(switch)
|
||||
self._bumpIndex()
|
||||
|
||||
return
|
||||
|
||||
def addSeparator(self) -> None:
|
||||
"""Add a blank entry in the content box."""
|
||||
spacer = QWidget(self)
|
||||
@@ -113,7 +107,6 @@ class NSwitchBox(QScrollArea):
|
||||
self._content.addWidget(spacer, self._index, 0, 1, 3, QtAlignLeft)
|
||||
self._widgets.append(spacer)
|
||||
self._bumpIndex()
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
@@ -122,7 +115,6 @@ class NSwitchBox(QScrollArea):
|
||||
def _emitSwitchSignal(self, identifier: str, state: bool) -> None:
|
||||
"""Emit a signal for a switch toggle."""
|
||||
self.switchToggled.emit(identifier, state)
|
||||
return
|
||||
|
||||
def _bumpIndex(self) -> None:
|
||||
"""Increase the index counter and make sure only the last
|
||||
@@ -131,4 +123,3 @@ class NSwitchBox(QScrollArea):
|
||||
self._content.setRowStretch(self._index, 0)
|
||||
self._content.setRowStretch(self._index + 1, 1)
|
||||
self._index += 1
|
||||
return
|
||||
|
||||
@@ -20,7 +20,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
@@ -45,6 +45,11 @@ API_URL = "https://api.github.com/repos/vkbo/novelwriter/releases/latest"
|
||||
|
||||
|
||||
class VersionInfoWidget(QWidget):
|
||||
"""Custom: version Info Label.
|
||||
|
||||
A custom widget that will show a clickable area for contacting
|
||||
GitHub and pulling the latest release version info.
|
||||
"""
|
||||
|
||||
def __init__(self, parent: QWidget) -> None:
|
||||
super().__init__(parent=parent)
|
||||
@@ -75,8 +80,6 @@ class VersionInfoWidget(QWidget):
|
||||
|
||||
self.setLayout(self._layout)
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Private Slots
|
||||
##
|
||||
@@ -93,7 +96,6 @@ class VersionInfoWidget(QWidget):
|
||||
lookup = _Retriever()
|
||||
lookup.signals.dataReady.connect(self._updateReleaseInfo)
|
||||
SHARED.runInThreadPool(lookup)
|
||||
return
|
||||
|
||||
##
|
||||
# Private Slots
|
||||
@@ -109,7 +111,6 @@ class VersionInfoWidget(QWidget):
|
||||
))
|
||||
else:
|
||||
self._lblRelease.setText(self._trLatest.format(reason or self.tr("Failed")))
|
||||
return
|
||||
|
||||
|
||||
class _Retriever(QRunnable):
|
||||
@@ -117,7 +118,6 @@ class _Retriever(QRunnable):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.signals = _RetrieverSignal()
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def run(self) -> None:
|
||||
@@ -140,7 +140,6 @@ class _Retriever(QRunnable):
|
||||
except Exception as e:
|
||||
logger.error("Failed to retrieve release info")
|
||||
self.signals.dataReady.emit("", str(e))
|
||||
return
|
||||
|
||||
|
||||
class _RetrieverSignal(QObject):
|
||||
|
||||
@@ -20,7 +20,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
|
||||
@@ -21,7 +21,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
@@ -100,7 +100,7 @@ def _wText(parent: ET.Element, text: str) -> ET.Element:
|
||||
|
||||
|
||||
def _mmToSz(value: float) -> int:
|
||||
"""Convert millimetres to internal margin size units"""
|
||||
"""Convert millimetres to internal margin size units."""
|
||||
return int(value*20.0*72.0/25.4)
|
||||
|
||||
|
||||
@@ -143,6 +143,7 @@ S_FNOTE = "FootnoteText"
|
||||
|
||||
|
||||
class DocXXmlRel(NamedTuple):
|
||||
"""DocX XML Rel Data."""
|
||||
|
||||
rId: str
|
||||
relType: str
|
||||
@@ -150,6 +151,7 @@ class DocXXmlRel(NamedTuple):
|
||||
|
||||
|
||||
class DocXXmlFile(NamedTuple):
|
||||
"""DocX XML File Data."""
|
||||
|
||||
xml: ET.Element
|
||||
path: str
|
||||
@@ -157,6 +159,7 @@ class DocXXmlFile(NamedTuple):
|
||||
|
||||
|
||||
class DocXParStyle(NamedTuple):
|
||||
"""DocX XML Paragraph Style Data."""
|
||||
|
||||
name: str
|
||||
styleId: str
|
||||
@@ -176,7 +179,7 @@ class DocXParStyle(NamedTuple):
|
||||
|
||||
|
||||
class ToDocX(Tokenizer):
|
||||
"""Core: DocX Document Writer
|
||||
"""Core: DocX Document Writer.
|
||||
|
||||
Extend the Tokenizer class to writer DocX Document files.
|
||||
"""
|
||||
@@ -202,8 +205,6 @@ class ToDocX(Tokenizer):
|
||||
self._usedNotes: dict[str, int] = {}
|
||||
self._usedFields: list[tuple[ET.Element, str]] = []
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Setters
|
||||
##
|
||||
@@ -214,25 +215,22 @@ class ToDocX(Tokenizer):
|
||||
"""Set the document page size and margins in millimetres."""
|
||||
self._pageSize = QSize(_mmToSz(width), _mmToSz(height))
|
||||
self._pageMargins = QMargins(_mmToSz(left), _mmToSz(top), _mmToSz(right), _mmToSz(bottom))
|
||||
return
|
||||
|
||||
def setHeaderFormat(self, value: str, offset: int) -> None:
|
||||
"""Set the document header format."""
|
||||
self._headerFormat = value.strip()
|
||||
self._pageOffset = offset
|
||||
return
|
||||
|
||||
##
|
||||
# Class Methods
|
||||
##
|
||||
|
||||
def initDocument(self) -> None:
|
||||
"""Initialises the DocX document structure."""
|
||||
"""Initialise the DocX document structure."""
|
||||
super().initDocument()
|
||||
self._fontFamily = self._textFont.family()
|
||||
self._fontSize = self._textFont.pointSizeF()
|
||||
self._generateStyles()
|
||||
return
|
||||
|
||||
def doConvert(self) -> None:
|
||||
"""Convert the list of text tokens into XML elements."""
|
||||
@@ -302,8 +300,6 @@ class ToDocX(Tokenizer):
|
||||
elif tType == BlockTyp.KEYWORD:
|
||||
self._processFragments(par, S_META, tText, tFormat)
|
||||
|
||||
return
|
||||
|
||||
def closeDocument(self) -> None:
|
||||
"""Generate all the XML."""
|
||||
self._coreXml()
|
||||
@@ -322,8 +318,6 @@ class ToDocX(Tokenizer):
|
||||
if self._usedNotes:
|
||||
self._footnotesXml()
|
||||
|
||||
return
|
||||
|
||||
def saveDocument(self, path: Path) -> None:
|
||||
"""Save the data to a .docx file."""
|
||||
# Content Lists
|
||||
@@ -373,8 +367,6 @@ class ToDocX(Tokenizer):
|
||||
xmlToZip(f"{rel.path}/{name}", rel.xml, outZip)
|
||||
xmlToZip("[Content_Types].xml", dTypes, outZip)
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
##
|
||||
@@ -454,8 +446,6 @@ class ToDocX(Tokenizer):
|
||||
if temp := text[fStart:]:
|
||||
par.addContent(self._textRunToXml(temp, xFmt, fClass, fLink))
|
||||
|
||||
return
|
||||
|
||||
def _textRunToXml(self, text: str | None, fmt: int, fClass: str, fLink: str) -> ET.Element:
|
||||
"""Encode the text run into XML."""
|
||||
xR = xmlElement(_wTag("r"))
|
||||
@@ -668,8 +658,6 @@ class ToDocX(Tokenizer):
|
||||
for style in styles:
|
||||
self._styles[style.styleId] = style
|
||||
|
||||
return
|
||||
|
||||
def _nextRelId(self) -> str:
|
||||
"""Generate the next unique rId."""
|
||||
return f"rId{len(self._rels) + 1}"
|
||||
@@ -1054,6 +1042,10 @@ class ToDocX(Tokenizer):
|
||||
|
||||
|
||||
class DocXParagraph:
|
||||
"""DocX Text Paragraph.
|
||||
|
||||
This class holds a single paragraph of a DocX document.
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
"_bottomMargin", "_breakAfter", "_breakBefore", "_content",
|
||||
@@ -1073,7 +1065,6 @@ class DocXParagraph:
|
||||
self._breakBefore = False
|
||||
self._breakAfter = False
|
||||
self._footnoteRef = False
|
||||
return
|
||||
|
||||
##
|
||||
# Properties
|
||||
@@ -1091,53 +1082,43 @@ class DocXParagraph:
|
||||
def setStyle(self, style: DocXParStyle | None) -> None:
|
||||
"""Set the paragraph style."""
|
||||
self._style = style
|
||||
return
|
||||
|
||||
def setAlignment(self, value: str) -> None:
|
||||
"""Set paragraph alignment."""
|
||||
if value in ("left", "center", "right", "both"):
|
||||
self._textAlign = value
|
||||
return
|
||||
|
||||
def setMarginTop(self, value: float) -> None:
|
||||
"""Set margin above in pt."""
|
||||
self._topMargin = value
|
||||
return
|
||||
|
||||
def setMarginBottom(self, value: float) -> None:
|
||||
"""Set margin below in pt."""
|
||||
self._bottomMargin = value
|
||||
return
|
||||
|
||||
def setMarginLeft(self, value: float) -> None:
|
||||
"""Set margin left in pt."""
|
||||
self._leftMargin = value
|
||||
return
|
||||
|
||||
def setMarginRight(self, value: float) -> None:
|
||||
"""Set margin right in pt."""
|
||||
self._rightMargin = value
|
||||
return
|
||||
|
||||
def setIndentFirst(self, state: bool) -> None:
|
||||
"""Set first line indent."""
|
||||
self._indentFirst = state
|
||||
return
|
||||
|
||||
def setPageBreakBefore(self, state: bool) -> None:
|
||||
"""Set page break before flag."""
|
||||
self._breakBefore = state
|
||||
return
|
||||
|
||||
def setPageBreakAfter(self, state: bool) -> None:
|
||||
"""Set page break after flag."""
|
||||
self._breakAfter = state
|
||||
return
|
||||
|
||||
def setIsFootnote(self, state: bool) -> None:
|
||||
"""Set is footnote flag."""
|
||||
self._footnoteRef = state
|
||||
return
|
||||
|
||||
##
|
||||
# Methods
|
||||
@@ -1146,10 +1127,9 @@ class DocXParagraph:
|
||||
def addContent(self, run: ET.Element) -> None:
|
||||
"""Add a run segment to the paragraph."""
|
||||
self._content.append(run)
|
||||
return
|
||||
|
||||
def toXml(self, body: ET.Element) -> None:
|
||||
"""Called after all content is set."""
|
||||
"""Generate the XML. Call after all content is set."""
|
||||
if style := self._style:
|
||||
xP = xmlSubElem(body, _wTag("p"))
|
||||
|
||||
@@ -1191,5 +1171,3 @@ class DocXParagraph:
|
||||
if self._breakAfter:
|
||||
xR = xmlSubElem(xP, _wTag("r"))
|
||||
xmlSubElem(xR, _wTag("br"), attrib={_wTag("type"): "page"})
|
||||
|
||||
return
|
||||
|
||||
@@ -20,7 +20,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
@@ -77,7 +77,7 @@ HTML_NONE = (0, "")
|
||||
|
||||
|
||||
class ToHtml(Tokenizer):
|
||||
"""Core: HTML Document Writer
|
||||
"""Core: HTML Document Writer.
|
||||
|
||||
Extend the Tokenizer class to writer HTML output. This class is
|
||||
also used by the Document Viewer, and Manuscript Build Preview.
|
||||
@@ -90,7 +90,6 @@ class ToHtml(Tokenizer):
|
||||
self._usedNotes: dict[str, int] = {}
|
||||
self._usedFields: list[tuple[int, str]] = []
|
||||
self.setReplaceUnicode(False)
|
||||
return
|
||||
|
||||
##
|
||||
# Setters
|
||||
@@ -101,7 +100,6 @@ class ToHtml(Tokenizer):
|
||||
class tags.
|
||||
"""
|
||||
self._cssStyles = cssStyles
|
||||
return
|
||||
|
||||
def setReplaceUnicode(self, doReplace: bool) -> None:
|
||||
"""Set the translation map to either minimal or full unicode for
|
||||
@@ -114,7 +112,6 @@ class ToHtml(Tokenizer):
|
||||
if doReplace:
|
||||
# Extend to all relevant Unicode characters
|
||||
self._trMap.update(str.maketrans(nwHtmlUnicode.U_TO_H))
|
||||
return
|
||||
|
||||
##
|
||||
# Class Methods
|
||||
@@ -130,7 +127,6 @@ class ToHtml(Tokenizer):
|
||||
"""
|
||||
super().doPreProcessing()
|
||||
self._text = self._text.translate(self._trMap)
|
||||
return
|
||||
|
||||
def doConvert(self) -> None:
|
||||
"""Convert the list of text tokens into an HTML document."""
|
||||
@@ -237,8 +233,6 @@ class ToHtml(Tokenizer):
|
||||
|
||||
self._pages.append("".join(lines))
|
||||
|
||||
return
|
||||
|
||||
def closeDocument(self) -> None:
|
||||
"""Run close document tasks."""
|
||||
# Replace fields if there are stats available
|
||||
@@ -265,8 +259,6 @@ class ToHtml(Tokenizer):
|
||||
|
||||
self._pages.append("".join(lines))
|
||||
|
||||
return
|
||||
|
||||
def saveDocument(self, path: Path) -> None:
|
||||
"""Save the data to an HTML file."""
|
||||
if path.suffix.lower() == ".json":
|
||||
@@ -309,14 +301,11 @@ class ToHtml(Tokenizer):
|
||||
|
||||
logger.info("Wrote file: %s", path)
|
||||
|
||||
return
|
||||
|
||||
def replaceTabs(self, nSpaces: int = 8, spaceChar: str = " ") -> None:
|
||||
"""Replace tabs with spaces in the html."""
|
||||
tabSpace = spaceChar*nSpaces
|
||||
pages = [aLine.replace("\t", tabSpace) for aLine in self._pages]
|
||||
self._pages = pages
|
||||
return
|
||||
|
||||
def getStyleSheet(self) -> list[str]:
|
||||
"""Generate a stylesheet for the current settings."""
|
||||
|
||||
@@ -21,7 +21,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
@@ -55,6 +55,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ComStyle(NamedTuple):
|
||||
"""Comment style info."""
|
||||
|
||||
label: str = ""
|
||||
labelClass: str = ""
|
||||
@@ -92,7 +93,7 @@ B_EMPTY: T_Block = (BlockTyp.EMPTY, "", "", [], BlockFmt.NONE)
|
||||
|
||||
|
||||
class Tokenizer(ABC):
|
||||
"""Core: Text Tokenizer Abstract Base Class
|
||||
"""Core: Text Tokenizer Abstract Base Class.
|
||||
|
||||
This is the base class for all document build classes. It parses the
|
||||
novelWriter markup format and generates a registry of tokens and
|
||||
@@ -224,8 +225,6 @@ class Tokenizer(ABC):
|
||||
self._dialogParser = DialogParser()
|
||||
self._dialogParser.initParser()
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Properties
|
||||
##
|
||||
@@ -253,94 +252,78 @@ class Tokenizer(ABC):
|
||||
"""Set language for the document."""
|
||||
if language:
|
||||
self._dLocale = QLocale(language)
|
||||
return
|
||||
|
||||
def setTheme(self, theme: TextDocumentTheme) -> None:
|
||||
"""Set the document colour theme."""
|
||||
self._theme = theme
|
||||
return
|
||||
|
||||
def setPartitionFormat(self, hFormat: str, hide: bool = False) -> None:
|
||||
"""Set the partition format pattern."""
|
||||
self._fmtPart = hFormat.strip()
|
||||
self._hidePart = hide
|
||||
return
|
||||
|
||||
def setChapterFormat(self, hFormat: str, hide: bool = False) -> None:
|
||||
"""Set the chapter format pattern."""
|
||||
self._fmtChapter = hFormat.strip()
|
||||
self._hideChapter = hide
|
||||
return
|
||||
|
||||
def setUnNumberedFormat(self, hFormat: str, hide: bool = False) -> None:
|
||||
"""Set the unnumbered format pattern."""
|
||||
self._fmtUnNum = hFormat.strip()
|
||||
self._hideUnNum = hide
|
||||
return
|
||||
|
||||
def setSceneFormat(self, hFormat: str, hide: bool = False) -> None:
|
||||
"""Set the scene format pattern and hidden status."""
|
||||
self._fmtScene = hFormat.strip()
|
||||
self._hideScene = hide
|
||||
return
|
||||
|
||||
def setHardSceneFormat(self, hFormat: str, hide: bool = False) -> None:
|
||||
"""Set the hard scene format pattern and hidden status."""
|
||||
self._fmtHScene = hFormat.strip()
|
||||
self._hideHScene = hide
|
||||
return
|
||||
|
||||
def setSectionFormat(self, hFormat: str, hide: bool = False) -> None:
|
||||
"""Set the section format pattern and hidden status."""
|
||||
self._fmtSection = hFormat.strip()
|
||||
self._hideSection = hide
|
||||
return
|
||||
|
||||
def setTitleStyle(self, center: bool, pageBreak: bool) -> None:
|
||||
"""Set the title heading style."""
|
||||
self._titleStyle = BlockFmt.CENTRE if center else BlockFmt.NONE
|
||||
self._titleStyle |= BlockFmt.PBB if pageBreak else BlockFmt.NONE
|
||||
return
|
||||
|
||||
def setPartitionStyle(self, center: bool, pageBreak: bool) -> None:
|
||||
"""Set the partition heading style."""
|
||||
self._partStyle = BlockFmt.CENTRE if center else BlockFmt.NONE
|
||||
self._partStyle |= BlockFmt.PBB if pageBreak else BlockFmt.NONE
|
||||
return
|
||||
|
||||
def setChapterStyle(self, center: bool, pageBreak: bool) -> None:
|
||||
"""Set the chapter heading style."""
|
||||
self._chapterStyle = BlockFmt.CENTRE if center else BlockFmt.NONE
|
||||
self._chapterStyle |= BlockFmt.PBB if pageBreak else BlockFmt.NONE
|
||||
return
|
||||
|
||||
def setSceneStyle(self, center: bool, pageBreak: bool) -> None:
|
||||
"""Set the scene heading style."""
|
||||
self._sceneStyle = BlockFmt.CENTRE if center else BlockFmt.NONE
|
||||
self._sceneStyle |= BlockFmt.PBB if pageBreak else BlockFmt.NONE
|
||||
return
|
||||
|
||||
def setTextFont(self, font: QFont) -> None:
|
||||
"""Set the build font."""
|
||||
self._textFont = fontMatcher(font)
|
||||
return
|
||||
|
||||
def setLineHeight(self, height: float) -> None:
|
||||
"""Set the line height between 0.5 and 5.0."""
|
||||
self._lineHeight = min(max(float(height), 0.5), 5.0)
|
||||
return
|
||||
|
||||
def setHeadingStyles(self, color: bool, scale: bool, bold: bool) -> None:
|
||||
"""Set text style for headings."""
|
||||
self._colorHeads = color
|
||||
self._scaleHeads = scale
|
||||
self._boldHeads = bold
|
||||
return
|
||||
|
||||
def setBlockIndent(self, indent: float) -> None:
|
||||
"""Set the block indent between 0.0 and 10.0."""
|
||||
self._blockIndent = min(max(float(indent), 0.0), 10.0)
|
||||
return
|
||||
|
||||
def setFirstLineIndent(self, state: bool, indent: float, first: bool) -> None:
|
||||
"""Set first line indent and whether to also indent first
|
||||
@@ -349,67 +332,54 @@ class Tokenizer(ABC):
|
||||
self._firstIndent = state
|
||||
self._firstWidth = indent
|
||||
self._indentFirst = first
|
||||
return
|
||||
|
||||
def setJustify(self, state: bool) -> None:
|
||||
"""Enable or disable text justification."""
|
||||
self._doJustify = state
|
||||
return
|
||||
|
||||
def setDialogHighlight(self, state: bool) -> None:
|
||||
"""Enable or disable dialogue highlighting."""
|
||||
self._hlightDialog = state
|
||||
return
|
||||
|
||||
def setTitleMargins(self, upper: float, lower: float) -> None:
|
||||
"""Set the upper and lower title margin."""
|
||||
self._marginTitle = (float(upper), float(lower))
|
||||
return
|
||||
|
||||
def setHead1Margins(self, upper: float, lower: float) -> None:
|
||||
"""Set the upper and lower heading 1 margin."""
|
||||
self._marginHead1 = (float(upper), float(lower))
|
||||
return
|
||||
|
||||
def setHead2Margins(self, upper: float, lower: float) -> None:
|
||||
"""Set the upper and lower heading 2 margin."""
|
||||
self._marginHead2 = (float(upper), float(lower))
|
||||
return
|
||||
|
||||
def setHead3Margins(self, upper: float, lower: float) -> None:
|
||||
"""Set the upper and lower heading 3 margin."""
|
||||
self._marginHead3 = (float(upper), float(lower))
|
||||
return
|
||||
|
||||
def setHead4Margins(self, upper: float, lower: float) -> None:
|
||||
"""Set the upper and lower heading 4 margin."""
|
||||
self._marginHead4 = (float(upper), float(lower))
|
||||
return
|
||||
|
||||
def setTextMargins(self, upper: float, lower: float) -> None:
|
||||
"""Set the upper and lower text margin."""
|
||||
self._marginText = (float(upper), float(lower))
|
||||
return
|
||||
|
||||
def setMetaMargins(self, upper: float, lower: float) -> None:
|
||||
"""Set the upper and lower meta text margin."""
|
||||
self._marginMeta = (float(upper), float(lower))
|
||||
return
|
||||
|
||||
def setSeparatorMargins(self, upper: float, lower: float) -> None:
|
||||
"""Set the upper and lower meta text margin."""
|
||||
self._marginSep = (float(upper), float(lower))
|
||||
return
|
||||
|
||||
def setLinkHeadings(self, state: bool) -> None:
|
||||
"""Enable or disable adding an anchor before headings."""
|
||||
self._linkHeadings = state
|
||||
return
|
||||
|
||||
def setBodyText(self, state: bool) -> None:
|
||||
"""Include body text in build."""
|
||||
self._doBodyText = state
|
||||
return
|
||||
|
||||
def setCommentType(self, comment: nwComment, state: bool) -> None:
|
||||
"""Toggle the inclusion og certain comment types."""
|
||||
@@ -417,22 +387,18 @@ class Tokenizer(ABC):
|
||||
self._doComments.add(comment)
|
||||
else:
|
||||
self._doComments.discard(comment)
|
||||
return
|
||||
|
||||
def setKeywords(self, state: bool) -> None:
|
||||
"""Include keywords in build."""
|
||||
self._doKeywords = state
|
||||
return
|
||||
|
||||
def setIgnoredKeywords(self, keywords: str) -> None:
|
||||
"""Comma separated string of keywords to ignore."""
|
||||
self._skipKeywords = set(x.lower().strip() for x in keywords.split(","))
|
||||
return
|
||||
|
||||
def setKeepLineBreaks(self, state: bool) -> None:
|
||||
"""Keep line breaks in paragraphs."""
|
||||
self._keepBreaks = state
|
||||
return
|
||||
|
||||
##
|
||||
# Class Methods
|
||||
@@ -460,12 +426,10 @@ class Tokenizer(ABC):
|
||||
self._classes["tag"] = self._theme.tag
|
||||
self._classes["keyword"] = self._theme.keyword
|
||||
self._classes["optional"] = self._theme.optional
|
||||
return
|
||||
|
||||
def setBreakNext(self) -> None:
|
||||
"""Set a page break for next block."""
|
||||
self._breakNext = True
|
||||
return
|
||||
|
||||
def addRootHeading(self, tHandle: str) -> None:
|
||||
"""Add a heading at the start of a new root folder."""
|
||||
@@ -491,8 +455,6 @@ class Tokenizer(ABC):
|
||||
if self._keepRaw:
|
||||
self._raw.append(f"#! {title}\n\n")
|
||||
|
||||
return
|
||||
|
||||
def setText(self, tHandle: str, text: str | None = None) -> None:
|
||||
"""Set the text for the tokenizer from a handle. If text is not
|
||||
set, it's is loaded from the file.
|
||||
@@ -503,7 +465,6 @@ class Tokenizer(ABC):
|
||||
self._text = text or self._project.storage.getDocumentText(tHandle)
|
||||
self._handle = tHandle
|
||||
self._isNovel = nwItem.itemLayout == nwItemLayout.DOCUMENT
|
||||
return
|
||||
|
||||
def doPreProcessing(self) -> None:
|
||||
"""Run pre-processing jobs before the text is tokenized."""
|
||||
@@ -512,7 +473,6 @@ class Tokenizer(ABC):
|
||||
replace = {f"<{k}>": v for k, v in entry.items()}
|
||||
rxRep = re.compile("|".join([re.escape(k) for k in replace]), flags=re.DOTALL)
|
||||
self._text = rxRep.sub(lambda x: replace[x.group(0)], self._text)
|
||||
return
|
||||
|
||||
def tokenizeText(self) -> None:
|
||||
"""Scan the text for either lines starting with specific
|
||||
@@ -590,13 +550,13 @@ class Tokenizer(ABC):
|
||||
self._breakNext = True
|
||||
continue
|
||||
|
||||
elif sLine == "[vspace]":
|
||||
if sLine == "[vspace]":
|
||||
tBlocks.append(
|
||||
(BlockTyp.SKIP, "", "", [], tStyle)
|
||||
)
|
||||
continue
|
||||
|
||||
elif sLine.startswith("[vspace:") and sLine.endswith("]"):
|
||||
if sLine.startswith("[vspace:") and sLine.endswith("]"):
|
||||
nSkip = checkInt(sLine[8:-1], 0)
|
||||
if nSkip >= 1:
|
||||
tBlocks.append(
|
||||
@@ -962,8 +922,6 @@ class Tokenizer(ABC):
|
||||
text = tText.replace(nwHeadFmt.BR, " ").replace("&", "&")
|
||||
self._outline[tKey] = f"{prefix}|{text}"
|
||||
|
||||
return
|
||||
|
||||
def countStats(self) -> None:
|
||||
"""Count stats on the tokenized text."""
|
||||
titleCount = self._counts.get(nwStats.TITLES, 0)
|
||||
@@ -1039,8 +997,6 @@ class Tokenizer(ABC):
|
||||
self._counts[nwStats.WCHARS_TEXT] = textWordChars
|
||||
self._counts[nwStats.WCHARS_TITLE] = titleWordChars
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
##
|
||||
@@ -1182,6 +1138,12 @@ class Tokenizer(ABC):
|
||||
|
||||
|
||||
class HeadingFormatter:
|
||||
"""Core: Format Text Headings.
|
||||
|
||||
This class holds the various chapter and scene counters and can
|
||||
apply the Build Settings header format settings based on internal
|
||||
counter state.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -1195,35 +1157,29 @@ class HeadingFormatter:
|
||||
self._chapter = chapter
|
||||
self._scene = scene
|
||||
self._absolute = absolute
|
||||
return
|
||||
|
||||
def setHandle(self, tHandle: str | None) -> None:
|
||||
"""Set the handle currently being processed."""
|
||||
self._handle = tHandle
|
||||
return
|
||||
|
||||
def incChapter(self) -> None:
|
||||
"""Increment the chapter counter."""
|
||||
self._chapter += 1
|
||||
return
|
||||
|
||||
def incScene(self) -> None:
|
||||
"""Increment the scene counters."""
|
||||
self._scene += 1
|
||||
self._absolute += 1
|
||||
return
|
||||
|
||||
def resetAll(self) -> None:
|
||||
"""Reset all counters."""
|
||||
self._chapter = 0
|
||||
self._scene = 0
|
||||
self._absolute = 0
|
||||
return
|
||||
|
||||
def resetScene(self) -> None:
|
||||
"""Reset the chapter scene counter."""
|
||||
self._scene = 0
|
||||
return
|
||||
|
||||
def apply(self, hFormat: str, text: str, nHead: int) -> str:
|
||||
"""Apply formatting to a specific heading."""
|
||||
|
||||
@@ -20,7 +20,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
@@ -79,7 +79,7 @@ EXT_MD = {
|
||||
|
||||
|
||||
class ToMarkdown(Tokenizer):
|
||||
"""Core: Markdown Document Writer
|
||||
"""Core: Markdown Document Writer.
|
||||
|
||||
Extend the Tokenizer class to writer Markdown output. It supports
|
||||
both Standard Markdown and Extended Markdown. The class also
|
||||
@@ -91,7 +91,6 @@ class ToMarkdown(Tokenizer):
|
||||
self._extended = extended
|
||||
self._usedNotes: dict[str, int] = {}
|
||||
self._usedFields: list[tuple[int, str]] = []
|
||||
return
|
||||
|
||||
##
|
||||
# Class Methods
|
||||
@@ -153,8 +152,6 @@ class ToMarkdown(Tokenizer):
|
||||
|
||||
self._pages.append("".join(lines))
|
||||
|
||||
return
|
||||
|
||||
def closeDocument(self) -> None:
|
||||
"""Run close document tasks."""
|
||||
# Replace fields if there are stats available
|
||||
@@ -181,20 +178,16 @@ class ToMarkdown(Tokenizer):
|
||||
lines.append("\n")
|
||||
self._pages.append("".join(lines))
|
||||
|
||||
return
|
||||
|
||||
def saveDocument(self, path: Path) -> None:
|
||||
"""Save the data to a plain text file."""
|
||||
with open(path, mode="w", encoding="utf-8") as outFile:
|
||||
outFile.write("".join(self._pages))
|
||||
logger.info("Wrote file: %s", path)
|
||||
return
|
||||
|
||||
def replaceTabs(self, nSpaces: int = 8, spaceChar: str = " ") -> None:
|
||||
"""Replace tabs with spaces."""
|
||||
spaces = spaceChar*nSpaces
|
||||
self._pages = [p.replace("\t", spaces) for p in self._pages]
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
|
||||
@@ -23,7 +23,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
@@ -128,7 +128,7 @@ FONT_WEIGHT_MAP = {"400": "normal", "700": "bold"}
|
||||
|
||||
|
||||
class ToOdt(Tokenizer):
|
||||
"""Core: Open Document Writer
|
||||
"""Core: Open Document Writer.
|
||||
|
||||
Extend the Tokenizer class to writer Open Document files. The output
|
||||
should conform to the 1.3 Extended standard.
|
||||
@@ -189,8 +189,6 @@ class ToOdt(Tokenizer):
|
||||
self._mDocLeft = "2.000cm"
|
||||
self._mDocRight = "2.000cm"
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Setters
|
||||
##
|
||||
@@ -205,20 +203,18 @@ class ToOdt(Tokenizer):
|
||||
self._mDocBtm = f"{bottom/10.0:.3f}cm"
|
||||
self._mDocLeft = f"{left/10.0:.3f}cm"
|
||||
self._mDocRight = f"{right/10.0:.3f}cm"
|
||||
return
|
||||
|
||||
def setHeaderFormat(self, value: str, offset: int) -> None:
|
||||
"""Set the document header format."""
|
||||
self._headerFormat = value.strip()
|
||||
self._pageOffset = offset
|
||||
return
|
||||
|
||||
##
|
||||
# Class Methods
|
||||
##
|
||||
|
||||
def initDocument(self) -> None:
|
||||
"""Initialises a new open document XML tree."""
|
||||
"""Initialise a new open document XML tree."""
|
||||
super().initDocument()
|
||||
|
||||
# Initialise Variables
|
||||
@@ -325,8 +321,6 @@ class ToOdt(Tokenizer):
|
||||
self._useableStyles()
|
||||
self._writeHeader()
|
||||
|
||||
return
|
||||
|
||||
def doConvert(self) -> None:
|
||||
"""Convert the list of text tokens into XML elements."""
|
||||
xText = self._xText
|
||||
@@ -395,8 +389,6 @@ class ToOdt(Tokenizer):
|
||||
elif tType == BlockTyp.KEYWORD:
|
||||
self._addTextPar(xText, S_META, oStyle, tText, tFmt=tFormat)
|
||||
|
||||
return
|
||||
|
||||
def closeDocument(self) -> None:
|
||||
"""Add additional collected information to the XML."""
|
||||
for style in self._autoPara.values():
|
||||
@@ -412,7 +404,6 @@ class ToOdt(Tokenizer):
|
||||
_mkTag("text", "name"): f"Manuscript{key[:1].upper()}{key[1:]}",
|
||||
})
|
||||
self._xText.insert(0, xFields)
|
||||
return
|
||||
|
||||
def saveDocument(self, path: Path) -> None:
|
||||
"""Save the data to an .fodt or .odt file."""
|
||||
@@ -456,8 +447,6 @@ class ToOdt(Tokenizer):
|
||||
|
||||
logger.info("Wrote file: %s", path)
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
##
|
||||
@@ -670,7 +659,7 @@ class ToOdt(Tokenizer):
|
||||
return None
|
||||
|
||||
def _emToCm(self, value: float) -> str:
|
||||
"""Converts an em value to centimetres."""
|
||||
"""Convert an em value to centimetres."""
|
||||
return f"{value*self._fontSize*2.54/72.0:.3f}cm"
|
||||
|
||||
def _emToPt(self, scale: float) -> str:
|
||||
@@ -705,8 +694,6 @@ class ToOdt(Tokenizer):
|
||||
_mkTag("fo", "margin-bottom"): self._emToCm(0.5),
|
||||
})
|
||||
|
||||
return
|
||||
|
||||
def _defaultStyles(self) -> None:
|
||||
"""Set the default styles."""
|
||||
hScale = self._scaleHeads
|
||||
@@ -783,8 +770,6 @@ class ToOdt(Tokenizer):
|
||||
_mkTag("number", "min-integer-digits"): "1",
|
||||
})
|
||||
|
||||
return
|
||||
|
||||
def _useableStyles(self) -> None:
|
||||
"""Set the usable styles."""
|
||||
hScale = self._scaleHeads
|
||||
@@ -954,8 +939,6 @@ class ToOdt(Tokenizer):
|
||||
style.packXML(self._xStyl)
|
||||
self._mainPara[style.name] = style
|
||||
|
||||
return
|
||||
|
||||
def _writeHeader(self) -> None:
|
||||
"""Write the header elements."""
|
||||
xPage = ET.SubElement(self._xMast, _mkTag("style", "master-page"), attrib={
|
||||
@@ -993,8 +976,6 @@ class ToOdt(Tokenizer):
|
||||
_mkTag("text", "style-name"): "Header"
|
||||
})
|
||||
|
||||
return
|
||||
|
||||
|
||||
# Auto-Style Classes
|
||||
# ==================
|
||||
@@ -1004,6 +985,7 @@ class ODTParagraphStyle:
|
||||
exporter. Only the used settings are exposed here to keep the class
|
||||
minimal and fast.
|
||||
"""
|
||||
|
||||
VALID_ALIGN: Final[list[str]] = ["start", "center", "end", "justify", "left", "right"]
|
||||
VALID_BREAK: Final[list[str]] = ["auto", "page", "even-page", "odd-page", "inherit"]
|
||||
VALID_LEVEL: Final[list[str]] = ["1", "2", "3", "4"]
|
||||
@@ -1046,8 +1028,6 @@ class ODTParagraphStyle:
|
||||
"opacity": ["loext", None],
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return self._name
|
||||
@@ -1059,7 +1039,6 @@ class ODTParagraphStyle:
|
||||
def setName(self, name: str) -> None:
|
||||
"""Set the paragraph style name."""
|
||||
self._name = name
|
||||
return
|
||||
|
||||
##
|
||||
# Attribute Setters
|
||||
@@ -1068,17 +1047,14 @@ class ODTParagraphStyle:
|
||||
def setDisplayName(self, value: str | None) -> None:
|
||||
"""Set style display name."""
|
||||
self._mAttr["display-name"][1] = value
|
||||
return
|
||||
|
||||
def setParentStyleName(self, value: str | None) -> None:
|
||||
"""Set parent style name."""
|
||||
self._mAttr["parent-style-name"][1] = value
|
||||
return
|
||||
|
||||
def setNextStyleName(self, value: str | None) -> None:
|
||||
"""Set next style name."""
|
||||
self._mAttr["next-style-name"][1] = value
|
||||
return
|
||||
|
||||
def setOutlineLevel(self, value: str | None) -> None:
|
||||
"""Set paragraph outline level."""
|
||||
@@ -1086,7 +1062,6 @@ class ODTParagraphStyle:
|
||||
self._mAttr["default-outline-level"][1] = value
|
||||
else:
|
||||
self._mAttr["default-outline-level"][1] = None
|
||||
return
|
||||
|
||||
def setClass(self, value: str | None) -> None:
|
||||
"""Set paragraph class."""
|
||||
@@ -1094,7 +1069,6 @@ class ODTParagraphStyle:
|
||||
self._mAttr["class"][1] = value
|
||||
else:
|
||||
self._mAttr["class"][1] = None
|
||||
return
|
||||
|
||||
##
|
||||
# Paragraph Setters
|
||||
@@ -1103,32 +1077,26 @@ class ODTParagraphStyle:
|
||||
def setMarginTop(self, value: str | None) -> None:
|
||||
"""Set paragraph top margin."""
|
||||
self._pAttr["margin-top"][1] = value
|
||||
return
|
||||
|
||||
def setMarginBottom(self, value: str | None) -> None:
|
||||
"""Set paragraph bottom margin."""
|
||||
self._pAttr["margin-bottom"][1] = value
|
||||
return
|
||||
|
||||
def setMarginLeft(self, value: str | None) -> None:
|
||||
"""Set paragraph left margin."""
|
||||
self._pAttr["margin-left"][1] = value
|
||||
return
|
||||
|
||||
def setMarginRight(self, value: str | None) -> None:
|
||||
"""Set paragraph right margin."""
|
||||
self._pAttr["margin-right"][1] = value
|
||||
return
|
||||
|
||||
def setTextIndent(self, value: str | None) -> None:
|
||||
"""Set text indentation."""
|
||||
self._pAttr["text-indent"][1] = value
|
||||
return
|
||||
|
||||
def setLineHeight(self, value: str | None) -> None:
|
||||
"""Set line height."""
|
||||
self._pAttr["line-height"][1] = value
|
||||
return
|
||||
|
||||
def setTextAlign(self, value: str | None) -> None:
|
||||
"""Set paragraph text alignment."""
|
||||
@@ -1136,7 +1104,6 @@ class ODTParagraphStyle:
|
||||
self._pAttr["text-align"][1] = value
|
||||
else:
|
||||
self._pAttr["text-align"][1] = None
|
||||
return
|
||||
|
||||
def setBreakBefore(self, value: str | None) -> None:
|
||||
"""Set page break before policy."""
|
||||
@@ -1144,7 +1111,6 @@ class ODTParagraphStyle:
|
||||
self._pAttr["break-before"][1] = value
|
||||
else:
|
||||
self._pAttr["break-before"][1] = None
|
||||
return
|
||||
|
||||
def setBreakAfter(self, value: str | None) -> None:
|
||||
"""Set page break after policy."""
|
||||
@@ -1152,7 +1118,6 @@ class ODTParagraphStyle:
|
||||
self._pAttr["break-after"][1] = value
|
||||
else:
|
||||
self._pAttr["break-after"][1] = None
|
||||
return
|
||||
|
||||
##
|
||||
# Text Setters
|
||||
@@ -1161,17 +1126,14 @@ class ODTParagraphStyle:
|
||||
def setFontName(self, value: str | None) -> None:
|
||||
"""Set font name."""
|
||||
self._tAttr["font-name"][1] = value
|
||||
return
|
||||
|
||||
def setFontFamily(self, value: str | None) -> None:
|
||||
"""Set font family."""
|
||||
self._tAttr["font-family"][1] = value
|
||||
return
|
||||
|
||||
def setFontSize(self, value: str | None) -> None:
|
||||
"""Set font size."""
|
||||
self._tAttr["font-size"][1] = value
|
||||
return
|
||||
|
||||
def setFontWeight(self, value: str | None) -> None:
|
||||
"""Set font weight."""
|
||||
@@ -1179,7 +1141,6 @@ class ODTParagraphStyle:
|
||||
self._tAttr["font-weight"][1] = value
|
||||
else:
|
||||
self._tAttr["font-weight"][1] = None
|
||||
return
|
||||
|
||||
def setColor(self, value: QColor | None) -> None:
|
||||
"""Set text colour."""
|
||||
@@ -1189,7 +1150,6 @@ class ODTParagraphStyle:
|
||||
else:
|
||||
self._tAttr["color"][1] = None
|
||||
self._tAttr["opacity"][1] = None
|
||||
return
|
||||
|
||||
##
|
||||
# Methods
|
||||
@@ -1233,14 +1193,13 @@ class ODTParagraphStyle:
|
||||
if attr := {_mkTag(n, m): v for m, (n, v) in self._tAttr.items() if v}:
|
||||
ET.SubElement(xEntry, _mkTag("style", "text-properties"), attrib=attr)
|
||||
|
||||
return
|
||||
|
||||
|
||||
class ODTTextStyle:
|
||||
"""Wrapper class for the text style setting used by the exporter.
|
||||
Only the used settings are exposed here to keep the class minimal
|
||||
and fast.
|
||||
"""
|
||||
|
||||
VALID_WEIGHT: Final[list[str]] = ["normal", "bold", *FONT_WEIGHT_NUM]
|
||||
VALID_STYLE: Final[list[str]] = ["normal", "italic", "oblique"]
|
||||
VALID_POS: Final[list[str]] = ["super", "sub"]
|
||||
@@ -1263,7 +1222,6 @@ class ODTTextStyle:
|
||||
"text-underline-width": ["style", None],
|
||||
"text-underline-color": ["style", None],
|
||||
}
|
||||
return
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
@@ -1279,7 +1237,6 @@ class ODTTextStyle:
|
||||
self._tAttr["font-weight"][1] = value
|
||||
else:
|
||||
self._tAttr["font-weight"][1] = None
|
||||
return
|
||||
|
||||
def setFontStyle(self, value: str | None) -> None:
|
||||
"""Set text font style."""
|
||||
@@ -1287,7 +1244,6 @@ class ODTTextStyle:
|
||||
self._tAttr["font-style"][1] = value
|
||||
else:
|
||||
self._tAttr["font-style"][1] = None
|
||||
return
|
||||
|
||||
def setColor(self, value: QColor | None) -> None:
|
||||
"""Set text colour."""
|
||||
@@ -1295,7 +1251,6 @@ class ODTTextStyle:
|
||||
self._tAttr["color"][1] = value.name(QtHexRgb)
|
||||
else:
|
||||
self._tAttr["color"][1] = None
|
||||
return
|
||||
|
||||
def setBackgroundColor(self, value: QColor | None) -> None:
|
||||
"""Set text background colour."""
|
||||
@@ -1303,7 +1258,6 @@ class ODTTextStyle:
|
||||
self._tAttr["background-color"][1] = value.name(QtHexRgb)
|
||||
else:
|
||||
self._tAttr["background-color"][1] = None
|
||||
return
|
||||
|
||||
def setTextPosition(self, value: str | None) -> None:
|
||||
"""Set text vertical position."""
|
||||
@@ -1311,7 +1265,6 @@ class ODTTextStyle:
|
||||
self._tAttr["text-position"][1] = f"{value} 58%"
|
||||
else:
|
||||
self._tAttr["text-position"][1] = None
|
||||
return
|
||||
|
||||
def setStrikeStyle(self, value: str | None) -> None:
|
||||
"""Set text line-trough style."""
|
||||
@@ -1319,7 +1272,6 @@ class ODTTextStyle:
|
||||
self._tAttr["text-line-through-style"][1] = value
|
||||
else:
|
||||
self._tAttr["text-line-through-style"][1] = None
|
||||
return
|
||||
|
||||
def setStrikeType(self, value: str | None) -> None:
|
||||
"""Set text line-through type."""
|
||||
@@ -1327,7 +1279,6 @@ class ODTTextStyle:
|
||||
self._tAttr["text-line-through-type"][1] = value
|
||||
else:
|
||||
self._tAttr["text-line-through-type"][1] = None
|
||||
return
|
||||
|
||||
def setUnderlineStyle(self, value: str | None) -> None:
|
||||
"""Set text underline style."""
|
||||
@@ -1335,7 +1286,6 @@ class ODTTextStyle:
|
||||
self._tAttr["text-underline-style"][1] = value
|
||||
else:
|
||||
self._tAttr["text-underline-style"][1] = None
|
||||
return
|
||||
|
||||
def setUnderlineWidth(self, value: str | None) -> None:
|
||||
"""Set text underline width."""
|
||||
@@ -1343,7 +1293,6 @@ class ODTTextStyle:
|
||||
self._tAttr["text-underline-width"][1] = value
|
||||
else:
|
||||
self._tAttr["text-underline-width"][1] = None
|
||||
return
|
||||
|
||||
def setUnderlineColor(self, value: str | None) -> None:
|
||||
"""Set text underline colour."""
|
||||
@@ -1351,7 +1300,6 @@ class ODTTextStyle:
|
||||
self._tAttr["text-underline-color"][1] = value
|
||||
else:
|
||||
self._tAttr["text-underline-color"][1] = None
|
||||
return
|
||||
|
||||
##
|
||||
# Methods
|
||||
@@ -1365,7 +1313,6 @@ class ODTTextStyle:
|
||||
})
|
||||
if attr := {_mkTag(n, m): v for m, (n, v) in self._tAttr.items() if v}:
|
||||
ET.SubElement(xEntry, _mkTag("style", "text-properties"), attrib=attr)
|
||||
return
|
||||
|
||||
|
||||
# XML Complex Element Helper Class
|
||||
@@ -1378,7 +1325,9 @@ X_SPAN_SING = 3
|
||||
|
||||
|
||||
class XMLParagraph:
|
||||
"""This is a helper class to manage the text content of a single
|
||||
"""ODT Text Paragraph.
|
||||
|
||||
This is a helper class to manage the text content of a single
|
||||
XML element using mixed content tags.
|
||||
|
||||
Rules:
|
||||
@@ -1408,8 +1357,6 @@ class XMLParagraph:
|
||||
self._rawTxt = ""
|
||||
self._xRoot.text = ""
|
||||
|
||||
return
|
||||
|
||||
def appendText(self, text: str) -> None:
|
||||
"""Append text to the XML element. We do this one character at
|
||||
the time in order to be able to process line breaks, tabs and
|
||||
@@ -1424,7 +1371,7 @@ class XMLParagraph:
|
||||
if c == " ":
|
||||
nSpaces += 1
|
||||
continue
|
||||
elif nSpaces > 0:
|
||||
if nSpaces > 0:
|
||||
self._processSpaces(nSpaces)
|
||||
nSpaces = 0
|
||||
|
||||
@@ -1468,8 +1415,6 @@ class XMLParagraph:
|
||||
# Handle trailing spaces
|
||||
self._processSpaces(nSpaces)
|
||||
|
||||
return
|
||||
|
||||
def appendSpan(self, text: str, style: str, link: str) -> None:
|
||||
"""Append a text span to the XML element. The span is always
|
||||
closed since we do not produce nested spans (like Libre Office).
|
||||
@@ -1491,7 +1436,6 @@ class XMLParagraph:
|
||||
self._nState = X_SPAN_TEXT
|
||||
self.appendText(text)
|
||||
self._nState = X_ROOT_TAIL
|
||||
return
|
||||
|
||||
def appendNode(self, xNode: ET.Element | None) -> None:
|
||||
"""Append an XML node to the paragraph. We only check for the
|
||||
@@ -1504,7 +1448,6 @@ class XMLParagraph:
|
||||
self._xTail = xNode
|
||||
self._xTail.tail = ""
|
||||
self._nState = X_ROOT_TAIL
|
||||
return
|
||||
|
||||
def checkError(self) -> tuple[int, str]:
|
||||
"""Check that the number of characters written matches the
|
||||
@@ -1575,5 +1518,3 @@ class XMLParagraph:
|
||||
self._xSing.tail = ""
|
||||
self._nState = X_SPAN_SING
|
||||
self._chrPos += nSpaces - 1
|
||||
|
||||
return
|
||||
|
||||
@@ -20,7 +20,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
@@ -56,6 +56,7 @@ T_TextStyle = tuple[QTextBlockFormat, QTextCharFormat]
|
||||
|
||||
|
||||
def newBlock(cursor: QTextCursor, bFmt: QTextBlockFormat) -> None:
|
||||
"""Insert a new block if not at the beginning of the document."""
|
||||
if cursor.position() > 0:
|
||||
cursor.insertBlock(bFmt)
|
||||
else:
|
||||
@@ -63,7 +64,7 @@ def newBlock(cursor: QTextCursor, bFmt: QTextBlockFormat) -> None:
|
||||
|
||||
|
||||
class ToQTextDocument(Tokenizer):
|
||||
"""Core: QTextDocument Writer
|
||||
"""Core: QTextDocument Writer.
|
||||
|
||||
Extend the Tokenizer class to generate a QTextDocument output. This
|
||||
is intended for usage in the document viewer and build tool preview.
|
||||
@@ -92,8 +93,6 @@ class ToQTextDocument(Tokenizer):
|
||||
self._pageSize = QPageSize(QPageSize.PageSizeId.A4)
|
||||
self._pageMargins = QMarginsF(20.0, 20.0, 20.0, 20.0)
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Properties
|
||||
##
|
||||
@@ -113,17 +112,14 @@ class ToQTextDocument(Tokenizer):
|
||||
"""Set the document page size and margins in millimetres."""
|
||||
self._pageSize = QPageSize(QSizeF(width, height), QPageSize.Unit.Millimeter)
|
||||
self._pageMargins = QMarginsF(left, top, right, bottom)
|
||||
return
|
||||
|
||||
def setShowNewPage(self, state: bool) -> None:
|
||||
"""Add markers for page breaks."""
|
||||
self._newPage = state
|
||||
return
|
||||
|
||||
def disableAnchors(self) -> None:
|
||||
"""Disable anchors for when writing to file."""
|
||||
self._anchors = False
|
||||
return
|
||||
|
||||
##
|
||||
# Class Methods
|
||||
@@ -200,8 +196,6 @@ class ToQTextDocument(Tokenizer):
|
||||
|
||||
self._init = True
|
||||
|
||||
return
|
||||
|
||||
def doConvert(self) -> None:
|
||||
"""Write text tokens into the document."""
|
||||
if not self._init:
|
||||
@@ -297,8 +291,6 @@ class ToQTextDocument(Tokenizer):
|
||||
self._document.setPageSize(printer.pageRect(QPrinter.Unit.DevicePixel).size())
|
||||
self._document.print(printer)
|
||||
|
||||
return
|
||||
|
||||
def closeDocument(self) -> None:
|
||||
"""Run close document tasks."""
|
||||
self._document.blockSignals(True)
|
||||
@@ -333,8 +325,6 @@ class ToQTextDocument(Tokenizer):
|
||||
|
||||
self._document.blockSignals(False)
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
##
|
||||
@@ -439,8 +429,6 @@ class ToQTextDocument(Tokenizer):
|
||||
# Insert whatever is left in the buffer
|
||||
cursor.insertText(stripEscape(temp[start:]), cFmt)
|
||||
|
||||
return
|
||||
|
||||
def _insertNewPageMarker(self, cursor: QTextCursor) -> None:
|
||||
"""Insert a new page marker."""
|
||||
if self._newPage:
|
||||
@@ -475,8 +463,6 @@ class ToQTextDocument(Tokenizer):
|
||||
if root := self._document.rootFrame():
|
||||
cursor.swap(root.lastCursorPosition())
|
||||
|
||||
return
|
||||
|
||||
def _genHeadStyle(self, hType: BlockTyp, hKey: str, rFmt: QTextBlockFormat) -> T_TextStyle:
|
||||
"""Generate a heading style set."""
|
||||
mTop, mBottom = self._mHead.get(hType, (0.0, 0.0))
|
||||
|
||||
@@ -20,7 +20,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
@@ -41,7 +41,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ToRaw(Tokenizer):
|
||||
"""Core: Raw novelWriter Text Writer
|
||||
"""Core: Raw novelWriter Text Writer.
|
||||
|
||||
A class that will collect the minimally altered original source text
|
||||
and write it to either a text or JSON file.
|
||||
@@ -51,7 +51,6 @@ class ToRaw(Tokenizer):
|
||||
super().__init__(project)
|
||||
self._keepRaw = True
|
||||
self._noTokens = True
|
||||
return
|
||||
|
||||
def doConvert(self) -> None:
|
||||
"""No conversion to perform."""
|
||||
@@ -86,10 +85,7 @@ class ToRaw(Tokenizer):
|
||||
|
||||
logger.info("Wrote file: %s", path)
|
||||
|
||||
return
|
||||
|
||||
def replaceTabs(self, nSpaces: int = 8, spaceChar: str = " ") -> None:
|
||||
"""Replace tabs with spaces."""
|
||||
spaces = spaceChar*nSpaces
|
||||
self._raw = [p.replace("\t", spaces) for p in self._raw]
|
||||
return
|
||||
|
||||
+23
-134
@@ -29,7 +29,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import bisect
|
||||
@@ -98,7 +98,7 @@ class _TagAction(IntFlag):
|
||||
|
||||
|
||||
class GuiDocEditor(QPlainTextEdit):
|
||||
"""Gui Widget: Main Document Editor"""
|
||||
"""Gui Widget: Main Document Editor."""
|
||||
|
||||
__slots__ = (
|
||||
"_autoReplace", "_completer", "_doReplace", "_docChanged", "_docHandle", "_followTag1",
|
||||
@@ -236,8 +236,6 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
|
||||
logger.debug("Ready: GuiDocEditor")
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Properties
|
||||
##
|
||||
@@ -290,15 +288,12 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
|
||||
self.itemHandleChanged.emit("")
|
||||
|
||||
return
|
||||
|
||||
def updateTheme(self) -> None:
|
||||
"""Update theme elements."""
|
||||
self.docSearch.updateTheme()
|
||||
self.docHeader.updateTheme()
|
||||
self.docFooter.updateTheme()
|
||||
self.docToolBar.updateTheme()
|
||||
return
|
||||
|
||||
def updateSyntaxColors(self) -> None:
|
||||
"""Update the syntax highlighting theme."""
|
||||
@@ -323,8 +318,6 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
self._selection.format.setBackground(self._lineColor)
|
||||
self._selection.format.setProperty(QTextFormat.Property.FullWidthSelection, True)
|
||||
|
||||
return
|
||||
|
||||
def initEditor(self) -> None:
|
||||
"""Initialise or re-initialise the editor with the user's
|
||||
settings. This function is both called when the editor is
|
||||
@@ -392,8 +385,6 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
else:
|
||||
self.clearEditor()
|
||||
|
||||
return
|
||||
|
||||
def loadText(self, tHandle: str, tLine: int | None = None) -> bool:
|
||||
"""Load text from a document into the editor. If we have an I/O
|
||||
error, we must handle this and clear the editor so that we don't
|
||||
@@ -471,7 +462,6 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
self.updateDocMargins()
|
||||
self.setDocumentChanged(True)
|
||||
QApplication.restoreOverrideCursor()
|
||||
return
|
||||
|
||||
def saveText(self) -> bool:
|
||||
"""Save the text currently in the editor to the NWDocument
|
||||
@@ -539,7 +529,6 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
vBar.setValue(vBar.value() + 1)
|
||||
count += 1
|
||||
QApplication.processEvents()
|
||||
return
|
||||
|
||||
def updateDocMargins(self) -> None:
|
||||
"""Automatically adjust the margins so the text is centred if
|
||||
@@ -580,8 +569,6 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
lM = max(self._vpMargin, fH)
|
||||
self.setViewportMargins(tM, uM, tM, lM)
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Getters
|
||||
##
|
||||
@@ -591,20 +578,19 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
QTextDocument->toRawText instead of toPlainText. The former preserves
|
||||
non-breaking spaces, the latter does not. We still want to get rid of
|
||||
paragraph and line separators though.
|
||||
|
||||
See: https://doc.qt.io/qt-6/qtextdocument.html#toPlainText
|
||||
"""
|
||||
text = self._qDocument.toRawText()
|
||||
text = text.replace(nwUnicode.U_LSEP, "\n") # Line separators
|
||||
text = text.replace(nwUnicode.U_PSEP, "\n") # Paragraph separators
|
||||
return text
|
||||
return text.replace(nwUnicode.U_PSEP, "\n") # Paragraph separators
|
||||
|
||||
def getSelectedText(self) -> str:
|
||||
"""Get currently selected text."""
|
||||
if (cursor := self.textCursor()).hasSelection():
|
||||
text = cursor.selectedText()
|
||||
text = text.replace(nwUnicode.U_LSEP, "\n") # Line separators
|
||||
text = text.replace(nwUnicode.U_PSEP, "\n") # Paragraph separators
|
||||
return text
|
||||
return text.replace(nwUnicode.U_PSEP, "\n") # Paragraph separators
|
||||
return ""
|
||||
|
||||
def getCursorPosition(self) -> int:
|
||||
@@ -625,7 +611,6 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
logger.debug("Document changed status is '%s'", state)
|
||||
self._docChanged = state
|
||||
self.editedStatusChanged.emit(self._docChanged)
|
||||
return
|
||||
|
||||
def setCursorPosition(self, position: int) -> None:
|
||||
"""Move the cursor to a given position in the document."""
|
||||
@@ -634,14 +619,12 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
cursor.setPosition(minmax(position, 0, chars-1))
|
||||
self.setTextCursor(cursor)
|
||||
self.centerCursor()
|
||||
return
|
||||
|
||||
def saveCursorPosition(self) -> None:
|
||||
"""Save the cursor position to the current project item."""
|
||||
if self._nwItem is not None:
|
||||
cursPos = self.getCursorPosition()
|
||||
self._nwItem.setCursorPos(cursPos)
|
||||
return
|
||||
|
||||
def setCursorLine(self, line: int | None) -> None:
|
||||
"""Move the cursor to a given line in the document."""
|
||||
@@ -650,7 +633,6 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
if block:
|
||||
self.setCursorPosition(block.position())
|
||||
logger.debug("Cursor moved to line %d", line)
|
||||
return
|
||||
|
||||
def setCursorSelection(self, start: int, length: int) -> None:
|
||||
"""Make a text selection."""
|
||||
@@ -659,14 +641,15 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
cursor.setPosition(start, QtMoveAnchor)
|
||||
cursor.setPosition(start + length, QtKeepAnchor)
|
||||
self.setTextCursor(cursor)
|
||||
return
|
||||
|
||||
##
|
||||
# Spell Checking
|
||||
##
|
||||
|
||||
def toggleSpellCheck(self, state: bool | None) -> None:
|
||||
"""This is the main spell check setting function, and this one
|
||||
"""Toggle spell checking.
|
||||
|
||||
This is the main spell check setting function, and this one
|
||||
should call all other setSpellCheck functions in other classes.
|
||||
If the spell check state is not defined (None), then toggle the
|
||||
current status saved in this class.
|
||||
@@ -690,8 +673,6 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
|
||||
logger.debug("Spell check is set to '%s'", str(state))
|
||||
|
||||
return
|
||||
|
||||
def spellCheckDocument(self) -> None:
|
||||
"""Rerun the highlighter to update spell checking status of the
|
||||
currently loaded text.
|
||||
@@ -703,7 +684,6 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
QApplication.restoreOverrideCursor()
|
||||
logger.debug("Document highlighted in %.3f ms", 1000*(time() - start))
|
||||
self.updateStatusMessage.emit(self.tr("Spell check complete"))
|
||||
return
|
||||
|
||||
##
|
||||
# General Class Methods
|
||||
@@ -832,7 +812,6 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
details=self.tr("File Location: {0}").format(self._nwDocument.fileLocation),
|
||||
log=False
|
||||
)
|
||||
return
|
||||
|
||||
def insertText(self, insert: str | nwDocInsert) -> None:
|
||||
"""Insert a specific type of text at the cursor position."""
|
||||
@@ -974,7 +953,6 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
event.acceptProposedAction()
|
||||
else:
|
||||
super().dragEnterEvent(event)
|
||||
return
|
||||
|
||||
def dragMoveEvent(self, event: QDragMoveEvent) -> None:
|
||||
"""Overload drag move event to handle dragged items."""
|
||||
@@ -982,7 +960,6 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
event.acceptProposedAction()
|
||||
else:
|
||||
super().dragMoveEvent(event)
|
||||
return
|
||||
|
||||
def dropEvent(self, event: QDropEvent) -> None:
|
||||
"""Overload drop event to handle dragged items."""
|
||||
@@ -992,7 +969,6 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
self.openDocumentRequest.emit(handles[0], nwDocMode.EDIT, "", True)
|
||||
else:
|
||||
super().dropEvent(event)
|
||||
return
|
||||
|
||||
def focusNextPrevChild(self, _next: bool) -> bool:
|
||||
"""Capture the focus request from the tab key on the text
|
||||
@@ -1019,7 +995,6 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
else:
|
||||
self._processTag(cursor)
|
||||
super().mouseReleaseEvent(event)
|
||||
return
|
||||
|
||||
def resizeEvent(self, event: QResizeEvent) -> None:
|
||||
"""If the text editor is resized, we must make sure the document
|
||||
@@ -1027,7 +1002,6 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
"""
|
||||
self.updateDocMargins()
|
||||
super().resizeEvent(event)
|
||||
return
|
||||
|
||||
##
|
||||
# Public Slots
|
||||
@@ -1035,14 +1009,13 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
|
||||
@pyqtSlot(str, Enum)
|
||||
def onProjectItemChanged(self, tHandle: str, change: nwChange) -> None:
|
||||
"""Called when an item label is changed to check if the document
|
||||
title bar needs updating,
|
||||
"""Process project item change. Called when an item label is
|
||||
changed to check if the document title bar needs updating.
|
||||
"""
|
||||
if tHandle == self._docHandle and change == nwChange.UPDATE:
|
||||
self.docHeader.setHandle(tHandle)
|
||||
self.docFooter.updateInfo()
|
||||
self.updateDocMargins()
|
||||
return
|
||||
|
||||
@pyqtSlot(str)
|
||||
def insertKeyWord(self, keyword: str) -> bool:
|
||||
@@ -1053,8 +1026,7 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
logger.error("Invalid keyword '%s'", keyword)
|
||||
return False
|
||||
logger.debug("Inserting keyword '%s'", keyword)
|
||||
state = self.insertNewBlock(f"{keyword}: ")
|
||||
return state
|
||||
return self.insertNewBlock(f"{keyword}: ")
|
||||
|
||||
@pyqtSlot()
|
||||
def toggleSearch(self) -> None:
|
||||
@@ -1063,14 +1035,12 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
self.closeSearch()
|
||||
else:
|
||||
self.beginSearch()
|
||||
return
|
||||
|
||||
@pyqtSlot(list, list)
|
||||
def updateChangedTags(self, updated: list[str], deleted: list[str]) -> None:
|
||||
"""Tags have changed, so just in case we rehighlight them."""
|
||||
if updated or deleted:
|
||||
self._qDocument.syntaxHighlighter.rehighlightByType(BLOCK_META)
|
||||
return
|
||||
|
||||
##
|
||||
# Private Slots
|
||||
@@ -1116,8 +1086,6 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
if self._autoReplace.process(text, cursor):
|
||||
self._qDocument.syntaxHighlighter.rehighlightBlock(cursor.block())
|
||||
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _cursorMoved(self) -> None:
|
||||
"""Triggered when the cursor moved in the editor."""
|
||||
@@ -1126,7 +1094,6 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
self._selection.cursor = self.textCursor()
|
||||
self._selection.cursor.clearSelection()
|
||||
self.setExtraSelections([self._selection])
|
||||
return
|
||||
|
||||
@pyqtSlot(int, int, str)
|
||||
def _insertCompletion(self, pos: int, length: int, text: str) -> None:
|
||||
@@ -1138,13 +1105,11 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
cursor.setPosition(check + length, QtKeepAnchor)
|
||||
cursor.insertText(text)
|
||||
self._completer.hide()
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _openContextFromCursor(self) -> None:
|
||||
"""Open the spell check context menu at the cursor."""
|
||||
self._openContextMenu(self.cursorRect().center())
|
||||
return
|
||||
|
||||
@pyqtSlot("QPoint")
|
||||
def _openContextMenu(self, pos: QPoint) -> None:
|
||||
@@ -1231,8 +1196,6 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
|
||||
ctxMenu.setParent(None)
|
||||
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _runDocumentTasks(self) -> None:
|
||||
"""Run timer document tasks."""
|
||||
@@ -1269,11 +1232,10 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
if not self.textCursor().hasSelection():
|
||||
# Selection counter should take precedence (#2155)
|
||||
self.docFooter.updateMainCount(mCount, False)
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _updateSelectedStatus(self) -> None:
|
||||
"""The user made a change in text selection. Forward this
|
||||
"""Process user change in text selection. Forward this
|
||||
information to the footer, and start the selection word counter.
|
||||
"""
|
||||
if self.textCursor().hasSelection():
|
||||
@@ -1282,7 +1244,6 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
else:
|
||||
self._timerSel.stop()
|
||||
self.docFooter.updateMainCount(0, False)
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _runSelCounter(self) -> None:
|
||||
@@ -1300,14 +1261,12 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
if self._docHandle and self._nwItem:
|
||||
self.docFooter.updateMainCount(cCount if CONFIG.useCharCount else wCount, True)
|
||||
self._timerSel.stop()
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _closeCurrentDocument(self) -> None:
|
||||
"""Close the document. Forwarded to the main Gui."""
|
||||
self.closeEditorRequest.emit()
|
||||
self.docToolBar.setVisible(False)
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _toggleToolBarVisibility(self) -> None:
|
||||
@@ -1315,7 +1274,6 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
state = not self.docToolBar.isVisible()
|
||||
self.docToolBar.setVisible(state)
|
||||
CONFIG.showEditToolBar = state
|
||||
return
|
||||
|
||||
##
|
||||
# Search & Replace
|
||||
@@ -1326,14 +1284,12 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
self.docSearch.setSearchText(self.getSelectedText() or None)
|
||||
resS, _ = self.findAllOccurences()
|
||||
self.docSearch.setResultCount(None, len(resS))
|
||||
return
|
||||
|
||||
def beginReplace(self) -> None:
|
||||
"""Initialise the search box and reset the replace text box."""
|
||||
self.beginSearch()
|
||||
self.docSearch.setReplaceText("")
|
||||
self.updateDocMargins()
|
||||
return
|
||||
|
||||
def findNext(self, goBack: bool = False) -> None:
|
||||
"""Search for the next or previous occurrence of the search bar
|
||||
@@ -1621,8 +1577,6 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
|
||||
self.setTextCursor(cursor)
|
||||
|
||||
return
|
||||
|
||||
def _replaceQuotes(self, sQuote: str, oQuote: str, cQuote: str) -> None:
|
||||
"""Replace all straight quotes in the selected text."""
|
||||
cursor = self.textCursor()
|
||||
@@ -1882,8 +1836,6 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
cursor.insertText(cleanText.rstrip() + "\n")
|
||||
cursor.endEditBlock()
|
||||
|
||||
return
|
||||
|
||||
def _insertCommentStructure(self, style: nwComment) -> None:
|
||||
"""Insert a shortcut/comment combo."""
|
||||
if self._docHandle and style == nwComment.FOOTNOTE:
|
||||
@@ -1925,7 +1877,6 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
cursor.endEditBlock()
|
||||
cursor.setPosition(pos)
|
||||
self.setTextCursor(cursor)
|
||||
return
|
||||
|
||||
def _addWord(self, word: str, block: QTextBlock, save: bool) -> None:
|
||||
"""Slot for the spell check context menu triggered when the user
|
||||
@@ -1934,7 +1885,6 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
logger.debug("Added '%s' to project dictionary, %s", word, "saved" if save else "unsaved")
|
||||
SHARED.spelling.addWord(word, save=save)
|
||||
self._qDocument.syntaxHighlighter.rehighlightBlock(block)
|
||||
return
|
||||
|
||||
def _processTag(
|
||||
self, cursor: QTextCursor | None = None, follow: bool = True, create: bool = False
|
||||
@@ -2008,7 +1958,6 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
if self._docHandle:
|
||||
text = block.text().lstrip("#").lstrip("!").strip()
|
||||
self.requestProjectItemRenamed.emit(self._docHandle, text)
|
||||
return
|
||||
|
||||
def _autoSelect(self) -> QTextCursor:
|
||||
"""Return a cursor which may or may not have a selection based
|
||||
@@ -2078,14 +2027,11 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
|
||||
self.setTextCursor(cursor)
|
||||
|
||||
return
|
||||
|
||||
def _makePosSelection(self, mode: QTextCursor.SelectionType, pos: QPoint) -> None:
|
||||
"""Select text based on selection mode, but first move cursor."""
|
||||
cursor = self.cursorForPosition(pos)
|
||||
self.setTextCursor(cursor)
|
||||
self._makeSelection(mode)
|
||||
return
|
||||
|
||||
def _allowAutoReplace(self, state: bool) -> None:
|
||||
"""Enable/disable the auto-replace feature temporarily."""
|
||||
@@ -2093,11 +2039,10 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
self._doReplace = CONFIG.doReplace
|
||||
else:
|
||||
self._doReplace = False
|
||||
return
|
||||
|
||||
|
||||
class CommandCompleter(QMenu):
|
||||
"""GuiWidget: Command Completer Menu
|
||||
"""GuiWidget: Command Completer Menu.
|
||||
|
||||
This is a context menu with options populated from the user's
|
||||
defined tags and keys. It also helps to type the meta data keyword
|
||||
@@ -2109,7 +2054,6 @@ class CommandCompleter(QMenu):
|
||||
|
||||
def __init__(self, parent: QWidget) -> None:
|
||||
super().__init__(parent=parent)
|
||||
return
|
||||
|
||||
def updateMetaText(self, text: str, pos: int) -> bool:
|
||||
"""Update the menu options based on the line of text."""
|
||||
@@ -2203,7 +2147,6 @@ class CommandCompleter(QMenu):
|
||||
super().keyPressEvent(event)
|
||||
elif isinstance(parent, GuiDocEditor):
|
||||
parent.keyPressEvent(event)
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
@@ -2212,11 +2155,10 @@ class CommandCompleter(QMenu):
|
||||
def _emitComplete(self, pos: int, length: int, value: str) -> None:
|
||||
"""Emit the signal to indicate a selection has been made."""
|
||||
self.complete.emit(pos, length, value)
|
||||
return
|
||||
|
||||
|
||||
class BackgroundWordCounter(QRunnable):
|
||||
"""The Off-GUI Thread Word Counter
|
||||
"""The Off-GUI Thread Word Counter.
|
||||
|
||||
A runnable for the word counter to be run in the thread pool off the
|
||||
main GUI thread.
|
||||
@@ -2228,9 +2170,9 @@ class BackgroundWordCounter(QRunnable):
|
||||
self._forSelection = forSelection
|
||||
self._isRunning = False
|
||||
self.signals = BackgroundWordCounterSignals()
|
||||
return
|
||||
|
||||
def isRunning(self) -> bool:
|
||||
"""Return True if the word counter is already running."""
|
||||
return self._isRunning
|
||||
|
||||
@pyqtSlot()
|
||||
@@ -2248,17 +2190,17 @@ class BackgroundWordCounter(QRunnable):
|
||||
self.signals.countsReady.emit(cC, wC, pC)
|
||||
self._isRunning = False
|
||||
|
||||
return
|
||||
|
||||
|
||||
class BackgroundWordCounterSignals(QObject):
|
||||
"""The QRunnable cannot emit a signal, so we need a simple QObject
|
||||
to hold the word counter signal.
|
||||
"""
|
||||
|
||||
countsReady = pyqtSignal(int, int, int)
|
||||
|
||||
|
||||
class TextAutoReplace:
|
||||
"""Encapsulates the editor auto replace feature."""
|
||||
|
||||
__slots__ = (
|
||||
"_doPadAfter", "_doPadBefore", "_padAfter", "_padBefore", "_padChar",
|
||||
@@ -2268,7 +2210,6 @@ class TextAutoReplace:
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.initSettings()
|
||||
return
|
||||
|
||||
def initSettings(self) -> None:
|
||||
"""Initialise the auto-replace settings from config."""
|
||||
@@ -2287,7 +2228,6 @@ class TextAutoReplace:
|
||||
self._padAfter = CONFIG.fmtPadAfter
|
||||
self._doPadBefore = bool(CONFIG.fmtPadBefore)
|
||||
self._doPadAfter = bool(CONFIG.fmtPadAfter)
|
||||
return
|
||||
|
||||
def process(self, text: str, cursor: QTextCursor) -> bool:
|
||||
"""Auto-replace text elements based on main configuration.
|
||||
@@ -2401,7 +2341,7 @@ class TextAutoReplace:
|
||||
|
||||
|
||||
class GuiDocToolBar(QWidget):
|
||||
"""The Formatting and Options Fold Out Menu
|
||||
"""The Formatting and Options Fold Out Menu.
|
||||
|
||||
Only used by DocEditor, and is opened by the first button in the
|
||||
header.
|
||||
@@ -2513,8 +2453,6 @@ class GuiDocToolBar(QWidget):
|
||||
|
||||
logger.debug("Ready: GuiDocToolBar")
|
||||
|
||||
return
|
||||
|
||||
def updateTheme(self) -> None:
|
||||
"""Initialise GUI elements that depend on specific settings."""
|
||||
syntax = SHARED.theme.syntaxTheme
|
||||
@@ -2537,11 +2475,9 @@ class GuiDocToolBar(QWidget):
|
||||
self.tbSuperscript.setThemeIcon("fmt_superscript")
|
||||
self.tbSubscript.setThemeIcon("fmt_subscript")
|
||||
|
||||
return
|
||||
|
||||
|
||||
class GuiDocEditSearch(QFrame):
|
||||
"""The Embedded Document Search/Replace Feature
|
||||
"""The Embedded Document Search/Replace Feature.
|
||||
|
||||
Only used by DocEditor, and is at a fixed position in the
|
||||
QTextEdit's viewport.
|
||||
@@ -2671,8 +2607,6 @@ class GuiDocEditSearch(QFrame):
|
||||
|
||||
logger.debug("Ready: GuiDocEditSearch")
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Properties
|
||||
##
|
||||
@@ -2721,14 +2655,12 @@ class GuiDocEditSearch(QFrame):
|
||||
self.searchBox.selectAll()
|
||||
if CONFIG.searchRegEx:
|
||||
self._alertSearchValid(True)
|
||||
return
|
||||
|
||||
def setReplaceText(self, text: str) -> None:
|
||||
"""Set the replace text."""
|
||||
self.showReplace.setChecked(True)
|
||||
self.replaceBox.setFocus()
|
||||
self.replaceBox.setText(text)
|
||||
return
|
||||
|
||||
def setResultCount(self, currRes: int | None, resCount: int | None) -> None:
|
||||
"""Set the count values for the current search."""
|
||||
@@ -2743,7 +2675,6 @@ class GuiDocEditSearch(QFrame):
|
||||
self.resultLabel.setMinimumWidth(minWidth)
|
||||
self.adjustSize()
|
||||
self.docEditor.updateDocMargins()
|
||||
return
|
||||
|
||||
##
|
||||
# Methods
|
||||
@@ -2759,7 +2690,6 @@ class GuiDocEditSearch(QFrame):
|
||||
self.resultLabel.setMinimumWidth(
|
||||
SHARED.theme.getTextWidth("?/?", SHARED.theme.guiFontSmall)
|
||||
)
|
||||
return
|
||||
|
||||
def updateTheme(self) -> None:
|
||||
"""Update theme elements."""
|
||||
@@ -2784,11 +2714,9 @@ class GuiDocEditSearch(QFrame):
|
||||
self.searchOpt.setStyleSheet("QToolBar {padding: 0;}")
|
||||
self.showReplace.setStyleSheet("QToolButton {border: none; background: transparent;}")
|
||||
|
||||
return
|
||||
|
||||
def cycleFocus(self) -> bool:
|
||||
"""The tab key just alternates focus between the two input
|
||||
boxes, if the replace box is visible.
|
||||
"""Cycle focus on tab key press. This just alternates focus
|
||||
between the two input boxes, if the replace box is visible.
|
||||
"""
|
||||
if self.searchBox.hasFocus():
|
||||
self.replaceBox.setFocus()
|
||||
@@ -2813,7 +2741,6 @@ class GuiDocEditSearch(QFrame):
|
||||
self.setVisible(False)
|
||||
self.docEditor.updateDocMargins()
|
||||
self.docEditor.setFocus()
|
||||
return
|
||||
|
||||
##
|
||||
# Private Slots
|
||||
@@ -2823,13 +2750,11 @@ class GuiDocEditSearch(QFrame):
|
||||
def _doSearch(self) -> None:
|
||||
"""Call the search action function for the document editor."""
|
||||
self.docEditor.findNext(goBack=(QApplication.keyboardModifiers() == QtModShift))
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _doReplace(self) -> None:
|
||||
"""Call the replace action function for the document editor."""
|
||||
self.docEditor.replaceNext()
|
||||
return
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def _doToggleReplace(self, state: bool) -> None:
|
||||
@@ -2838,43 +2763,36 @@ class GuiDocEditSearch(QFrame):
|
||||
self.replaceButton.setVisible(state)
|
||||
self.adjustSize()
|
||||
self.docEditor.updateDocMargins()
|
||||
return
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def _doToggleCase(self, state: bool) -> None:
|
||||
"""Enable/disable case sensitive mode."""
|
||||
CONFIG.searchCase = state
|
||||
return
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def _doToggleWord(self, state: bool) -> None:
|
||||
"""Enable/disable whole word search mode."""
|
||||
CONFIG.searchWord = state
|
||||
return
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def _doToggleRegEx(self, state: bool) -> None:
|
||||
"""Enable/disable regular expression search mode."""
|
||||
CONFIG.searchRegEx = state
|
||||
return
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def _doToggleLoop(self, state: bool) -> None:
|
||||
"""Enable/disable looping the search."""
|
||||
CONFIG.searchLoop = state
|
||||
return
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def _doToggleProject(self, state: bool) -> None:
|
||||
"""Enable/disable continuing search in next project file."""
|
||||
CONFIG.searchNextFile = state
|
||||
return
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def _doToggleMatchCap(self, state: bool) -> None:
|
||||
"""Enable/disable preserving capitalisation when replacing."""
|
||||
CONFIG.searchMatchCap = state
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
@@ -2890,11 +2808,10 @@ class GuiDocEditSearch(QFrame):
|
||||
palette.text().color() if isValid else SHARED.theme.errorText
|
||||
)
|
||||
self.searchBox.setPalette(palette)
|
||||
return
|
||||
|
||||
|
||||
class GuiDocEditHeader(QWidget):
|
||||
"""The Embedded Document Header
|
||||
"""The Embedded Document Header.
|
||||
|
||||
Only used by DocEditor, and is at a fixed position in the
|
||||
QTextEdit's viewport.
|
||||
@@ -2985,8 +2902,6 @@ class GuiDocEditHeader(QWidget):
|
||||
|
||||
logger.debug("Ready: GuiDocEditHeader")
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Methods
|
||||
##
|
||||
@@ -3003,7 +2918,6 @@ class GuiDocEditHeader(QWidget):
|
||||
self.searchButton.setVisible(False)
|
||||
self.closeButton.setVisible(False)
|
||||
self.minmaxButton.setVisible(False)
|
||||
return
|
||||
|
||||
def setOutline(self, data: dict[int, str]) -> None:
|
||||
"""Set the document outline dataset."""
|
||||
@@ -3015,13 +2929,11 @@ class GuiDocEditHeader(QWidget):
|
||||
action.triggered.connect(qtLambda(self._gotoBlock, number))
|
||||
self._docOutline = data
|
||||
logger.debug("Document outline updated in %.3f ms", 1000*(time() - tStart))
|
||||
return
|
||||
|
||||
def updateFont(self) -> None:
|
||||
"""Update the font settings."""
|
||||
self.setFont(SHARED.theme.guiFont)
|
||||
self.itemTitle.setFont(SHARED.theme.guiFontSmall)
|
||||
return
|
||||
|
||||
def updateTheme(self) -> None:
|
||||
"""Update theme elements."""
|
||||
@@ -3040,8 +2952,6 @@ class GuiDocEditHeader(QWidget):
|
||||
|
||||
self.matchColors()
|
||||
|
||||
return
|
||||
|
||||
def matchColors(self) -> None:
|
||||
"""Update the colours of the widget to match those of the syntax
|
||||
theme rather than the main GUI.
|
||||
@@ -3055,12 +2965,10 @@ class GuiDocEditHeader(QWidget):
|
||||
self.itemTitle.setTextColors(
|
||||
color=palette.windowText().color(), faded=SHARED.theme.fadedText
|
||||
)
|
||||
return
|
||||
|
||||
def changeFocusState(self, state: bool) -> None:
|
||||
"""Toggle focus state."""
|
||||
self.itemTitle.setColorState(state)
|
||||
return
|
||||
|
||||
def setHandle(self, tHandle: str) -> None:
|
||||
"""Set the document title from the handle, or alternatively, set
|
||||
@@ -3081,8 +2989,6 @@ class GuiDocEditHeader(QWidget):
|
||||
self.closeButton.setVisible(True)
|
||||
self.minmaxButton.setVisible(True)
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Private Slots
|
||||
##
|
||||
@@ -3092,19 +2998,16 @@ class GuiDocEditHeader(QWidget):
|
||||
"""Trigger the close editor on the main window."""
|
||||
self.clearHeader()
|
||||
self.closeDocumentRequest.emit()
|
||||
return
|
||||
|
||||
@pyqtSlot(int)
|
||||
def _gotoBlock(self, blockNumber: int) -> None:
|
||||
"""Move cursor to a specific heading."""
|
||||
self.docEditor.setCursorLine(blockNumber + 1)
|
||||
return
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def _focusModeChanged(self, focusMode: bool) -> None:
|
||||
"""Update minimise/maximise icon of the Focus Mode button."""
|
||||
self.minmaxButton.setThemeIcon("minimise" if focusMode else "maximise", "blue")
|
||||
return
|
||||
|
||||
##
|
||||
# Events
|
||||
@@ -3116,11 +3019,10 @@ class GuiDocEditHeader(QWidget):
|
||||
"""
|
||||
if event.button() == QtMouseLeft:
|
||||
self.docEditor.requestProjectItemSelected.emit(self._docHandle or "", True)
|
||||
return
|
||||
|
||||
|
||||
class GuiDocEditFooter(QWidget):
|
||||
"""The Embedded Document Footer
|
||||
"""The Embedded Document Footer.
|
||||
|
||||
Only used by DocEditor, and is at a fixed position in the
|
||||
QTextEdit's viewport.
|
||||
@@ -3205,8 +3107,6 @@ class GuiDocEditFooter(QWidget):
|
||||
|
||||
logger.debug("Ready: GuiDocEditFooter")
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Methods
|
||||
##
|
||||
@@ -3216,7 +3116,6 @@ class GuiDocEditFooter(QWidget):
|
||||
self._trMainCount = trStats(nwLabels.STATS_DISPLAY[
|
||||
nwStats.CHARS if CONFIG.useCharCount else nwStats.WORDS
|
||||
])
|
||||
return
|
||||
|
||||
def updateFont(self) -> None:
|
||||
"""Update the font settings."""
|
||||
@@ -3224,7 +3123,6 @@ class GuiDocEditFooter(QWidget):
|
||||
self.statusText.setFont(SHARED.theme.guiFontSmall)
|
||||
self.linesText.setFont(SHARED.theme.guiFontSmall)
|
||||
self.wordsText.setFont(SHARED.theme.guiFontSmall)
|
||||
return
|
||||
|
||||
def updateTheme(self) -> None:
|
||||
"""Update theme elements."""
|
||||
@@ -3232,7 +3130,6 @@ class GuiDocEditFooter(QWidget):
|
||||
self.linesIcon.setPixmap(SHARED.theme.getPixmap("lines", (iPx, iPx)))
|
||||
self.wordsIcon.setPixmap(SHARED.theme.getPixmap("stats", (iPx, iPx)))
|
||||
self.matchColors()
|
||||
return
|
||||
|
||||
def matchColors(self) -> None:
|
||||
"""Update the colours of the widget to match those of the syntax
|
||||
@@ -3250,8 +3147,6 @@ class GuiDocEditFooter(QWidget):
|
||||
self.linesText.setPalette(palette)
|
||||
self.wordsText.setPalette(palette)
|
||||
|
||||
return
|
||||
|
||||
def setHandle(self, tHandle: str | None) -> None:
|
||||
"""Set the handle that will populate the footer's data."""
|
||||
self._docHandle = tHandle
|
||||
@@ -3264,8 +3159,6 @@ class GuiDocEditFooter(QWidget):
|
||||
self.updateInfo()
|
||||
self.updateMainCount(0, False)
|
||||
|
||||
return
|
||||
|
||||
def updateInfo(self) -> None:
|
||||
"""Update the content of text labels."""
|
||||
if self._tItem is None:
|
||||
@@ -3280,8 +3173,6 @@ class GuiDocEditFooter(QWidget):
|
||||
self.statusIcon.setPixmap(sIcon)
|
||||
self.statusText.setText(sText)
|
||||
|
||||
return
|
||||
|
||||
def updateLineCount(self, cursor: QTextCursor) -> None:
|
||||
"""Update the line and document position counter."""
|
||||
if document := cursor.document():
|
||||
@@ -3291,7 +3182,6 @@ class GuiDocEditFooter(QWidget):
|
||||
self.linesText.setText(
|
||||
self._trLineCount.format(f"{cLine:n}", f"{100*cPos//cCount:d} %")
|
||||
)
|
||||
return
|
||||
|
||||
def updateMainCount(self, count: int, selection: bool) -> None:
|
||||
"""Update main counter information."""
|
||||
@@ -3304,4 +3194,3 @@ class GuiDocEditFooter(QWidget):
|
||||
else:
|
||||
text = self._trMainCount.format("0", "+0")
|
||||
self.wordsText.setText(text)
|
||||
return
|
||||
|
||||
@@ -21,7 +21,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
@@ -57,6 +57,7 @@ BLOCK_TITLE = 4
|
||||
|
||||
|
||||
class GuiDocHighlighter(QSyntaxHighlighter):
|
||||
"""GUI: Editor Syntax Highlighter."""
|
||||
|
||||
__slots__ = (
|
||||
"_cmnRules", "_dialogParser", "_hStyles", "_isInactive", "_isNovel",
|
||||
@@ -85,8 +86,6 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
||||
|
||||
logger.debug("Ready: GuiDocHighlighter")
|
||||
|
||||
return
|
||||
|
||||
def initHighlighter(self) -> None:
|
||||
"""Initialise the syntax highlighter, setting all the colour
|
||||
rules and building the RegExes.
|
||||
@@ -255,8 +254,6 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
||||
self._txtRules.append((rxRule, hlRule))
|
||||
self._cmnRules.append((rxRule, hlRule))
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Setters
|
||||
##
|
||||
@@ -264,7 +261,6 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
||||
def setSpellCheck(self, state: bool) -> None:
|
||||
"""Enable/disable the real time spell checker."""
|
||||
self._spellCheck = state
|
||||
return
|
||||
|
||||
def setHandle(self, tHandle: str) -> None:
|
||||
"""Set the handle of the currently highlighted document."""
|
||||
@@ -275,7 +271,6 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
||||
self._isNovel = item.isDocumentLayout()
|
||||
self._isInactive = item.isInactiveClass()
|
||||
logger.debug("Syntax highlighter enabled for item '%s'", tHandle)
|
||||
return
|
||||
|
||||
##
|
||||
# Methods
|
||||
@@ -293,7 +288,6 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
||||
if block.userState() & cType > 0:
|
||||
self.rehighlightBlock(block)
|
||||
logger.debug("Document highlighted in %.3f ms" % (1000*(time() - tStart)))
|
||||
return
|
||||
|
||||
##
|
||||
# Highlight Block
|
||||
@@ -506,10 +500,13 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
||||
|
||||
self._hStyles[name] = charFormat
|
||||
|
||||
return
|
||||
|
||||
|
||||
class TextBlockData(QTextBlockUserData):
|
||||
"""Custom QTextBlock Data.
|
||||
|
||||
Custom data stored in a single text block. The spell check state is
|
||||
cached here and used when correcting misspelled text.
|
||||
"""
|
||||
|
||||
__slots__ = ("_metaData", "_offset", "_spellErrors", "_text")
|
||||
|
||||
@@ -519,7 +516,6 @@ class TextBlockData(QTextBlockUserData):
|
||||
self._offset = 0
|
||||
self._metaData: list[tuple[int, int, str, str]] = []
|
||||
self._spellErrors: list[tuple[int, int, str]] = []
|
||||
return
|
||||
|
||||
@property
|
||||
def metaData(self) -> list[tuple[int, int, str, str]]:
|
||||
@@ -553,8 +549,6 @@ class TextBlockData(QTextBlockUserData):
|
||||
self._text = text.replace("\u02bc", "'").replace("_", " ")
|
||||
self._offset = offset
|
||||
|
||||
return
|
||||
|
||||
def spellCheck(self, utf16Map: list[int] | None) -> list[tuple[int, int, str]]:
|
||||
"""Run the spell checker and cache the result, and return the
|
||||
list of spell check errors.
|
||||
|
||||
@@ -23,7 +23,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
@@ -60,6 +60,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GuiDocViewer(QTextBrowser):
|
||||
"""GUI: Document Viewer."""
|
||||
|
||||
closeDocumentRequest = pyqtSignal()
|
||||
documentLoaded = pyqtSignal(str)
|
||||
@@ -110,8 +111,6 @@ class GuiDocViewer(QTextBrowser):
|
||||
|
||||
logger.debug("Ready: GuiDocViewer")
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Properties
|
||||
##
|
||||
@@ -138,13 +137,11 @@ class GuiDocViewer(QTextBrowser):
|
||||
self.setSearchPaths([""])
|
||||
self._docHandle = None
|
||||
self.docHeader.clearHeader()
|
||||
return
|
||||
|
||||
def updateTheme(self) -> None:
|
||||
"""Update theme elements."""
|
||||
self.docHeader.updateTheme()
|
||||
self.docFooter.updateTheme()
|
||||
return
|
||||
|
||||
def initViewer(self) -> None:
|
||||
"""Set editor settings from main config."""
|
||||
@@ -206,8 +203,6 @@ class GuiDocViewer(QTextBrowser):
|
||||
# If we have a document open, we should reload it in case the font changed
|
||||
self.reloadText()
|
||||
|
||||
return
|
||||
|
||||
def loadText(self, tHandle: str, updateHistory: bool = True) -> bool:
|
||||
"""Load text into the viewer from an item handle."""
|
||||
if not SHARED.project.tree.checkType(tHandle, nwItemType.FILE):
|
||||
@@ -280,7 +275,6 @@ class GuiDocViewer(QTextBrowser):
|
||||
"""Reload the text in the current document."""
|
||||
if self._docHandle:
|
||||
self.loadText(self._docHandle, updateHistory=False)
|
||||
return
|
||||
|
||||
def docAction(self, action: nwDocAction) -> bool:
|
||||
"""Process document actions on the current document."""
|
||||
@@ -308,7 +302,6 @@ class GuiDocViewer(QTextBrowser):
|
||||
def clearNavHistory(self) -> None:
|
||||
"""Clear the navigation history."""
|
||||
self.docHistory.clear()
|
||||
return
|
||||
|
||||
def updateDocMargins(self) -> None:
|
||||
"""Automatically adjust the margins so the text is centred."""
|
||||
@@ -337,8 +330,6 @@ class GuiDocViewer(QTextBrowser):
|
||||
self.docFooter.setGeometry(tB, fY, tW, fH)
|
||||
self.setViewportMargins(tM, max(cM, tH), tM, max(cM, fH))
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Setters
|
||||
##
|
||||
@@ -347,7 +338,6 @@ class GuiDocViewer(QTextBrowser):
|
||||
"""Set the scrollbar position."""
|
||||
if (vBar := self.verticalScrollBar()) and vBar.isVisible():
|
||||
vBar.setValue(pos)
|
||||
return
|
||||
|
||||
##
|
||||
# Public Slots
|
||||
@@ -359,7 +349,6 @@ class GuiDocViewer(QTextBrowser):
|
||||
if tHandle == self._docHandle and change == nwChange.UPDATE:
|
||||
self.docHeader.setHandle(tHandle)
|
||||
self.updateDocMargins()
|
||||
return
|
||||
|
||||
@pyqtSlot(str)
|
||||
def navigateTo(self, anchor: str) -> None:
|
||||
@@ -367,7 +356,6 @@ class GuiDocViewer(QTextBrowser):
|
||||
if isinstance(anchor, str) and anchor.startswith("#"):
|
||||
logger.debug("Moving to anchor '%s'", anchor)
|
||||
self.setSource(QUrl(anchor))
|
||||
return
|
||||
|
||||
##
|
||||
# Private Slots
|
||||
@@ -377,13 +365,11 @@ class GuiDocViewer(QTextBrowser):
|
||||
def navBackward(self) -> None:
|
||||
"""Navigate backwards in the document view history."""
|
||||
self.docHistory.backward()
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def navForward(self) -> None:
|
||||
"""Navigate forwards in the document view history."""
|
||||
self.docHistory.forward()
|
||||
return
|
||||
|
||||
@pyqtSlot("QUrl")
|
||||
def _linkClicked(self, url: QUrl) -> None:
|
||||
@@ -396,7 +382,6 @@ class GuiDocViewer(QTextBrowser):
|
||||
self.navigateTo(link)
|
||||
elif link.startswith("http"):
|
||||
QDesktopServices.openUrl(QUrl(url))
|
||||
return
|
||||
|
||||
@pyqtSlot("QPoint")
|
||||
def _openContextMenu(self, point: QPoint) -> None:
|
||||
@@ -430,8 +415,6 @@ class GuiDocViewer(QTextBrowser):
|
||||
|
||||
ctxMenu.setParent(None)
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Events
|
||||
##
|
||||
@@ -440,7 +423,6 @@ class GuiDocViewer(QTextBrowser):
|
||||
"""Update document margins when widget is resized."""
|
||||
self.updateDocMargins()
|
||||
super().resizeEvent(event)
|
||||
return
|
||||
|
||||
def mouseReleaseEvent(self, event: QMouseEvent) -> None:
|
||||
"""Capture mouse click events on the document."""
|
||||
@@ -450,7 +432,6 @@ class GuiDocViewer(QTextBrowser):
|
||||
self.navForward()
|
||||
else:
|
||||
super().mouseReleaseEvent(event)
|
||||
return
|
||||
|
||||
def dragEnterEvent(self, event: QDragEnterEvent) -> None:
|
||||
"""Overload drag enter event to handle dragged items."""
|
||||
@@ -458,7 +439,6 @@ class GuiDocViewer(QTextBrowser):
|
||||
event.acceptProposedAction()
|
||||
else:
|
||||
super().dragEnterEvent(event)
|
||||
return
|
||||
|
||||
def dragMoveEvent(self, event: QDragMoveEvent) -> None:
|
||||
"""Overload drag move event to handle dragged items."""
|
||||
@@ -466,7 +446,6 @@ class GuiDocViewer(QTextBrowser):
|
||||
event.acceptProposedAction()
|
||||
else:
|
||||
super().dragMoveEvent(event)
|
||||
return
|
||||
|
||||
def dropEvent(self, event: QDropEvent) -> None:
|
||||
"""Overload drop event to handle dragged items."""
|
||||
@@ -476,7 +455,6 @@ class GuiDocViewer(QTextBrowser):
|
||||
self.openDocumentRequest.emit(handles[0], nwDocMode.VIEW, "", True)
|
||||
else:
|
||||
super().dropEvent(event)
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
@@ -500,16 +478,18 @@ class GuiDocViewer(QTextBrowser):
|
||||
|
||||
self.setTextCursor(cursor)
|
||||
|
||||
return
|
||||
|
||||
def _makePosSelection(self, selType: QTextCursor.SelectionType, pos: QPoint) -> None:
|
||||
"""Handle text selection at a given location."""
|
||||
self.setTextCursor(self.cursorForPosition(pos))
|
||||
self._makeSelection(selType)
|
||||
return
|
||||
|
||||
|
||||
class GuiDocViewHistory:
|
||||
"""GUI: Document Viewer History.
|
||||
|
||||
This class holds the navigation history for the viewer panel, which
|
||||
is used for backward/forward navigation.
|
||||
"""
|
||||
|
||||
def __init__(self, docViewer: GuiDocViewer) -> None:
|
||||
self.docViewer = docViewer
|
||||
@@ -517,7 +497,6 @@ class GuiDocViewHistory:
|
||||
self._posHistory = []
|
||||
self._currPos = -1
|
||||
self._prevPos = -1
|
||||
return
|
||||
|
||||
def clear(self) -> None:
|
||||
"""Clear the view history."""
|
||||
@@ -526,7 +505,6 @@ class GuiDocViewHistory:
|
||||
self._posHistory = []
|
||||
self._currPos = -1
|
||||
self._prevPos = -1
|
||||
return
|
||||
|
||||
def append(self, tHandle: str) -> bool:
|
||||
"""Append a document handle and its scroll bar position to the
|
||||
@@ -566,7 +544,6 @@ class GuiDocViewHistory:
|
||||
self._currPos = newPos
|
||||
self._updateNavButtons()
|
||||
self._dumpHistory()
|
||||
return
|
||||
|
||||
def backward(self) -> None:
|
||||
"""Navigate to the previous entry in the view history."""
|
||||
@@ -580,7 +557,6 @@ class GuiDocViewHistory:
|
||||
self._currPos = newPos
|
||||
self._updateNavButtons()
|
||||
self._dumpHistory()
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
@@ -590,12 +566,10 @@ class GuiDocViewHistory:
|
||||
"""Update the scrollbar position of the previous entry."""
|
||||
if self._prevPos >= 0 and self._prevPos < len(self._posHistory):
|
||||
self._posHistory[self._prevPos] = self.docViewer.scrollPosition
|
||||
return
|
||||
|
||||
def _updateNavButtons(self) -> None:
|
||||
"""Update the navigation buttons in the document header."""
|
||||
self.docViewer.docHeader.updateNavButtons(0, len(self._navHistory) - 1, self._currPos)
|
||||
return
|
||||
|
||||
def _truncateHistory(self, atPos: int) -> None:
|
||||
"""Truncate the navigation history to the given position. Also
|
||||
@@ -606,7 +580,6 @@ class GuiDocViewHistory:
|
||||
self._posHistory = self._posHistory[nSkip:atPos + 1]
|
||||
self._currPos -= nSkip
|
||||
self._prevPos -= nSkip
|
||||
return
|
||||
|
||||
def _dumpHistory(self) -> None:
|
||||
"""Debug function to dump history to the logger. Since it is a
|
||||
@@ -616,11 +589,10 @@ class GuiDocViewHistory:
|
||||
for i, (h, p) in enumerate(zip(self._navHistory, self._posHistory, strict=False)):
|
||||
a = ">" if i == self._currPos else " "
|
||||
logger.debug(f"History {i + 1:02d}: {a} {h:13s} [x:{p}]")
|
||||
return
|
||||
|
||||
|
||||
class GuiDocViewHeader(QWidget):
|
||||
"""The Embedded Document Header
|
||||
"""The Embedded Document Header.
|
||||
|
||||
Only used by DocViewer, and is at a fixed position in the
|
||||
QTextBrowser's viewport.
|
||||
@@ -711,8 +683,6 @@ class GuiDocViewHeader(QWidget):
|
||||
|
||||
logger.debug("Ready: GuiDocViewHeader")
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Methods
|
||||
##
|
||||
@@ -730,7 +700,6 @@ class GuiDocViewHeader(QWidget):
|
||||
self.editButton.setVisible(False)
|
||||
self.refreshButton.setVisible(False)
|
||||
self.closeButton.setVisible(False)
|
||||
return
|
||||
|
||||
def setOutline(self, data: dict[str, tuple[str, int]]) -> None:
|
||||
"""Set the document outline dataset."""
|
||||
@@ -750,13 +719,11 @@ class GuiDocViewHeader(QWidget):
|
||||
lambda _, title=title: self.docViewer.navigateTo(f"#{tHandle}:{title}")
|
||||
)
|
||||
self._docOutline = data
|
||||
return
|
||||
|
||||
def updateFont(self) -> None:
|
||||
"""Update the font settings."""
|
||||
self.setFont(SHARED.theme.guiFont)
|
||||
self.itemTitle.setFont(SHARED.theme.guiFontSmall)
|
||||
return
|
||||
|
||||
def updateTheme(self) -> None:
|
||||
"""Update theme elements."""
|
||||
@@ -777,8 +744,6 @@ class GuiDocViewHeader(QWidget):
|
||||
|
||||
self.matchColors()
|
||||
|
||||
return
|
||||
|
||||
def matchColors(self) -> None:
|
||||
"""Update the colours of the widget to match those of the syntax
|
||||
theme rather than the main GUI.
|
||||
@@ -792,15 +757,13 @@ class GuiDocViewHeader(QWidget):
|
||||
self.itemTitle.setTextColors(
|
||||
color=palette.windowText().color(), faded=SHARED.theme.fadedText
|
||||
)
|
||||
return
|
||||
|
||||
def changeFocusState(self, state: bool) -> None:
|
||||
"""Toggle focus state."""
|
||||
self.itemTitle.setColorState(state)
|
||||
return
|
||||
|
||||
def setHandle(self, tHandle: str) -> None:
|
||||
"""Sets the document title from the handle, or alternatively,
|
||||
"""Set the document title from the handle, or alternatively,
|
||||
set the whole document path.
|
||||
"""
|
||||
self._docHandle = tHandle
|
||||
@@ -819,13 +782,10 @@ class GuiDocViewHeader(QWidget):
|
||||
self.refreshButton.setVisible(True)
|
||||
self.closeButton.setVisible(True)
|
||||
|
||||
return
|
||||
|
||||
def updateNavButtons(self, firstIdx: int, lastIdx: int, currIdx: int) -> None:
|
||||
"""Enable and disable nav buttons based on index in history."""
|
||||
self.backButton.setEnabled(currIdx > firstIdx)
|
||||
self.forwardButton.setEnabled(currIdx < lastIdx)
|
||||
return
|
||||
|
||||
##
|
||||
# Private Slots
|
||||
@@ -836,20 +796,17 @@ class GuiDocViewHeader(QWidget):
|
||||
"""Trigger the close editor/viewer on the main window."""
|
||||
self.clearHeader()
|
||||
self.docViewer.closeDocumentRequest.emit()
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _refreshDocument(self) -> None:
|
||||
"""Reload the content of the document."""
|
||||
self.docViewer.reloadDocumentRequest.emit()
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _editDocument(self) -> None:
|
||||
"""Open the document in the editor."""
|
||||
if tHandle := self._docHandle:
|
||||
self.docViewer.openDocumentRequest.emit(tHandle, nwDocMode.EDIT, "", True)
|
||||
return
|
||||
|
||||
##
|
||||
# Events
|
||||
@@ -861,11 +818,10 @@ class GuiDocViewHeader(QWidget):
|
||||
"""
|
||||
if event.button() == QtMouseLeft:
|
||||
self.docViewer.requestProjectItemSelected.emit(self._docHandle, True)
|
||||
return
|
||||
|
||||
|
||||
class GuiDocViewFooter(QWidget):
|
||||
"""The Embedded Document Footer
|
||||
"""The Embedded Document Footer.
|
||||
|
||||
Only used by DocViewer, and is at a fixed position in the
|
||||
QTextBrowser's viewport.
|
||||
@@ -944,8 +900,6 @@ class GuiDocViewFooter(QWidget):
|
||||
|
||||
logger.debug("Ready: GuiDocViewFooter")
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Methods
|
||||
##
|
||||
@@ -956,7 +910,6 @@ class GuiDocViewFooter(QWidget):
|
||||
self.showComments.setFont(SHARED.theme.guiFontSmall)
|
||||
self.showSynopsis.setFont(SHARED.theme.guiFontSmall)
|
||||
self.showNotes.setFont(SHARED.theme.guiFontSmall)
|
||||
return
|
||||
|
||||
def updateTheme(self) -> None:
|
||||
"""Update theme elements."""
|
||||
@@ -977,8 +930,6 @@ class GuiDocViewFooter(QWidget):
|
||||
|
||||
self.matchColors()
|
||||
|
||||
return
|
||||
|
||||
def matchColors(self) -> None:
|
||||
"""Update the colours of the widget to match those of the syntax
|
||||
theme rather than the main GUI.
|
||||
@@ -989,7 +940,6 @@ class GuiDocViewFooter(QWidget):
|
||||
palette.setColor(QPalette.ColorRole.WindowText, syntax.text)
|
||||
palette.setColor(QPalette.ColorRole.Text, syntax.text)
|
||||
self.setPalette(palette)
|
||||
return
|
||||
|
||||
##
|
||||
# Private Slots
|
||||
@@ -1000,18 +950,15 @@ class GuiDocViewFooter(QWidget):
|
||||
"""Toggle the view comment button and reload the document."""
|
||||
CONFIG.viewComments = state
|
||||
self.docViewer.reloadText()
|
||||
return
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def _doToggleSynopsis(self, state: bool) -> None:
|
||||
"""Toggle the view synopsis button and reload the document."""
|
||||
CONFIG.viewSynopsis = state
|
||||
self.docViewer.reloadText()
|
||||
return
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def _doToggleNotes(self, state: bool) -> None:
|
||||
"""Toggle the view notes button and reload the document."""
|
||||
CONFIG.viewNotes = state
|
||||
self.docViewer.reloadText()
|
||||
return
|
||||
|
||||
@@ -20,7 +20,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
@@ -49,6 +49,10 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GuiDocViewerPanel(QWidget):
|
||||
"""GUI: Document Viewer Panel.
|
||||
|
||||
The panel of project meta data below the viewer.
|
||||
"""
|
||||
|
||||
openDocumentRequest = pyqtSignal(str, Enum, str, bool)
|
||||
loadDocumentTagRequest = pyqtSignal(str, Enum)
|
||||
@@ -96,8 +100,6 @@ class GuiDocViewerPanel(QWidget):
|
||||
|
||||
logger.debug("Ready: GuiDocViewerPanel")
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Methods
|
||||
##
|
||||
@@ -113,7 +115,6 @@ class GuiDocViewerPanel(QWidget):
|
||||
for tab in self.kwTabs.values():
|
||||
tab.updateTheme()
|
||||
self._loadAllTags()
|
||||
return
|
||||
|
||||
def openProjectTasks(self) -> None:
|
||||
"""Run open project tasks."""
|
||||
@@ -124,7 +125,6 @@ class GuiDocViewerPanel(QWidget):
|
||||
for key, value in colWidths.items():
|
||||
if key in self.kwTabs and isinstance(value, list):
|
||||
self.kwTabs[key].setColumnWidths(value)
|
||||
return
|
||||
|
||||
def closeProjectTasks(self) -> None:
|
||||
"""Run close project tasks."""
|
||||
@@ -133,7 +133,6 @@ class GuiDocViewerPanel(QWidget):
|
||||
hideInactive = self.aInactive.isChecked()
|
||||
SHARED.project.options.setValue("GuiDocViewerPanel", "colWidths", colWidths)
|
||||
SHARED.project.options.setValue("GuiDocViewerPanel", "hideInactive", hideInactive)
|
||||
return
|
||||
|
||||
##
|
||||
# Public Slots
|
||||
@@ -145,7 +144,6 @@ class GuiDocViewerPanel(QWidget):
|
||||
self.tabBackRefs.clearContent()
|
||||
for cTab in self.kwTabs.values():
|
||||
cTab.clearContent()
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def indexHasAppeared(self) -> None:
|
||||
@@ -153,7 +151,6 @@ class GuiDocViewerPanel(QWidget):
|
||||
self._loadAllTags()
|
||||
self._updateTabVisibility()
|
||||
self.updateHandle(self._lastHandle)
|
||||
return
|
||||
|
||||
@pyqtSlot(str, Enum)
|
||||
def onProjectItemChanged(self, tHandle: str, change: nwChange) -> None:
|
||||
@@ -168,14 +165,12 @@ class GuiDocViewerPanel(QWidget):
|
||||
else:
|
||||
self.kwTabs[tClass].removeEntry(key)
|
||||
self._updateTabVisibility()
|
||||
return
|
||||
|
||||
@pyqtSlot(str)
|
||||
def updateHandle(self, tHandle: str | None) -> None:
|
||||
"""Update the document handle."""
|
||||
self._lastHandle = tHandle
|
||||
self.tabBackRefs.refreshContent(tHandle or None)
|
||||
return
|
||||
|
||||
@pyqtSlot(list, list)
|
||||
def updateChangedTags(self, updated: list[str], deleted: list[str]) -> None:
|
||||
@@ -191,14 +186,12 @@ class GuiDocViewerPanel(QWidget):
|
||||
else:
|
||||
logger.warning("Could not remove tag '%s' from view panel", key)
|
||||
self._updateTabVisibility()
|
||||
return
|
||||
|
||||
@pyqtSlot(str)
|
||||
def updateStatusLabels(self, kind: str) -> None:
|
||||
"""Update the importance labels."""
|
||||
if kind == "i":
|
||||
self._loadAllTags()
|
||||
return
|
||||
|
||||
##
|
||||
# Private Slots
|
||||
@@ -212,7 +205,6 @@ class GuiDocViewerPanel(QWidget):
|
||||
cTab.clearContent()
|
||||
self._loadAllTags()
|
||||
self._updateTabVisibility()
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
@@ -222,7 +214,6 @@ class GuiDocViewerPanel(QWidget):
|
||||
"""Hide class tabs with no content."""
|
||||
for tClass, cTab in self.kwTabs.items():
|
||||
self.mainTabs.setTabVisible(self.idTabs[tClass], cTab.countEntries() > 0)
|
||||
return
|
||||
|
||||
def _loadAllTags(self) -> None:
|
||||
"""Load all tags into the tabs."""
|
||||
@@ -230,7 +221,6 @@ class GuiDocViewerPanel(QWidget):
|
||||
for key, name, tClass, iItem, hItem in data:
|
||||
if tClass in self.kwTabs and iItem and hItem:
|
||||
self.kwTabs[tClass].addUpdateEntry(key, name, iItem, hItem)
|
||||
return
|
||||
|
||||
|
||||
class _ViewPanelBackRefs(QTreeWidget):
|
||||
@@ -278,8 +268,6 @@ class _ViewPanelBackRefs(QTreeWidget):
|
||||
self.clicked.connect(self._treeItemClicked)
|
||||
self.doubleClicked.connect(self._treeItemDoubleClicked)
|
||||
|
||||
return
|
||||
|
||||
def updateTheme(self) -> None:
|
||||
"""Update theme elements."""
|
||||
self._editIcon = SHARED.theme.getIcon("edit", "green")
|
||||
@@ -288,13 +276,11 @@ class _ViewPanelBackRefs(QTreeWidget):
|
||||
if item := self.topLevelItem(i):
|
||||
item.setIcon(self.C_EDIT, self._editIcon)
|
||||
item.setIcon(self.C_VIEW, self._viewIcon)
|
||||
return
|
||||
|
||||
def clearContent(self) -> None:
|
||||
"""Clear the widget."""
|
||||
self.clear()
|
||||
self._treeMap = {}
|
||||
return
|
||||
|
||||
def refreshContent(self, dHandle: str | None) -> None:
|
||||
"""Update the content."""
|
||||
@@ -303,7 +289,6 @@ class _ViewPanelBackRefs(QTreeWidget):
|
||||
refs = SHARED.project.index.getBackReferenceList(dHandle)
|
||||
for tHandle, (sTitle, hItem) in refs.items():
|
||||
self._setTreeItemValues(tHandle, sTitle, hItem)
|
||||
return
|
||||
|
||||
def refreshDocument(self, tHandle: str) -> None:
|
||||
"""Refresh document meta data."""
|
||||
@@ -311,7 +296,6 @@ class _ViewPanelBackRefs(QTreeWidget):
|
||||
for sTitle, hItem in iItem.items():
|
||||
if f"{tHandle}:{sTitle}" in self._treeMap:
|
||||
self._setTreeItemValues(tHandle, sTitle, hItem)
|
||||
return
|
||||
|
||||
##
|
||||
# Private Slots
|
||||
@@ -325,7 +309,6 @@ class _ViewPanelBackRefs(QTreeWidget):
|
||||
self._parent.openDocumentRequest.emit(tHandle, nwDocMode.EDIT, "", True)
|
||||
elif index.column() == self.C_VIEW:
|
||||
self._parent.openDocumentRequest.emit(tHandle, nwDocMode.VIEW, "", True)
|
||||
return
|
||||
|
||||
@pyqtSlot("QModelIndex")
|
||||
def _treeItemDoubleClicked(self, index: QModelIndex) -> None:
|
||||
@@ -333,7 +316,6 @@ class _ViewPanelBackRefs(QTreeWidget):
|
||||
tHandle = index.siblingAtColumn(self.C_DATA).data(self.D_HANDLE)
|
||||
if index.column() not in (self.C_EDIT, self.C_VIEW):
|
||||
self._parent.openDocumentRequest.emit(tHandle, nwDocMode.VIEW, "", True)
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
@@ -362,8 +344,6 @@ class _ViewPanelBackRefs(QTreeWidget):
|
||||
self.addTopLevelItem(trItem)
|
||||
self._treeMap[tKey] = trItem
|
||||
|
||||
return
|
||||
|
||||
|
||||
class _ViewPanelKeyWords(QTreeWidget):
|
||||
|
||||
@@ -418,14 +398,11 @@ class _ViewPanelKeyWords(QTreeWidget):
|
||||
self.clicked.connect(self._treeItemClicked)
|
||||
self.doubleClicked.connect(self._treeItemDoubleClicked)
|
||||
|
||||
return
|
||||
|
||||
def updateTheme(self) -> None:
|
||||
"""Update theme elements."""
|
||||
self._classIcon = SHARED.theme.getIcon(nwLabels.CLASS_ICON[self._class], "root")
|
||||
self._editIcon = SHARED.theme.getIcon("edit", "green")
|
||||
self._viewIcon = SHARED.theme.getIcon("view", "blue")
|
||||
return
|
||||
|
||||
def countEntries(self) -> int:
|
||||
"""Return the number of items in the list."""
|
||||
@@ -435,7 +412,6 @@ class _ViewPanelKeyWords(QTreeWidget):
|
||||
"""Clear the list."""
|
||||
self._treeMap = {}
|
||||
self.clear()
|
||||
return
|
||||
|
||||
def addUpdateEntry(self, tag: str, name: str, iItem: IndexNode, hItem: IndexHeading) -> None:
|
||||
"""Add a new entry, or update an existing one."""
|
||||
@@ -470,8 +446,6 @@ class _ViewPanelKeyWords(QTreeWidget):
|
||||
self.addTopLevelItem(trItem)
|
||||
self._treeMap[tag] = trItem
|
||||
|
||||
return
|
||||
|
||||
def removeEntry(self, tag: str) -> bool:
|
||||
"""Remove a tag from the list."""
|
||||
if tag in self._treeMap:
|
||||
@@ -487,7 +461,6 @@ class _ViewPanelKeyWords(QTreeWidget):
|
||||
self.setColumnWidth(self.C_IMPORT, checkInt(widths[1], 100))
|
||||
self.setColumnWidth(self.C_DOC, checkInt(widths[2], 100))
|
||||
self.setColumnWidth(self.C_TITLE, checkInt(widths[3], 100))
|
||||
return
|
||||
|
||||
def getColumnWidths(self) -> list[int]:
|
||||
"""Get the widths of the user-adjustable columns."""
|
||||
@@ -510,7 +483,6 @@ class _ViewPanelKeyWords(QTreeWidget):
|
||||
self._parent.loadDocumentTagRequest.emit(tag, nwDocMode.EDIT)
|
||||
elif index.column() == self.C_VIEW:
|
||||
self._parent.loadDocumentTagRequest.emit(tag, nwDocMode.VIEW)
|
||||
return
|
||||
|
||||
@pyqtSlot("QModelIndex")
|
||||
def _treeItemDoubleClicked(self, index: QModelIndex) -> None:
|
||||
@@ -518,4 +490,3 @@ class _ViewPanelKeyWords(QTreeWidget):
|
||||
tag = index.siblingAtColumn(self.C_DATA).data(self.D_TAG)
|
||||
if index.column() not in (self.C_EDIT, self.C_VIEW):
|
||||
self._parent.loadDocumentTagRequest.emit(tag, nwDocMode.VIEW)
|
||||
return
|
||||
|
||||
@@ -20,7 +20,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
@@ -42,6 +42,11 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GuiTextDocument(QTextDocument):
|
||||
"""Custom: Modified QTextDocument.
|
||||
|
||||
A special text document format that incorporates a few additional
|
||||
features including spell checking.
|
||||
"""
|
||||
|
||||
def __init__(self, parent: QObject) -> None:
|
||||
super().__init__(parent=parent)
|
||||
@@ -52,11 +57,8 @@ class GuiTextDocument(QTextDocument):
|
||||
|
||||
logger.debug("Ready: GuiTextDocument")
|
||||
|
||||
return
|
||||
|
||||
def __del__(self) -> None: # pragma: no cover
|
||||
logger.debug("Delete: GuiTextDocument")
|
||||
return
|
||||
|
||||
##
|
||||
# Properties
|
||||
@@ -96,8 +98,6 @@ class GuiTextDocument(QTextDocument):
|
||||
logger.debug("Loaded %d text blocks in %.3f ms", count, 1000*(tMid - tStart))
|
||||
logger.debug("Highlighted document in %.3f ms", 1000*(tEnd - tMid))
|
||||
|
||||
return
|
||||
|
||||
def metaDataAtPos(self, pos: int) -> tuple[str, str]:
|
||||
"""Check if there is meta data available at a given position in
|
||||
the document, and if so, return it.
|
||||
@@ -146,4 +146,3 @@ class GuiTextDocument(QTextDocument):
|
||||
def setSpellCheckState(self, state: bool) -> None:
|
||||
"""Set the spell check state of the syntax highlighter."""
|
||||
self._syntax.setSpellCheck(state)
|
||||
return
|
||||
|
||||
@@ -20,7 +20,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
@@ -43,6 +43,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GuiItemDetails(QWidget):
|
||||
"""GUI: Project Item Details Panel."""
|
||||
|
||||
def __init__(self, parent: QWidget) -> None:
|
||||
super().__init__(parent=parent)
|
||||
@@ -190,8 +191,6 @@ class GuiItemDetails(QWidget):
|
||||
|
||||
logger.debug("Ready: GuiItemDetails")
|
||||
|
||||
return
|
||||
|
||||
###
|
||||
# Class Methods
|
||||
##
|
||||
@@ -210,7 +209,6 @@ class GuiItemDetails(QWidget):
|
||||
self.cCountData.clear()
|
||||
self.wCountData.clear()
|
||||
self.pCountData.clear()
|
||||
return
|
||||
|
||||
def refreshDetails(self) -> None:
|
||||
"""Reload the content of the details panel."""
|
||||
@@ -219,7 +217,6 @@ class GuiItemDetails(QWidget):
|
||||
def updateTheme(self) -> None:
|
||||
"""Update theme elements."""
|
||||
self.updateViewBox(self._handle)
|
||||
return
|
||||
|
||||
def updateViewBox(self, tHandle: str | None) -> None:
|
||||
"""Populate the details box from a given handle."""
|
||||
@@ -283,4 +280,3 @@ class GuiItemDetails(QWidget):
|
||||
self.updateViewBox(tHandle)
|
||||
elif change == nwChange.DELETE:
|
||||
self.updateViewBox(None)
|
||||
return
|
||||
|
||||
@@ -20,7 +20,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
@@ -82,8 +82,6 @@ class GuiMainMenu(QMenuBar):
|
||||
|
||||
logger.debug("Ready: GuiMainMenu")
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Public Slots
|
||||
##
|
||||
@@ -92,7 +90,6 @@ class GuiMainMenu(QMenuBar):
|
||||
def setSpellCheckState(self, state: bool) -> None:
|
||||
"""Forward spell check check state to its action."""
|
||||
self.aSpellCheck.setChecked(state)
|
||||
return
|
||||
|
||||
##
|
||||
# Private Slots
|
||||
@@ -105,21 +102,18 @@ class GuiMainMenu(QMenuBar):
|
||||
decision, just pass a None to the function and let it decide.
|
||||
"""
|
||||
self.mainGui.docEditor.toggleSpellCheck(None)
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _openUserManualFile(self) -> None:
|
||||
"""Open the documentation in PDF format."""
|
||||
if isinstance(CONFIG.pdfDocs, Path):
|
||||
openExternalPath(CONFIG.pdfDocs)
|
||||
return
|
||||
|
||||
@pyqtSlot(str)
|
||||
def _changeSpelling(self, language: str) -> None:
|
||||
"""Change the spell check language."""
|
||||
SHARED.project.data.setSpellLang(language)
|
||||
SHARED.updateSpellCheckLanguage()
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
@@ -188,8 +182,6 @@ class GuiMainMenu(QMenuBar):
|
||||
self.aExitNW.triggered.connect(qtLambda(self.mainGui.closeMain))
|
||||
self.mainGui.addAction(self.aExitNW)
|
||||
|
||||
return
|
||||
|
||||
def _buildDocumentMenu(self) -> None:
|
||||
"""Assemble the Document menu."""
|
||||
# Document
|
||||
@@ -236,8 +228,6 @@ class GuiMainMenu(QMenuBar):
|
||||
self.aImportFile = qtAddAction(self.docuMenu, self.tr("Import Text from File"))
|
||||
self.aImportFile.triggered.connect(qtLambda(self.mainGui.importDocument))
|
||||
|
||||
return
|
||||
|
||||
def _buildEditMenu(self) -> None:
|
||||
"""Assemble the Edit menu."""
|
||||
# Edit
|
||||
@@ -305,8 +295,6 @@ class GuiMainMenu(QMenuBar):
|
||||
)
|
||||
self.mainGui.addAction(self.aSelectPar)
|
||||
|
||||
return
|
||||
|
||||
def _buildViewMenu(self) -> None:
|
||||
"""Assemble the View menu."""
|
||||
# View
|
||||
@@ -367,8 +355,6 @@ class GuiMainMenu(QMenuBar):
|
||||
self.aFullScreen.triggered.connect(self.mainGui.toggleFullScreenMode)
|
||||
self.mainGui.addAction(self.aFullScreen)
|
||||
|
||||
return
|
||||
|
||||
def _buildInsertMenu(self) -> None:
|
||||
"""Assemble the Insert menu."""
|
||||
# Insert
|
||||
@@ -646,8 +632,6 @@ class GuiMainMenu(QMenuBar):
|
||||
lambda: self.requestDocInsert.emit(nwDocInsert.FOOTNOTE)
|
||||
)
|
||||
|
||||
return
|
||||
|
||||
def _buildFormatMenu(self) -> None:
|
||||
"""Assemble the Format menu."""
|
||||
# Format
|
||||
@@ -901,8 +885,6 @@ class GuiMainMenu(QMenuBar):
|
||||
lambda: self.requestDocAction.emit(nwDocAction.RM_BREAKS)
|
||||
)
|
||||
|
||||
return
|
||||
|
||||
def _buildSearchMenu(self) -> None:
|
||||
"""Assemble the Search menu."""
|
||||
# Search
|
||||
@@ -948,8 +930,6 @@ class GuiMainMenu(QMenuBar):
|
||||
self.aFindProj.setShortcut("Ctrl+Shift+F")
|
||||
self.aFindProj.triggered.connect(qtLambda(self.requestViewChange.emit, nwView.SEARCH))
|
||||
|
||||
return
|
||||
|
||||
def _buildToolsMenu(self) -> None:
|
||||
"""Assemble the Tools menu."""
|
||||
# Tools
|
||||
@@ -1019,8 +999,6 @@ class GuiMainMenu(QMenuBar):
|
||||
self.aPreferences.triggered.connect(self.mainGui.showPreferencesDialog)
|
||||
self.mainGui.addAction(self.aPreferences)
|
||||
|
||||
return
|
||||
|
||||
def _buildHelpMenu(self) -> None:
|
||||
"""Assemble the Help menu."""
|
||||
# Help
|
||||
@@ -1066,5 +1044,3 @@ class GuiMainMenu(QMenuBar):
|
||||
# Document > Main Website
|
||||
self.aWebsite = qtAddAction(self.helpMenu, self.tr("The novelWriter Website"))
|
||||
self.aWebsite.triggered.connect(qtLambda(SHARED.openWebsite, nwConst.URL_WEB))
|
||||
|
||||
return
|
||||
|
||||
@@ -24,7 +24,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
@@ -55,6 +55,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GuiNovelView(QWidget):
|
||||
"""GUI: Novel View Panel."""
|
||||
|
||||
# Signals for user interaction with the novel tree
|
||||
selectedItemChanged = pyqtSignal(str)
|
||||
@@ -82,8 +83,6 @@ class GuiNovelView(QWidget):
|
||||
self.getSelectedHandle = self.novelTree.getSelectedHandle
|
||||
self.refreshCurrentTree = self.novelBar.forceRefreshNovelTree
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Methods
|
||||
##
|
||||
@@ -91,19 +90,16 @@ class GuiNovelView(QWidget):
|
||||
def updateTheme(self) -> None:
|
||||
"""Update theme elements."""
|
||||
self.novelBar.updateTheme()
|
||||
return
|
||||
|
||||
def initSettings(self) -> None:
|
||||
"""Initialise GUI elements that depend on specific settings."""
|
||||
self.novelTree.initSettings()
|
||||
return
|
||||
|
||||
def clearNovelView(self) -> None:
|
||||
"""Clear project-related GUI content."""
|
||||
self.novelBar.clearContent()
|
||||
self.novelBar.setEnabled(False)
|
||||
self.novelTree.clearContent()
|
||||
return
|
||||
|
||||
def openProjectTasks(self) -> None:
|
||||
"""Run open project tasks."""
|
||||
@@ -125,8 +121,6 @@ class GuiNovelView(QWidget):
|
||||
|
||||
self.novelTree.setLastColSize(lastColSize)
|
||||
|
||||
return
|
||||
|
||||
def closeProjectTasks(self) -> None:
|
||||
"""Run closing project tasks."""
|
||||
logger.debug("Saving State: GuiNovelView")
|
||||
@@ -140,12 +134,9 @@ class GuiNovelView(QWidget):
|
||||
|
||||
self.clearNovelView()
|
||||
|
||||
return
|
||||
|
||||
def setTreeFocus(self) -> None:
|
||||
"""Set the focus to the tree widget."""
|
||||
self.novelTree.setFocus()
|
||||
return
|
||||
|
||||
def treeHasFocus(self) -> bool:
|
||||
"""Check if the novel tree has focus."""
|
||||
@@ -159,22 +150,20 @@ class GuiNovelView(QWidget):
|
||||
def setCurrentNovel(self, rootHandle: str | None) -> None:
|
||||
"""Set the current novel to display."""
|
||||
self.novelTree.setNovelModel(rootHandle)
|
||||
return
|
||||
|
||||
@pyqtSlot(str)
|
||||
def setActiveHandle(self, tHandle: str) -> None:
|
||||
"""Highlight the rows associated with a given handle."""
|
||||
self.novelTree.setActiveHandle(tHandle)
|
||||
return
|
||||
|
||||
@pyqtSlot(str, Enum)
|
||||
def updateRootItem(self, tHandle: str, change: nwChange) -> None:
|
||||
"""If any root item changes, rebuild the novel root menu."""
|
||||
self.novelBar.buildNovelRootMenu()
|
||||
return
|
||||
|
||||
|
||||
class GuiNovelToolBar(QWidget):
|
||||
"""GUI: Novel View Panel ToolBar."""
|
||||
|
||||
def __init__(self, novelView: GuiNovelView) -> None:
|
||||
super().__init__(parent=novelView)
|
||||
@@ -249,8 +238,6 @@ class GuiNovelToolBar(QWidget):
|
||||
|
||||
logger.debug("Ready: GuiNovelToolBar")
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Methods
|
||||
##
|
||||
@@ -277,20 +264,16 @@ class GuiNovelToolBar(QWidget):
|
||||
|
||||
self.forceRefreshNovelTree()
|
||||
|
||||
return
|
||||
|
||||
def clearContent(self) -> None:
|
||||
"""Run clearing project tasks."""
|
||||
self.novelValue.clear()
|
||||
self.novelValue.setToolTip("")
|
||||
return
|
||||
|
||||
def buildNovelRootMenu(self) -> None:
|
||||
"""Build the novel root menu."""
|
||||
self.novelValue.refreshNovelList()
|
||||
self.novelView.setCurrentNovel(self.novelValue.handle)
|
||||
self.tbNovel.setVisible(self.novelValue.count() > 1)
|
||||
return
|
||||
|
||||
def setCurrentRoot(self, rootHandle: str | None) -> None:
|
||||
"""Set the current active root handle."""
|
||||
@@ -300,7 +283,6 @@ class GuiNovelToolBar(QWidget):
|
||||
SHARED.project.data.setLastHandle(rootHandle, "novel")
|
||||
self.novelView.setCurrentNovel(rootHandle)
|
||||
self.novelView.novelTree.setAccessibleName(self.novelValue.currentText())
|
||||
return
|
||||
|
||||
def setLastColType(self, colType: nwNovelExtra, doRefresh: bool = True) -> None:
|
||||
"""Set the last column type."""
|
||||
@@ -309,7 +291,6 @@ class GuiNovelToolBar(QWidget):
|
||||
if doRefresh:
|
||||
self.forceRefreshNovelTree()
|
||||
self.novelView.novelTree.resizeColumns()
|
||||
return
|
||||
|
||||
def setActive(self, state: bool) -> None:
|
||||
"""Set the widget active state, which enables automatic tree
|
||||
@@ -322,7 +303,6 @@ class GuiNovelToolBar(QWidget):
|
||||
and self._refresh.get(handle, False)
|
||||
):
|
||||
self._refreshNovelTree(self.novelValue.handle)
|
||||
return
|
||||
|
||||
##
|
||||
# Public Slots
|
||||
@@ -335,7 +315,6 @@ class GuiNovelToolBar(QWidget):
|
||||
self.novelView.setCurrentNovel(tHandle)
|
||||
SHARED.project.index.refreshNovelModel(tHandle)
|
||||
self._refresh[tHandle] = False
|
||||
return
|
||||
|
||||
##
|
||||
# Private Slots
|
||||
@@ -349,7 +328,6 @@ class GuiNovelToolBar(QWidget):
|
||||
self._refresh[tHandle] = False
|
||||
else:
|
||||
self._refresh[tHandle] = True
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _selectLastColumnSize(self) -> None:
|
||||
@@ -361,7 +339,6 @@ class GuiNovelToolBar(QWidget):
|
||||
if isOk:
|
||||
self.novelView.novelTree.setLastColSize(newSize)
|
||||
self.novelView.novelTree.resizeColumns()
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
@@ -374,10 +351,10 @@ class GuiNovelToolBar(QWidget):
|
||||
aLast.setActionGroup(self.gLastCol)
|
||||
aLast.triggered.connect(qtLambda(self.setLastColType, colType))
|
||||
self.aLastCol[colType] = aLast
|
||||
return
|
||||
|
||||
|
||||
class GuiNovelTree(NTreeView):
|
||||
"""GUI: Novel View Panel Tree."""
|
||||
|
||||
def __init__(self, novelView: GuiNovelView) -> None:
|
||||
super().__init__(parent=novelView)
|
||||
@@ -414,8 +391,6 @@ class GuiNovelTree(NTreeView):
|
||||
|
||||
logger.debug("Ready: GuiNovelTree")
|
||||
|
||||
return
|
||||
|
||||
def initSettings(self) -> None:
|
||||
"""Set or update tree widget settings."""
|
||||
if CONFIG.hideVScroll:
|
||||
@@ -426,7 +401,6 @@ class GuiNovelTree(NTreeView):
|
||||
self.setHorizontalScrollBarPolicy(QtScrollAlwaysOff)
|
||||
else:
|
||||
self.setHorizontalScrollBarPolicy(QtScrollAsNeeded)
|
||||
return
|
||||
|
||||
##
|
||||
# Properties
|
||||
@@ -466,25 +440,21 @@ class GuiNovelTree(NTreeView):
|
||||
self.resizeColumns()
|
||||
else:
|
||||
self.clearContent()
|
||||
return
|
||||
|
||||
def setActiveHandle(self, tHandle: str | None) -> None:
|
||||
"""Set the handle to be highlighted."""
|
||||
self._actHandle = tHandle
|
||||
if viewport := self.viewport():
|
||||
viewport.repaint()
|
||||
return
|
||||
|
||||
def setLastColType(self, colType: nwNovelExtra) -> None:
|
||||
"""Set the extra column type."""
|
||||
self._lastColType = colType
|
||||
SHARED.project.index.setNovelModelExtraColumn(colType)
|
||||
return
|
||||
|
||||
def setLastColSize(self, colSize: int) -> None:
|
||||
"""Set the extra column size between 15% and 75%."""
|
||||
self._lastColSize = minmax(colSize, 15, 75)/100.0
|
||||
return
|
||||
|
||||
##
|
||||
# Class Methods
|
||||
@@ -493,7 +463,6 @@ class GuiNovelTree(NTreeView):
|
||||
def clearContent(self) -> None:
|
||||
"""Clear the tree view."""
|
||||
self.setModel(None)
|
||||
return
|
||||
|
||||
def resizeColumns(self) -> None:
|
||||
"""Set the correct column sizes."""
|
||||
@@ -506,7 +475,6 @@ class GuiNovelTree(NTreeView):
|
||||
if model.columns == 4:
|
||||
header.setSectionResizeMode(3, QtHeaderToContents)
|
||||
header.setMaximumSectionSize(int(self._lastColSize * vp.width()))
|
||||
return
|
||||
|
||||
##
|
||||
# Overloads
|
||||
@@ -517,7 +485,6 @@ class GuiNovelTree(NTreeView):
|
||||
if (model := self._getModel()) and model.handle(index) == self._actHandle:
|
||||
painter.fillRect(opt.rect, self.palette().alternateBase())
|
||||
super().drawRow(painter, opt, index)
|
||||
return
|
||||
|
||||
##
|
||||
# Events
|
||||
@@ -527,7 +494,6 @@ class GuiNovelTree(NTreeView):
|
||||
"""Process size changed."""
|
||||
super().resizeEvent(event)
|
||||
self.resizeColumns()
|
||||
return
|
||||
|
||||
##
|
||||
# Private Slots
|
||||
@@ -535,36 +501,33 @@ class GuiNovelTree(NTreeView):
|
||||
|
||||
@pyqtSlot(QModelIndex)
|
||||
def _onSingleClick(self, index: QModelIndex) -> None:
|
||||
"""The user single-clicked an index."""
|
||||
"""Process user single-click on an index."""
|
||||
if index.isValid() and (model := self._getModel()):
|
||||
if (tHandle := model.handle(index)) and (sTitle := model.key(index)):
|
||||
self.novelView.selectedItemChanged.emit(tHandle)
|
||||
if index.column() == model.columnCount(index) - 1:
|
||||
pos = self.mapToGlobal(self.visualRect(index).topRight())
|
||||
self._popMetaBox(pos, tHandle, sTitle)
|
||||
return
|
||||
|
||||
@pyqtSlot(QModelIndex)
|
||||
def _onDoubleClick(self, index: QModelIndex) -> None:
|
||||
"""The user double-clicked an index."""
|
||||
"""Process user double-click on an index."""
|
||||
if (
|
||||
(model := self._getModel())
|
||||
and (tHandle := model.handle(index))
|
||||
and (sTitle := model.key(index))
|
||||
):
|
||||
self.novelView.openDocumentRequest.emit(tHandle, nwDocMode.EDIT, sTitle, False)
|
||||
return
|
||||
|
||||
@pyqtSlot(QModelIndex)
|
||||
def _onMiddleClick(self, index: QModelIndex) -> None:
|
||||
"""The user middle-clicked an index."""
|
||||
"""Process user middle-click on an index."""
|
||||
if (
|
||||
(model := self._getModel())
|
||||
and (tHandle := model.handle(index))
|
||||
and (sTitle := model.key(index))
|
||||
):
|
||||
self.novelView.openDocumentRequest.emit(tHandle, nwDocMode.VIEW, sTitle, False)
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
@@ -582,7 +545,6 @@ class GuiNovelTree(NTreeView):
|
||||
"""Generate a reference list for a given reference key."""
|
||||
if tags := ", ".join(refs.get(key, [])):
|
||||
lines.append(f"<b>{trConst(nwLabels.KEY_NAME[key])}:</b> {tags}")
|
||||
return
|
||||
|
||||
if head := SHARED.project.index.getItemHeading(tHandle, sTitle):
|
||||
logger.debug("Generating meta data tooltip for '%s:%s'", tHandle, sTitle)
|
||||
@@ -610,4 +572,3 @@ class GuiNovelTree(NTreeView):
|
||||
text = f"<p>{refs}</p>"
|
||||
if tooltip := (text + synopsis or self.tr("No meta data")):
|
||||
QToolTip.showText(qPos, tooltip)
|
||||
return
|
||||
|
||||
+10
-57
@@ -24,7 +24,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import csv
|
||||
@@ -58,6 +58,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GuiOutlineView(QWidget):
|
||||
"""GUI: Project Outline Panel."""
|
||||
|
||||
loadDocumentTagRequest = pyqtSignal(str, Enum)
|
||||
openDocumentRequest = pyqtSignal(str, Enum, str, bool)
|
||||
@@ -96,8 +97,6 @@ class GuiOutlineView(QWidget):
|
||||
# Function Mappings
|
||||
self.getSelectedHandle = self.outlineTree.getSelectedHandle
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Methods
|
||||
##
|
||||
@@ -109,24 +108,20 @@ class GuiOutlineView(QWidget):
|
||||
self.outlineTree.refreshTree(
|
||||
rootHandle=SHARED.project.data.getLastHandle("outline"), overRide=True
|
||||
)
|
||||
return
|
||||
|
||||
def initSettings(self) -> None:
|
||||
"""Initialise GUI elements that depend on specific settings."""
|
||||
self.outlineTree.initSettings()
|
||||
self.outlineData.initSettings()
|
||||
return
|
||||
|
||||
def refreshTree(self) -> None:
|
||||
"""Refresh the current tree."""
|
||||
self.outlineTree.refreshTree(rootHandle=SHARED.project.data.getLastHandle("outline"))
|
||||
return
|
||||
|
||||
def clearOutline(self) -> None:
|
||||
"""Clear project-related GUI content."""
|
||||
self.outlineData.clearDetails()
|
||||
self.outlineBar.setEnabled(False)
|
||||
return
|
||||
|
||||
def openProjectTasks(self) -> None:
|
||||
"""Run open project tasks."""
|
||||
@@ -142,8 +137,6 @@ class GuiOutlineView(QWidget):
|
||||
self.outlineBar.setEnabled(True)
|
||||
self.outlineData.loadGuiSettings()
|
||||
|
||||
return
|
||||
|
||||
def closeProjectTasks(self) -> None:
|
||||
"""Run closing project tasks."""
|
||||
if self.outlineTree.wasRendered:
|
||||
@@ -152,7 +145,6 @@ class GuiOutlineView(QWidget):
|
||||
self.outlineTree.closeProjectTasks()
|
||||
self.outlineData.updateClasses()
|
||||
self.clearOutline()
|
||||
return
|
||||
|
||||
def splitSizes(self) -> list[int]:
|
||||
"""Get the sizes of the splitter widget."""
|
||||
@@ -175,7 +167,6 @@ class GuiOutlineView(QWidget):
|
||||
"""Handle tasks whenever a root folders changes."""
|
||||
self.outlineBar.populateNovelList()
|
||||
self.outlineData.updateClasses()
|
||||
return
|
||||
|
||||
##
|
||||
# Private Slots
|
||||
@@ -188,23 +179,21 @@ class GuiOutlineView(QWidget):
|
||||
of columns has changed.
|
||||
"""
|
||||
self.outlineBar.setColumnHiddenState(self.outlineTree.hiddenColumns)
|
||||
return
|
||||
|
||||
@pyqtSlot(str)
|
||||
def _tagClicked(self, link: str) -> None:
|
||||
"""Capture the click of a tag in the details panel."""
|
||||
if link:
|
||||
self.loadDocumentTagRequest.emit(link, nwDocMode.VIEW)
|
||||
return
|
||||
|
||||
@pyqtSlot(str)
|
||||
def _rootItemChanged(self, tHandle: str) -> None:
|
||||
"""Handle root novel changed or needs to be refreshed."""
|
||||
self.outlineTree.refreshTree(rootHandle=(tHandle or None), overRide=True)
|
||||
return
|
||||
|
||||
|
||||
class GuiOutlineToolBar(QToolBar):
|
||||
"""GUI: Project Outline Panel ToolBar."""
|
||||
|
||||
loadNovelRootRequest = pyqtSignal(str)
|
||||
outlineExportRequest = pyqtSignal()
|
||||
@@ -263,8 +252,6 @@ class GuiOutlineToolBar(QToolBar):
|
||||
|
||||
logger.debug("Ready: GuiOutlineToolBar")
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Methods
|
||||
##
|
||||
@@ -278,22 +265,18 @@ class GuiOutlineToolBar(QToolBar):
|
||||
self.tbColumns.setIcon(SHARED.theme.getIcon("more_vertical"))
|
||||
self.tbColumns.setStyleSheet("QToolButton::menu-indicator {image: none;}")
|
||||
self.novelLabel.setTextColors(color=self.palette().windowText().color())
|
||||
return
|
||||
|
||||
def populateNovelList(self) -> None:
|
||||
"""Reload the content of the novel list."""
|
||||
self.novelValue.refreshNovelList()
|
||||
return
|
||||
|
||||
def setCurrentRoot(self, rootHandle: str | None) -> None:
|
||||
"""Set the current active root handle."""
|
||||
self.novelValue.setHandle(rootHandle)
|
||||
return
|
||||
|
||||
def setColumnHiddenState(self, hiddenState: dict[nwOutline, bool]) -> None:
|
||||
"""Forward the change of column hidden states to the menu."""
|
||||
self.mColumns.setHiddenState(hiddenState)
|
||||
return
|
||||
|
||||
##
|
||||
# Private Slots
|
||||
@@ -303,22 +286,20 @@ class GuiOutlineToolBar(QToolBar):
|
||||
def _novelValueChanged(self, tHandle: str) -> None:
|
||||
"""Emit a signal containing the handle of the selected item."""
|
||||
self.loadNovelRootRequest.emit(tHandle)
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _refreshRequested(self) -> None:
|
||||
"""Emit a signal containing the handle of the selected item."""
|
||||
self.loadNovelRootRequest.emit(self.novelValue.handle)
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _exportRequested(self) -> None:
|
||||
"""Emit a signal that an export of the outline was requested."""
|
||||
self.outlineExportRequest.emit()
|
||||
return
|
||||
|
||||
|
||||
class GuiOutlineTree(QTreeWidget):
|
||||
"""GUI: Project Outline Panel Tree."""
|
||||
|
||||
DEF_WIDTH: Final[dict[nwOutline, int]] = {
|
||||
nwOutline.TITLE: 200,
|
||||
@@ -422,8 +403,6 @@ class GuiOutlineTree(QTreeWidget):
|
||||
|
||||
logger.debug("Ready: GuiOutlineTree")
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Properties
|
||||
##
|
||||
@@ -451,7 +430,6 @@ class GuiOutlineTree(QTreeWidget):
|
||||
self.setHorizontalScrollBarPolicy(QtScrollAlwaysOff)
|
||||
else:
|
||||
self.setHorizontalScrollBarPolicy(QtScrollAsNeeded)
|
||||
return
|
||||
|
||||
def clearContent(self) -> None:
|
||||
"""Clear the tree and header and set the default values for the
|
||||
@@ -474,8 +452,6 @@ class GuiOutlineTree(QTreeWidget):
|
||||
|
||||
self._treeNCols = len(self._treeOrder)
|
||||
|
||||
return
|
||||
|
||||
def updateTheme(self) -> None:
|
||||
"""Update theme elements."""
|
||||
iType = nwItemType.FILE
|
||||
@@ -488,15 +464,14 @@ class GuiOutlineTree(QTreeWidget):
|
||||
"H3": SHARED.theme.getItemIcon(iType, iClass, iLayout, "H3"),
|
||||
"H4": SHARED.theme.getItemIcon(iType, iClass, iLayout, "H4"),
|
||||
}
|
||||
return
|
||||
|
||||
def refreshTree(
|
||||
self, rootHandle: str | None = None,
|
||||
overRide: bool = False, novelChanged: bool = False
|
||||
) -> None:
|
||||
"""Called whenever the Outline tab is activated and controls
|
||||
what data to load, and if necessary, force a rebuild of the
|
||||
tree.
|
||||
"""Refresh the outline tree. Called whenever the Outline tab is
|
||||
activated and controls what data to load, and if necessary,
|
||||
force a rebuild of the tree.
|
||||
"""
|
||||
# If it's the first time, we always build
|
||||
if self._firstView or (self._firstView and overRide):
|
||||
@@ -518,11 +493,10 @@ class GuiOutlineTree(QTreeWidget):
|
||||
return
|
||||
|
||||
def closeProjectTasks(self) -> None:
|
||||
"""Called before a project is closed."""
|
||||
"""Call before a project is closed."""
|
||||
self._saveHeaderState()
|
||||
self.clearContent()
|
||||
self._firstView = True
|
||||
return
|
||||
|
||||
def getSelectedHandle(self) -> tuple[str | None, str | None]:
|
||||
"""Get the currently selected handle. If multiple items are
|
||||
@@ -546,7 +520,6 @@ class GuiOutlineTree(QTreeWidget):
|
||||
if hItem in self._colIdx:
|
||||
self.setColumnHidden(self._colIdx[hItem], not isChecked)
|
||||
self._saveHeaderState()
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def exportOutline(self) -> None:
|
||||
@@ -562,7 +535,6 @@ class GuiOutlineTree(QTreeWidget):
|
||||
writer.writerows(
|
||||
self._dumpNovelData(self.outlineView.outlineBar.novelValue.handle)
|
||||
)
|
||||
return
|
||||
|
||||
##
|
||||
# Private Slots
|
||||
@@ -577,7 +549,6 @@ class GuiOutlineTree(QTreeWidget):
|
||||
tHandle, sTitle = self.getSelectedHandle()
|
||||
if tHandle:
|
||||
self.outlineView.openDocumentRequest.emit(tHandle, nwDocMode.EDIT, sTitle or "", True)
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _onItemSelectionChanged(self) -> None:
|
||||
@@ -588,7 +559,6 @@ class GuiOutlineTree(QTreeWidget):
|
||||
tHandle = items[0].data(self._colIdx[nwOutline.TITLE], self.D_HANDLE)
|
||||
sTitle = items[0].data(self._colIdx[nwOutline.TITLE], self.D_TITLE)
|
||||
self.activeItemChanged.emit(tHandle, sTitle)
|
||||
return
|
||||
|
||||
@pyqtSlot(int, int, int)
|
||||
def _columnMoved(self, logIdx: int, oldVisualIdx: int, newVisualIdx: int) -> None:
|
||||
@@ -597,7 +567,6 @@ class GuiOutlineTree(QTreeWidget):
|
||||
"""
|
||||
self._treeOrder.insert(newVisualIdx, self._treeOrder.pop(oldVisualIdx))
|
||||
self._saveHeaderState()
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
@@ -637,8 +606,6 @@ class GuiOutlineTree(QTreeWidget):
|
||||
|
||||
self.hiddenStateChanged.emit()
|
||||
|
||||
return
|
||||
|
||||
def _saveHeaderState(self) -> None:
|
||||
"""Save the state of the main tree header, that is, column
|
||||
order, column width and column hidden state. We don't want to
|
||||
@@ -661,7 +628,6 @@ class GuiOutlineTree(QTreeWidget):
|
||||
pOptions = SHARED.project.options
|
||||
pOptions.setValue("GuiOutline", "columnState", colState)
|
||||
pOptions.saveSettings()
|
||||
return
|
||||
|
||||
def _populateTree(self, rootHandle: str | None) -> None:
|
||||
"""Build the tree based on the project index, and the header
|
||||
@@ -746,8 +712,6 @@ class GuiOutlineTree(QTreeWidget):
|
||||
self._lastBuild = time()
|
||||
logger.debug("Project outline built in %.3f ms", 1000.0*(time() - tStart))
|
||||
|
||||
return
|
||||
|
||||
def _dumpNovelData(self, rootHandle: str | None) -> list[list[str | int]]:
|
||||
"""Dump all novel data into a table."""
|
||||
sLabel = SHARED.project.localLookup("Story Structure")
|
||||
@@ -821,6 +785,7 @@ class GuiOutlineTree(QTreeWidget):
|
||||
|
||||
|
||||
class GuiOutlineHeaderMenu(QMenu):
|
||||
"""GUI: Project Outline Panel Header Selection Menu."""
|
||||
|
||||
columnToggled = pyqtSignal(bool, Enum)
|
||||
|
||||
@@ -844,8 +809,6 @@ class GuiOutlineHeaderMenu(QMenu):
|
||||
)
|
||||
self.addAction(self.actionMap[hItem])
|
||||
|
||||
return
|
||||
|
||||
def setHiddenState(self, hiddenState: dict[nwOutline, bool]) -> None:
|
||||
"""Overwrite the checked state of the columns as the inverse of
|
||||
the hidden state. Skip the TITLE column as it cannot be hidden.
|
||||
@@ -859,10 +822,9 @@ class GuiOutlineHeaderMenu(QMenu):
|
||||
|
||||
self.acceptToggle = True
|
||||
|
||||
return
|
||||
|
||||
|
||||
class GuiOutlineDetails(QScrollArea):
|
||||
"""GUI: Project Outline Panel Details View."""
|
||||
|
||||
LVL_MAP: Final[dict[str, str]] = {
|
||||
"H1": QT_TRANSLATE_NOOP("GuiOutlineDetails", "Title"),
|
||||
@@ -1007,8 +969,6 @@ class GuiOutlineDetails(QScrollArea):
|
||||
|
||||
logger.debug("Ready: GuiOutlineDetails")
|
||||
|
||||
return
|
||||
|
||||
def initSettings(self) -> None:
|
||||
"""Set or update outline settings."""
|
||||
if CONFIG.hideVScroll:
|
||||
@@ -1020,7 +980,6 @@ class GuiOutlineDetails(QScrollArea):
|
||||
else:
|
||||
self.setHorizontalScrollBarPolicy(QtScrollAsNeeded)
|
||||
self.updateClasses()
|
||||
return
|
||||
|
||||
def loadGuiSettings(self) -> None:
|
||||
"""Run open project tasks."""
|
||||
@@ -1031,7 +990,6 @@ class GuiOutlineDetails(QScrollArea):
|
||||
pOptions.getInt("GuiOutlineDetails", "detailsWidth", width//3),
|
||||
pOptions.getInt("GuiOutlineDetails", "tagsWidth", 2*width//3),
|
||||
])
|
||||
return
|
||||
|
||||
def saveGuiSettings(self) -> None:
|
||||
"""Run close project tasks."""
|
||||
@@ -1040,7 +998,6 @@ class GuiOutlineDetails(QScrollArea):
|
||||
pOptions = SHARED.project.options
|
||||
pOptions.setValue("GuiOutlineDetails", "detailsWidth", mainSplit[0])
|
||||
pOptions.setValue("GuiOutlineDetails", "tagsWidth", mainSplit[1])
|
||||
return
|
||||
|
||||
def clearDetails(self) -> None:
|
||||
"""Clear all the data labels."""
|
||||
@@ -1057,7 +1014,6 @@ class GuiOutlineDetails(QScrollArea):
|
||||
value.clear()
|
||||
|
||||
self.updateClasses()
|
||||
return
|
||||
|
||||
##
|
||||
# Slots
|
||||
@@ -1090,8 +1046,6 @@ class GuiOutlineDetails(QScrollArea):
|
||||
for key, (_, value) in self.tagValues.items():
|
||||
value.setText(self._formatTags(novRefs, key))
|
||||
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def updateClasses(self) -> None:
|
||||
"""Update the visibility status of class details."""
|
||||
@@ -1102,7 +1056,6 @@ class GuiOutlineDetails(QScrollArea):
|
||||
label, value = self.tagValues[key]
|
||||
label.setVisible(visible)
|
||||
value.setVisible(visible)
|
||||
return
|
||||
|
||||
@staticmethod
|
||||
def _formatTags(refs: dict[str, list[str]], key: str) -> str:
|
||||
|
||||
@@ -25,7 +25,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
@@ -61,7 +61,9 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GuiProjectView(QWidget):
|
||||
"""This is a wrapper class holding all the elements of the project
|
||||
"""GUI: Project View.
|
||||
|
||||
This is a wrapper class holding all the elements of the project
|
||||
tree. The core object is the project tree itself. Most methods
|
||||
available are mapped through to the project tree class.
|
||||
"""
|
||||
@@ -130,8 +132,6 @@ class GuiProjectView(QWidget):
|
||||
# Function Mappings
|
||||
self.getSelectedHandle = self.projTree.getSelectedHandle
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Methods
|
||||
##
|
||||
@@ -139,19 +139,16 @@ class GuiProjectView(QWidget):
|
||||
def updateTheme(self) -> None:
|
||||
"""Update theme elements."""
|
||||
self.projBar.updateTheme()
|
||||
return
|
||||
|
||||
def initSettings(self) -> None:
|
||||
"""Initialise GUI elements that depend on specific settings."""
|
||||
self.projTree.initSettings()
|
||||
return
|
||||
|
||||
def closeProjectTasks(self) -> None:
|
||||
"""Clear project-related GUI content."""
|
||||
self.projBar.clearContent()
|
||||
self.projBar.setEnabled(False)
|
||||
self.projTree.clearTree()
|
||||
return
|
||||
|
||||
def openProjectTasks(self) -> None:
|
||||
"""Run open project tasks."""
|
||||
@@ -159,26 +156,23 @@ class GuiProjectView(QWidget):
|
||||
self.projBar.buildTemplatesMenu()
|
||||
self.projBar.buildQuickLinksMenu()
|
||||
self.projBar.setEnabled(True)
|
||||
return
|
||||
|
||||
def setTreeFocus(self) -> None:
|
||||
"""Forward the set focus call to the tree widget."""
|
||||
self.projTree.setFocus()
|
||||
return
|
||||
|
||||
def treeHasFocus(self) -> bool:
|
||||
"""Check if the project tree has focus."""
|
||||
return self.projTree.hasFocus()
|
||||
|
||||
def connectMenuActions(self, rename: QAction, delete: QAction, trash: QAction) -> None:
|
||||
"""Main menu actions passed to the project tree."""
|
||||
"""Connect main menu actions passed to the project tree."""
|
||||
self.projTree.addAction(rename)
|
||||
self.projTree.addAction(delete)
|
||||
self.projTree.addAction(trash)
|
||||
rename.triggered.connect(self.renameTreeItem)
|
||||
delete.triggered.connect(self.projTree.processDeleteRequest)
|
||||
trash.triggered.connect(self.projTree.emptyTrash)
|
||||
return
|
||||
|
||||
##
|
||||
# Public Slots
|
||||
@@ -197,41 +191,36 @@ class GuiProjectView(QWidget):
|
||||
if dlgOk:
|
||||
nwItem.setName(newLabel)
|
||||
nwItem.notifyToRefresh()
|
||||
return
|
||||
|
||||
@pyqtSlot(str, bool)
|
||||
def setSelectedHandle(self, tHandle: str, doScroll: bool = False) -> None:
|
||||
"""Select an item and optionally scroll it into view."""
|
||||
self.projTree.setSelectedHandle(tHandle, doScroll=doScroll)
|
||||
return
|
||||
|
||||
@pyqtSlot(str)
|
||||
def setActiveHandle(self, tHandle: str | None) -> None:
|
||||
"""Highlight the active handle."""
|
||||
self.projTree.setActiveHandle(tHandle)
|
||||
return
|
||||
|
||||
@pyqtSlot(str, Enum)
|
||||
def onProjectItemChanged(self, tHandle: str, change: nwChange) -> None:
|
||||
"""Refresh other content when project item changed."""
|
||||
self.projBar.processTemplateDocuments(tHandle)
|
||||
return
|
||||
|
||||
@pyqtSlot(str)
|
||||
def createFileFromTemplate(self, tHandle: str) -> None:
|
||||
"""Create a new document from a template."""
|
||||
logger.debug("Template selected: '%s'", tHandle)
|
||||
self.projTree.newTreeItem(nwItemType.FILE, copyDoc=tHandle)
|
||||
return
|
||||
|
||||
@pyqtSlot(str, Enum)
|
||||
def updateRootItem(self, tHandle: str, change: nwChange) -> None:
|
||||
"""Process root item changes."""
|
||||
self.projBar.buildQuickLinksMenu()
|
||||
return
|
||||
|
||||
|
||||
class GuiProjectToolBar(QWidget):
|
||||
"""GUI> Project View ToolBar."""
|
||||
|
||||
newDocumentFromTemplate = pyqtSignal(str)
|
||||
|
||||
@@ -351,8 +340,6 @@ class GuiProjectToolBar(QWidget):
|
||||
|
||||
logger.debug("Ready: GuiProjectToolBar")
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Methods
|
||||
##
|
||||
@@ -383,13 +370,10 @@ class GuiProjectToolBar(QWidget):
|
||||
self.buildQuickLinksMenu()
|
||||
self._buildRootMenu()
|
||||
|
||||
return
|
||||
|
||||
def clearContent(self) -> None:
|
||||
"""Clear dynamic content on the tool bar."""
|
||||
self.mQuick.clear()
|
||||
self.mTemplates.clearMenu()
|
||||
return
|
||||
|
||||
def buildQuickLinksMenu(self) -> None:
|
||||
"""Build the quick link menu."""
|
||||
@@ -402,14 +386,12 @@ class GuiProjectToolBar(QWidget):
|
||||
action.triggered.connect(
|
||||
qtLambda(self.projView.setSelectedHandle, tHandle, doScroll=True)
|
||||
)
|
||||
return
|
||||
|
||||
def buildTemplatesMenu(self) -> None:
|
||||
"""Build the templates menu."""
|
||||
for tHandle, _ in SHARED.project.tree.iterRoots(nwItemClass.TEMPLATE):
|
||||
for dHandle in SHARED.project.tree.subTree(tHandle):
|
||||
self.processTemplateDocuments(dHandle)
|
||||
return
|
||||
|
||||
def processTemplateDocuments(self, tHandle: str) -> None:
|
||||
"""Process change in tree items to update menu content."""
|
||||
@@ -418,7 +400,6 @@ class GuiProjectToolBar(QWidget):
|
||||
self.mTemplates.addUpdate(tHandle, item.itemName, item.getMainIcon())
|
||||
elif tHandle in self.mTemplates:
|
||||
self.mTemplates.remove(tHandle)
|
||||
return
|
||||
|
||||
##
|
||||
# Public Slots
|
||||
@@ -436,7 +417,6 @@ class GuiProjectToolBar(QWidget):
|
||||
self.aAddChap.setVisible(allowDoc)
|
||||
self.aAddPart.setVisible(allowDoc)
|
||||
self.aAddEmpty.setVisible(allowDoc)
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
@@ -451,7 +431,6 @@ class GuiProjectToolBar(QWidget):
|
||||
qtLambda(self.projTree.newTreeItem, nwItemType.ROOT, itemClass)
|
||||
)
|
||||
self.mAddRoot.addAction(aNew)
|
||||
return
|
||||
|
||||
self.mAddRoot.clear()
|
||||
addClass(nwItemClass.NOVEL)
|
||||
@@ -467,10 +446,9 @@ class GuiProjectToolBar(QWidget):
|
||||
addClass(nwItemClass.ARCHIVE)
|
||||
addClass(nwItemClass.TEMPLATE)
|
||||
|
||||
return
|
||||
|
||||
|
||||
class GuiProjectTree(QTreeView):
|
||||
"""GUI: Project View Tree."""
|
||||
|
||||
def __init__(self, projView: GuiProjectView) -> None:
|
||||
super().__init__(parent=projView)
|
||||
@@ -520,8 +498,6 @@ class GuiProjectTree(QTreeView):
|
||||
|
||||
logger.debug("Ready: GuiProjectTree")
|
||||
|
||||
return
|
||||
|
||||
def initSettings(self) -> None:
|
||||
"""Set or update tree widget settings."""
|
||||
if CONFIG.hideVScroll:
|
||||
@@ -532,7 +508,6 @@ class GuiProjectTree(QTreeView):
|
||||
self.setHorizontalScrollBarPolicy(QtScrollAlwaysOff)
|
||||
else:
|
||||
self.setHorizontalScrollBarPolicy(QtScrollAsNeeded)
|
||||
return
|
||||
|
||||
##
|
||||
# External Methods
|
||||
@@ -541,7 +516,6 @@ class GuiProjectTree(QTreeView):
|
||||
def setActiveHandle(self, tHandle: str | None) -> None:
|
||||
"""Set the handle to be highlighted."""
|
||||
self._actHandle = tHandle
|
||||
return
|
||||
|
||||
def getSelectedHandle(self) -> str | None:
|
||||
"""Get the currently selected handle."""
|
||||
@@ -556,7 +530,6 @@ class GuiProjectTree(QTreeView):
|
||||
def clearTree(self) -> None:
|
||||
"""Clear the tree view."""
|
||||
self.setModel(None)
|
||||
return
|
||||
|
||||
def loadModel(self) -> None:
|
||||
"""Load and prepare a new project model."""
|
||||
@@ -583,8 +556,6 @@ class GuiProjectTree(QTreeView):
|
||||
|
||||
self.restoreExpandedState()
|
||||
|
||||
return
|
||||
|
||||
def restoreExpandedState(self) -> None:
|
||||
"""Expand all nodes that were previously expanded."""
|
||||
if model := self._getModel():
|
||||
@@ -592,7 +563,6 @@ class GuiProjectTree(QTreeView):
|
||||
for index in model.allExpanded():
|
||||
self.setExpanded(index, True)
|
||||
self.blockSignals(False)
|
||||
return
|
||||
|
||||
def setSelectedHandle(self, tHandle: str | None, doScroll: bool = False) -> None:
|
||||
"""Set a specific handle as the selected item."""
|
||||
@@ -601,7 +571,6 @@ class GuiProjectTree(QTreeView):
|
||||
if doScroll:
|
||||
self.scrollTo(index, QAbstractItemView.ScrollHint.PositionAtCenter)
|
||||
self.projView.selectedItemChanged.emit(tHandle)
|
||||
return
|
||||
|
||||
def newTreeItem(
|
||||
self, itemType: nwItemType, itemClass: nwItemClass | None = None,
|
||||
@@ -805,7 +774,6 @@ class GuiProjectTree(QTreeView):
|
||||
SHARED.warn(self.tr("Could not duplicate all items."))
|
||||
self.setEnabled(True)
|
||||
self.restoreExpandedState()
|
||||
return
|
||||
|
||||
##
|
||||
# Events and Overloads
|
||||
@@ -825,14 +793,12 @@ class GuiProjectTree(QTreeView):
|
||||
self.projView.openDocumentRequest.emit(
|
||||
node.item.itemHandle, nwDocMode.VIEW, "", False
|
||||
)
|
||||
return
|
||||
|
||||
def drawRow(self, painter: QPainter, opt: QStyleOptionViewItem, index: QModelIndex) -> None:
|
||||
"""Draw a box on the active row."""
|
||||
if (node := self._getNode(index)) and node.item.itemHandle == self._actHandle:
|
||||
painter.fillRect(opt.rect, self.palette().alternateBase())
|
||||
super().drawRow(painter, opt, index)
|
||||
return
|
||||
|
||||
##
|
||||
# Public Slots
|
||||
@@ -843,14 +809,12 @@ class GuiProjectTree(QTreeView):
|
||||
"""Move an item up in the tree."""
|
||||
if model := self._getModel():
|
||||
model.internalMove(self.currentIndex(), -1)
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def moveItemDown(self) -> None:
|
||||
"""Move an item down in the tree."""
|
||||
if model := self._getModel():
|
||||
model.internalMove(self.currentIndex(), 1)
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def goToSiblingUp(self) -> None:
|
||||
@@ -858,7 +822,6 @@ class GuiProjectTree(QTreeView):
|
||||
if (node := self._getNode(self.currentIndex())) and (parent := node.parent()):
|
||||
if (move := parent.child(node.row() - 1)) and (model := self._getModel()):
|
||||
self.setCurrentIndex(model.indexFromNode(move))
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def goToSiblingDown(self) -> None:
|
||||
@@ -866,7 +829,6 @@ class GuiProjectTree(QTreeView):
|
||||
if (node := self._getNode(self.currentIndex())) and (parent := node.parent()):
|
||||
if (move := parent.child(node.row() + 1)) and (model := self._getModel()):
|
||||
self.setCurrentIndex(model.indexFromNode(move))
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def goToParent(self) -> None:
|
||||
@@ -877,7 +839,6 @@ class GuiProjectTree(QTreeView):
|
||||
and (parent := node.parent())
|
||||
):
|
||||
self.setCurrentIndex(model.indexFromNode(parent))
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def goToFirstChild(self) -> None:
|
||||
@@ -888,13 +849,11 @@ class GuiProjectTree(QTreeView):
|
||||
and (child := node.child(0))
|
||||
):
|
||||
self.setCurrentIndex(model.indexFromNode(child))
|
||||
return
|
||||
|
||||
@pyqtSlot(QModelIndex)
|
||||
def expandFromIndex(self, index: QModelIndex) -> None:
|
||||
"""Expand all nodes from index."""
|
||||
self.expandRecursively(index)
|
||||
return
|
||||
|
||||
@pyqtSlot(QModelIndex)
|
||||
def collapseFromIndex(self, index: QModelIndex) -> None:
|
||||
@@ -902,7 +861,6 @@ class GuiProjectTree(QTreeView):
|
||||
if (model := self._getModel()) and (node := model.node(index)):
|
||||
for child in node.allChildren():
|
||||
self.setExpanded(model.indexFromNode(child), False)
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def processDeleteRequest(
|
||||
@@ -968,9 +926,7 @@ class GuiProjectTree(QTreeView):
|
||||
@pyqtSlot()
|
||||
@pyqtSlot("QPoint")
|
||||
def openContextMenu(self, point: QPoint | None = None) -> None:
|
||||
"""The user right clicked an element in the project tree, so we
|
||||
open a context menu in-place.
|
||||
"""
|
||||
"""Open a context menu in-place where the user clicked."""
|
||||
if model := self._getModel():
|
||||
if point is None:
|
||||
point = self.visualRect(self.currentIndex()).center()
|
||||
@@ -987,7 +943,6 @@ class GuiProjectTree(QTreeView):
|
||||
if viewport := self.viewport():
|
||||
ctxMenu.exec(viewport.mapToGlobal(point))
|
||||
ctxMenu.setParent(None)
|
||||
return
|
||||
|
||||
##
|
||||
# Private Slots
|
||||
@@ -995,10 +950,9 @@ class GuiProjectTree(QTreeView):
|
||||
|
||||
@pyqtSlot(QModelIndex, QModelIndex)
|
||||
def _onSelectionChange(self, current: QModelIndex, previous: QModelIndex) -> None:
|
||||
"""The user changed which item is selected."""
|
||||
"""Process user changing which item is selected."""
|
||||
if node := self._getNode(current):
|
||||
self.projView.selectedItemChanged.emit(node.item.itemHandle)
|
||||
return
|
||||
|
||||
@pyqtSlot(QModelIndex)
|
||||
def _onDoubleClick(self, index: QModelIndex) -> None:
|
||||
@@ -1012,21 +966,18 @@ class GuiProjectTree(QTreeView):
|
||||
)
|
||||
else:
|
||||
self.setExpanded(index, not self.isExpanded(index))
|
||||
return
|
||||
|
||||
@pyqtSlot(QModelIndex)
|
||||
def _onNodeCollapsed(self, index: QModelIndex) -> None:
|
||||
"""Capture a node collapse, and pass it to the model."""
|
||||
if node := self._getNode(index):
|
||||
node.setExpanded(False)
|
||||
return
|
||||
|
||||
@pyqtSlot(QModelIndex)
|
||||
def _onNodeExpanded(self, index: QModelIndex) -> None:
|
||||
"""Capture a node expand, and pass it to the model."""
|
||||
if node := self._getNode(index):
|
||||
node.setExpanded(True)
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
@@ -1038,7 +989,6 @@ class GuiProjectTree(QTreeView):
|
||||
if model := self.selectionModel():
|
||||
# Selection model can be None (#2173)
|
||||
model.clearCurrentIndex()
|
||||
return
|
||||
|
||||
def _selectedRows(self) -> list[QModelIndex]:
|
||||
"""Return all column 0 indexes."""
|
||||
@@ -1066,7 +1016,6 @@ class _UpdatableMenu(QMenu):
|
||||
self._map: dict[str, QAction] = {}
|
||||
self.setTitle(self.tr("From Template"))
|
||||
self.triggered.connect(self._actionTriggered)
|
||||
return
|
||||
|
||||
def __contains__(self, tHandle: str) -> bool:
|
||||
"""Look up a handle in the menu."""
|
||||
@@ -1088,7 +1037,6 @@ class _UpdatableMenu(QMenu):
|
||||
self.addAction(action)
|
||||
self._map[tHandle] = action
|
||||
self.setActionsVisible(True)
|
||||
return
|
||||
|
||||
def remove(self, tHandle: str) -> None:
|
||||
"""Remove a template item."""
|
||||
@@ -1096,19 +1044,16 @@ class _UpdatableMenu(QMenu):
|
||||
self.removeAction(action)
|
||||
if not self._map:
|
||||
self.setActionsVisible(False)
|
||||
return
|
||||
|
||||
def clearMenu(self) -> None:
|
||||
"""Clear all menu content."""
|
||||
self._map.clear()
|
||||
self.clear()
|
||||
return
|
||||
|
||||
def setActionsVisible(self, value: bool) -> None:
|
||||
"""Set the visibility of root action."""
|
||||
if action := self.menuAction():
|
||||
action.setVisible(value)
|
||||
return
|
||||
|
||||
##
|
||||
# Private Slots
|
||||
@@ -1118,7 +1063,6 @@ class _UpdatableMenu(QMenu):
|
||||
def _actionTriggered(self, action: QAction) -> None:
|
||||
"""Translate the menu trigger into an item trigger."""
|
||||
self.menuItemTriggered.emit(str(action.data()))
|
||||
return
|
||||
|
||||
|
||||
class _TreeContextMenu(QMenu):
|
||||
@@ -1139,11 +1083,9 @@ class _TreeContextMenu(QMenu):
|
||||
self._indices = indices
|
||||
self._children = node.childCount() > 0
|
||||
logger.debug("Ready: _TreeContextMenu")
|
||||
return
|
||||
|
||||
def __del__(self) -> None: # pragma: no cover
|
||||
logger.debug("Delete: _TreeContextMenu")
|
||||
return
|
||||
|
||||
##
|
||||
# Methods
|
||||
@@ -1155,7 +1097,6 @@ class _TreeContextMenu(QMenu):
|
||||
action.triggered.connect(self._tree.emptyTrash)
|
||||
if self._children:
|
||||
self._expandCollapse()
|
||||
return
|
||||
|
||||
def buildSingleSelectMenu(self) -> None:
|
||||
"""Build the single-select menu."""
|
||||
@@ -1191,15 +1132,12 @@ class _TreeContextMenu(QMenu):
|
||||
action.triggered.connect(qtLambda(self._tree.duplicateFromHandle, self._handle))
|
||||
self._deleteOrTrash()
|
||||
|
||||
return
|
||||
|
||||
def buildMultiSelectMenu(self) -> None:
|
||||
"""Build the multi-select menu."""
|
||||
self._itemActive()
|
||||
self._itemStatusImport(True)
|
||||
self.addSeparator()
|
||||
self._deleteOrTrash()
|
||||
return
|
||||
|
||||
##
|
||||
# Menu Builders
|
||||
@@ -1217,7 +1155,6 @@ class _TreeContextMenu(QMenu):
|
||||
self._view.openDocumentRequest.emit,
|
||||
self._handle, nwDocMode.VIEW, "", False
|
||||
))
|
||||
return
|
||||
|
||||
def _itemCreation(self) -> None:
|
||||
"""Add create item actions."""
|
||||
@@ -1228,7 +1165,6 @@ class _TreeContextMenu(QMenu):
|
||||
menu.addAction(self._view.projBar.aAddEmpty)
|
||||
menu.addAction(self._view.projBar.aAddNote)
|
||||
menu.addAction(self._view.projBar.aAddFolder)
|
||||
return
|
||||
|
||||
def _itemHeader(self) -> None:
|
||||
"""Check if there is a header that can be used for rename."""
|
||||
@@ -1238,7 +1174,6 @@ class _TreeContextMenu(QMenu):
|
||||
action.triggered.connect(
|
||||
qtLambda(self._view.renameTreeItem, self._handle, hItem.title)
|
||||
)
|
||||
return
|
||||
|
||||
def _itemActive(self) -> None:
|
||||
"""Add Active/Inactive actions."""
|
||||
@@ -1253,7 +1188,6 @@ class _TreeContextMenu(QMenu):
|
||||
else:
|
||||
action = qtAddAction(self, self.tr("Toggle Active"))
|
||||
action.triggered.connect(self._toggleItemActive)
|
||||
return
|
||||
|
||||
def _itemStatusImport(self, multi: bool) -> None:
|
||||
"""Add actions for changing status or importance."""
|
||||
@@ -1295,7 +1229,6 @@ class _TreeContextMenu(QMenu):
|
||||
self._view.projectSettingsRequest.emit,
|
||||
GuiProjectSettings.PAGE_IMPORT
|
||||
))
|
||||
return
|
||||
|
||||
def _itemTransform(self, isFile: bool, isFolder: bool) -> None:
|
||||
"""Add actions for the Transform menu."""
|
||||
@@ -1338,15 +1271,12 @@ class _TreeContextMenu(QMenu):
|
||||
action = qtAddAction(menu, self.tr("Split Document by Headings"))
|
||||
action.triggered.connect(qtLambda(self._tree.splitDocument, self._handle))
|
||||
|
||||
return
|
||||
|
||||
def _expandCollapse(self) -> None:
|
||||
"""Add actions for expand and collapse."""
|
||||
action = qtAddAction(self, self.tr("Expand All"))
|
||||
action.triggered.connect(qtLambda(self._tree.expandFromIndex, self._indices[0]))
|
||||
action = qtAddAction(self, self.tr("Collapse All"))
|
||||
action.triggered.connect(qtLambda(self._tree.collapseFromIndex, self._indices[0]))
|
||||
return
|
||||
|
||||
def _deleteOrTrash(self) -> None:
|
||||
"""Add move to Trash action."""
|
||||
@@ -1359,7 +1289,6 @@ class _TreeContextMenu(QMenu):
|
||||
text = self.tr("Move to Trash")
|
||||
action = qtAddAction(self, text)
|
||||
action.triggered.connect(self._tree.processDeleteRequest)
|
||||
return
|
||||
|
||||
##
|
||||
# Private Slots
|
||||
@@ -1371,7 +1300,6 @@ class _TreeContextMenu(QMenu):
|
||||
if self._item.isFileType():
|
||||
self._item.setActive(not self._item.isActive)
|
||||
self._item.notifyToRefresh()
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
@@ -1385,13 +1313,11 @@ class _TreeContextMenu(QMenu):
|
||||
node.item.setActive(state)
|
||||
refresh.append(node.item.itemHandle)
|
||||
SHARED.project.tree.refreshItems(refresh)
|
||||
return
|
||||
|
||||
def _changeItemStatus(self, key: str) -> None:
|
||||
"""Set a new status value of an item."""
|
||||
self._item.setStatus(key)
|
||||
self._item.notifyToRefresh()
|
||||
return
|
||||
|
||||
def _iterSetItemStatus(self, key: str) -> None:
|
||||
"""Change the status value for multiple items."""
|
||||
@@ -1401,13 +1327,11 @@ class _TreeContextMenu(QMenu):
|
||||
node.item.setStatus(key)
|
||||
refresh.append(node.item.itemHandle)
|
||||
SHARED.project.tree.refreshItems(refresh)
|
||||
return
|
||||
|
||||
def _changeItemImport(self, key: str) -> None:
|
||||
"""Set a new importance value of an item."""
|
||||
self._item.setImport(key)
|
||||
self._item.notifyToRefresh()
|
||||
return
|
||||
|
||||
def _iterSetItemImport(self, key: str) -> None:
|
||||
"""Change the status value for multiple items."""
|
||||
@@ -1417,7 +1341,6 @@ class _TreeContextMenu(QMenu):
|
||||
node.item.setImport(key)
|
||||
refresh.append(node.item.itemHandle)
|
||||
SHARED.project.tree.refreshItems(refresh)
|
||||
return
|
||||
|
||||
def _changeItemLayout(self, itemLayout: nwItemLayout) -> None:
|
||||
"""Set a new item layout value of an item."""
|
||||
@@ -1428,7 +1351,6 @@ class _TreeContextMenu(QMenu):
|
||||
elif itemLayout == nwItemLayout.NOTE:
|
||||
self._item.setLayout(nwItemLayout.NOTE)
|
||||
self._item.notifyToRefresh()
|
||||
return
|
||||
|
||||
def _convertFolderToFile(self, itemLayout: nwItemLayout) -> None:
|
||||
"""Convert a folder to a note or document."""
|
||||
@@ -1448,4 +1370,3 @@ class _TreeContextMenu(QMenu):
|
||||
self._item.notifyToRefresh()
|
||||
else:
|
||||
logger.info("Folder conversion cancelled")
|
||||
return
|
||||
|
||||
@@ -20,7 +20,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
@@ -50,6 +50,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GuiProjectSearch(QWidget):
|
||||
"""GUI: Project Search Panel."""
|
||||
|
||||
C_NAME = 0
|
||||
C_RESULT = 0
|
||||
@@ -151,8 +152,6 @@ class GuiProjectSearch(QWidget):
|
||||
|
||||
logger.debug("Ready: GuiProjectSearch")
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Methods
|
||||
##
|
||||
@@ -174,8 +173,6 @@ class GuiProjectSearch(QWidget):
|
||||
self.toggleWord.setIcon(SHARED.theme.getIcon("search_word"))
|
||||
self.toggleRegEx.setIcon(SHARED.theme.getIcon("search_regex"))
|
||||
|
||||
return
|
||||
|
||||
def processReturn(self) -> None:
|
||||
"""Process a return keypress forwarded from the main GUI."""
|
||||
if self.searchText.hasFocus():
|
||||
@@ -189,7 +186,6 @@ class GuiProjectSearch(QWidget):
|
||||
self.openDocumentSelectRequest.emit(
|
||||
str(data[0]), checkInt(data[1], -1), checkInt(data[2], -1), False
|
||||
)
|
||||
return
|
||||
|
||||
def beginSearch(self, text: str = "") -> None:
|
||||
"""Focus the search box and select its text, if any."""
|
||||
@@ -198,20 +194,17 @@ class GuiProjectSearch(QWidget):
|
||||
if text:
|
||||
self.searchText.setText(text.partition("\n")[0])
|
||||
self.searchText.selectAll()
|
||||
return
|
||||
|
||||
def closeProjectTasks(self) -> None:
|
||||
"""Run close project tasks."""
|
||||
self._map = {}
|
||||
self.searchText.clear()
|
||||
self.searchResult.clear()
|
||||
return
|
||||
|
||||
def refreshCurrentSearch(self) -> None:
|
||||
"""Refresh the search if there is one."""
|
||||
if self.searchResult.topLevelItemCount() > 0:
|
||||
self._processSearch()
|
||||
return
|
||||
|
||||
##
|
||||
# Events
|
||||
@@ -238,7 +231,6 @@ class GuiProjectSearch(QWidget):
|
||||
self.searchText.setFocus()
|
||||
else:
|
||||
super().keyPressEvent(event)
|
||||
return
|
||||
|
||||
##
|
||||
# Public Slots
|
||||
@@ -252,7 +244,6 @@ class GuiProjectSearch(QWidget):
|
||||
results, capped = self._search.searchText(SHARED.mainGui.docEditor.getText())
|
||||
self._displayResultSet(SHARED.project.tree[tHandle], results, capped)
|
||||
logger.debug("Updated search for '%s' in %.3f ms", tHandle, 1000*(time() - start))
|
||||
return
|
||||
|
||||
##
|
||||
# Private Slots
|
||||
@@ -278,7 +269,6 @@ class GuiProjectSearch(QWidget):
|
||||
self._time = time()
|
||||
QApplication.restoreOverrideCursor()
|
||||
self._blocked = False
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _searchResultSelected(self) -> None:
|
||||
@@ -288,7 +278,6 @@ class GuiProjectSearch(QWidget):
|
||||
self.selectedItemChanged.emit(str(data[0]))
|
||||
elif data := items[0].data(0, self.D_HANDLE):
|
||||
self.selectedItemChanged.emit(str(data))
|
||||
return
|
||||
|
||||
@pyqtSlot("QTreeWidgetItem*", int)
|
||||
def _searchResultDoubleClicked(self, item: QTreeWidgetItem, column: int) -> None:
|
||||
@@ -297,28 +286,24 @@ class GuiProjectSearch(QWidget):
|
||||
self.openDocumentSelectRequest.emit(
|
||||
str(data[0]), checkInt(data[1], -1), checkInt(data[2], -1), True
|
||||
)
|
||||
return
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def _toggleCase(self, state: bool) -> None:
|
||||
"""Enable/disable case sensitive mode."""
|
||||
CONFIG.searchProjCase = state
|
||||
self.refreshCurrentSearch()
|
||||
return
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def _toggleWord(self, state: bool) -> None:
|
||||
"""Enable/disable whole word search mode."""
|
||||
CONFIG.searchProjWord = state
|
||||
self.refreshCurrentSearch()
|
||||
return
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def _toggleRegEx(self, state: bool) -> None:
|
||||
"""Enable/disable regular expression search mode."""
|
||||
CONFIG.searchProjRegEx = state
|
||||
self.refreshCurrentSearch()
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
@@ -360,5 +345,3 @@ class GuiProjectSearch(QWidget):
|
||||
self.searchResult.setFirstColumnSpanned(i, parent, True)
|
||||
|
||||
QApplication.processEvents()
|
||||
|
||||
return
|
||||
|
||||
@@ -20,7 +20,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
@@ -45,6 +45,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GuiSideBar(QWidget):
|
||||
"""GUI: Main Window SideBar."""
|
||||
|
||||
requestViewChange = pyqtSignal(nwView)
|
||||
|
||||
@@ -126,8 +127,6 @@ class GuiSideBar(QWidget):
|
||||
|
||||
logger.debug("Ready: GuiSideBar")
|
||||
|
||||
return
|
||||
|
||||
def updateTheme(self) -> None:
|
||||
"""Initialise GUI elements that depend on specific settings."""
|
||||
buttonStyle = SHARED.theme.getStyleSheet(STYLES_BIG_TOOLBUTTON)
|
||||
@@ -153,8 +152,6 @@ class GuiSideBar(QWidget):
|
||||
|
||||
self._setThemeModeIcon()
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Private Slots
|
||||
##
|
||||
@@ -171,7 +168,6 @@ class GuiSideBar(QWidget):
|
||||
CONFIG.themeMode = nwTheme.AUTO
|
||||
self.mainGui.checkThemeUpdate()
|
||||
self._setThemeModeIcon()
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
@@ -181,7 +177,6 @@ class GuiSideBar(QWidget):
|
||||
"""Set the theme button icon."""
|
||||
self.tbTheme.setThemeIcon(nwLabels.THEME_MODE_ICON[CONFIG.themeMode])
|
||||
self.tbTheme.setToolTip(trConst(nwLabels.THEME_MODE_LABEL[CONFIG.themeMode]))
|
||||
return
|
||||
|
||||
|
||||
class _PopRightMenu(QMenu):
|
||||
|
||||
@@ -20,7 +20,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
@@ -41,6 +41,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GuiMainStatus(QStatusBar):
|
||||
"""GUI: Main Window Status Bar."""
|
||||
|
||||
def __init__(self, parent: QWidget) -> None:
|
||||
super().__init__(parent=parent)
|
||||
@@ -112,8 +113,6 @@ class GuiMainStatus(QStatusBar):
|
||||
self.updateTheme()
|
||||
self.clearStatus()
|
||||
|
||||
return
|
||||
|
||||
def initSettings(self) -> None:
|
||||
"""Apply user settings."""
|
||||
if CONFIG.useCharCount:
|
||||
@@ -122,7 +121,6 @@ class GuiMainStatus(QStatusBar):
|
||||
else:
|
||||
self._trStatsCount = trStats(nwLabels.STATS_DISPLAY[nwStats.WORDS])
|
||||
self._trStatsTip = self.tr("Total word count (session change)")
|
||||
return
|
||||
|
||||
def clearStatus(self) -> None:
|
||||
"""Reset all widgets on the status bar to default values."""
|
||||
@@ -132,7 +130,6 @@ class GuiMainStatus(QStatusBar):
|
||||
self.setProjectStatus(None)
|
||||
self.setDocumentStatus(None)
|
||||
self.updateTime()
|
||||
return
|
||||
|
||||
def updateTheme(self) -> None:
|
||||
"""Update theme elements."""
|
||||
@@ -149,8 +146,6 @@ class GuiMainStatus(QStatusBar):
|
||||
self.docIcon.setColors(colNone, colSaved, colUnsaved)
|
||||
self.projIcon.setColors(colNone, colSaved, colUnsaved)
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Setters
|
||||
##
|
||||
@@ -158,17 +153,14 @@ class GuiMainStatus(QStatusBar):
|
||||
def setRefTime(self, refTime: float) -> None:
|
||||
"""Set the reference time for the status bar clock."""
|
||||
self._refTime = refTime
|
||||
return
|
||||
|
||||
def setProjectStatus(self, state: bool | None) -> None:
|
||||
"""Set the project status colour icon."""
|
||||
self.projIcon.setState(state)
|
||||
return
|
||||
|
||||
def setDocumentStatus(self, state: bool | None) -> None:
|
||||
"""Set the document status colour icon."""
|
||||
self.docIcon.setState(state)
|
||||
return
|
||||
|
||||
def setUserIdle(self, idle: bool) -> None:
|
||||
"""Change the idle status icon."""
|
||||
@@ -180,13 +172,11 @@ class GuiMainStatus(QStatusBar):
|
||||
else:
|
||||
self.timeIcon.setPixmap(self.timePixmap)
|
||||
self._userIdle = idle
|
||||
return
|
||||
|
||||
def setProjectStats(self, pWC: int, sWC: int) -> None:
|
||||
"""Update the current project statistics."""
|
||||
self.statsText.setText(self._trStatsCount.format(f"{pWC:n}", f"{sWC:+n}"))
|
||||
self.statsText.setToolTip(self._trStatsTip)
|
||||
return
|
||||
|
||||
def updateTime(self, idleTime: float = 0.0) -> None:
|
||||
"""Update the session clock."""
|
||||
@@ -198,7 +188,6 @@ class GuiMainStatus(QStatusBar):
|
||||
else:
|
||||
sessTime = round(time() - self._refTime)
|
||||
self.timeText.setText(formatTime(sessTime))
|
||||
return
|
||||
|
||||
##
|
||||
# Public Slots
|
||||
@@ -209,7 +198,6 @@ class GuiMainStatus(QStatusBar):
|
||||
"""Set the status bar message to display."""
|
||||
self.showMessage(message, nwConst.STATUS_MSG_TIMEOUT)
|
||||
QApplication.processEvents()
|
||||
return
|
||||
|
||||
@pyqtSlot(str, str)
|
||||
def setLanguage(self, language: str, provider: str) -> None:
|
||||
@@ -220,19 +208,16 @@ class GuiMainStatus(QStatusBar):
|
||||
else:
|
||||
self.langText.setText(QLocale(language).nativeLanguageName().title())
|
||||
self.langText.setToolTip(f"{language} ({provider})" if provider else language)
|
||||
return
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def updateProjectStatus(self, status: bool) -> None:
|
||||
"""Update the project status."""
|
||||
self.setProjectStatus(not status)
|
||||
return
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def updateDocumentStatus(self, status: bool) -> None:
|
||||
"""Update the document status."""
|
||||
self.setDocumentStatus(not status)
|
||||
return
|
||||
|
||||
##
|
||||
# Private Slots
|
||||
@@ -244,7 +229,6 @@ class GuiMainStatus(QStatusBar):
|
||||
state = not CONFIG.showSessionTime
|
||||
self.timeText.setVisible(state)
|
||||
CONFIG.showSessionTime = state
|
||||
return
|
||||
|
||||
##
|
||||
# Debug
|
||||
@@ -279,4 +263,3 @@ class GuiMainStatus(QStatusBar):
|
||||
)
|
||||
self.showMessage(f"Debug [{stamp}] {message}", 6000)
|
||||
logger.debug("[MEMINFO] %s", message)
|
||||
return
|
||||
|
||||
@@ -21,7 +21,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
@@ -58,6 +58,7 @@ STYLES_BIG_TOOLBUTTON = "bigToolButton"
|
||||
|
||||
@dataclass
|
||||
class ThemeEntry:
|
||||
"""Theme data."""
|
||||
|
||||
name: str
|
||||
dark: bool
|
||||
@@ -65,6 +66,7 @@ class ThemeEntry:
|
||||
|
||||
|
||||
class ThemeMeta:
|
||||
"""Theme meta data."""
|
||||
|
||||
name: str = ""
|
||||
mode: str = ""
|
||||
@@ -74,6 +76,7 @@ class ThemeMeta:
|
||||
|
||||
|
||||
class IconsMeta:
|
||||
"""Icon theme meta data."""
|
||||
|
||||
name: str = ""
|
||||
author: str = ""
|
||||
@@ -81,6 +84,7 @@ class IconsMeta:
|
||||
|
||||
|
||||
class SyntaxColors:
|
||||
"""Colours for the syntax highlighter."""
|
||||
|
||||
back: QColor = QColor(255, 255, 255)
|
||||
text: QColor = QColor(0, 0, 0)
|
||||
@@ -106,7 +110,7 @@ class SyntaxColors:
|
||||
|
||||
|
||||
class GuiTheme:
|
||||
"""Gui Theme Class
|
||||
"""Gui Theme Class.
|
||||
|
||||
Handles the look and feel of novelWriter.
|
||||
"""
|
||||
@@ -190,8 +194,6 @@ class GuiTheme:
|
||||
logger.debug("Text 'N' Height: %d", self.textNHeight)
|
||||
logger.debug("Text 'N' Width: %d", self.textNWidth)
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Properties
|
||||
##
|
||||
@@ -206,7 +208,7 @@ class GuiTheme:
|
||||
##
|
||||
|
||||
def getTextWidth(self, text: str, font: QFont | None = None) -> int:
|
||||
"""Returns the width needed to contain a given piece of text in
|
||||
"""Return the width needed to contain a given piece of text in
|
||||
pixels.
|
||||
"""
|
||||
if isinstance(font, QFont):
|
||||
@@ -238,8 +240,6 @@ class GuiTheme:
|
||||
self.iconCache.initIcons()
|
||||
self.loadTheme()
|
||||
|
||||
return
|
||||
|
||||
def isDesktopDarkMode(self) -> bool:
|
||||
"""Check if the desktop is in dark mode."""
|
||||
if CONFIG.verQtValue >= 0x060500 and (hint := QGuiApplication.styleHints()):
|
||||
@@ -507,7 +507,6 @@ class GuiTheme:
|
||||
"""Set the colour for a named colour."""
|
||||
self._qColors[key] = QColor(color)
|
||||
self._svgColors[key] = color.name(QColor.NameFormat.HexRgb).encode("utf-8")
|
||||
return
|
||||
|
||||
def _resetTheme(self) -> None:
|
||||
"""Reset GUI colours to default values."""
|
||||
@@ -559,8 +558,6 @@ class GuiTheme:
|
||||
self._setBaseColor("inactive", red)
|
||||
self._setBaseColor("disabled", faded)
|
||||
|
||||
return
|
||||
|
||||
def _readColor(self, parser: ConfigParser, section: str, name: str) -> QColor:
|
||||
"""Parse a colour value from a config string."""
|
||||
return self.parseColor(parser.get(section, name, fallback="default"))
|
||||
@@ -570,7 +567,6 @@ class GuiTheme:
|
||||
) -> None:
|
||||
"""Set a palette colour value from a config string."""
|
||||
self._guiPalette.setBrush(value, self._readColor(parser, section, name))
|
||||
return
|
||||
|
||||
def _buildStyleSheets(self, palette: QPalette) -> None:
|
||||
"""Build default style sheets."""
|
||||
@@ -602,8 +598,6 @@ class GuiTheme:
|
||||
"QToolButton::menu-indicator {image: none;} "
|
||||
)
|
||||
|
||||
return
|
||||
|
||||
def _scanThemes(self, files: list[Path]) -> None:
|
||||
"""Scan the GUI themes folder and list all themes."""
|
||||
parser = ConfigParser()
|
||||
@@ -631,8 +625,6 @@ class GuiTheme:
|
||||
logger.debug("Checking theme config '%s'", key)
|
||||
self._allThemes[key] = ThemeEntry(name, dark, item)
|
||||
|
||||
return
|
||||
|
||||
|
||||
class GuiIcons:
|
||||
"""The icon class manages the content of the assets/icons folder,
|
||||
@@ -672,8 +664,6 @@ class GuiIcons:
|
||||
# None Icon
|
||||
self._noIcon = QIcon(str(CONFIG.assetPath("icons") / "none.svg"))
|
||||
|
||||
return
|
||||
|
||||
def clear(self) -> None:
|
||||
"""Clear the icon cache."""
|
||||
self._svgData = {}
|
||||
@@ -681,7 +671,6 @@ class GuiIcons:
|
||||
self._headerDec = []
|
||||
self._headerDecNarrow = []
|
||||
self._meta = ThemeMeta()
|
||||
return
|
||||
|
||||
##
|
||||
# Properties
|
||||
@@ -703,7 +692,6 @@ class GuiIcons:
|
||||
_listContent(icons, CONFIG.assetPath("icons"), ".icons")
|
||||
_listContent(icons, CONFIG.dataPath("icons"), ".icons")
|
||||
self._scanThemes(icons)
|
||||
return
|
||||
|
||||
def loadTheme(self, theme: str) -> None:
|
||||
"""Update the theme map. This is more of an init, since many of
|
||||
@@ -781,7 +769,7 @@ class GuiIcons:
|
||||
self, tType: nwItemType, tClass: nwItemClass, tLayout: nwItemLayout, hLevel: str = "H0"
|
||||
) -> QIcon:
|
||||
"""Get the correct icon for a project item based on type, class
|
||||
and heading level
|
||||
and heading level.
|
||||
"""
|
||||
name = None
|
||||
color = "default"
|
||||
@@ -942,8 +930,6 @@ class GuiIcons:
|
||||
logger.debug("Checking icon theme '%s'", key)
|
||||
self._allThemes[key] = ThemeEntry(name, False, item)
|
||||
|
||||
return
|
||||
|
||||
|
||||
# Module Functions
|
||||
# ================
|
||||
@@ -952,4 +938,3 @@ def _listContent(data: list[Path], path: Path, extension: str) -> None:
|
||||
"""List files of a specific type and extend the list."""
|
||||
if path.is_dir():
|
||||
data.extend(n for n in path.iterdir() if n.is_file() and n.suffix == extension)
|
||||
return
|
||||
|
||||
+3
-64
@@ -20,7 +20,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
@@ -68,7 +68,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GuiMain(QMainWindow):
|
||||
"""Main GUI Window
|
||||
"""Main GUI Window.
|
||||
|
||||
The Main GUI window class is the entry point of the application. It
|
||||
is split up into GUI components, assembled in the init function.
|
||||
@@ -323,13 +323,10 @@ class GuiMain(QMainWindow):
|
||||
self.mainStatus.setStatusMessage(self.tr("novelWriter is ready ..."))
|
||||
CONFIG.splashMessage("novelWriter is ready ...")
|
||||
|
||||
return
|
||||
|
||||
def initMain(self) -> None:
|
||||
"""Initialise elements that depend on user settings."""
|
||||
self.asProjTimer.setInterval(int(CONFIG.autoSaveProj*1000))
|
||||
self.asDocTimer.setInterval(int(CONFIG.autoSaveDoc*1000))
|
||||
return
|
||||
|
||||
def postLaunchTasks(self, cmdOpen: str | None) -> None:
|
||||
"""Process tasks after the main window has been created."""
|
||||
@@ -351,8 +348,6 @@ class GuiMain(QMainWindow):
|
||||
# before showing any dialogs
|
||||
QTimer.singleShot(50, self.showPostLaunchDialogs)
|
||||
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def showPostLaunchDialogs(self) -> None:
|
||||
"""Show post launch dialogs."""
|
||||
@@ -370,8 +365,6 @@ class GuiMain(QMainWindow):
|
||||
).format(f"<a href='{nwConst.URL_RELEASES}'>", "</a>")
|
||||
SHARED.info(f"{trVersion}<br>{trRelease}")
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Project Actions
|
||||
##
|
||||
@@ -534,7 +527,6 @@ class GuiMain(QMainWindow):
|
||||
SHARED.setFocusMode(False)
|
||||
self.saveDocument()
|
||||
self.docEditor.clearEditor()
|
||||
return
|
||||
|
||||
def openDocument(
|
||||
self,
|
||||
@@ -591,7 +583,6 @@ class GuiMain(QMainWindow):
|
||||
self.openDocument(nHandle, tLine=1, doScroll=True)
|
||||
elif wrapAround:
|
||||
self.openDocument(fHandle, tLine=1, doScroll=True)
|
||||
return
|
||||
|
||||
def saveDocument(self, force: bool = False) -> None:
|
||||
"""Save the current documents."""
|
||||
@@ -599,13 +590,11 @@ class GuiMain(QMainWindow):
|
||||
self.docEditor.saveCursorPosition()
|
||||
if force or self.docEditor.docChanged:
|
||||
self.docEditor.saveText()
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def forceSaveDocument(self) -> None:
|
||||
"""Save document even of it has not changed."""
|
||||
self.saveDocument(force=True)
|
||||
return
|
||||
|
||||
def viewDocument(self, tHandle: str | None = None, sTitle: str | None = None) -> bool:
|
||||
"""Load a document for viewing in the view panel."""
|
||||
@@ -760,8 +749,6 @@ class GuiMain(QMainWindow):
|
||||
if not beQuiet:
|
||||
SHARED.info(self.tr("The project index has been successfully rebuilt."))
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Main Dialogs
|
||||
##
|
||||
@@ -772,7 +759,6 @@ class GuiMain(QMainWindow):
|
||||
dialog = GuiWelcome(self)
|
||||
dialog.openProjectRequest.connect(self._openProjectFromWelcome)
|
||||
dialog.exec()
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def showPreferencesDialog(self) -> None:
|
||||
@@ -780,7 +766,6 @@ class GuiMain(QMainWindow):
|
||||
dialog = GuiPreferences(self)
|
||||
dialog.newPreferencesReady.connect(self._processConfigChanges)
|
||||
dialog.exec()
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
@pyqtSlot(int)
|
||||
@@ -790,7 +775,6 @@ class GuiMain(QMainWindow):
|
||||
dialog = GuiProjectSettings(self, gotoPage=focusTab)
|
||||
dialog.newProjectSettingsReady.connect(self._processProjectSettingsChanges)
|
||||
dialog.exec()
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def showNovelDetailsDialog(self) -> None:
|
||||
@@ -799,7 +783,6 @@ class GuiMain(QMainWindow):
|
||||
dialog = GuiNovelDetails(self)
|
||||
dialog.activateDialog()
|
||||
dialog.updateValues()
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def showBuildManuscriptDialog(self) -> None:
|
||||
@@ -809,7 +792,6 @@ class GuiMain(QMainWindow):
|
||||
dialog = GuiManuscript(self)
|
||||
dialog.activateDialog()
|
||||
dialog.loadContent()
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def showProjectWordListDialog(self) -> None:
|
||||
@@ -818,7 +800,6 @@ class GuiMain(QMainWindow):
|
||||
dialog = GuiWordList(self)
|
||||
dialog.newWordListReady.connect(self._processWordListChanges)
|
||||
dialog.exec()
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def showWritingStatsDialog(self) -> None:
|
||||
@@ -828,21 +809,18 @@ class GuiMain(QMainWindow):
|
||||
dialog = GuiWritingStats(self)
|
||||
dialog.activateDialog()
|
||||
dialog.populateGUI()
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def showAboutNWDialog(self) -> None:
|
||||
"""Show the novelWriter about dialog."""
|
||||
dialog = GuiAbout(self)
|
||||
dialog.exec()
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def showAboutQtDialog(self) -> None:
|
||||
"""Show the Qt about dialog."""
|
||||
msgBox = QMessageBox(self)
|
||||
msgBox.aboutQt(self, "About Qt")
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def showDictionariesDialog(self) -> None:
|
||||
@@ -852,7 +830,6 @@ class GuiMain(QMainWindow):
|
||||
if not dialog.initDialog():
|
||||
dialog.close()
|
||||
SHARED.error(self.tr("Could not initialise the dialog."))
|
||||
return
|
||||
|
||||
##
|
||||
# Main Window Actions
|
||||
@@ -915,7 +892,6 @@ class GuiMain(QMainWindow):
|
||||
self.refreshThemeColors(syntax=True)
|
||||
self.docEditor.initEditor()
|
||||
self.docViewer.initViewer()
|
||||
return
|
||||
|
||||
def refreshThemeColors(self, syntax: bool = False, force: bool = False) -> None:
|
||||
"""Refresh the GUI theme."""
|
||||
@@ -937,8 +913,6 @@ class GuiMain(QMainWindow):
|
||||
if syntax:
|
||||
self.docEditor.updateSyntaxColors()
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Events
|
||||
##
|
||||
@@ -947,14 +921,12 @@ class GuiMain(QMainWindow):
|
||||
"""Capture application change events."""
|
||||
if int(event.type()) == 210: # ThemeChange
|
||||
self.checkThemeUpdate()
|
||||
return
|
||||
|
||||
def closeEvent(self, event: QCloseEvent) -> None:
|
||||
"""Capture the closing event of the GUI and call the close
|
||||
function to handle all the close process steps.
|
||||
"""
|
||||
event.accept() if self.closeMain() else event.ignore()
|
||||
return
|
||||
|
||||
##
|
||||
# Public Slots
|
||||
@@ -962,30 +934,26 @@ class GuiMain(QMainWindow):
|
||||
|
||||
@pyqtSlot()
|
||||
def toggleFullScreenMode(self) -> None:
|
||||
"""Toggle full screen mode"""
|
||||
"""Toggle full screen mode."""
|
||||
self.setWindowState(self.windowState() ^ Qt.WindowState.WindowFullScreen)
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def closeDocEditor(self) -> None:
|
||||
"""Close the document editor. This does not hide the editor."""
|
||||
self.closeDocument()
|
||||
SHARED.project.data.setLastHandle(None, "editor")
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def closeDocViewer(self) -> None:
|
||||
"""Close the document viewer."""
|
||||
self.closeViewerPanel()
|
||||
SHARED.project.data.setLastHandle(None, "viewer")
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def toggleFocusMode(self) -> None:
|
||||
"""Toggle focus mode."""
|
||||
if self.docEditor.docHandle:
|
||||
SHARED.setFocusMode(not SHARED.focusMode)
|
||||
return
|
||||
|
||||
##
|
||||
# Private Slots
|
||||
@@ -1003,7 +971,6 @@ class GuiMain(QMainWindow):
|
||||
docViewer = True
|
||||
self.docEditor.changeFocusState(docEditor)
|
||||
self.docViewer.changeFocusState(docViewer)
|
||||
return
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def _focusModeChanged(self, focusMode: bool) -> None:
|
||||
@@ -1034,7 +1001,6 @@ class GuiMain(QMainWindow):
|
||||
|
||||
if cursorVisible:
|
||||
self.docEditor.ensureCursorVisibleNoCentre()
|
||||
return
|
||||
|
||||
@pyqtSlot(nwFocus)
|
||||
def _switchFocus(self, paneNo: nwFocus) -> None:
|
||||
@@ -1082,8 +1048,6 @@ class GuiMain(QMainWindow):
|
||||
self._changeView(nwView.OUTLINE, exitFocus=True)
|
||||
self.outlineView.setTreeFocus()
|
||||
|
||||
return
|
||||
|
||||
@pyqtSlot(bool, bool, bool, bool)
|
||||
def _processConfigChanges(self, restart: bool, tree: bool, theme: bool, syntax: bool) -> None:
|
||||
"""Refresh GUI based on flags from the Preferences dialog."""
|
||||
@@ -1115,8 +1079,6 @@ class GuiMain(QMainWindow):
|
||||
"Some changes will not be applied until novelWriter has been restarted."
|
||||
))
|
||||
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _processProjectSettingsChanges(self) -> None:
|
||||
"""Refresh data dependent on project settings."""
|
||||
@@ -1124,7 +1086,6 @@ class GuiMain(QMainWindow):
|
||||
SHARED.updateSpellCheckLanguage()
|
||||
self.itemDetails.refreshDetails()
|
||||
self._updateWindowTitle(SHARED.project.data.name)
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _processWordListChanges(self) -> None:
|
||||
@@ -1132,7 +1093,6 @@ class GuiMain(QMainWindow):
|
||||
logger.debug("Reloading word list")
|
||||
SHARED.updateSpellCheckLanguage(reload=True)
|
||||
self.docEditor.spellCheckDocument()
|
||||
return
|
||||
|
||||
@pyqtSlot(str, nwDocMode)
|
||||
def _followTag(self, tag: str, mode: nwDocMode) -> None:
|
||||
@@ -1151,7 +1111,6 @@ class GuiMain(QMainWindow):
|
||||
self.openDocument(tHandle, sTitle=sTitle)
|
||||
elif mode == nwDocMode.VIEW:
|
||||
self.viewDocument(tHandle=tHandle, sTitle=sTitle)
|
||||
return
|
||||
|
||||
@pyqtSlot(Path)
|
||||
def _openProjectFromWelcome(self, path: Path) -> None:
|
||||
@@ -1160,7 +1119,6 @@ class GuiMain(QMainWindow):
|
||||
self.openProject(path)
|
||||
if not SHARED.hasProject:
|
||||
self.showWelcomeDialog()
|
||||
return
|
||||
|
||||
@pyqtSlot(str, nwDocMode, str, bool)
|
||||
def _openDocument(self, tHandle: str, mode: nwDocMode, sTitle: str, setFocus: bool) -> None:
|
||||
@@ -1170,7 +1128,6 @@ class GuiMain(QMainWindow):
|
||||
self.openDocument(tHandle, sTitle=sTitle, changeFocus=setFocus)
|
||||
elif mode == nwDocMode.VIEW:
|
||||
self.viewDocument(tHandle=tHandle, sTitle=sTitle)
|
||||
return
|
||||
|
||||
@pyqtSlot(str, int, int, bool)
|
||||
def _openDocumentSelection(
|
||||
@@ -1179,7 +1136,6 @@ class GuiMain(QMainWindow):
|
||||
"""Open a document and select a section of the text."""
|
||||
if self.openDocument(tHandle, changeFocus=changeFocus):
|
||||
self.docEditor.setCursorSelection(selStart, selLength)
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _reloadViewer(self) -> None:
|
||||
@@ -1188,7 +1144,6 @@ class GuiMain(QMainWindow):
|
||||
# If the two panels have the same document, save any changes in the editor
|
||||
self.saveDocument()
|
||||
self.docViewer.reloadText()
|
||||
return
|
||||
|
||||
@pyqtSlot(nwView)
|
||||
def _changeView(self, view: nwView, exitFocus: bool = False) -> None:
|
||||
@@ -1219,8 +1174,6 @@ class GuiMain(QMainWindow):
|
||||
isNovel = self.projStack.currentWidget() == self.novelView
|
||||
self.novelView.setActive(isMain and isNovel)
|
||||
|
||||
return
|
||||
|
||||
@pyqtSlot(nwDocAction)
|
||||
def _passDocumentAction(self, action: nwDocAction) -> None:
|
||||
"""Pass on a document action to the editor or viewer based on
|
||||
@@ -1230,7 +1183,6 @@ class GuiMain(QMainWindow):
|
||||
self.docEditor.docAction(action)
|
||||
elif self.docViewer.hasFocus():
|
||||
self.docViewer.docAction(action)
|
||||
return
|
||||
|
||||
@pyqtSlot(str)
|
||||
@pyqtSlot(nwDocInsert)
|
||||
@@ -1240,14 +1192,12 @@ class GuiMain(QMainWindow):
|
||||
"""
|
||||
if self.docEditor.hasFocus():
|
||||
self.docEditor.insertText(content)
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _toggleViewerPanelVisibility(self) -> None:
|
||||
"""Toggle the visibility of the document viewer panel."""
|
||||
CONFIG.showViewerPanel = not CONFIG.showViewerPanel
|
||||
self.docViewerPanel.setVisible(CONFIG.showViewerPanel)
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _timeTick(self) -> None:
|
||||
@@ -1263,7 +1213,6 @@ class GuiMain(QMainWindow):
|
||||
self._updateStatusWordCount()
|
||||
if CONFIG.memInfo: # pragma: no cover
|
||||
self.mainStatus.memInfo()
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _autoSaveProject(self) -> None:
|
||||
@@ -1274,7 +1223,6 @@ class GuiMain(QMainWindow):
|
||||
if doSave:
|
||||
logger.debug("Auto-saving project")
|
||||
self.saveProject(autoSave=True)
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _autoSaveDocument(self) -> None:
|
||||
@@ -1282,7 +1230,6 @@ class GuiMain(QMainWindow):
|
||||
if SHARED.hasProject and self.docEditor.docChanged:
|
||||
logger.debug("Auto-saving document")
|
||||
self.saveDocument()
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _updateStatusWordCount(self) -> None:
|
||||
@@ -1312,8 +1259,6 @@ class GuiMain(QMainWindow):
|
||||
|
||||
self.mainStatus.setProjectStats(cTotal, cTotal - iTotal)
|
||||
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _keyPressReturn(self) -> None:
|
||||
"""Process a return or enter keypress in the main window."""
|
||||
@@ -1321,7 +1266,6 @@ class GuiMain(QMainWindow):
|
||||
self.projSearch.processReturn()
|
||||
else:
|
||||
self.openSelectedItem()
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _keyPressEscape(self) -> None:
|
||||
@@ -1330,7 +1274,6 @@ class GuiMain(QMainWindow):
|
||||
self.docEditor.closeSearch()
|
||||
elif SHARED.focusMode:
|
||||
SHARED.setFocusMode(False)
|
||||
return
|
||||
|
||||
@pyqtSlot(int)
|
||||
def _mainStackChanged(self, index: int) -> None:
|
||||
@@ -1338,7 +1281,6 @@ class GuiMain(QMainWindow):
|
||||
if self.mainStack.widget(index) == self.outlineView:
|
||||
if SHARED.hasProject:
|
||||
self.outlineView.refreshTree()
|
||||
return
|
||||
|
||||
@pyqtSlot(int)
|
||||
def _projStackChanged(self, index: int) -> None:
|
||||
@@ -1350,7 +1292,6 @@ class GuiMain(QMainWindow):
|
||||
elif widget == self.novelView:
|
||||
sHandle, _ = self.novelView.getSelectedHandle()
|
||||
self.itemDetails.updateViewBox(sHandle)
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
@@ -1363,9 +1304,7 @@ class GuiMain(QMainWindow):
|
||||
width = minmax(size[0], 900, availSize.width())
|
||||
height = minmax(size[1], 500, availSize.height())
|
||||
self.resize(width, height)
|
||||
return
|
||||
|
||||
def _updateWindowTitle(self, projName: str | None = None) -> None:
|
||||
"""Set the window title and add the project's name."""
|
||||
self.setWindowTitle(" - ".join(filter(None, [projName, CONFIG.appName])))
|
||||
return
|
||||
|
||||
+8
-39
@@ -21,7 +21,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
@@ -57,6 +57,12 @@ RX_HTML = re.compile(r"<.*?>")
|
||||
|
||||
|
||||
class SharedData(QObject):
|
||||
"""Shared Data Singleton.
|
||||
|
||||
This is the class instantiated as the SHARED singleton. It holds
|
||||
various globally needed data and pointers to important objects like
|
||||
the main GUI, the current project, and the GUI theme.
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
"_gui", "_idleRefTime", "_idleTime", "_lastAlert", "_lockedBy",
|
||||
@@ -96,8 +102,6 @@ class SharedData(QObject):
|
||||
self._clock.setInterval(1000)
|
||||
self._clock.timeout.connect(lambda: self.mainClockTick.emit())
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Properties
|
||||
##
|
||||
@@ -169,7 +173,6 @@ class SharedData(QObject):
|
||||
if state is not self._focusMode:
|
||||
self._focusMode = state
|
||||
self.focusModeChanged.emit(state)
|
||||
return
|
||||
|
||||
##
|
||||
# Methods
|
||||
@@ -181,7 +184,6 @@ class SharedData(QObject):
|
||||
"""
|
||||
self._theme = theme
|
||||
self._theme.initThemes()
|
||||
return
|
||||
|
||||
def initSharedData(self, gui: GuiMain) -> None:
|
||||
"""Initialise the SharedData instance. This must be called as
|
||||
@@ -194,7 +196,6 @@ class SharedData(QObject):
|
||||
logger.debug("Ready: SharedData")
|
||||
if pool := QThreadPool.globalInstance():
|
||||
logger.debug("Thread Pool Max Count: %d", pool.maxThreadCount())
|
||||
return
|
||||
|
||||
def closeDocument(self, tHandle: str | None = None) -> None:
|
||||
"""Close the document editor, optionally a specific document."""
|
||||
@@ -202,7 +203,6 @@ class SharedData(QObject):
|
||||
self.mainGui.closeDocument()
|
||||
if tHandle is None or tHandle == self.mainGui.docViewer.docHandle:
|
||||
self.mainGui.closeViewerPanel()
|
||||
return
|
||||
|
||||
def saveEditor(self, tHandle: str | None = None) -> None:
|
||||
"""Save the editor content, optionally a specific document."""
|
||||
@@ -213,7 +213,6 @@ class SharedData(QObject):
|
||||
):
|
||||
logger.debug("Saving editor document before action")
|
||||
docEditor.saveText()
|
||||
return
|
||||
|
||||
def openProject(self, path: str | Path, clearLock: bool = False) -> bool:
|
||||
"""Open a project."""
|
||||
@@ -246,7 +245,6 @@ class SharedData(QObject):
|
||||
self.project.closeProject(self._idleTime)
|
||||
self._resetProject()
|
||||
self._resetIdleTimer()
|
||||
return
|
||||
|
||||
def updateSpellCheckLanguage(self, reload: bool = False) -> None:
|
||||
"""Update the active spell check language from settings."""
|
||||
@@ -256,7 +254,6 @@ class SharedData(QObject):
|
||||
self.spelling.setLanguage(language)
|
||||
_, provider = self.spelling.describeDict()
|
||||
self.spellLanguageChanged.emit(language, provider)
|
||||
return
|
||||
|
||||
def updateIdleTime(self, currTime: float, userIdle: bool) -> None:
|
||||
"""Update the idle time record. If the userIdle flag is True,
|
||||
@@ -267,47 +264,40 @@ class SharedData(QObject):
|
||||
if userIdle:
|
||||
self._idleTime += currTime - self._idleRefTime
|
||||
self._idleRefTime = currTime
|
||||
return
|
||||
|
||||
def initMainProgress(self, maximum: int, inclusive: bool = False) -> None:
|
||||
"""Start a session for the main progress bar."""
|
||||
if gui := self._gui:
|
||||
gui.mainProgress.setMaximum(maximum - (1 if inclusive else 0))
|
||||
gui.mainProgress.setValue(0)
|
||||
return
|
||||
|
||||
def incMainProgress(self) -> None:
|
||||
"""Increment the value for the main progress bar."""
|
||||
if gui := self._gui:
|
||||
gui.mainProgress.setValue(gui.mainProgress.value() + 1)
|
||||
QApplication.processEvents()
|
||||
return
|
||||
|
||||
def clearMainProgress(self, delay: float = 1.0) -> None:
|
||||
"""Clear the main progress bar."""
|
||||
if gui := self._gui:
|
||||
QTimer.singleShot(int(delay*1000), gui.mainProgress.reset)
|
||||
return
|
||||
|
||||
def newStatusMessage(self, message: str) -> None:
|
||||
"""Request a new status message. This is a callable function for
|
||||
core classes that cannot emit signals on their own.
|
||||
"""
|
||||
self.projectStatusMessage.emit(message)
|
||||
return
|
||||
|
||||
def setGlobalProjectState(self, state: bool) -> None:
|
||||
"""Change the global project status. This is a callable function
|
||||
for core classes that cannot emit signals on their own.
|
||||
"""
|
||||
self.projectStatusChanged.emit(state)
|
||||
return
|
||||
|
||||
def runInThreadPool(self, runnable: QRunnable, priority: int = 0) -> None:
|
||||
"""Queue a runnable in the application thread pool."""
|
||||
if pool := QThreadPool.globalInstance():
|
||||
pool.start(runnable, priority=priority)
|
||||
return
|
||||
|
||||
def getProjectPath(
|
||||
self, parent: QWidget,
|
||||
@@ -346,13 +336,11 @@ class SharedData(QObject):
|
||||
def openWebsite(self, url: str) -> None:
|
||||
"""Open a URL in the system's default browser."""
|
||||
QDesktopServices.openUrl(QUrl(url))
|
||||
return
|
||||
|
||||
@pyqtSlot(str, nwItemClass)
|
||||
def createNewNote(self, tag: str, itemClass: nwItemClass) -> None:
|
||||
"""Process new note request."""
|
||||
self.project.createNewNote(tag, itemClass)
|
||||
return
|
||||
|
||||
##
|
||||
# Signal Proxies
|
||||
@@ -364,37 +352,31 @@ class SharedData(QObject):
|
||||
"""Emit the indexChangedTags signal."""
|
||||
if self._project and self._project.data.uuid == project.data.uuid:
|
||||
self.indexChangedTags.emit(updated, deleted)
|
||||
return
|
||||
|
||||
def emitIndexCleared(self, project: NWProject) -> None:
|
||||
"""Emit the indexCleared signal."""
|
||||
if self._project and self._project.data.uuid == project.data.uuid:
|
||||
self.indexCleared.emit()
|
||||
return
|
||||
|
||||
def emitIndexAvailable(self, project: NWProject) -> None:
|
||||
"""Emit the indexAvailable signal."""
|
||||
if self._project and self._project.data.uuid == project.data.uuid:
|
||||
self.indexAvailable.emit()
|
||||
return
|
||||
|
||||
def emitStatusLabelsChanged(self, project: NWProject, kind: T_StatusKind) -> None:
|
||||
"""Emit the statusLabelsChanged signal."""
|
||||
if self._project and self._project.data.uuid == project.data.uuid:
|
||||
self.statusLabelsChanged.emit(kind)
|
||||
return
|
||||
|
||||
def emitProjectItemChanged(self, project: NWProject, handle: str, change: nwChange) -> None:
|
||||
"""Emit the projectItemChanged signal."""
|
||||
if self._project and self._project.data.uuid == project.data.uuid:
|
||||
self.projectItemChanged.emit(handle, change)
|
||||
return
|
||||
|
||||
def emitRootFolderChanged(self, project: NWProject, handle: str, change: nwChange) -> None:
|
||||
"""Emit the rootFolderChanged signal."""
|
||||
if self._project and self._project.data.uuid == project.data.uuid:
|
||||
self.rootFolderChanged.emit(handle, change)
|
||||
return
|
||||
|
||||
##
|
||||
# Alert Boxes
|
||||
@@ -409,7 +391,6 @@ class SharedData(QObject):
|
||||
if log:
|
||||
self._logMessage(self._lastAlert, logger.info)
|
||||
alert.exec()
|
||||
return
|
||||
|
||||
def warn(self, text: str, info: str = "", details: str = "", log: bool = True) -> None:
|
||||
"""Open a warning alert box."""
|
||||
@@ -420,7 +401,6 @@ class SharedData(QObject):
|
||||
if log:
|
||||
self._logMessage(self._lastAlert, logger.warning)
|
||||
alert.exec()
|
||||
return
|
||||
|
||||
def error(self, text: str, info: str = "", details: str = "", log: bool = True,
|
||||
exc: Exception | None = None) -> None:
|
||||
@@ -434,7 +414,6 @@ class SharedData(QObject):
|
||||
if log:
|
||||
self._logMessage(self._lastAlert, logger.error)
|
||||
alert.exec()
|
||||
return
|
||||
|
||||
def question(self, text: str, info: str = "", details: str = "", warn: bool = False) -> bool:
|
||||
"""Open a question box."""
|
||||
@@ -443,8 +422,7 @@ class SharedData(QObject):
|
||||
alert.setAlertType(_GuiAlert.WARN if warn else _GuiAlert.ASK, True)
|
||||
self._lastAlert = alert.logMessage
|
||||
alert.exec()
|
||||
isYes = alert.result() == QMessageBox.StandardButton.Yes
|
||||
return isYes
|
||||
return alert.result() == QMessageBox.StandardButton.Yes
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
@@ -454,7 +432,6 @@ class SharedData(QObject):
|
||||
"""Print message to log."""
|
||||
for text in message.split("<br>"):
|
||||
log(RX_HTML.sub("", text), stacklevel=3)
|
||||
return
|
||||
|
||||
def _resetProject(self) -> None:
|
||||
"""Create a new project and spell checking instance."""
|
||||
@@ -467,13 +444,11 @@ class SharedData(QObject):
|
||||
self._spelling = NWSpellEnchant(self._project)
|
||||
self.updateSpellCheckLanguage()
|
||||
self._focusMode = False
|
||||
return
|
||||
|
||||
def _resetIdleTimer(self) -> None:
|
||||
"""Reset the timer data for the idle timer."""
|
||||
self._idleRefTime = time()
|
||||
self._idleTime = 0.0
|
||||
return
|
||||
|
||||
def _closeToolDialogs(self) -> None:
|
||||
"""Close all open tool dialogs."""
|
||||
@@ -481,7 +456,6 @@ class SharedData(QObject):
|
||||
for widget in self.mainGui.children():
|
||||
if isinstance(widget, NToolDialog):
|
||||
widget.close()
|
||||
return
|
||||
|
||||
|
||||
class _GuiAlert(QMessageBox):
|
||||
@@ -496,11 +470,9 @@ class _GuiAlert(QMessageBox):
|
||||
self._theme = theme
|
||||
self._message = ""
|
||||
logger.debug("Ready: _GuiAlert")
|
||||
return
|
||||
|
||||
def __del__(self) -> None: # pragma: no cover
|
||||
logger.debug("Delete: _GuiAlert")
|
||||
return
|
||||
|
||||
@property
|
||||
def logMessage(self) -> str:
|
||||
@@ -512,14 +484,12 @@ class _GuiAlert(QMessageBox):
|
||||
self.setText(text)
|
||||
self.setInformativeText(info)
|
||||
self.setDetailedText(details)
|
||||
return
|
||||
|
||||
def setException(self, exception: Exception) -> None:
|
||||
"""Add exception details."""
|
||||
info = self.informativeText()
|
||||
text = f"<b>{type(exception).__name__}</b>: {exception!s}"
|
||||
self.setInformativeText(f"{info}<br>{text}" if info else text)
|
||||
return
|
||||
|
||||
def setAlertType(self, level: int, isYesNo: bool) -> None:
|
||||
"""Set the type of alert and whether the dialog should have
|
||||
@@ -542,4 +512,3 @@ class _GuiAlert(QMessageBox):
|
||||
elif level == self.ASK:
|
||||
self.setIconPixmap(self._theme.getPixmap("alert_question", (pSz, pSz), "blue"))
|
||||
self.setWindowTitle(self.tr("Question"))
|
||||
return
|
||||
|
||||
@@ -20,7 +20,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
@@ -38,6 +38,14 @@ SPLASH_IMG = Path(__file__).parent / "assets" / "images" / "splash.png"
|
||||
|
||||
|
||||
class NSplashScreen(QSplashScreen):
|
||||
"""GUI: App Launch Splash Screen.
|
||||
|
||||
A small splash screen that is shown as novelWriter starts up. Its
|
||||
primary purpose is to provide user feedback that the app is being
|
||||
initiated when there are delays in the process while Qt waits for
|
||||
responses from the OS, or has to load particularly large data sets
|
||||
like when the system has a lot of fonts installed.
|
||||
"""
|
||||
|
||||
__slots__ = ("_color", "_rect", "_text")
|
||||
|
||||
@@ -52,17 +60,14 @@ class NSplashScreen(QSplashScreen):
|
||||
self._color = QColor(26, 52, 78)
|
||||
self._rect = QRect(144, 110, 440, 30)
|
||||
self._text = ""
|
||||
return
|
||||
|
||||
def __del__(self) -> None: # pragma: no cover
|
||||
logger.debug("Delete: NSplashScreen")
|
||||
return
|
||||
|
||||
def drawContents(self, painter: QPainter) -> None:
|
||||
"""Draw the text message."""
|
||||
painter.setPen(self._color)
|
||||
painter.drawText(self._rect, Qt.AlignmentFlag.AlignLeft, self._text)
|
||||
return
|
||||
|
||||
def showStatus(self, message: str) -> None:
|
||||
"""Update the status message."""
|
||||
@@ -71,4 +76,3 @@ class NSplashScreen(QSplashScreen):
|
||||
if message:
|
||||
logger.info("[Splash] %s", message)
|
||||
sleep(0.025)
|
||||
return
|
||||
|
||||
@@ -21,7 +21,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
from novelwriter.enum import nwComment
|
||||
|
||||
@@ -22,7 +22,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
@@ -74,9 +74,11 @@ def preProcessText(text: str, keepHeaders: bool = True) -> list[str]:
|
||||
|
||||
|
||||
def standardCounter(text: str) -> tuple[int, int, int]:
|
||||
"""A counter that counts paragraphs, words and characters.
|
||||
This is the standard counter that includes headings in the word and
|
||||
character counts.
|
||||
"""Return a standard count.
|
||||
|
||||
A counter that counts paragraphs, words and characters. This is the
|
||||
standard counter that includes headings in the word and character
|
||||
counts.
|
||||
"""
|
||||
cCount = 0
|
||||
wCount = 0
|
||||
@@ -124,7 +126,9 @@ def standardCounter(text: str) -> tuple[int, int, int]:
|
||||
|
||||
|
||||
def bodyTextCounter(text: str) -> tuple[int, int, int]:
|
||||
"""A counter that counts body text words, characters, and characters
|
||||
"""Return a body text count.
|
||||
|
||||
A counter that counts body text words, characters, and characters
|
||||
without white spaces.
|
||||
"""
|
||||
wCount = 0
|
||||
|
||||
@@ -21,7 +21,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
@@ -32,6 +32,7 @@ from novelwriter.constants import nwRegEx, nwUnicode
|
||||
|
||||
|
||||
class RegExPatterns:
|
||||
"""Compiled RegEx Patterns."""
|
||||
|
||||
AMBIGUOUS = (nwUnicode.U_APOS, nwUnicode.U_RSQUO)
|
||||
|
||||
@@ -132,6 +133,7 @@ REGEX_PATTERNS = RegExPatterns()
|
||||
|
||||
|
||||
class DialogParser:
|
||||
"""A callable parser for finding dialog regions in text."""
|
||||
|
||||
__slots__ = (
|
||||
"_alternate", "_breakD", "_breakQ", "_dialog", "_enabled", "_mode",
|
||||
@@ -147,7 +149,6 @@ class DialogParser:
|
||||
self._breakD = None
|
||||
self._breakQ = None
|
||||
self._mode = ""
|
||||
return
|
||||
|
||||
@property
|
||||
def enabled(self) -> bool:
|
||||
@@ -174,8 +175,6 @@ class DialogParser:
|
||||
self._narrator = narrator
|
||||
self._mode = f" {narrator}"
|
||||
|
||||
return
|
||||
|
||||
def __call__(self, text: str) -> list[tuple[int, int]]:
|
||||
"""Caller wrapper for dialogue processing."""
|
||||
temp: list[int] = []
|
||||
|
||||
@@ -20,7 +20,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
@@ -45,6 +45,12 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GuiDictionaries(NNonBlockingDialog):
|
||||
"""GUI: Spell Check Dictionary Tool.
|
||||
|
||||
A helper tool for downloading and extracting dictionaries to a
|
||||
location where Enchant can find them. This tool is only needed on
|
||||
Windows.
|
||||
"""
|
||||
|
||||
def __init__(self, parent: QWidget) -> None:
|
||||
super().__init__(parent=parent)
|
||||
@@ -123,11 +129,8 @@ class GuiDictionaries(NNonBlockingDialog):
|
||||
|
||||
logger.debug("Ready: GuiDictionaries")
|
||||
|
||||
return
|
||||
|
||||
def __del__(self) -> None: # pragma: no cover
|
||||
logger.debug("Delete: GuiDictionaries")
|
||||
return
|
||||
|
||||
def initDialog(self) -> bool:
|
||||
"""Prepare and check that we can proceed."""
|
||||
@@ -164,7 +167,6 @@ class GuiDictionaries(NNonBlockingDialog):
|
||||
"""Capture the user closing the window."""
|
||||
event.accept()
|
||||
self.softDelete()
|
||||
return
|
||||
|
||||
##
|
||||
# Private Slots
|
||||
@@ -182,7 +184,6 @@ class GuiDictionaries(NNonBlockingDialog):
|
||||
if soxFile:
|
||||
path = Path(soxFile).absolute()
|
||||
self.huInput.setText(str(path))
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _doImportHunspell(self) -> None:
|
||||
@@ -202,14 +203,12 @@ class GuiDictionaries(NNonBlockingDialog):
|
||||
self._appendLog(formatException(exc), err=True)
|
||||
else:
|
||||
self._appendLog(procErr, err=True)
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _doOpenInstallLocation(self) -> None:
|
||||
"""Open the dictionary folder."""
|
||||
if not openExternalPath(Path(self.inPath.text())):
|
||||
SHARED.error("Path not found.")
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
@@ -247,4 +246,3 @@ class GuiDictionaries(NNonBlockingDialog):
|
||||
cursor.movePosition(QTextCursor.MoveOperation.End)
|
||||
cursor.deleteChar()
|
||||
self.infoBox.setTextCursor(cursor)
|
||||
return
|
||||
|
||||
@@ -20,7 +20,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
@@ -42,6 +42,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GuiLipsum(NDialog):
|
||||
"""GUI: Lorem Ipsum Text Tool."""
|
||||
|
||||
def __init__(self, parent: QWidget) -> None:
|
||||
super().__init__(parent=parent)
|
||||
@@ -111,11 +112,8 @@ class GuiLipsum(NDialog):
|
||||
|
||||
logger.debug("Ready: GuiLipsum")
|
||||
|
||||
return
|
||||
|
||||
def __del__(self) -> None: # pragma: no cover
|
||||
logger.debug("Delete: GuiLipsum")
|
||||
return
|
||||
|
||||
@property
|
||||
def lipsumText(self) -> str:
|
||||
@@ -145,4 +143,3 @@ class GuiLipsum(NDialog):
|
||||
pCount = self.paraCount.value()
|
||||
self._lipsumText = "\n\n".join(lipsumText[0:pCount]) + "\n\n"
|
||||
self.close()
|
||||
return
|
||||
|
||||
@@ -20,7 +20,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
@@ -54,7 +54,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GuiManuscriptBuild(NDialog):
|
||||
"""GUI Tools: Manuscript Build Dialog
|
||||
"""GUI Tools: Manuscript Build Dialog.
|
||||
|
||||
This is the tool for running the build itself. It can be accessed
|
||||
independently of the Manuscript Build Tool.
|
||||
@@ -244,11 +244,8 @@ class GuiManuscriptBuild(NDialog):
|
||||
|
||||
logger.debug("Ready: GuiManuscriptBuild")
|
||||
|
||||
return
|
||||
|
||||
def __del__(self) -> None: # pragma: no cover
|
||||
logger.debug("Delete: GuiManuscriptBuild")
|
||||
return
|
||||
|
||||
##
|
||||
# Events
|
||||
@@ -261,7 +258,6 @@ class GuiManuscriptBuild(NDialog):
|
||||
self._saveSettings()
|
||||
event.accept()
|
||||
self.softDelete()
|
||||
return
|
||||
|
||||
##
|
||||
# Private Slots
|
||||
@@ -278,7 +274,6 @@ class GuiManuscriptBuild(NDialog):
|
||||
self._openOutputFolder()
|
||||
elif role == QtRoleReject:
|
||||
self.close()
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _doSelectPath(self) -> None:
|
||||
@@ -290,7 +285,6 @@ class GuiManuscriptBuild(NDialog):
|
||||
)
|
||||
if savePath:
|
||||
self.buildPath.setText(savePath)
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _doResetBuildName(self) -> None:
|
||||
@@ -298,13 +292,11 @@ class GuiManuscriptBuild(NDialog):
|
||||
bName = f"{SHARED.project.data.name} - {self._build.name}"
|
||||
self.buildName.setText(bName)
|
||||
self._build.setLastBuildName(bName)
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _resetProgress(self) -> None:
|
||||
"""Set the progress bar back to 0."""
|
||||
self.buildProgress.setValue(0)
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
@@ -371,7 +363,6 @@ class GuiManuscriptBuild(NDialog):
|
||||
pOptions.setValue("GuiManuscriptBuild", "fmtWidth", mainSplit[0])
|
||||
pOptions.setValue("GuiManuscriptBuild", "sumWidth", mainSplit[1])
|
||||
pOptions.saveSettings()
|
||||
return
|
||||
|
||||
def _populateContentList(self) -> None:
|
||||
"""Build the content list."""
|
||||
@@ -396,9 +387,6 @@ class GuiManuscriptBuild(NDialog):
|
||||
item.setIcon(nwItem.getMainIcon())
|
||||
self.listContent.addItem(item)
|
||||
|
||||
return
|
||||
|
||||
def _openOutputFolder(self) -> None:
|
||||
"""Open the build folder in the system's file explorer."""
|
||||
openExternalPath(Path(self.buildPath.text()))
|
||||
return
|
||||
|
||||
@@ -20,7 +20,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
@@ -66,7 +66,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GuiManuscript(NToolDialog):
|
||||
"""GUI Tools: Manuscript Tool
|
||||
"""GUI Tools: Manuscript Tool.
|
||||
|
||||
The dialog displays all the users build definitions, a preview panel
|
||||
for the manuscript, and can trigger the actual build dialog to build
|
||||
@@ -251,11 +251,8 @@ class GuiManuscript(NToolDialog):
|
||||
|
||||
logger.debug("Ready: GuiManuscript")
|
||||
|
||||
return
|
||||
|
||||
def __del__(self) -> None: # pragma: no cover
|
||||
logger.debug("Delete: GuiManuscript")
|
||||
return
|
||||
|
||||
def loadContent(self) -> None:
|
||||
"""Load dialog content from project data."""
|
||||
@@ -272,8 +269,6 @@ class GuiManuscript(NToolDialog):
|
||||
self.buildList.setCurrentItem(self._buildMap[selected])
|
||||
QTimer.singleShot(200, self._generatePreview)
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Events
|
||||
##
|
||||
@@ -290,7 +285,6 @@ class GuiManuscript(NToolDialog):
|
||||
obj.close()
|
||||
event.accept()
|
||||
self.softDelete()
|
||||
return
|
||||
|
||||
##
|
||||
# Private Slots
|
||||
@@ -302,14 +296,12 @@ class GuiManuscript(NToolDialog):
|
||||
build = BuildSettings()
|
||||
build.setName(self.tr("My Manuscript"))
|
||||
self._openSettingsDialog(build)
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _editSelectedBuild(self) -> None:
|
||||
"""Edit the currently selected build settings entry."""
|
||||
if build := self._getSelectedBuild():
|
||||
self._openSettingsDialog(build)
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _copySelectedBuild(self) -> None:
|
||||
@@ -320,14 +312,12 @@ class GuiManuscript(NToolDialog):
|
||||
self._updateBuildsList()
|
||||
if item := self._buildMap.get(new.buildID):
|
||||
item.setSelected(True)
|
||||
return
|
||||
|
||||
@pyqtSlot("QListWidgetItem*", "QListWidgetItem*")
|
||||
def _updateBuildDetails(self, current: QListWidgetItem, previous: QListWidgetItem) -> None:
|
||||
"""Process change of build selection to update the details."""
|
||||
if current and (build := self._builds.getBuild(current.data(self.D_KEY))):
|
||||
self.buildDetails.updateInfo(build)
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _deleteSelectedBuild(self) -> None:
|
||||
@@ -338,7 +328,6 @@ class GuiManuscript(NToolDialog):
|
||||
dialog.close()
|
||||
self._builds.removeBuild(build.buildID)
|
||||
self._updateBuildsList()
|
||||
return
|
||||
|
||||
@pyqtSlot(BuildSettings)
|
||||
def _processNewSettings(self, build: BuildSettings) -> None:
|
||||
@@ -347,7 +336,6 @@ class GuiManuscript(NToolDialog):
|
||||
self._updateBuildItem(build)
|
||||
if (current := self.buildList.currentItem()) and current.data(self.D_KEY) == build.buildID:
|
||||
self._updateBuildDetails(current, current)
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _generatePreview(self) -> None:
|
||||
@@ -399,15 +387,12 @@ class GuiManuscript(NToolDialog):
|
||||
if build.changed:
|
||||
self._builds.setBuild(build)
|
||||
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _printDocument(self) -> None:
|
||||
"""Open the print preview dialog."""
|
||||
preview = QPrintPreviewDialog(self)
|
||||
preview.paintRequested.connect(self.docPreview.printPreview)
|
||||
preview.exec()
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
@@ -456,8 +441,6 @@ class GuiManuscript(NToolDialog):
|
||||
pOptions.setValue("GuiManuscript", "showNewPage", showNewPage)
|
||||
pOptions.saveSettings()
|
||||
|
||||
return
|
||||
|
||||
def _openSettingsDialog(self, build: BuildSettings) -> None:
|
||||
"""Open the build settings dialog."""
|
||||
if dialog := self._findSettingsDialog(build.buildID):
|
||||
@@ -482,7 +465,6 @@ class GuiManuscript(NToolDialog):
|
||||
bItem.setData(self.D_KEY, key)
|
||||
self.buildList.addItem(bItem)
|
||||
self._buildMap[key] = bItem
|
||||
return
|
||||
|
||||
def _updateBuildItem(self, build: BuildSettings) -> None:
|
||||
"""Update the entry of a specific build item."""
|
||||
@@ -490,7 +472,6 @@ class GuiManuscript(NToolDialog):
|
||||
item.setText(build.name)
|
||||
else: # Probably a new item
|
||||
self._updateBuildsList()
|
||||
return
|
||||
|
||||
def _findSettingsDialog(self, buildID: str) -> GuiBuildSettings | None:
|
||||
"""Return an open build settings dialog for a given build, if
|
||||
@@ -521,8 +502,6 @@ class _DetailsWidget(QWidget):
|
||||
self.outerBox.setContentsMargins(0, 0, 0, 0)
|
||||
self.setLayout(self.outerBox)
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Getters
|
||||
##
|
||||
@@ -547,7 +526,6 @@ class _DetailsWidget(QWidget):
|
||||
def setColumnWidth(self, value: int) -> None:
|
||||
"""Set the width of the first column."""
|
||||
self.listView.setColumnWidth(0, value)
|
||||
return
|
||||
|
||||
def setExpandedState(self, state: list[bool]) -> None:
|
||||
"""Set the expanded state of each top level item."""
|
||||
@@ -556,7 +534,6 @@ class _DetailsWidget(QWidget):
|
||||
item = self.listView.topLevelItem(i)
|
||||
if isinstance(item, QTreeWidgetItem):
|
||||
item.setExpanded((state[i] if i < count else True) and item.childCount() > 0)
|
||||
return
|
||||
|
||||
##
|
||||
# Methods
|
||||
@@ -637,8 +614,6 @@ class _DetailsWidget(QWidget):
|
||||
# Restore expanded state
|
||||
self.setExpandedState(expanded)
|
||||
|
||||
return
|
||||
|
||||
|
||||
class _OutlineWidget(QWidget):
|
||||
|
||||
@@ -663,8 +638,6 @@ class _OutlineWidget(QWidget):
|
||||
self.outerBox.setContentsMargins(0, 0, 0, 0)
|
||||
self.setLayout(self.outerBox)
|
||||
|
||||
return
|
||||
|
||||
def updateOutline(self, data: dict[str, str]) -> None:
|
||||
"""Update the outline."""
|
||||
if isinstance(data, dict) and data != self._outline:
|
||||
@@ -705,8 +678,6 @@ class _OutlineWidget(QWidget):
|
||||
self.listView.setIndentation(SHARED.theme.baseIconHeight if indent else 4)
|
||||
self._outline = data
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Private Slots
|
||||
##
|
||||
@@ -714,7 +685,6 @@ class _OutlineWidget(QWidget):
|
||||
def _onItemClick(self, item: QTreeWidgetItem) -> None:
|
||||
"""Process tree item click."""
|
||||
self.outlineEntryClicked.emit(str(item.data(0, self.D_LINE)))
|
||||
return
|
||||
|
||||
|
||||
class _PreviewWidget(QTextBrowser):
|
||||
@@ -787,8 +757,6 @@ class _PreviewWidget(QTextBrowser):
|
||||
self.ageTimer.timeout.connect(self._updateBuildAge)
|
||||
self.ageTimer.start()
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Setters
|
||||
##
|
||||
@@ -797,7 +765,6 @@ class _PreviewWidget(QTextBrowser):
|
||||
"""Set the build name for the document label."""
|
||||
self._buildName = name
|
||||
self._updateBuildAge()
|
||||
return
|
||||
|
||||
def setTextFont(self, font: QFont) -> None:
|
||||
"""Set the text font properties and then reset for sub-widgets.
|
||||
@@ -807,7 +774,6 @@ class _PreviewWidget(QTextBrowser):
|
||||
self.setFont(font)
|
||||
self.buildProgress.setFont(SHARED.theme.guiFont)
|
||||
self.ageLabel.setFont(SHARED.theme.guiFontSmall)
|
||||
return
|
||||
|
||||
##
|
||||
# Methods
|
||||
@@ -823,13 +789,11 @@ class _PreviewWidget(QTextBrowser):
|
||||
self._scrollPos = vBar.value()
|
||||
self.setPlaceholderText("")
|
||||
self.clear()
|
||||
return
|
||||
|
||||
def buildStep(self, value: int) -> None:
|
||||
"""Update the progress bar value."""
|
||||
self.buildProgress.setValue(value)
|
||||
QApplication.processEvents()
|
||||
return
|
||||
|
||||
def setContent(self, document: QTextDocument) -> None:
|
||||
"""Set the content of the preview widget."""
|
||||
@@ -850,8 +814,6 @@ class _PreviewWidget(QTextBrowser):
|
||||
QApplication.processEvents()
|
||||
QTimer.singleShot(300, self._postUpdate)
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Events
|
||||
##
|
||||
@@ -860,7 +822,6 @@ class _PreviewWidget(QTextBrowser):
|
||||
"""Capture resize and update the document margins."""
|
||||
super().resizeEvent(event)
|
||||
self._updateDocMargins()
|
||||
return
|
||||
|
||||
##
|
||||
# Public Slots
|
||||
@@ -874,14 +835,12 @@ class _PreviewWidget(QTextBrowser):
|
||||
printer.setPageOrientation(QPageLayout.Orientation.Portrait)
|
||||
document.print(printer)
|
||||
QApplication.restoreOverrideCursor()
|
||||
return
|
||||
|
||||
@pyqtSlot(str)
|
||||
def navigateTo(self, anchor: str) -> None:
|
||||
"""Go to a specific #link in the document."""
|
||||
logger.debug("Moving to anchor '#%s'", anchor)
|
||||
self.setSource(QUrl(f"#{anchor}"))
|
||||
return
|
||||
|
||||
##
|
||||
# Private Slots
|
||||
@@ -896,7 +855,6 @@ class _PreviewWidget(QTextBrowser):
|
||||
self.navigateTo(link.lstrip("#"))
|
||||
elif link.startswith("http"):
|
||||
QDesktopServices.openUrl(QUrl(url))
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _updateBuildAge(self) -> None:
|
||||
@@ -909,7 +867,6 @@ class _PreviewWidget(QTextBrowser):
|
||||
))
|
||||
else:
|
||||
self.ageLabel.setText("<b>{0}</b>".format(self.tr("No Preview")))
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _postUpdate(self) -> None:
|
||||
@@ -917,7 +874,6 @@ class _PreviewWidget(QTextBrowser):
|
||||
self.buildProgress.setVisible(False)
|
||||
if vBar := self.verticalScrollBar():
|
||||
vBar.setValue(self._scrollPos)
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
@@ -936,7 +892,6 @@ class _PreviewWidget(QTextBrowser):
|
||||
self.ageLabel.setGeometry(tB, tB, vW, tH)
|
||||
self.setViewportMargins(0, tH, 0, 0)
|
||||
self.buildProgress.move((vW-pS)//2, (vH-pS)//2)
|
||||
return
|
||||
|
||||
|
||||
class _StatsWidget(QWidget):
|
||||
@@ -969,8 +924,6 @@ class _StatsWidget(QWidget):
|
||||
|
||||
self._toggleView(False)
|
||||
|
||||
return
|
||||
|
||||
def updateStats(self, data: dict[str, int]) -> None:
|
||||
"""Update the stats values from a Tokenizer stats dict."""
|
||||
# Minimal
|
||||
@@ -992,8 +945,6 @@ class _StatsWidget(QWidget):
|
||||
self.maxHeadWordChars.setText(f"{data.get(nwStats.WCHARS_TITLE, 0):n}")
|
||||
self.maxTextWordChars.setText(f"{data.get(nwStats.WCHARS_TEXT, 0):n}")
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Private Slots
|
||||
##
|
||||
@@ -1013,7 +964,6 @@ class _StatsWidget(QWidget):
|
||||
self.minWidget.adjustSize()
|
||||
self.mainStack.adjustSize()
|
||||
self.adjustSize()
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
@@ -1105,5 +1055,3 @@ class _StatsWidget(QWidget):
|
||||
|
||||
self.minWidget.setLayout(self.minLayout)
|
||||
self.maxWidget.setLayout(self.maxLayout)
|
||||
|
||||
return
|
||||
|
||||
@@ -20,7 +20,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
@@ -62,7 +62,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GuiBuildSettings(NToolDialog):
|
||||
"""GUI Tools: Manuscript Build Settings Dialog
|
||||
"""GUI Tools: Manuscript Build Settings Dialog.
|
||||
|
||||
The main tool for configuring manuscript builds. It's a GUI tool for
|
||||
editing JSON build definitions, wrapped as a BuildSettings object.
|
||||
@@ -153,11 +153,8 @@ class GuiBuildSettings(NToolDialog):
|
||||
|
||||
logger.debug("Ready: GuiBuildSettings")
|
||||
|
||||
return
|
||||
|
||||
def __del__(self) -> None: # pragma: no cover
|
||||
logger.debug("Delete: GuiBuildSettings")
|
||||
return
|
||||
|
||||
def loadContent(self) -> None:
|
||||
"""Populate the child widgets."""
|
||||
@@ -165,7 +162,6 @@ class GuiBuildSettings(NToolDialog):
|
||||
self.optTabSelect.loadContent()
|
||||
self.optTabHeadings.loadContent()
|
||||
self.optTabFormatting.loadContent()
|
||||
return
|
||||
|
||||
##
|
||||
# Properties
|
||||
@@ -190,7 +186,6 @@ class GuiBuildSettings(NToolDialog):
|
||||
self._saveSettings()
|
||||
event.accept()
|
||||
self.softDelete()
|
||||
return
|
||||
|
||||
##
|
||||
# Private Slots
|
||||
@@ -206,7 +201,6 @@ class GuiBuildSettings(NToolDialog):
|
||||
elif pageId >= self.OPT_FORMATTING:
|
||||
self.toolStack.setCurrentWidget(self.optTabFormatting)
|
||||
self.optTabFormatting.scrollToSection(pageId)
|
||||
return
|
||||
|
||||
@pyqtSlot("QAbstractButton*")
|
||||
def _dialogButtonClicked(self, button: QAbstractButton) -> None:
|
||||
@@ -222,7 +216,6 @@ class GuiBuildSettings(NToolDialog):
|
||||
elif role == QtRoleReject:
|
||||
self._build.resetChangedState()
|
||||
self.close()
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
@@ -238,7 +231,6 @@ class GuiBuildSettings(NToolDialog):
|
||||
).format(self._build.name)):
|
||||
self._emitBuildData()
|
||||
self._build.resetChangedState()
|
||||
return
|
||||
|
||||
def _saveSettings(self) -> None:
|
||||
"""Save the various user settings."""
|
||||
@@ -250,20 +242,17 @@ class GuiBuildSettings(NToolDialog):
|
||||
pOptions.setValue("GuiBuildSettings", "treeWidth", treeWidth)
|
||||
pOptions.setValue("GuiBuildSettings", "filterWidth", filterWidth)
|
||||
pOptions.saveSettings()
|
||||
return
|
||||
|
||||
def _applyChanges(self) -> None:
|
||||
"""Apply all settings changes to the build object."""
|
||||
self._build.setName(self.editBuildName.text())
|
||||
self.optTabHeadings.saveContent()
|
||||
self.optTabFormatting.saveContent()
|
||||
return
|
||||
|
||||
def _emitBuildData(self) -> None:
|
||||
"""Assemble the build data and emit the signal."""
|
||||
self.newSettingsReady.emit(self._build)
|
||||
self._build.resetChangedState()
|
||||
return
|
||||
|
||||
|
||||
class _FilterTab(NFixedPage):
|
||||
@@ -382,13 +371,10 @@ class _FilterTab(NFixedPage):
|
||||
|
||||
self.setCentralWidget(self.mainSplit)
|
||||
|
||||
return
|
||||
|
||||
def loadContent(self) -> None:
|
||||
"""Populate the widgets."""
|
||||
self._populateTree()
|
||||
self._populateFilters()
|
||||
return
|
||||
|
||||
def mainSplitSizes(self) -> tuple[int, int]:
|
||||
"""Extract the sizes of the main splitter."""
|
||||
@@ -409,7 +395,6 @@ class _FilterTab(NFixedPage):
|
||||
elif key.startswith("root:"):
|
||||
self._build.setAllowRoot(key[5:], state)
|
||||
self._populateTree()
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
@@ -453,8 +438,6 @@ class _FilterTab(NFixedPage):
|
||||
|
||||
self._setTreeItemMode()
|
||||
|
||||
return
|
||||
|
||||
def _populateFilters(self) -> None:
|
||||
"""Populate the filter options switches."""
|
||||
self.filterOpt.clear()
|
||||
@@ -489,8 +472,6 @@ class _FilterTab(NFixedPage):
|
||||
default=self._build.isRootAllowed(tHandle)
|
||||
)
|
||||
|
||||
return
|
||||
|
||||
def _setSelectedMode(self, mode: int) -> None:
|
||||
"""Set the mode for the selected items."""
|
||||
items = self.optTree.selectedItems()
|
||||
@@ -511,8 +492,6 @@ class _FilterTab(NFixedPage):
|
||||
|
||||
self._setTreeItemMode()
|
||||
|
||||
return
|
||||
|
||||
def _setTreeItemMode(self) -> None:
|
||||
"""Update the filtered mode icon on all items."""
|
||||
filtered = self._build.buildItemFilter(SHARED.project)
|
||||
@@ -529,11 +508,10 @@ class _FilterTab(NFixedPage):
|
||||
item.setToolTip(self.C_STATUS, self._trIncluded)
|
||||
else:
|
||||
item.setIcon(self.C_STATUS, self._statusFlags[self.F_NONE])
|
||||
return
|
||||
|
||||
def _scanChildren(self, item: QTreeWidgetItem | None, items: list) -> list[QTreeWidgetItem]:
|
||||
"""This is a recursive function returning all items in a tree
|
||||
starting at a given QTreeWidgetItem.
|
||||
"""Recursively return all items in a tree starting at a given
|
||||
QTreeWidgetItem.
|
||||
"""
|
||||
if isinstance(item, QTreeWidgetItem):
|
||||
items.append(item)
|
||||
@@ -791,8 +769,6 @@ class _HeadingsTab(NScrollablePage):
|
||||
|
||||
self.setCentralLayout(self.outerBox)
|
||||
|
||||
return
|
||||
|
||||
def loadContent(self) -> None:
|
||||
"""Populate the widgets."""
|
||||
def fmtBreak(text: str) -> str:
|
||||
@@ -821,7 +797,6 @@ class _HeadingsTab(NScrollablePage):
|
||||
self.breakPart.setChecked(self._build.getBool("headings.breakPart"))
|
||||
self.breakChapter.setChecked(self._build.getBool("headings.breakChapter"))
|
||||
self.breakScene.setChecked(self._build.getBool("headings.breakScene"))
|
||||
return
|
||||
|
||||
def saveContent(self) -> None:
|
||||
"""Save choices back into build object."""
|
||||
@@ -841,7 +816,6 @@ class _HeadingsTab(NScrollablePage):
|
||||
self._build.setValue("headings.breakPart", self.breakPart.isChecked())
|
||||
self._build.setValue("headings.breakChapter", self.breakChapter.isChecked())
|
||||
self._build.setValue("headings.breakScene", self.breakScene.isChecked())
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
@@ -853,7 +827,6 @@ class _HeadingsTab(NScrollablePage):
|
||||
cursor = self.editTextBox.textCursor()
|
||||
cursor.insertText(text)
|
||||
self.editTextBox.setFocus()
|
||||
return
|
||||
|
||||
def _editHeading(self, heading: int) -> None:
|
||||
"""Populate the form with a specific heading format."""
|
||||
@@ -886,8 +859,6 @@ class _HeadingsTab(NScrollablePage):
|
||||
self.editTextBox.setPlainText(text.replace(nwUnicode.U_LBREAK, "\n"))
|
||||
self.lblEditForm.setText(self.tr("Editing: {0}").format(label))
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Private Slots
|
||||
##
|
||||
@@ -934,7 +905,6 @@ class _HeadingSyntaxHighlighter(QSyntaxHighlighter):
|
||||
self._fmtSymbol.setForeground(syntax.head)
|
||||
self._fmtFormat = QTextCharFormat()
|
||||
self._fmtFormat.setForeground(syntax.emph)
|
||||
return
|
||||
|
||||
def highlightBlock(self, text: str) -> None:
|
||||
"""Add syntax highlighting to the text block."""
|
||||
@@ -947,7 +917,6 @@ class _HeadingSyntaxHighlighter(QSyntaxHighlighter):
|
||||
ddots = heading.find(":")
|
||||
if ddots > 0:
|
||||
self.setFormat(pos + ddots, 1, self._fmtSymbol)
|
||||
return
|
||||
|
||||
|
||||
class _FormattingTab(NScrollableForm):
|
||||
@@ -961,8 +930,6 @@ class _FormattingTab(NScrollableForm):
|
||||
self.setHelpTextStyle(SHARED.theme.helpText)
|
||||
self.buildForm()
|
||||
|
||||
return
|
||||
|
||||
def buildForm(self) -> None:
|
||||
"""Build the formatting form."""
|
||||
section = 10
|
||||
@@ -1290,8 +1257,6 @@ class _FormattingTab(NScrollableForm):
|
||||
# Finalise
|
||||
self.finalise()
|
||||
|
||||
return
|
||||
|
||||
def loadContent(self) -> None:
|
||||
"""Populate the widgets."""
|
||||
# Text Content
|
||||
@@ -1391,8 +1356,6 @@ class _FormattingTab(NScrollableForm):
|
||||
self.htmlAddStyles.setChecked(self._build.getBool("html.addStyles"))
|
||||
self.htmlPreserveTabs.setChecked(self._build.getBool("html.preserveTabs"))
|
||||
|
||||
return
|
||||
|
||||
def saveContent(self) -> None:
|
||||
"""Save choices back into build object."""
|
||||
# Text Content
|
||||
@@ -1458,8 +1421,6 @@ class _FormattingTab(NScrollableForm):
|
||||
self._build.setValue("html.addStyles", self.htmlAddStyles.isChecked())
|
||||
self._build.setValue("html.preserveTabs", self.htmlPreserveTabs.isChecked())
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Private Slots
|
||||
##
|
||||
@@ -1472,11 +1433,10 @@ class _FormattingTab(NScrollableForm):
|
||||
self._textFont = fontMatcher(font)
|
||||
self.textFont.setText(describeFont(self._textFont))
|
||||
self.textFont.setCursorPosition(0)
|
||||
return
|
||||
|
||||
@pyqtSlot(int)
|
||||
def _changeUnit(self, index: int) -> None:
|
||||
"""The current unit change, so recalculate sizes."""
|
||||
"""Process current unit change to recalculate sizes."""
|
||||
newUnit = self.pageUnit.itemData(index)
|
||||
newScale = nwLabels.UNIT_SCALE.get(newUnit, 1.0)
|
||||
reScale = self._unitScale/newScale
|
||||
@@ -1531,11 +1491,9 @@ class _FormattingTab(NScrollableForm):
|
||||
self._unitScale = newScale
|
||||
self._changePageSize(self.pageSize.currentIndex())
|
||||
|
||||
return
|
||||
|
||||
@pyqtSlot(int)
|
||||
def _changePageSize(self, index: int) -> None:
|
||||
"""The page size has changed."""
|
||||
"""Process page size change."""
|
||||
w, h = nwLabels.PAPER_SIZE[self.pageSize.itemData(index)] if index >= 0 else (-1.0, -1.0)
|
||||
if w > 0.0 and h > 0.0:
|
||||
self.pageWidth.blockSignals(True)
|
||||
@@ -1544,23 +1502,20 @@ class _FormattingTab(NScrollableForm):
|
||||
self.pageHeight.blockSignals(True)
|
||||
self.pageHeight.setValue(h/self._unitScale)
|
||||
self.pageHeight.blockSignals(False)
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _pageSizeValueChanged(self) -> None:
|
||||
"""The user has changed the page size spin boxes, so we flip
|
||||
the page size box to Custom.
|
||||
"""Process that the user has changed the page size spin boxes,
|
||||
so we flip the page size box to Custom.
|
||||
"""
|
||||
index = self.pageSize.findData("Custom")
|
||||
if index >= 0:
|
||||
self.pageSize.setCurrentIndex(index)
|
||||
return
|
||||
|
||||
def _resetPageHeader(self) -> None:
|
||||
"""Reset the ODT header format to default."""
|
||||
self.odtPageHeader.setText(nwHeadFmt.DOC_AUTO)
|
||||
self.odtPageHeader.setCursorPosition(0)
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
@@ -1573,4 +1528,3 @@ class _FormattingTab(NScrollableForm):
|
||||
current.append(keyword)
|
||||
verified = set(x for x in current if x in nwKeyWords.VALID_KEYS)
|
||||
self.ignoredKeywords.setText(", ".join(verified))
|
||||
return
|
||||
|
||||
@@ -20,7 +20,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
@@ -52,6 +52,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GuiNovelDetails(NNonBlockingDialog):
|
||||
"""GUI: Novel Details Tool."""
|
||||
|
||||
PAGE_OVERVIEW = 1
|
||||
PAGE_CONTENTS = 2
|
||||
@@ -130,11 +131,8 @@ class GuiNovelDetails(NNonBlockingDialog):
|
||||
|
||||
logger.debug("Ready: GuiNovelDetails")
|
||||
|
||||
return
|
||||
|
||||
def __del__(self) -> None: # pragma: no cover
|
||||
logger.debug("Delete: GuiNovelDetails")
|
||||
return
|
||||
|
||||
##
|
||||
# Methods
|
||||
@@ -146,7 +144,6 @@ class GuiNovelDetails(NNonBlockingDialog):
|
||||
self.overviewPage.updateProjectData()
|
||||
self.overviewPage.novelValueChanged(handle)
|
||||
self.contentsPage.novelValueChanged(handle)
|
||||
return
|
||||
|
||||
##
|
||||
# Events
|
||||
@@ -157,7 +154,6 @@ class GuiNovelDetails(NNonBlockingDialog):
|
||||
self._saveSettings()
|
||||
event.accept()
|
||||
self.softDelete()
|
||||
return
|
||||
|
||||
##
|
||||
# Private Slots
|
||||
@@ -170,7 +166,6 @@ class GuiNovelDetails(NNonBlockingDialog):
|
||||
self.mainStack.setCurrentWidget(self.overviewPage)
|
||||
elif pageId == self.PAGE_CONTENTS:
|
||||
self.mainStack.setCurrentWidget(self.contentsPage)
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
@@ -185,7 +180,6 @@ class GuiNovelDetails(NNonBlockingDialog):
|
||||
options.setValue("GuiNovelDetails", "winHeight", self.height())
|
||||
options.setValue("GuiNovelDetails", "novelRoot", novelRoot)
|
||||
self.contentsPage.saveSettings()
|
||||
return
|
||||
|
||||
|
||||
class _OverviewPage(NScrollablePage):
|
||||
@@ -248,8 +242,6 @@ class _OverviewPage(NScrollablePage):
|
||||
|
||||
self.setCentralLayout(self.outerBox)
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Methods
|
||||
##
|
||||
@@ -266,7 +258,6 @@ class _OverviewPage(NScrollablePage):
|
||||
self.projWords.setText(f"{wcNovel + wcNotes:n}")
|
||||
self.projNovels.setText(f"{wcNovel:n}")
|
||||
self.projNotes.setText(f"{wcNotes:n}")
|
||||
return
|
||||
|
||||
##
|
||||
# Public Slots
|
||||
@@ -286,8 +277,6 @@ class _OverviewPage(NScrollablePage):
|
||||
self.novelChapters.setText(f"{hCounts[2]:n}")
|
||||
self.novelScenes.setText(f"{hCounts[3]:n}")
|
||||
|
||||
return
|
||||
|
||||
|
||||
class _ContentsPage(NFixedPage):
|
||||
|
||||
@@ -400,8 +389,6 @@ class _ContentsPage(NFixedPage):
|
||||
|
||||
self.setCentralLayout(self.outerBox)
|
||||
|
||||
return
|
||||
|
||||
def saveSettings(self) -> None:
|
||||
"""Save the user GUI settings."""
|
||||
options = SHARED.project.options
|
||||
@@ -413,7 +400,6 @@ class _ContentsPage(NFixedPage):
|
||||
options.setValue("GuiNovelDetails", "wordsPerPage", self.wpValue.value())
|
||||
options.setValue("GuiNovelDetails", "countFrom", self.poValue.value())
|
||||
options.setValue("GuiNovelDetails", "clearDouble", self.dblValue.isChecked())
|
||||
return
|
||||
|
||||
##
|
||||
# Public Slots
|
||||
@@ -426,7 +412,6 @@ class _ContentsPage(NFixedPage):
|
||||
self._prepareData(tHandle)
|
||||
self._populateTree()
|
||||
self._currentRoot = tHandle
|
||||
return
|
||||
|
||||
##
|
||||
# Private Slots
|
||||
@@ -496,8 +481,6 @@ class _ContentsPage(NFixedPage):
|
||||
|
||||
self.tocTree.addTopLevelItem(newItem)
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
##
|
||||
@@ -507,4 +490,3 @@ class _ContentsPage(NFixedPage):
|
||||
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
|
||||
|
||||
@@ -20,7 +20,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
@@ -56,6 +56,11 @@ PANEL_ALPHA = 178
|
||||
|
||||
|
||||
class GuiWelcome(NDialog):
|
||||
"""GUI: Welcome Dialog.
|
||||
|
||||
This is the main dialog shown when novelWriter launches or when the
|
||||
user wants to create or open another project.
|
||||
"""
|
||||
|
||||
openProjectRequest = pyqtSignal(Path)
|
||||
|
||||
@@ -161,11 +166,8 @@ class GuiWelcome(NDialog):
|
||||
|
||||
logger.debug("Ready: GuiWelcome")
|
||||
|
||||
return
|
||||
|
||||
def __del__(self) -> None: # pragma: no cover
|
||||
logger.debug("Delete: GuiWelcome")
|
||||
return
|
||||
|
||||
##
|
||||
# Events
|
||||
@@ -180,14 +182,12 @@ class GuiWelcome(NDialog):
|
||||
painter.drawPixmap(0, hWin - hPix, self.bgImage.scaledToHeight(hPix, tMode))
|
||||
painter.end()
|
||||
super().paintEvent(event)
|
||||
return
|
||||
|
||||
def closeEvent(self, event: QCloseEvent) -> None:
|
||||
"""Capture the user closing the window and save settings."""
|
||||
self._saveSettings()
|
||||
event.accept()
|
||||
self.softDelete()
|
||||
return
|
||||
|
||||
##
|
||||
# Private Slots
|
||||
@@ -199,28 +199,24 @@ class GuiWelcome(NDialog):
|
||||
self.mainStack.setCurrentWidget(self.tabNew)
|
||||
self._setButtonVisibility()
|
||||
self.tabNew.enterForm()
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _showOpenProjectPage(self) -> None:
|
||||
"""Show the open exiting project page."""
|
||||
self.mainStack.setCurrentWidget(self.tabOpen)
|
||||
self._setButtonVisibility()
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _browseForProject(self) -> None:
|
||||
"""Browse for a project to open."""
|
||||
if path := SHARED.getProjectPath(self, path=CONFIG.homePath(), allowZip=False):
|
||||
self._openProjectPath(path)
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _openSelectedItem(self) -> None:
|
||||
"""Open the currently selected project item."""
|
||||
if self.mainStack.currentWidget() == self.tabOpen:
|
||||
self.tabOpen.openSelectedItem()
|
||||
return
|
||||
|
||||
@pyqtSlot(Path)
|
||||
def _openProjectPath(self, path: Path) -> None:
|
||||
@@ -231,7 +227,6 @@ class GuiWelcome(NDialog):
|
||||
self.hide()
|
||||
self.openProjectRequest.emit(path)
|
||||
self.close()
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
@@ -241,7 +236,6 @@ class GuiWelcome(NDialog):
|
||||
"""Save the user GUI settings."""
|
||||
logger.debug("Saving State: GuiWelcome")
|
||||
CONFIG.setWelcomeWinSize(self.width(), self.height())
|
||||
return
|
||||
|
||||
def _setButtonVisibility(self) -> None:
|
||||
"""Change the visibility of the dialog buttons."""
|
||||
@@ -255,7 +249,6 @@ class GuiWelcome(NDialog):
|
||||
self.btnOpen.setFocus()
|
||||
else:
|
||||
self.btnCreate.setFocus()
|
||||
return
|
||||
|
||||
|
||||
class _OpenProjectPage(QWidget):
|
||||
@@ -308,8 +301,6 @@ class _OpenProjectPage(QWidget):
|
||||
f"QLineEdit {{border: none; background: {baseCol}; padding: 4px;}} "
|
||||
)
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Public Slots
|
||||
##
|
||||
@@ -319,7 +310,6 @@ class _OpenProjectPage(QWidget):
|
||||
"""Open the currently selected project item."""
|
||||
if (selection := self.listWidget.selectedIndexes()) and (index := selection[0]).isValid():
|
||||
self.openProjectRequest.emit(Path(str(index.data()[1])))
|
||||
return
|
||||
|
||||
##
|
||||
# Private Slots
|
||||
@@ -335,14 +325,12 @@ class _OpenProjectPage(QWidget):
|
||||
self.selectedPath.setToolTip(text)
|
||||
self.selectedPath.setCursorPosition(0)
|
||||
self.aMissing.setVisible(not (Path(value) / nwFiles.PROJ_FILE).is_file())
|
||||
return
|
||||
|
||||
@pyqtSlot(QModelIndex)
|
||||
def _projectDoubleClicked(self, index: QModelIndex) -> None:
|
||||
"""Process double click on project item."""
|
||||
if index.isValid():
|
||||
self.openProjectRequest.emit(Path(str(index.data()[1])))
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _deleteSelectedItem(self) -> None:
|
||||
@@ -355,7 +343,6 @@ class _OpenProjectPage(QWidget):
|
||||
if SHARED.question(text):
|
||||
self.listModel.removeEntry(index)
|
||||
self._selectFirstItem()
|
||||
return
|
||||
|
||||
@pyqtSlot("QPoint")
|
||||
def _openContextMenu(self, pos: QPoint) -> None:
|
||||
@@ -368,7 +355,6 @@ class _OpenProjectPage(QWidget):
|
||||
action.triggered.connect(self._deleteSelectedItem)
|
||||
ctxMenu.exec(self.mapToGlobal(pos))
|
||||
ctxMenu.setParent(None)
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
@@ -379,7 +365,6 @@ class _OpenProjectPage(QWidget):
|
||||
index = self.listModel.index(0)
|
||||
self.listWidget.setCurrentIndex(index)
|
||||
self._projectClicked(index)
|
||||
return
|
||||
|
||||
|
||||
class _ProjectListItem(QStyledItemDelegate):
|
||||
@@ -407,8 +392,6 @@ class _ProjectListItem(QStyledItemDelegate):
|
||||
|
||||
self._icon = SHARED.theme.getPixmap("proj_nwx", (iPx, iPx))
|
||||
|
||||
return
|
||||
|
||||
def paint(self, painter: QPainter, opt: QStyleOptionViewItem, index: QModelIndex) -> None:
|
||||
"""Paint a project entry on the canvas."""
|
||||
rect = opt.rect
|
||||
@@ -430,8 +413,6 @@ class _ProjectListItem(QStyledItemDelegate):
|
||||
painter.drawText(rect.adjusted(x, y, 0, 0), tFlag, details)
|
||||
painter.restore()
|
||||
|
||||
return
|
||||
|
||||
def sizeHint(self, opt: QStyleOptionViewItem, index: QModelIndex) -> QSize:
|
||||
"""Set the size hint to fixed height."""
|
||||
return QSize(opt.rect.width(), self._hPx)
|
||||
@@ -449,7 +430,6 @@ class _ProjectListModel(QAbstractListModel):
|
||||
when = CONFIG.localDate(datetime.fromtimestamp(time))
|
||||
data.append((title, path, f"{opened}: {when}, {words}: {formatInt(count)}"))
|
||||
self._data = data
|
||||
return
|
||||
|
||||
def rowCount(self, parent: QModelIndex | None = None) -> int:
|
||||
"""Return the size of the model."""
|
||||
@@ -516,8 +496,6 @@ class _NewProjectPage(QWidget):
|
||||
f"_NewProjectForm {{border: none; background: {baseCol};}} "
|
||||
)
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Public Slots
|
||||
##
|
||||
@@ -689,13 +667,10 @@ class _NewProjectForm(QWidget):
|
||||
self._updateProjPath()
|
||||
self._updateFillInfo()
|
||||
|
||||
return
|
||||
|
||||
def enterForm(self) -> None:
|
||||
"""Focus the project name field when entering the form."""
|
||||
self.projName.setFocus()
|
||||
self.projName.selectAll()
|
||||
return
|
||||
|
||||
def getProjectData(self) -> dict:
|
||||
"""Collect form data and return it as a dictionary."""
|
||||
@@ -733,14 +708,12 @@ class _NewProjectForm(QWidget):
|
||||
self._basePath = Path(path)
|
||||
self._updateProjPath()
|
||||
CONFIG.setLastPath("project", path)
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _updateProjPath(self) -> None:
|
||||
"""Update the path box to show the full project path."""
|
||||
projName = makeFileNameSafe(self.projName.text().strip())
|
||||
self.projPath.setText(str(self._basePath / projName))
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _syncSwitches(self) -> None:
|
||||
@@ -750,21 +723,18 @@ class _NewProjectForm(QWidget):
|
||||
addWorld = self.addWorld.isChecked()
|
||||
if not (addPlot or addChar or addWorld):
|
||||
self.addNotes.setChecked(False)
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _setFillBlank(self) -> None:
|
||||
"""Set fill mode to blank project."""
|
||||
self._fillMode = self.FILL_BLANK
|
||||
self._updateFillInfo()
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _setFillSample(self) -> None:
|
||||
"""Set fill mode to sample project."""
|
||||
self._fillMode = self.FILL_SAMPLE
|
||||
self._updateFillInfo()
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _setFillCopy(self) -> None:
|
||||
@@ -773,7 +743,6 @@ class _NewProjectForm(QWidget):
|
||||
self._fillMode = self.FILL_COPY
|
||||
self._copyPath = copyPath
|
||||
self._updateFillInfo()
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
@@ -793,5 +762,3 @@ class _NewProjectForm(QWidget):
|
||||
self.projFill.setToolTip(text)
|
||||
self.projFill.setCursorPosition(0)
|
||||
self.extraWidget.setVisible(self._fillMode == self.FILL_BLANK)
|
||||
|
||||
return
|
||||
|
||||
@@ -20,7 +20,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
@@ -54,7 +54,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GuiWritingStats(NToolDialog):
|
||||
"""GUI Tools: Writing Statistics
|
||||
"""GUI Tools: Writing Statistics.
|
||||
|
||||
Displays data from the NWSessionLog object.
|
||||
"""
|
||||
@@ -311,11 +311,8 @@ class GuiWritingStats(NToolDialog):
|
||||
|
||||
logger.debug("Ready: GuiWritingStats")
|
||||
|
||||
return
|
||||
|
||||
def __del__(self) -> None: # pragma: no cover
|
||||
logger.debug("Delete: GuiWritingStats")
|
||||
return
|
||||
|
||||
def populateGUI(self) -> None:
|
||||
"""Populate list box with data from the log file."""
|
||||
@@ -323,7 +320,6 @@ class GuiWritingStats(NToolDialog):
|
||||
self._loadLogFile()
|
||||
self._updateListBox()
|
||||
QApplication.restoreOverrideCursor()
|
||||
return
|
||||
|
||||
##
|
||||
# Events
|
||||
@@ -333,7 +329,6 @@ class GuiWritingStats(NToolDialog):
|
||||
"""Capture the user closing the window."""
|
||||
event.accept()
|
||||
self.softDelete()
|
||||
return
|
||||
|
||||
##
|
||||
# Private Slots
|
||||
@@ -377,8 +372,6 @@ class GuiWritingStats(NToolDialog):
|
||||
|
||||
self.close()
|
||||
|
||||
return
|
||||
|
||||
def _saveData(self, dataFmt: int) -> bool:
|
||||
"""Save the content of the list box to a file."""
|
||||
fileExt = ""
|
||||
@@ -498,8 +491,6 @@ class GuiWritingStats(NToolDialog):
|
||||
self.notesWords.setText(f"{ttNotes:n}")
|
||||
self.totalWords.setText(f"{ttWords:n}")
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Private Slots
|
||||
##
|
||||
@@ -622,5 +613,3 @@ class GuiWritingStats(NToolDialog):
|
||||
self.timeFilter += sDiff
|
||||
|
||||
self.labelFilter.setText(formatTime(round(self.timeFilter)))
|
||||
|
||||
return
|
||||
|
||||
@@ -20,7 +20,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
from PyQt6.QtCore import Qt
|
||||
|
||||
+1
-8
@@ -23,7 +23,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
@@ -50,7 +50,6 @@ OS_WIN = sys.platform.startswith("win32")
|
||||
def printVersion(args: argparse.Namespace) -> None:
|
||||
"""Print the novelWriter version and exit."""
|
||||
print(extractVersion(beQuiet=True)[0], end=None)
|
||||
return
|
||||
|
||||
|
||||
def installPackages(args: argparse.Namespace) -> None:
|
||||
@@ -79,8 +78,6 @@ def installPackages(args: argparse.Namespace) -> None:
|
||||
print(str(exc))
|
||||
sys.exit(1)
|
||||
|
||||
return
|
||||
|
||||
|
||||
def cleanBuildDirs(args: argparse.Namespace) -> None:
|
||||
"""Recursively delete the 'build' and 'dist' folders."""
|
||||
@@ -111,8 +108,6 @@ def cleanBuildDirs(args: argparse.Namespace) -> None:
|
||||
|
||||
print("")
|
||||
|
||||
return
|
||||
|
||||
|
||||
def genMacOSPlist(args: argparse.Namespace) -> None:
|
||||
"""Set necessary values for .plist file for MacOS build."""
|
||||
@@ -134,8 +129,6 @@ def genMacOSPlist(args: argparse.Namespace) -> None:
|
||||
print(f"Writing Info.plist to {outDir}/Info.plist")
|
||||
writeFile(outDir / "Info.plist", plistXML)
|
||||
|
||||
return
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
"""Parse command line options and run the commands."""
|
||||
|
||||
+39
-19
@@ -66,26 +66,34 @@ preview = true
|
||||
|
||||
# Rules: https://docs.astral.sh/ruff/rules
|
||||
select = [
|
||||
"A", # flake8-builtins (A)
|
||||
"ANN", # flake8-annotations (ANN)
|
||||
"B", # flake8-bugbear (B)
|
||||
"E", # pycodestyle (E)
|
||||
"F", # Pyflakes (F)
|
||||
"FA", # flake8-future-annotations (FA)
|
||||
"PERF", # Perflint (PERF)
|
||||
"PLC", # Pylint Convention (PLC)
|
||||
"PLE", # Pylint Error (PLE)
|
||||
"PLW", # Pylint Warning (PLW)
|
||||
"Q", # flake8-quotes (Q)
|
||||
"RUF", # Ruff-specific rules (RUF)
|
||||
"SLF", # flake8-self (SLF)
|
||||
"SLOT", # flake8-slots (SLOT)
|
||||
"TC", # flake8-type-checking (TC)
|
||||
"UP", # pyupgrade (UP)
|
||||
"W", # pycodestyle (W)
|
||||
"A", # flake8-builtins (A)
|
||||
"ANN", # flake8-annotations (ANN)
|
||||
"B", # flake8-bugbear (B)
|
||||
"D", # pydocstyle (D)
|
||||
"E", # pycodestyle (E)
|
||||
"F", # Pyflakes (F)
|
||||
"FA", # flake8-future-annotations (FA)
|
||||
"PERF", # Perflint (PERF)
|
||||
"PLC", # Pylint Convention (PLC)
|
||||
"PLE", # Pylint Error (PLE)
|
||||
"PLR17", # Refactor (PLR) - Only PLR17xx
|
||||
"PLW", # Pylint Warning (PLW)
|
||||
"Q", # flake8-quotes (Q)
|
||||
"RET", # flake8-return (RET)
|
||||
"RUF", # Ruff-specific rules (RUF)
|
||||
"SLF", # flake8-self (SLF)
|
||||
"SLOT", # flake8-slots (SLOT)
|
||||
"TC", # flake8-type-checking (TC)
|
||||
"UP", # pyupgrade (UP)
|
||||
"W", # pycodestyle (W)
|
||||
]
|
||||
ignore = [
|
||||
"ANN401", # any-type
|
||||
"D105", # undocumented-magic-method
|
||||
"D107", # undocumented-public-init
|
||||
"D203", # incorrect-blank-line-before-class
|
||||
"D205", # missing-blank-line-after-summary
|
||||
"D213", # multi-line-summary-second-line
|
||||
"E221", # multiple-spaces-before-operator
|
||||
"E226", # missing-whitespace-around-arithmetic-operator
|
||||
"E228", # missing-whitespace-around-modulo-operator
|
||||
@@ -95,6 +103,7 @@ ignore = [
|
||||
"PLC1901", # compare-to-empty-string
|
||||
"PLW0108", # unnecessary-lambda
|
||||
"PLW2901", # redefined-loop-name
|
||||
"RET505", # superfluous-else-return
|
||||
"RUF001", # ambiguous-unicode-character-string
|
||||
"RUF002", # ambiguous-unicode-character-docstring
|
||||
"RUF015", # unnecessary-iterable-allocation-for-first-element
|
||||
@@ -103,9 +112,21 @@ ignore = [
|
||||
]
|
||||
|
||||
[tool.ruff.lint.per-file-ignores]
|
||||
"tests/*" = ["ANN", "SLF", "TC", "PLC2701"]
|
||||
"tests/*" = ["ANN", "SLF", "TC", "PLC2701", "D101", "D102"]
|
||||
"utils/*" = ["ANN", "SLF", "TC"]
|
||||
|
||||
[tool.ruff.lint.pydocstyle]
|
||||
ignore-decorators = [
|
||||
"abc.abstractmethod",
|
||||
"property",
|
||||
"PyQt6.QtCore.pyqtProperty",
|
||||
"typing.overload",
|
||||
"pytest.fixture",
|
||||
]
|
||||
|
||||
[tool.ruff.lint.pylint]
|
||||
max-nested-blocks = 10
|
||||
|
||||
[tool.ruff.format]
|
||||
quote-style = "double"
|
||||
|
||||
@@ -114,7 +135,6 @@ include = ["novelwriter"]
|
||||
exclude = ["**/__pycache__"]
|
||||
|
||||
reportIncompatibleMethodOverride = false
|
||||
|
||||
pythonVersion = "3.10"
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
|
||||
+2
-10
@@ -17,7 +17,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
@@ -70,8 +70,6 @@ def resetConfigVars():
|
||||
CONFIG.emphLabels = True
|
||||
CONFIG.lineHighlight = True
|
||||
|
||||
return
|
||||
|
||||
|
||||
##
|
||||
# Auto Fixtures
|
||||
@@ -88,7 +86,6 @@ def sessionFixture():
|
||||
(_TMP_ROOT / "manual.pdf").touch()
|
||||
(_SRC_ROOT / "novelwriter" / "assets"/ "manual.pdf").touch()
|
||||
(_SRC_ROOT / "novelwriter" / "assets"/ "manual_fr.pdf").touch()
|
||||
return
|
||||
|
||||
|
||||
@pytest.fixture(scope="function", autouse=True)
|
||||
@@ -107,8 +104,6 @@ def functionFixture(qtbot):
|
||||
resetConfigVars()
|
||||
logging.getLogger("novelwriter").setLevel(logging.INFO)
|
||||
|
||||
return
|
||||
|
||||
|
||||
##
|
||||
# Core Test Folders
|
||||
@@ -251,13 +246,11 @@ def prjLipsum():
|
||||
if dstDir.exists():
|
||||
shutil.rmtree(dstDir)
|
||||
|
||||
return
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def ipsumText():
|
||||
"""Return five paragraphs of Lorem Ipsum text."""
|
||||
thatIpsum = [(
|
||||
return [(
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc maximus justo non dictum co"
|
||||
"mmodo. Curabitur lacinia tempor orci vel luctus. Phasellus porta metus eu massa luctus, e"
|
||||
"get euismod risus rhoncus. Vestibulum sed arcu nisi. Maecenas pretium facilisis velit, ve"
|
||||
@@ -305,4 +298,3 @@ def ipsumText():
|
||||
" a nisl. Etiam eget tristique dui. Nulla sed mi finibus, venenatis tellus non, maximus en"
|
||||
"im."
|
||||
)]
|
||||
return thatIpsum
|
||||
|
||||
+3
-4
@@ -17,7 +17,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
from unittest.mock import MagicMock
|
||||
@@ -35,7 +35,6 @@ class MockGuiMain(QWidget):
|
||||
self.docViewer = MagicMock()
|
||||
self.mainProgress = MagicMock()
|
||||
self.projPath = ""
|
||||
return
|
||||
|
||||
def postLaunchTasks(self, cmdOpen):
|
||||
return
|
||||
@@ -45,7 +44,6 @@ class MockGuiMain(QWidget):
|
||||
|
||||
def openProject(self, projPath):
|
||||
self.projPath = projPath
|
||||
return
|
||||
|
||||
def rebuildIndex(self):
|
||||
return
|
||||
@@ -64,7 +62,6 @@ class MockTheme:
|
||||
self.guiFont = QFont()
|
||||
self.guiFontB = QFont()
|
||||
self.guiFontBU = QFont()
|
||||
return
|
||||
|
||||
def initThemes(self) -> None:
|
||||
return
|
||||
@@ -96,8 +93,10 @@ class MockApp:
|
||||
# Mock functions that will raise errors instead.
|
||||
|
||||
def causeOSError(*args, **kwargs):
|
||||
"""Raise an OSError."""
|
||||
raise OSError("Mock OSError")
|
||||
|
||||
|
||||
def causeException(*args, **kwargs):
|
||||
"""Raise an Exception."""
|
||||
raise Exception("Mock Exception")
|
||||
|
||||
@@ -17,7 +17,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
@@ -794,7 +794,6 @@ def testBaseCommon_openExternalPath(monkeypatch, tstPaths):
|
||||
def mockOpenUrl(url: QUrl) -> None:
|
||||
nonlocal lastUrl
|
||||
lastUrl = url.toString()
|
||||
return
|
||||
|
||||
monkeypatch.setattr(QDesktopServices, "openUrl", mockOpenUrl)
|
||||
assert openExternalPath(Path("/foo/bar")) is False
|
||||
|
||||
@@ -17,7 +17,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
|
||||
@@ -17,7 +17,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
|
||||
@@ -17,7 +17,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
@@ -40,7 +40,7 @@ from tests.tools import clearLogHandlers
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseInit_Launch(caplog, monkeypatch, fncPath):
|
||||
"""Check launching the main GUI. This test """
|
||||
"""Check launching the main GUI."""
|
||||
monkeypatch.setattr(NSplashScreen, "finish", lambda *a: None)
|
||||
monkeypatch.setattr("novelwriter.splash.sleep", lambda *a: None)
|
||||
monkeypatch.setattr("novelwriter._createApp", lambda *a: Mock())
|
||||
|
||||
@@ -17,7 +17,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
@@ -17,7 +17,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
@@ -39,7 +39,7 @@ from tests.tools import C, buildTestProject
|
||||
|
||||
|
||||
def isUUID(value):
|
||||
"""Checks if a value is a valid UUID object."""
|
||||
"""Check if a value is a valid UUID object."""
|
||||
try:
|
||||
uuid.UUID(value)
|
||||
return True
|
||||
|
||||
@@ -17,7 +17,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import shutil
|
||||
@@ -669,7 +669,6 @@ def testCoreTools_ProjectBuilderCopyPlain(monkeypatch, caplog, mockGUI, prjLipsu
|
||||
@pytest.mark.core
|
||||
def testCoreTools_ProjectBuilderCopyZipped(monkeypatch, caplog, mockGUI, fncPath, mockRnd):
|
||||
"""Create a new project copied from existing zipped project."""
|
||||
|
||||
# Create a project
|
||||
origPath = fncPath / "original"
|
||||
srcProject = NWProject()
|
||||
|
||||
@@ -17,7 +17,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
@@ -17,7 +17,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -17,7 +17,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
@@ -17,7 +17,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -17,7 +17,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
@@ -491,8 +491,7 @@ def testCoreItem_LayoutSetter(mockGUI):
|
||||
|
||||
@pytest.mark.core
|
||||
def testCoreItem_ClassDefaults(mockGUI):
|
||||
"""Test the setter for the default values.
|
||||
"""
|
||||
"""Test the setter for the default values."""
|
||||
project = NWProject()
|
||||
item = NWItem(project, "0000000000000")
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -17,7 +17,7 @@ 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/>.
|
||||
"""
|
||||
""" # noqa
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user