Add more linting rules (#2287)

This commit is contained in:
Veronica Berglyd Olsen
2025-04-07 17:35:20 +02:00
committed by GitHub
86 changed files with 545 additions and 438 deletions
+6 -2
View File
@@ -23,11 +23,15 @@ jobs:
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Install Dependencies - name: Install Dependencies
run: pip install -r requirements.txt -r requirements-dev.txt run: pip install -r requirements.txt -r requirements-dev.txt
- name: Run Ruff - name: Ruff Check
run: | run: |
ruff --version ruff --version
ruff check ruff check
- name: Run Pyright - name: Pyright Check
run: | run: |
pyright --version pyright --version
pyright pyright
- name: Isort Check
run: |
isort --version
isort --check .
+1 -1
View File
@@ -24,7 +24,7 @@ initFile = os.path.join(
os.path.dirname(__file__), os.pardir, os.pardir, os.path.dirname(__file__), os.pardir, os.pardir,
"novelwriter", "__init__.py" "novelwriter", "__init__.py"
) )
with open(initFile) as inFile: with open(initFile, encoding="utf-8") as inFile:
for aLine in inFile: for aLine in inFile:
if aLine.startswith("__version__"): if aLine.startswith("__version__"):
release = aLine.split('"')[1].strip() release = aLine.split('"')[1].strip()
+2 -2
View File
@@ -7,8 +7,8 @@ import os
import sys import sys
try: try:
import PyQt6.QtCore # noqa: F401 import PyQt6.QtCore
import PyQt6.QtGui # noqa: F401 import PyQt6.QtGui
import PyQt6.QtWidgets # noqa: F401 import PyQt6.QtWidgets # noqa: F401
except Exception: except Exception:
print("ERROR: Failed to load dependency PyQt6") print("ERROR: Failed to load dependency PyQt6")
+2 -2
View File
@@ -36,7 +36,7 @@ from novelwriter.config import Config
from novelwriter.error import exceptionHandler from novelwriter.error import exceptionHandler
from novelwriter.shared import SharedData from novelwriter.shared import SharedData
if TYPE_CHECKING: # pragma: no cover if TYPE_CHECKING:
from novelwriter.guimain import GuiMain from novelwriter.guimain import GuiMain
# Package Meta # Package Meta
@@ -148,7 +148,7 @@ def main(sysArgs: list | None = None) -> GuiMain | None:
inOpts, inRemain = getopt.getopt(sysArgs, shortOpt, longOpt) inOpts, inRemain = getopt.getopt(sysArgs, shortOpt, longOpt)
except getopt.GetoptError as exc: except getopt.GetoptError as exc:
print(helpMsg) print(helpMsg)
print(f"ERROR: {str(exc)}") print(f"ERROR: {exc!s}")
sys.exit(2) sys.exit(2)
if len(inRemain) > 0: if len(inRemain) > 0:
+11 -9
View File
@@ -29,11 +29,10 @@ import unicodedata
import uuid import uuid
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
from collections.abc import Callable
from configparser import ConfigParser from configparser import ConfigParser
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
from typing import Any, Literal, TypeGuard, TypeVar from typing import TYPE_CHECKING, Any, Literal, TypeGuard, TypeVar
from urllib.parse import urljoin from urllib.parse import urljoin
from urllib.request import pathname2url from urllib.request import pathname2url
@@ -45,6 +44,9 @@ from novelwriter.constants import nwConst, nwLabels, nwUnicode, trConst
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
from novelwriter.error import logException from novelwriter.error import logException
if TYPE_CHECKING:
from collections.abc import Callable
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
_Type = TypeVar("_Type") _Type = TypeVar("_Type")
@@ -347,7 +349,7 @@ def fuzzyTime(seconds: int) -> str:
elif seconds < 3300: # 55 minutes elif seconds < 3300: # 55 minutes
return QCoreApplication.translate( return QCoreApplication.translate(
"Common", "{0} minutes ago" "Common", "{0} minutes ago"
).format(int(round(seconds/60))) ).format(round(seconds/60))
elif seconds < 5400: # 90 minutes elif seconds < 5400: # 90 minutes
return QCoreApplication.translate( return QCoreApplication.translate(
"Common", "an hour ago" "Common", "an hour ago"
@@ -355,7 +357,7 @@ def fuzzyTime(seconds: int) -> str:
elif seconds < 84600: # 23.5 hours elif seconds < 84600: # 23.5 hours
return QCoreApplication.translate( return QCoreApplication.translate(
"Common", "{0} hours ago" "Common", "{0} hours ago"
).format(int(round(seconds/3600))) ).format(round(seconds/3600))
elif seconds < 129600: # 1.5 days elif seconds < 129600: # 1.5 days
return QCoreApplication.translate( return QCoreApplication.translate(
"Common", "a day ago" "Common", "a day ago"
@@ -363,7 +365,7 @@ def fuzzyTime(seconds: int) -> str:
elif seconds < 561600: # 6.5 days elif seconds < 561600: # 6.5 days
return QCoreApplication.translate( return QCoreApplication.translate(
"Common", "{0} days ago" "Common", "{0} days ago"
).format(int(round(seconds/86400))) ).format(round(seconds/86400))
elif seconds < 907200: # 10.5 days elif seconds < 907200: # 10.5 days
return QCoreApplication.translate( return QCoreApplication.translate(
"Common", "a week ago" "Common", "a week ago"
@@ -371,7 +373,7 @@ def fuzzyTime(seconds: int) -> str:
elif seconds < 2419200: # 28 days elif seconds < 2419200: # 28 days
return QCoreApplication.translate( return QCoreApplication.translate(
"Common", "{0} weeks ago" "Common", "{0} weeks ago"
).format(int(round(seconds/604800))) ).format(round(seconds/604800))
elif seconds < 3888000: # 45 days elif seconds < 3888000: # 45 days
return QCoreApplication.translate( return QCoreApplication.translate(
"Common", "a month ago" "Common", "a month ago"
@@ -379,7 +381,7 @@ def fuzzyTime(seconds: int) -> str:
elif seconds < 29808000: # 345 days elif seconds < 29808000: # 345 days
return QCoreApplication.translate( return QCoreApplication.translate(
"Common", "{0} months ago" "Common", "{0} months ago"
).format(int(round(seconds/2592000))) ).format(round(seconds/2592000))
elif seconds < 47336400: # 1.5 years elif seconds < 47336400: # 1.5 years
return QCoreApplication.translate( return QCoreApplication.translate(
"Common", "a year ago" "Common", "a year ago"
@@ -387,7 +389,7 @@ def fuzzyTime(seconds: int) -> str:
else: else:
return QCoreApplication.translate( return QCoreApplication.translate(
"Common", "{0} years ago" "Common", "{0} years ago"
).format(int(round(seconds/31557600))) ).format(round(seconds/31557600))
def numberToRoman(value: int, toLower: bool = False) -> str: def numberToRoman(value: int, toLower: bool = False) -> str:
@@ -423,7 +425,7 @@ def describeFont(font: QFont) -> str:
info = QFontInfo(font) info = QFontInfo(font)
family = info.family() family = info.family()
styles = [v for v in info.styleName().split() if v not in family] styles = [v for v in info.styleName().split() if v not in family]
return " ".join([f"{info.pointSize()} pt", family] + styles) return " ".join([f"{info.pointSize()} pt", family, *styles])
return "Error" return "Error"
+30 -27
View File
@@ -29,10 +29,9 @@ import json
import logging import logging
import sys import sys
from datetime import datetime
from pathlib import Path from pathlib import Path
from time import time from time import time
from typing import TYPE_CHECKING from typing import TYPE_CHECKING, Final
from PyQt6.QtCore import ( from PyQt6.QtCore import (
PYQT_VERSION, PYQT_VERSION_STR, QT_VERSION, QT_VERSION_STR, QLibraryInfo, PYQT_VERSION, PYQT_VERSION_STR, QT_VERSION, QT_VERSION_STR, QLibraryInfo,
@@ -48,7 +47,9 @@ from novelwriter.common import (
from novelwriter.constants import nwFiles, nwUnicode from novelwriter.constants import nwFiles, nwUnicode
from novelwriter.error import formatException, logException from novelwriter.error import formatException, logException
if TYPE_CHECKING: # pragma: no cover if TYPE_CHECKING:
from datetime import datetime
from novelwriter.core.projectdata import NWProjectData from novelwriter.core.projectdata import NWProjectData
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -62,29 +63,27 @@ DEF_TREECOL = "theme"
class Config: class Config:
__slots__ = ( __slots__ = (
"_confPath", "_dataPath", "_homePath", "_backPath", "_appPath", "_appRoot", "_hasError", "_appPath", "_appRoot", "_backPath", "_backupPath", "_confPath", "_dLocale", "_dShortDate",
"_errData", "_nwLangPath", "_qtLangPath", "_qLocale", "_dLocale", "_dShortDate", "_dShortDateTime", "_dataPath", "_errData", "_hasError", "_homePath", "_manuals",
"_dShortDateTime", "_qtTrans", "_manuals", "_recentProjects", "_recentPaths", "_nwLangPath", "_qLocale", "_qtLangPath", "_qtTrans", "_recentPaths", "_recentProjects",
"_backupPath", "allowOpenDial", "altDialogClose", "altDialogOpen", "appHandle", "appName",
"askBeforeBackup", "askBeforeExit", "autoSaveDoc", "autoSaveProj", "autoScroll",
"appName", "appHandle", "guiLocale", "guiTheme", "guiSyntax", "guiFont", "hideVScroll", "autoScrollPos", "autoSelect", "backupOnClose", "cursorWidth", "dialogLine", "dialogStyle",
"hideHScroll", "lastNotes", "nativeFont", "useCharCount", "iconTheme", "iconColTree", "doJustify", "doReplace", "doReplaceDQuote", "doReplaceDash", "doReplaceDots",
"iconColDocs", "mainWinSize", "welcomeWinSize", "prefsWinSize", "mainPanePos", "doReplaceSQuote", "emphLabels", "fmtApostrophe", "fmtDQuoteClose", "fmtDQuoteOpen",
"viewPanePos", "outlinePanePos", "autoSaveProj", "autoSaveDoc", "emphLabels", "fmtPadAfter", "fmtPadBefore", "fmtPadThin", "fmtSQuoteClose", "fmtSQuoteOpen",
"backupOnClose", "askBeforeBackup", "askBeforeExit", "textFont", "textWidth", "textMargin", "focusWidth", "guiFont", "guiLocale", "guiSyntax", "guiTheme", "hasEnchant",
"tabWidth", "cursorWidth", "focusWidth", "hideFocusFooter", "showFullPath", "autoSelect", "hideFocusFooter", "hideHScroll", "hideVScroll", "highlightEmph", "hostName",
"doJustify", "showTabsNSpaces", "showLineEndings", "showMultiSpaces", "doReplace", "iconColDocs", "iconColTree", "iconTheme", "incNotesWCount", "isDebug", "kernelVer",
"doReplaceSQuote", "doReplaceDQuote", "doReplaceDash", "doReplaceDots", "autoScroll", "lastNotes", "mainPanePos", "mainWinSize", "memInfo", "narratorBreak", "narratorDialog",
"autoScrollPos", "scrollPastEnd", "dialogStyle", "allowOpenDial", "dialogLine", "nativeFont", "osDarwin", "osLinux", "osType", "osUnknown", "osWindows", "outlinePanePos",
"narratorBreak", "narratorDialog", "altDialogOpen", "altDialogClose", "highlightEmph", "prefsWinSize", "scrollPastEnd", "searchCase", "searchLoop", "searchMatchCap",
"stopWhenIdle", "userIdleTime", "incNotesWCount", "fmtApostrophe", "fmtSQuoteOpen", "searchNextFile", "searchProjCase", "searchProjRegEx", "searchProjWord", "searchRegEx",
"fmtSQuoteClose", "fmtDQuoteOpen", "fmtDQuoteClose", "fmtPadBefore", "fmtPadAfter", "searchWord", "showEditToolBar", "showFullPath", "showLineEndings", "showMultiSpaces",
"fmtPadThin", "spellLanguage", "showViewerPanel", "showEditToolBar", "showSessionTime", "showSessionTime", "showTabsNSpaces", "showViewerPanel", "spellLanguage", "stopWhenIdle",
"viewComments", "viewSynopsis", "searchCase", "searchWord", "searchRegEx", "searchLoop", "tabWidth", "textFont", "textMargin", "textWidth", "useCharCount", "userIdleTime",
"searchNextFile", "searchMatchCap", "searchProjCase", "searchProjWord", "searchProjRegEx", "verPyQtString", "verPyQtValue", "verPyString", "verQtString", "verQtValue",
"verQtString", "verQtValue", "verPyQtString", "verPyQtValue", "verPyString", "osType", "viewComments", "viewPanePos", "viewSynopsis", "welcomeWinSize",
"osLinux", "osWindows", "osDarwin", "osUnknown", "hostName", "kernelVer", "isDebug",
"memInfo", "hasEnchant",
) )
LANG_NW = 1 LANG_NW = 1
@@ -306,6 +305,10 @@ class Config:
def pdfDocs(self) -> Path | None: def pdfDocs(self) -> Path | None:
return self._manuals.get(f"manual_{self.locale.name()}", self._manuals.get("manual")) return self._manuals.get(f"manual_{self.locale.name()}", self._manuals.get("manual"))
@property
def nwLangPath(self) -> Path:
return self._nwLangPath
@property @property
def locale(self) -> QLocale: def locale(self) -> QLocale:
return self._dLocale return self._dLocale
@@ -918,7 +921,7 @@ class RecentProjects:
class RecentPaths: class RecentPaths:
KEYS = ["default", "project", "import", "outline", "stats"] KEYS: Final[list[str]] = ["default", "project", "import", "outline", "stats"]
def __init__(self, config: Config) -> None: def __init__(self, config: Config) -> None:
self._conf = config self._conf = config
+37 -35
View File
@@ -23,6 +23,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
from __future__ import annotations from __future__ import annotations
from typing import Final
from PyQt6.QtCore import QT_TRANSLATE_NOOP, QCoreApplication from PyQt6.QtCore import QT_TRANSLATE_NOOP, QCoreApplication
from novelwriter.enum import ( from novelwriter.enum import (
@@ -99,7 +101,7 @@ class nwShortcode:
FOOTNOTE_B = "[footnote:" FOOTNOTE_B = "[footnote:"
FIELD_B = "[field:" FIELD_B = "[field:"
COMMENT_STYLES = { COMMENT_STYLES: Final[dict[nwComment, str]] = {
nwComment.FOOTNOTE: "[footnote:{0}]", nwComment.FOOTNOTE: "[footnote:{0}]",
nwComment.COMMENT: "[comment:{0}]", nwComment.COMMENT: "[comment:{0}]",
} }
@@ -110,13 +112,13 @@ class nwShortcode:
class nwStyles: class nwStyles:
H_VALID = ("H0", "H1", "H2", "H3", "H4") H_VALID = ("H0", "H1", "H2", "H3", "H4")
H_LEVEL = {"H0": 0, "H1": 1, "H2": 2, "H3": 3, "H4": 4} H_LEVEL: Final[dict[str, int]] = {"H0": 0, "H1": 1, "H2": 2, "H3": 3, "H4": 4}
H_SIZES = {0: 2.50, 1: 2.00, 2: 1.75, 3: 1.50, 4: 1.25} H_SIZES: Final[dict[int, float]] = {0: 2.50, 1: 2.00, 2: 1.75, 3: 1.50, 4: 1.25}
T_NORMAL = 1.0 T_NORMAL = 1.0
T_SMALL = 0.8 T_SMALL = 0.8
T_LABEL = { T_LABEL: Final[dict[str, str]] = {
"H0": QT_TRANSLATE_NOOP("Constant", "Title"), "H0": QT_TRANSLATE_NOOP("Constant", "Title"),
"H1": QT_TRANSLATE_NOOP("Constant", "Heading 1 (Partition)"), "H1": QT_TRANSLATE_NOOP("Constant", "Heading 1 (Partition)"),
"H2": QT_TRANSLATE_NOOP("Constant", "Heading 2 (Chapter)"), "H2": QT_TRANSLATE_NOOP("Constant", "Heading 2 (Chapter)"),
@@ -125,7 +127,7 @@ class nwStyles:
"TT": QT_TRANSLATE_NOOP("Constant", "Text Paragraph"), "TT": QT_TRANSLATE_NOOP("Constant", "Text Paragraph"),
"SP": QT_TRANSLATE_NOOP("Constant", "Scene Separator"), "SP": QT_TRANSLATE_NOOP("Constant", "Scene Separator"),
} }
T_MARGIN = { T_MARGIN: Final[dict[str, tuple[float, float]]] = {
"H0": (1.50, 0.60), # Title margins (top, bottom) "H0": (1.50, 0.60), # Title margins (top, bottom)
"H1": (1.50, 0.60), # Heading 1 margins (top, bottom) "H1": (1.50, 0.60), # Heading 1 margins (top, bottom)
"H2": (1.50, 0.60), # Heading 2 margins (top, bottom) "H2": (1.50, 0.60), # Heading 2 margins (top, bottom)
@@ -174,20 +176,20 @@ class nwKeyWords:
MENTION_KEY = "@mention" MENTION_KEY = "@mention"
# Note: The order here affects the order of menu entries # Note: The order here affects the order of menu entries
ALL_KEYS = [ ALL_KEYS: Final[list[str]] = [
TAG_KEY, POV_KEY, FOCUS_KEY, CHAR_KEY, PLOT_KEY, TIME_KEY, WORLD_KEY, TAG_KEY, POV_KEY, FOCUS_KEY, CHAR_KEY, PLOT_KEY, TIME_KEY, WORLD_KEY,
OBJECT_KEY, ENTITY_KEY, CUSTOM_KEY, STORY_KEY, MENTION_KEY, OBJECT_KEY, ENTITY_KEY, CUSTOM_KEY, STORY_KEY, MENTION_KEY,
] ]
CAN_CREATE = [ CAN_CREATE: Final[list[str]] = [
POV_KEY, FOCUS_KEY, CHAR_KEY, PLOT_KEY, TIME_KEY, WORLD_KEY, POV_KEY, FOCUS_KEY, CHAR_KEY, PLOT_KEY, TIME_KEY, WORLD_KEY,
OBJECT_KEY, ENTITY_KEY, CUSTOM_KEY, OBJECT_KEY, ENTITY_KEY, CUSTOM_KEY,
] ]
# Set of Valid Keys # Set of Valid Keys
VALID_KEYS = set(ALL_KEYS) VALID_KEYS: Final[set[str]] = set(ALL_KEYS)
# Map from Keys to Item Class # Map from Keys to Item Class
KEY_CLASS = { KEY_CLASS: Final[dict[str, nwItemClass]] = {
POV_KEY: nwItemClass.CHARACTER, POV_KEY: nwItemClass.CHARACTER,
FOCUS_KEY: nwItemClass.CHARACTER, FOCUS_KEY: nwItemClass.CHARACTER,
CHAR_KEY: nwItemClass.CHARACTER, CHAR_KEY: nwItemClass.CHARACTER,
@@ -203,7 +205,7 @@ class nwKeyWords:
class nwLists: class nwLists:
USER_CLASSES = [ USER_CLASSES: Final[list[nwItemClass]] = [
nwItemClass.CHARACTER, nwItemClass.CHARACTER,
nwItemClass.PLOT, nwItemClass.PLOT,
nwItemClass.WORLD, nwItemClass.WORLD,
@@ -229,7 +231,7 @@ class nwStats:
WORDS_TITLE = "titleWords" WORDS_TITLE = "titleWords"
# Note: The order here affects the order of menu entries # Note: The order here affects the order of menu entries
ALL_FIELDS = [ ALL_FIELDS: Final[list[str]] = [
WORDS, WORDS_TEXT, WORDS_TITLE, WORDS, WORDS_TEXT, WORDS_TITLE,
CHARS, CHARS_TEXT, CHARS_TITLE, CHARS, CHARS_TEXT, CHARS_TITLE,
WCHARS_ALL, WCHARS_TEXT, WCHARS_TITLE, WCHARS_ALL, WCHARS_TEXT, WCHARS_TITLE,
@@ -239,7 +241,7 @@ class nwStats:
class nwLabels: class nwLabels:
CLASS_NAME = { CLASS_NAME: Final[dict[nwItemClass, str]] = {
nwItemClass.NO_CLASS: QT_TRANSLATE_NOOP("Constant", "None"), nwItemClass.NO_CLASS: QT_TRANSLATE_NOOP("Constant", "None"),
nwItemClass.NOVEL: QT_TRANSLATE_NOOP("Constant", "Novel"), nwItemClass.NOVEL: QT_TRANSLATE_NOOP("Constant", "Novel"),
nwItemClass.PLOT: QT_TRANSLATE_NOOP("Constant", "Plot"), nwItemClass.PLOT: QT_TRANSLATE_NOOP("Constant", "Plot"),
@@ -253,7 +255,7 @@ class nwLabels:
nwItemClass.TEMPLATE: QT_TRANSLATE_NOOP("Constant", "Templates"), nwItemClass.TEMPLATE: QT_TRANSLATE_NOOP("Constant", "Templates"),
nwItemClass.TRASH: QT_TRANSLATE_NOOP("Constant", "Trash"), nwItemClass.TRASH: QT_TRANSLATE_NOOP("Constant", "Trash"),
} }
CLASS_ICON = { CLASS_ICON: Final[dict[nwItemClass, str]] = {
nwItemClass.NO_CLASS: "cls_none", nwItemClass.NO_CLASS: "cls_none",
nwItemClass.NOVEL: "cls_novel", nwItemClass.NOVEL: "cls_novel",
nwItemClass.PLOT: "cls_plot", nwItemClass.PLOT: "cls_plot",
@@ -267,12 +269,12 @@ class nwLabels:
nwItemClass.TEMPLATE: "cls_template", nwItemClass.TEMPLATE: "cls_template",
nwItemClass.TRASH: "cls_trash", nwItemClass.TRASH: "cls_trash",
} }
LAYOUT_NAME = { LAYOUT_NAME: Final[dict[nwItemLayout, str]] = {
nwItemLayout.NO_LAYOUT: QT_TRANSLATE_NOOP("Constant", "None"), nwItemLayout.NO_LAYOUT: QT_TRANSLATE_NOOP("Constant", "None"),
nwItemLayout.DOCUMENT: QT_TRANSLATE_NOOP("Constant", "Novel Document"), nwItemLayout.DOCUMENT: QT_TRANSLATE_NOOP("Constant", "Novel Document"),
nwItemLayout.NOTE: QT_TRANSLATE_NOOP("Constant", "Project Note"), nwItemLayout.NOTE: QT_TRANSLATE_NOOP("Constant", "Project Note"),
} }
ITEM_DESCRIPTION = { ITEM_DESCRIPTION: Final[dict[str, str]] = {
"none": QT_TRANSLATE_NOOP("Constant", "None"), "none": QT_TRANSLATE_NOOP("Constant", "None"),
"root": QT_TRANSLATE_NOOP("Constant", "Root Folder"), "root": QT_TRANSLATE_NOOP("Constant", "Root Folder"),
"folder": QT_TRANSLATE_NOOP("Constant", "Folder"), "folder": QT_TRANSLATE_NOOP("Constant", "Folder"),
@@ -283,11 +285,11 @@ class nwLabels:
"doc_h4": QT_TRANSLATE_NOOP("Constant", "Novel Section"), "doc_h4": QT_TRANSLATE_NOOP("Constant", "Novel Section"),
"note": QT_TRANSLATE_NOOP("Constant", "Project Note"), "note": QT_TRANSLATE_NOOP("Constant", "Project Note"),
} }
ACTIVE_NAME = { ACTIVE_NAME: Final[dict[str, str]] = {
"checked": QT_TRANSLATE_NOOP("Constant", "Active"), "checked": QT_TRANSLATE_NOOP("Constant", "Active"),
"unchecked": QT_TRANSLATE_NOOP("Constant", "Inactive"), "unchecked": QT_TRANSLATE_NOOP("Constant", "Inactive"),
} }
KEY_NAME = { KEY_NAME: Final[dict[str, str]] = {
nwKeyWords.TAG_KEY: QT_TRANSLATE_NOOP("Constant", "Tag"), nwKeyWords.TAG_KEY: QT_TRANSLATE_NOOP("Constant", "Tag"),
nwKeyWords.POV_KEY: QT_TRANSLATE_NOOP("Constant", "Point of View"), nwKeyWords.POV_KEY: QT_TRANSLATE_NOOP("Constant", "Point of View"),
nwKeyWords.FOCUS_KEY: QT_TRANSLATE_NOOP("Constant", "Focus"), nwKeyWords.FOCUS_KEY: QT_TRANSLATE_NOOP("Constant", "Focus"),
@@ -301,7 +303,7 @@ class nwLabels:
nwKeyWords.STORY_KEY: QT_TRANSLATE_NOOP("Constant", "Story"), nwKeyWords.STORY_KEY: QT_TRANSLATE_NOOP("Constant", "Story"),
nwKeyWords.MENTION_KEY: QT_TRANSLATE_NOOP("Constant", "Mentions"), nwKeyWords.MENTION_KEY: QT_TRANSLATE_NOOP("Constant", "Mentions"),
} }
KEY_SHORTCUT = { KEY_SHORTCUT: Final[dict[str, str]] = {
nwKeyWords.TAG_KEY: "Ctrl+K, G", nwKeyWords.TAG_KEY: "Ctrl+K, G",
nwKeyWords.POV_KEY: "Ctrl+K, V", nwKeyWords.POV_KEY: "Ctrl+K, V",
nwKeyWords.FOCUS_KEY: "Ctrl+K, F", nwKeyWords.FOCUS_KEY: "Ctrl+K, F",
@@ -315,7 +317,7 @@ class nwLabels:
nwKeyWords.STORY_KEY: "Ctrl+K, N", nwKeyWords.STORY_KEY: "Ctrl+K, N",
nwKeyWords.MENTION_KEY: "Ctrl+K, M", nwKeyWords.MENTION_KEY: "Ctrl+K, M",
} }
OUTLINE_COLS = { OUTLINE_COLS: Final[dict[nwOutline, str]] = {
nwOutline.TITLE: QT_TRANSLATE_NOOP("Constant", "Title"), nwOutline.TITLE: QT_TRANSLATE_NOOP("Constant", "Title"),
nwOutline.LEVEL: QT_TRANSLATE_NOOP("Constant", "Level"), nwOutline.LEVEL: QT_TRANSLATE_NOOP("Constant", "Level"),
nwOutline.LABEL: QT_TRANSLATE_NOOP("Constant", "Document"), nwOutline.LABEL: QT_TRANSLATE_NOOP("Constant", "Document"),
@@ -337,7 +339,7 @@ class nwLabels:
nwOutline.MENTION: KEY_NAME[nwKeyWords.MENTION_KEY], nwOutline.MENTION: KEY_NAME[nwKeyWords.MENTION_KEY],
nwOutline.SYNOP: QT_TRANSLATE_NOOP("Constant", "Synopsis"), nwOutline.SYNOP: QT_TRANSLATE_NOOP("Constant", "Synopsis"),
} }
STATS_NAME = { STATS_NAME: Final[dict[str, str]] = {
nwStats.CHARS: QT_TRANSLATE_NOOP("Stats", "Characters"), nwStats.CHARS: QT_TRANSLATE_NOOP("Stats", "Characters"),
nwStats.CHARS_TEXT: QT_TRANSLATE_NOOP("Stats", "Characters in Text"), nwStats.CHARS_TEXT: QT_TRANSLATE_NOOP("Stats", "Characters in Text"),
nwStats.CHARS_TITLE: QT_TRANSLATE_NOOP("Stats", "Characters in Headings"), nwStats.CHARS_TITLE: QT_TRANSLATE_NOOP("Stats", "Characters in Headings"),
@@ -350,7 +352,7 @@ class nwLabels:
nwStats.WORDS_TEXT: QT_TRANSLATE_NOOP("Stats", "Words in Text"), nwStats.WORDS_TEXT: QT_TRANSLATE_NOOP("Stats", "Words in Text"),
nwStats.WORDS_TITLE: QT_TRANSLATE_NOOP("Stats", "Words in Headings"), nwStats.WORDS_TITLE: QT_TRANSLATE_NOOP("Stats", "Words in Headings"),
} }
BUILD_FMT = { BUILD_FMT: Final[dict[nwBuildFmt, str]] = {
nwBuildFmt.ODT: QT_TRANSLATE_NOOP("Constant", "Open Document (.odt)"), nwBuildFmt.ODT: QT_TRANSLATE_NOOP("Constant", "Open Document (.odt)"),
nwBuildFmt.FODT: QT_TRANSLATE_NOOP("Constant", "Flat Open Document (.fodt)"), nwBuildFmt.FODT: QT_TRANSLATE_NOOP("Constant", "Flat Open Document (.fodt)"),
nwBuildFmt.DOCX: QT_TRANSLATE_NOOP("Constant", "Microsoft Word Document (.docx)"), nwBuildFmt.DOCX: QT_TRANSLATE_NOOP("Constant", "Microsoft Word Document (.docx)"),
@@ -362,7 +364,7 @@ class nwLabels:
nwBuildFmt.J_HTML: QT_TRANSLATE_NOOP("Constant", "JSON + HTML 5 (.json)"), nwBuildFmt.J_HTML: QT_TRANSLATE_NOOP("Constant", "JSON + HTML 5 (.json)"),
nwBuildFmt.J_NWD: QT_TRANSLATE_NOOP("Constant", "JSON + novelWriter Markup (.json)"), nwBuildFmt.J_NWD: QT_TRANSLATE_NOOP("Constant", "JSON + novelWriter Markup (.json)"),
} }
BUILD_EXT = { BUILD_EXT: Final[dict[nwBuildFmt, str]] = {
nwBuildFmt.ODT: ".odt", nwBuildFmt.ODT: ".odt",
nwBuildFmt.FODT: ".fodt", nwBuildFmt.FODT: ".fodt",
nwBuildFmt.DOCX: ".docx", nwBuildFmt.DOCX: ".docx",
@@ -374,7 +376,7 @@ class nwLabels:
nwBuildFmt.J_HTML: ".json", nwBuildFmt.J_HTML: ".json",
nwBuildFmt.J_NWD: ".json", nwBuildFmt.J_NWD: ".json",
} }
SHAPES_PLAIN = { SHAPES_PLAIN: Final[dict[nwStatusShape, str]] = {
nwStatusShape.SQUARE: QT_TRANSLATE_NOOP("Constant", "Square"), nwStatusShape.SQUARE: QT_TRANSLATE_NOOP("Constant", "Square"),
nwStatusShape.TRIANGLE: QT_TRANSLATE_NOOP("Constant", "Triangle"), nwStatusShape.TRIANGLE: QT_TRANSLATE_NOOP("Constant", "Triangle"),
nwStatusShape.NABLA: QT_TRANSLATE_NOOP("Constant", "Nabla"), nwStatusShape.NABLA: QT_TRANSLATE_NOOP("Constant", "Nabla"),
@@ -384,42 +386,42 @@ class nwLabels:
nwStatusShape.STAR: QT_TRANSLATE_NOOP("Constant", "Star"), nwStatusShape.STAR: QT_TRANSLATE_NOOP("Constant", "Star"),
nwStatusShape.PACMAN: QT_TRANSLATE_NOOP("Constant", "Pacman"), nwStatusShape.PACMAN: QT_TRANSLATE_NOOP("Constant", "Pacman"),
} }
SHAPES_CIRCLE = { SHAPES_CIRCLE: Final[dict[nwStatusShape, str]] = {
nwStatusShape.CIRCLE_Q: QT_TRANSLATE_NOOP("Constant", "1/4 Circle"), nwStatusShape.CIRCLE_Q: QT_TRANSLATE_NOOP("Constant", "1/4 Circle"),
nwStatusShape.CIRCLE_H: QT_TRANSLATE_NOOP("Constant", "Half Circle"), nwStatusShape.CIRCLE_H: QT_TRANSLATE_NOOP("Constant", "Half Circle"),
nwStatusShape.CIRCLE_T: QT_TRANSLATE_NOOP("Constant", "3/4 Circle"), nwStatusShape.CIRCLE_T: QT_TRANSLATE_NOOP("Constant", "3/4 Circle"),
nwStatusShape.CIRCLE: QT_TRANSLATE_NOOP("Constant", "Full Circle"), nwStatusShape.CIRCLE: QT_TRANSLATE_NOOP("Constant", "Full Circle"),
} }
SHAPES_BARS = { SHAPES_BARS: Final[dict[nwStatusShape, str]] = {
nwStatusShape.BARS_1: QT_TRANSLATE_NOOP("Constant", "1 Bar"), nwStatusShape.BARS_1: QT_TRANSLATE_NOOP("Constant", "1 Bar"),
nwStatusShape.BARS_2: QT_TRANSLATE_NOOP("Constant", "2 Bars"), nwStatusShape.BARS_2: QT_TRANSLATE_NOOP("Constant", "2 Bars"),
nwStatusShape.BARS_3: QT_TRANSLATE_NOOP("Constant", "3 Bars"), nwStatusShape.BARS_3: QT_TRANSLATE_NOOP("Constant", "3 Bars"),
nwStatusShape.BARS_4: QT_TRANSLATE_NOOP("Constant", "4 Bars"), nwStatusShape.BARS_4: QT_TRANSLATE_NOOP("Constant", "4 Bars"),
} }
SHAPES_BLOCKS = { SHAPES_BLOCKS: Final[dict[nwStatusShape, str]] = {
nwStatusShape.BLOCK_1: QT_TRANSLATE_NOOP("Constant", "1 Block"), nwStatusShape.BLOCK_1: QT_TRANSLATE_NOOP("Constant", "1 Block"),
nwStatusShape.BLOCK_2: QT_TRANSLATE_NOOP("Constant", "2 Blocks"), nwStatusShape.BLOCK_2: QT_TRANSLATE_NOOP("Constant", "2 Blocks"),
nwStatusShape.BLOCK_3: QT_TRANSLATE_NOOP("Constant", "3 Blocks"), nwStatusShape.BLOCK_3: QT_TRANSLATE_NOOP("Constant", "3 Blocks"),
nwStatusShape.BLOCK_4: QT_TRANSLATE_NOOP("Constant", "4 Blocks"), nwStatusShape.BLOCK_4: QT_TRANSLATE_NOOP("Constant", "4 Blocks"),
} }
FILE_FILTERS = { FILE_FILTERS: Final[dict[str, str]] = {
"*.txt": QT_TRANSLATE_NOOP("Constant", "Text files"), "*.txt": QT_TRANSLATE_NOOP("Constant", "Text files"),
"*.md": QT_TRANSLATE_NOOP("Constant", "Markdown files"), "*.md": QT_TRANSLATE_NOOP("Constant", "Markdown files"),
"*.nwd": QT_TRANSLATE_NOOP("Constant", "novelWriter files"), "*.nwd": QT_TRANSLATE_NOOP("Constant", "novelWriter files"),
"*.csv": QT_TRANSLATE_NOOP("Constant", "CSV files"), "*.csv": QT_TRANSLATE_NOOP("Constant", "CSV files"),
"*": QT_TRANSLATE_NOOP("Constant", "All files"), "*": QT_TRANSLATE_NOOP("Constant", "All files"),
} }
UNIT_NAME = { UNIT_NAME: Final[dict[str, str]] = {
"mm": QT_TRANSLATE_NOOP("Constant", "Millimetres"), "mm": QT_TRANSLATE_NOOP("Constant", "Millimetres"),
"cm": QT_TRANSLATE_NOOP("Constant", "Centimetres"), "cm": QT_TRANSLATE_NOOP("Constant", "Centimetres"),
"in": QT_TRANSLATE_NOOP("Constant", "Inches"), "in": QT_TRANSLATE_NOOP("Constant", "Inches"),
} }
UNIT_SCALE = { UNIT_SCALE: Final[dict[str, float]] = {
"mm": 1.0, "mm": 1.0,
"cm": 10.0, "cm": 10.0,
"in": 25.4, "in": 25.4,
} }
PAPER_NAME = { PAPER_NAME: Final[dict[str, str]] = {
"A4": QT_TRANSLATE_NOOP("Constant", "A4"), "A4": QT_TRANSLATE_NOOP("Constant", "A4"),
"A5": QT_TRANSLATE_NOOP("Constant", "A5"), "A5": QT_TRANSLATE_NOOP("Constant", "A5"),
"A6": QT_TRANSLATE_NOOP("Constant", "A6"), "A6": QT_TRANSLATE_NOOP("Constant", "A6"),
@@ -427,7 +429,7 @@ class nwLabels:
"Letter": QT_TRANSLATE_NOOP("Constant", "US Letter"), "Letter": QT_TRANSLATE_NOOP("Constant", "US Letter"),
"Custom": QT_TRANSLATE_NOOP("Constant", "Custom"), "Custom": QT_TRANSLATE_NOOP("Constant", "Custom"),
} }
PAPER_SIZE = { PAPER_SIZE: Final[dict[str, tuple[float, float]]] = {
"A4": (210.0, 297.0), "A4": (210.0, 297.0),
"A5": (148.0, 210.0), "A5": (148.0, 210.0),
"A6": (105.0, 148.0), "A6": (105.0, 148.0),
@@ -435,7 +437,7 @@ class nwLabels:
"Letter": (215.9, 279.4), "Letter": (215.9, 279.4),
"Custom": (-1.0, -1.0), "Custom": (-1.0, -1.0),
} }
THEME_COLORS = { THEME_COLORS: Final[dict[str, str]] = {
"theme": QT_TRANSLATE_NOOP("Constant", "Theme Colours"), "theme": QT_TRANSLATE_NOOP("Constant", "Theme Colours"),
"default": QT_TRANSLATE_NOOP("Constant", "Foreground Colour"), "default": QT_TRANSLATE_NOOP("Constant", "Foreground Colour"),
"faded": QT_TRANSLATE_NOOP("Constant", "Faded Colour"), "faded": QT_TRANSLATE_NOOP("Constant", "Faded Colour"),
@@ -462,7 +464,7 @@ class nwHeadFmt:
CHAR_POV = "{Char:POV}" CHAR_POV = "{Char:POV}"
CHAR_FOCUS = "{Char:Focus}" CHAR_FOCUS = "{Char:Focus}"
PAGE_HEADERS = [ PAGE_HEADERS: Final[list[str]] = [
TITLE, CH_NUM, CH_WORD, CH_ROMU, CH_ROML, SC_NUM, SC_ABS, TITLE, CH_NUM, CH_WORD, CH_ROMU, CH_ROML, SC_NUM, SC_ABS,
CHAR_POV, CHAR_FOCUS CHAR_POV, CHAR_FOCUS
] ]
@@ -478,7 +480,7 @@ class nwQuotes:
"""Allowed quotation marks. """Allowed quotation marks.
Source: https://en.wikipedia.org/wiki/Quotation_mark Source: https://en.wikipedia.org/wiki/Quotation_mark
""" """
SYMBOLS = { SYMBOLS: Final[dict[str, str]] = {
"\u0027": QT_TRANSLATE_NOOP("Constant", "Straight single quotation mark"), "\u0027": QT_TRANSLATE_NOOP("Constant", "Straight single quotation mark"),
"\u0022": QT_TRANSLATE_NOOP("Constant", "Straight double quotation mark"), "\u0022": QT_TRANSLATE_NOOP("Constant", "Straight double quotation mark"),
@@ -645,7 +647,7 @@ class nwUnicode:
class nwHtmlUnicode: class nwHtmlUnicode:
U_TO_H = { U_TO_H: Final[dict[str, str]] = {
# Quotes # Quotes
nwUnicode.U_QUOT: nwUnicode.H_QUOT, nwUnicode.U_QUOT: nwUnicode.H_QUOT,
nwUnicode.U_APOS: nwUnicode.H_APOS, nwUnicode.U_APOS: nwUnicode.H_APOS,
+12 -9
View File
@@ -28,9 +28,9 @@ import json
import logging import logging
import uuid import uuid
from collections.abc import Iterable
from enum import Enum from enum import Enum
from pathlib import Path from pathlib import Path
from typing import TYPE_CHECKING
from PyQt6.QtCore import QT_TRANSLATE_NOOP, QCoreApplication from PyQt6.QtCore import QT_TRANSLATE_NOOP, QCoreApplication
@@ -41,6 +41,9 @@ from novelwriter.core.project import NWProject
from novelwriter.enum import nwBuildFmt from novelwriter.enum import nwBuildFmt
from novelwriter.error import logException from novelwriter.error import logException
if TYPE_CHECKING:
from collections.abc import Iterable
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
T_BuildValue = str | int | float | bool T_BuildValue = str | int | float | bool
@@ -227,9 +230,9 @@ class BuildSettings:
@classmethod @classmethod
def fromDict(cls, data: dict) -> BuildSettings: def fromDict(cls, data: dict) -> BuildSettings:
"""Create a build settings object from a dict.""" """Create a build settings object from a dict."""
cls = BuildSettings() new = cls()
cls.unpack(data) new.unpack(data)
return cls return new
## ##
# Properties # Properties
@@ -514,11 +517,11 @@ class BuildSettings:
@classmethod @classmethod
def duplicate(cls, source: BuildSettings) -> BuildSettings: def duplicate(cls, source: BuildSettings) -> BuildSettings:
"""Make a copy of another build.""" """Make a copy of another build."""
cls = BuildSettings() new = cls()
cls.unpack(source.pack()) new.unpack(source.pack())
cls._uuid = str(uuid.uuid4()) new._uuid = str(uuid.uuid4())
cls._name = f"{source.name} 2" new._name = f"{source.name} 2"
return cls return new
class BuildCollection: class BuildCollection:
+8 -4
View File
@@ -30,9 +30,9 @@ import logging
import re import re
import shutil import shutil
from collections.abc import Iterable
from functools import partial from functools import partial
from pathlib import Path from pathlib import Path
from typing import TYPE_CHECKING
from zipfile import ZipFile, is_zipfile from zipfile import ZipFile, is_zipfile
from PyQt6.QtCore import QCoreApplication from PyQt6.QtCore import QCoreApplication
@@ -40,10 +40,14 @@ from PyQt6.QtCore import QCoreApplication
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
from novelwriter.common import isHandle, minmax, simplified from novelwriter.common import isHandle, minmax, simplified
from novelwriter.constants import nwConst, nwFiles, nwItemClass, nwStats from novelwriter.constants import nwConst, nwFiles, nwItemClass, nwStats
from novelwriter.core.item import NWItem
from novelwriter.core.project import NWProject from novelwriter.core.project import NWProject
from novelwriter.core.storage import NWStorageCreate from novelwriter.core.storage import NWStorageCreate
if TYPE_CHECKING:
from collections.abc import Iterable
from novelwriter.core.item import NWItem
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -284,7 +288,7 @@ class DocDuplicator:
class DocSearch: class DocSearch:
def __init__(self) -> None: def __init__(self) -> None:
self._regEx = re.compile("") self._regEx = re.compile(r"")
self._opts = re.UNICODE | re.IGNORECASE self._opts = re.UNICODE | re.IGNORECASE
self._words = False self._words = False
self._escape = True self._escape = True
@@ -381,7 +385,7 @@ class ProjectBuilder:
path = data.get("path", None) or None path = data.get("path", None) or None
if isinstance(path, str | Path): if isinstance(path, str | Path):
self._path = Path(path).resolve() self._path = Path(path).resolve()
if data.get("sample", False): if data.get("sample"):
return self._extractSampleProject(self._path) return self._extractSampleProject(self._path)
elif data.get("template"): elif data.get("template"):
return self._copyProject(self._path, data) return self._copyProject(self._path, data)
+11 -7
View File
@@ -25,26 +25,30 @@ from __future__ import annotations
import logging import logging
from collections.abc import Iterable from typing import TYPE_CHECKING
from pathlib import Path
from PyQt6.QtGui import QFont from PyQt6.QtGui import QFont
from novelwriter import CONFIG from novelwriter import CONFIG
from novelwriter.constants import nwLabels from novelwriter.constants import nwLabels
from novelwriter.core.buildsettings import BuildSettings
from novelwriter.core.item import NWItem from novelwriter.core.item import NWItem
from novelwriter.core.project import NWProject
from novelwriter.enum import nwBuildFmt from novelwriter.enum import nwBuildFmt
from novelwriter.error import formatException, logException from novelwriter.error import formatException, logException
from novelwriter.formats.todocx import ToDocX from novelwriter.formats.todocx import ToDocX
from novelwriter.formats.tohtml import ToHtml from novelwriter.formats.tohtml import ToHtml
from novelwriter.formats.tokenizer import Tokenizer
from novelwriter.formats.tomarkdown import ToMarkdown from novelwriter.formats.tomarkdown import ToMarkdown
from novelwriter.formats.toodt import ToOdt from novelwriter.formats.toodt import ToOdt
from novelwriter.formats.toqdoc import ToQTextDocument from novelwriter.formats.toqdoc import ToQTextDocument
from novelwriter.formats.toraw import ToRaw from novelwriter.formats.toraw import ToRaw
if TYPE_CHECKING:
from collections.abc import Iterable
from pathlib import Path
from novelwriter.core.buildsettings import BuildSettings
from novelwriter.core.project import NWProject
from novelwriter.formats.tokenizer import Tokenizer
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -56,8 +60,8 @@ class NWBuildDocument:
""" """
__slots__ = ( __slots__ = (
"_project", "_build", "_queue", "_error", "_cache", "_count", "_build", "_cache", "_count", "_error", "_outline", "_project",
"_outline", "_queue",
) )
def __init__(self, project: NWProject, build: BuildSettings) -> None: def __init__(self, project: NWProject, build: BuildSettings) -> None:
+2 -2
View File
@@ -31,11 +31,11 @@ from time import time
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from novelwriter.common import formatTimeStamp, isHandle from novelwriter.common import formatTimeStamp, isHandle
from novelwriter.core.item import NWItem
from novelwriter.enum import nwItemClass, nwItemLayout from novelwriter.enum import nwItemClass, nwItemLayout
from novelwriter.error import formatException, logException from novelwriter.error import formatException, logException
if TYPE_CHECKING: # pragma: no cover if TYPE_CHECKING:
from novelwriter.core.item import NWItem
from novelwriter.core.project import NWProject from novelwriter.core.project import NWProject
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
+4 -3
View File
@@ -29,7 +29,6 @@ import json
import logging import logging
import random import random
from collections.abc import ItemsView, Iterable
from pathlib import Path from pathlib import Path
from time import time from time import time
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
@@ -44,7 +43,9 @@ from novelwriter.error import logException
from novelwriter.text.comments import processComment from novelwriter.text.comments import processComment
from novelwriter.text.counting import standardCounter from novelwriter.text.counting import standardCounter
if TYPE_CHECKING: # pragma: no cover if TYPE_CHECKING:
from collections.abc import ItemsView, Iterable
from novelwriter.core.item import NWItem from novelwriter.core.item import NWItem
from novelwriter.core.project import NWProject from novelwriter.core.project import NWProject
@@ -906,7 +907,7 @@ class ItemIndex:
IndexHeading object for each heading of the text. IndexHeading object for each heading of the text.
""" """
__slots__ = ("_project", "_tags", "_items") __slots__ = ("_items", "_project", "_tags")
def __init__(self, project: NWProject, tagsIndex: TagsIndex) -> None: def __init__(self, project: NWProject, tagsIndex: TagsIndex) -> None:
self._project = project self._project = project
+6 -5
View File
@@ -28,14 +28,15 @@ from __future__ import annotations
import logging import logging
from collections.abc import ItemsView, Sequence
from typing import TYPE_CHECKING, Literal from typing import TYPE_CHECKING, Literal
from novelwriter import CONFIG from novelwriter import CONFIG
from novelwriter.common import checkInt, isListInstance, isTitleTag from novelwriter.common import checkInt, isListInstance, isTitleTag
from novelwriter.constants import nwKeyWords, nwStyles from novelwriter.constants import nwKeyWords, nwStyles
if TYPE_CHECKING: # pragma: no cover if TYPE_CHECKING:
from collections.abc import ItemsView, Sequence
from novelwriter.core.index import TagsIndex from novelwriter.core.index import TagsIndex
from novelwriter.core.item import NWItem from novelwriter.core.item import NWItem
@@ -57,7 +58,7 @@ class IndexNode:
must be reset each time the item is re-indexed. must be reset each time the item is re-indexed.
""" """
__slots__ = ("_tags", "_handle", "_item", "_headings", "_notes", "_count") __slots__ = ("_count", "_handle", "_headings", "_item", "_notes", "_tags")
def __init__(self, tagsIndex: TagsIndex, tHandle: str, nwItem: NWItem) -> None: def __init__(self, tagsIndex: TagsIndex, tHandle: str, nwItem: NWItem) -> None:
self._tags = tagsIndex self._tags = tagsIndex
@@ -205,8 +206,8 @@ class IndexHeading:
""" """
__slots__ = ( __slots__ = (
"_tags", "_key", "_line", "_level", "_title", "_comments", "_counts", "_key", "_level", "_line", "_refs", "_tag",
"_counts", "_tag", "_refs", "_comments", "_tags", "_title",
) )
def __init__( def __init__(
+26 -26
View File
@@ -27,8 +27,6 @@ import logging
from typing import TYPE_CHECKING, Any from typing import TYPE_CHECKING, Any
from PyQt6.QtGui import QFont, QIcon
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
from novelwriter.common import ( from novelwriter.common import (
checkInt, isHandle, isItemClass, isItemLayout, isItemType, simplified, checkInt, isHandle, isItemClass, isItemLayout, isItemType, simplified,
@@ -37,7 +35,9 @@ from novelwriter.common import (
from novelwriter.constants import nwLabels, nwStyles, trConst from novelwriter.constants import nwLabels, nwStyles, trConst
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
if TYPE_CHECKING: # pragma: no cover if TYPE_CHECKING:
from PyQt6.QtGui import QFont, QIcon
from novelwriter.core.project import NWProject from novelwriter.core.project import NWProject
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -53,10 +53,10 @@ class NWItem:
""" """
__slots__ = ( __slots__ = (
"_project", "_name", "_handle", "_parent", "_root", "_order", "_active", "_charCount", "_class", "_cursorPos", "_expanded",
"_type", "_class", "_layout", "_status", "_import", "_active", "_handle", "_heading", "_import", "_initCount", "_layout", "_name",
"_expanded", "_heading", "_charCount", "_wordCount", "_order", "_paraCount", "_parent", "_project", "_root", "_status",
"_paraCount", "_cursorPos", "_initCount", "_type", "_wordCount",
) )
def __init__(self, project: NWProject, handle: str) -> None: def __init__(self, project: NWProject, handle: str) -> None:
@@ -264,25 +264,25 @@ class NWItem:
@classmethod @classmethod
def duplicate(cls, source: NWItem, handle: str) -> NWItem: def duplicate(cls, source: NWItem, handle: str) -> NWItem:
"""Make a copy of an item.""" """Make a copy of an item."""
cls = NWItem(source._project, handle) new = cls(source._project, handle)
cls._name = source._name new._name = source._name
cls._parent = source._parent new._parent = source._parent
cls._root = source._root new._root = source._root
cls._order = source._order new._order = source._order
cls._type = source._type new._type = source._type
cls._class = source._class new._class = source._class
cls._layout = source._layout new._layout = source._layout
cls._status = source._status new._status = source._status
cls._import = source._import new._import = source._import
cls._active = source._active new._active = source._active
cls._expanded = source._expanded new._expanded = source._expanded
cls._heading = source._heading new._heading = source._heading
cls._charCount = source._charCount new._charCount = source._charCount
cls._wordCount = source._wordCount new._wordCount = source._wordCount
cls._paraCount = source._paraCount new._paraCount = source._paraCount
cls._cursorPos = source._cursorPos new._cursorPos = source._cursorPos
cls._initCount = source._initCount new._initCount = source._initCount
return cls return new
## ##
# Action Methods # Action Methods
+17 -18
View File
@@ -37,7 +37,7 @@ from novelwriter.core.item import NWItem
from novelwriter.enum import nwItemClass from novelwriter.enum import nwItemClass
from novelwriter.types import QtAlignRight from novelwriter.types import QtAlignRight
if TYPE_CHECKING: # pragma: no cover if TYPE_CHECKING:
from novelwriter.core.tree import NWTree from novelwriter.core.tree import NWTree
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -89,7 +89,7 @@ class ProjectNode:
C_ACTIVE = 2 C_ACTIVE = 2
C_STATUS = 3 C_STATUS = 3
__slots__ = ("_item", "_children", "_parent", "_row", "_cache", "_flags", "_count") __slots__ = ("_cache", "_children", "_count", "_flags", "_item", "_parent", "_row")
def __init__(self, item: NWItem) -> None: def __init__(self, item: NWItem) -> None:
self._item = item self._item = item
@@ -162,7 +162,7 @@ class ProjectNode:
def updateCount(self, propagate: bool = True) -> None: def updateCount(self, propagate: bool = True) -> None:
"""Update counts, and propagate upwards in the tree.""" """Update counts, and propagate upwards in the tree."""
self._count = self._item.wordCount + sum(c._count for c in self._children) self._count = self._item.wordCount + sum(c._count for c in self._children) # noqa: SLF001
self._cache[C_COUNT_TEXT] = f"{self._count:n}" self._cache[C_COUNT_TEXT] = f"{self._count:n}"
if propagate and (parent := self._parent): if propagate and (parent := self._parent):
parent.updateCount() parent.updateCount()
@@ -257,13 +257,13 @@ class ProjectNode:
"""Recursively add all nodes to a list.""" """Recursively add all nodes to a list."""
for node in self._children: for node in self._children:
children.append(node) children.append(node)
node._recursiveAppendChildren(children) node._recursiveAppendChildren(children) # noqa: SLF001
return return
def _refreshChildrenPos(self) -> None: def _refreshChildrenPos(self) -> None:
"""Update the row value on all children.""" """Update the row value on all children."""
for n, child in enumerate(self._children): for n, child in enumerate(self._children):
child._row = n child._row = n # noqa: SLF001
child.item.setOrder(n) child.item.setOrder(n)
return return
@@ -290,12 +290,12 @@ class ProjectModel(QAbstractItemModel):
methods needed primarily by the project tree GUI component. methods needed primarily by the project tree GUI component.
""" """
__slots__ = ("_tree", "_root") __slots__ = ("_root", "_tree")
def __init__(self, tree: NWTree) -> None: def __init__(self, tree: NWTree) -> None:
super().__init__() super().__init__()
self._tree = tree self._tree = tree
self._root = ProjectNode(NWItem(tree._project, INV_ROOT)) self._root = ProjectNode(NWItem(tree.project, INV_ROOT))
self._root.item.setName("Invisible Root") self._root.item.setName("Invisible Root")
logger.debug("Ready: ProjectModel") logger.debug("Ready: ProjectModel")
return return
@@ -391,10 +391,10 @@ class ProjectModel(QAbstractItemModel):
) -> bool: ) -> bool:
"""Process mime data drop.""" """Process mime data drop."""
if self.canDropMimeData(data, action, row, column, parent): if self.canDropMimeData(data, action, row, column, parent):
items = [] items = [
for handle in decodeMimeHandles(data): index for handle in decodeMimeHandles(data)
if (index := self.indexFromHandle(handle)).isValid(): if (index := self.indexFromHandle(handle)).isValid()
items.append(index) ]
self.multiMove(items, parent, row) self.multiMove(items, parent, row)
return True return True
return False return False
@@ -490,7 +490,7 @@ class ProjectModel(QAbstractItemModel):
if temp := self.removeChild(index.parent(), index.row()): if temp := self.removeChild(index.parent(), index.row()):
self.insertChild(temp, target, pos) self.insertChild(temp, target, pos)
for child in reversed(node.allChildren()): for child in reversed(node.allChildren()):
node._updateRelationships(child) node._updateRelationships(child) # noqa: SLF001
child.item.notifyToRefresh() child.item.notifyToRefresh()
node.item.notifyToRefresh() node.item.notifyToRefresh()
return return
@@ -501,16 +501,15 @@ class ProjectModel(QAbstractItemModel):
def clear(self) -> None: def clear(self) -> None:
"""Clear the project model.""" """Clear the project model."""
self._root._children.clear() self._root.children.clear()
return return
def allExpanded(self) -> list[QModelIndex]: def allExpanded(self) -> list[QModelIndex]:
"""Return a list of all expanded items.""" """Return a list of all expanded items."""
expanded = [] return [
for node in self._root.allChildren(): self.createIndex(node.row(), 0, node) for node in self._root.allChildren()
if node._item.isExpanded: if node.item.isExpanded
expanded.append(self.createIndex(node.row(), 0, node)) ]
return expanded
def trashSelection(self, indices: list[QModelIndex]) -> bool: def trashSelection(self, indices: list[QModelIndex]) -> bool:
"""Check if a selection of indices are all in trash or not.""" """Check if a selection of indices are all in trash or not."""
+6 -2
View File
@@ -25,15 +25,19 @@ from __future__ import annotations
import logging import logging
from typing import TYPE_CHECKING
from PyQt6.QtCore import QAbstractTableModel, QModelIndex, Qt from PyQt6.QtCore import QAbstractTableModel, QModelIndex, Qt
from PyQt6.QtGui import QIcon, QPixmap from PyQt6.QtGui import QIcon, QPixmap
from novelwriter import SHARED from novelwriter import SHARED
from novelwriter.constants import nwKeyWords, nwLabels, nwStyles, trConst from novelwriter.constants import nwKeyWords, nwLabels, nwStyles, trConst
from novelwriter.core.indexdata import IndexHeading, IndexNode
from novelwriter.enum import nwNovelExtra from novelwriter.enum import nwNovelExtra
from novelwriter.types import QtAlignRight from novelwriter.types import QtAlignRight
if TYPE_CHECKING:
from novelwriter.core.indexdata import IndexHeading, IndexNode
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
C_FACTOR = 0x0100 C_FACTOR = 0x0100
@@ -50,7 +54,7 @@ T_NodeData = str | QIcon | QPixmap | Qt.AlignmentFlag | None
class NovelModel(QAbstractTableModel): class NovelModel(QAbstractTableModel):
__slots__ = ("_rows", "_more", "_columns", "_extraKey", "_extraLabel") __slots__ = ("_columns", "_extraKey", "_extraLabel", "_more", "_rows")
def __init__(self) -> None: def __init__(self) -> None:
super().__init__() super().__init__()
+1 -1
View File
@@ -35,7 +35,7 @@ from novelwriter.common import checkBool, checkFloat, checkInt, checkString, jso
from novelwriter.constants import nwFiles from novelwriter.constants import nwFiles
from novelwriter.error import logException from novelwriter.error import logException
if TYPE_CHECKING: # pragma: no cover if TYPE_CHECKING:
from novelwriter.core.project import NWProject from novelwriter.core.project import NWProject
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
+9 -6
View File
@@ -30,6 +30,7 @@ from enum import Enum
from functools import partial from functools import partial
from pathlib import Path from pathlib import Path
from time import time from time import time
from typing import TYPE_CHECKING
from PyQt6.QtCore import QCoreApplication from PyQt6.QtCore import QCoreApplication
@@ -44,12 +45,14 @@ from novelwriter.core.options import OptionState
from novelwriter.core.projectdata import NWProjectData from novelwriter.core.projectdata import NWProjectData
from novelwriter.core.projectxml import ProjectXMLReader, ProjectXMLWriter, XMLReadState from novelwriter.core.projectxml import ProjectXMLReader, ProjectXMLWriter, XMLReadState
from novelwriter.core.sessions import NWSessionLog from novelwriter.core.sessions import NWSessionLog
from novelwriter.core.status import T_StatusKind, T_UpdateEntry
from novelwriter.core.storage import NWStorage, NWStorageOpen from novelwriter.core.storage import NWStorage, NWStorageOpen
from novelwriter.core.tree import NWTree from novelwriter.core.tree import NWTree
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
from novelwriter.error import logException from novelwriter.error import logException
if TYPE_CHECKING:
from novelwriter.core.status import T_StatusKind, T_UpdateEntry
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -64,8 +67,8 @@ class NWProjectState(Enum):
class NWProject: class NWProject:
__slots__ = ( __slots__ = (
"_options", "_storage", "_data", "_tree", "_index", "_session", "_changed", "_data", "_index", "_langData", "_options", "_session",
"_langData", "_changed", "_valid", "_state", "tr", "_state", "_storage", "_tree", "_valid", "tr",
) )
def __init__(self) -> None: def __init__(self) -> None:
@@ -557,13 +560,13 @@ class NWProject:
def _loadProjectLocalisation(self) -> bool: def _loadProjectLocalisation(self) -> bool:
"""Load the language data for the current project language.""" """Load the language data for the current project language."""
if self._data.language is None or CONFIG._nwLangPath is None: if self._data.language is None or CONFIG.nwLangPath is None:
self._langData = {} self._langData = {}
return False return False
langFile = Path(CONFIG._nwLangPath) / f"project_{self._data.language}.json" langFile = Path(CONFIG.nwLangPath) / f"project_{self._data.language}.json"
if not langFile.is_file(): if not langFile.is_file():
langFile = Path(CONFIG._nwLangPath) / "project_en_GB.json" langFile = Path(CONFIG.nwLangPath) / "project_en_GB.json"
try: try:
with open(langFile, mode="r", encoding="utf-8") as inFile: with open(langFile, mode="r", encoding="utf-8") as inFile:
+1 -1
View File
@@ -34,7 +34,7 @@ from novelwriter.common import (
) )
from novelwriter.core.status import NWStatus from novelwriter.core.status import NWStatus
if TYPE_CHECKING: # pragma: no cover if TYPE_CHECKING:
from novelwriter.core.project import NWProject from novelwriter.core.project import NWProject
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
+1 -1
View File
@@ -39,7 +39,7 @@ from novelwriter.common import (
hexToInt, simplified, xmlIndent, yesNo hexToInt, simplified, xmlIndent, yesNo
) )
if TYPE_CHECKING: # pragma: no cover if TYPE_CHECKING:
from novelwriter.core.projectdata import NWProjectData from novelwriter.core.projectdata import NWProjectData
from novelwriter.core.status import NWStatus from novelwriter.core.status import NWStatus
+3 -2
View File
@@ -26,7 +26,6 @@ from __future__ import annotations
import json import json
import logging import logging
from collections.abc import Iterable
from pathlib import Path from pathlib import Path
from time import time from time import time
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
@@ -35,7 +34,9 @@ from novelwriter.common import formatTimeStamp
from novelwriter.constants import nwFiles from novelwriter.constants import nwFiles
from novelwriter.error import logException from novelwriter.error import logException
if TYPE_CHECKING: # pragma: no cover if TYPE_CHECKING:
from collections.abc import Iterable
from novelwriter.core.project import NWProject from novelwriter.core.project import NWProject
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
+3 -2
View File
@@ -27,7 +27,6 @@ from __future__ import annotations
import json import json
import logging import logging
from collections.abc import Iterator
from pathlib import Path from pathlib import Path
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
@@ -36,7 +35,9 @@ from PyQt6.QtCore import QLocale
from novelwriter.constants import nwFiles from novelwriter.constants import nwFiles
from novelwriter.error import logException from novelwriter.error import logException
if TYPE_CHECKING: # pragma: no cover if TYPE_CHECKING:
from collections.abc import Iterator
from novelwriter.core.project import NWProject from novelwriter.core.project import NWProject
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
+9 -7
View File
@@ -28,8 +28,7 @@ import dataclasses
import logging import logging
import random import random
from collections.abc import Iterable from typing import TYPE_CHECKING, Literal, TypeGuard
from typing import Literal, TypeGuard
from PyQt6.QtCore import QPointF, Qt from PyQt6.QtCore import QPointF, Qt
from PyQt6.QtGui import QColor, QIcon, QPainter, QPainterPath, QPixmap, QPolygonF from PyQt6.QtGui import QColor, QIcon, QPainter, QPainterPath, QPixmap, QPolygonF
@@ -39,6 +38,9 @@ from novelwriter.common import simplified
from novelwriter.enum import nwStatusShape from novelwriter.enum import nwStatusShape
from novelwriter.types import QtPaintAntiAlias, QtTransparent from novelwriter.types import QtPaintAntiAlias, QtTransparent
if TYPE_CHECKING:
from collections.abc import Iterable
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -54,10 +56,10 @@ class StatusEntry:
@classmethod @classmethod
def duplicate(cls, source: StatusEntry) -> StatusEntry: def duplicate(cls, source: StatusEntry) -> StatusEntry:
"""Create a deep copy of the source object.""" """Create a deep copy of the source object."""
cls = dataclasses.replace(source) status = dataclasses.replace(source)
cls.color = QColor(source.color) status.color = QColor(source.color)
cls.icon = QIcon(source.icon) status.icon = QIcon(source.icon)
return cls return status
NO_ENTRY = StatusEntry("", QColor(0, 0, 0), nwStatusShape.SQUARE, QIcon(), 0) NO_ENTRY = StatusEntry("", QColor(0, 0, 0), nwStatusShape.SQUARE, QIcon(), 0)
@@ -71,7 +73,7 @@ class NWStatus:
STATUS = "s" STATUS = "s"
IMPORT = "i" IMPORT = "i"
__slots__ = ("_store", "_default", "_prefix", "_height") __slots__ = ("_default", "_height", "_prefix", "_store")
def __init__(self, prefix: T_StatusKind) -> None: def __init__(self, prefix: T_StatusKind) -> None:
self._store: dict[str, StatusEntry] = {} self._store: dict[str, StatusEntry] = {}
+1 -1
View File
@@ -40,7 +40,7 @@ from novelwriter.core.projectxml import ProjectXMLReader, ProjectXMLWriter
from novelwriter.core.spellcheck import UserDictionary from novelwriter.core.spellcheck import UserDictionary
from novelwriter.error import logException from novelwriter.error import logException
if TYPE_CHECKING: # pragma: no cover if TYPE_CHECKING:
from novelwriter.core.project import NWProject from novelwriter.core.project import NWProject
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
+11 -6
View File
@@ -27,7 +27,6 @@ from __future__ import annotations
import logging import logging
import random import random
from collections.abc import Iterable, Iterator
from pathlib import Path from pathlib import Path
from typing import TYPE_CHECKING, Literal, overload from typing import TYPE_CHECKING, Literal, overload
@@ -40,7 +39,9 @@ from novelwriter.core.itemmodel import ProjectModel, ProjectNode
from novelwriter.enum import nwChange, nwItemClass, nwItemLayout, nwItemType from novelwriter.enum import nwChange, nwItemClass, nwItemLayout, nwItemType
from novelwriter.error import logException from novelwriter.error import logException
if TYPE_CHECKING: # pragma: no cover if TYPE_CHECKING:
from collections.abc import Iterable, Iterator
from novelwriter.core.project import NWProject from novelwriter.core.project import NWProject
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -60,7 +61,7 @@ class NWTree:
also used for file names. also used for file names.
""" """
__slots__ = ("_project", "_model", "_items", "_nodes", "_trash", "_ready") __slots__ = ("_items", "_model", "_nodes", "_project", "_ready", "_trash")
def __init__(self, project: NWProject) -> None: def __init__(self, project: NWProject) -> None:
self._project = project self._project = project
@@ -107,6 +108,11 @@ class NWTree:
# Properties # Properties
## ##
@property
def project(self) -> NWProject:
"""Return the parent project."""
return self._project
@property @property
def trash(self) -> ProjectNode | None: def trash(self) -> ProjectNode | None:
"""Return trash node, if it exists.""" """Return trash node, if it exists."""
@@ -396,7 +402,7 @@ class NWTree:
def checkType(self, tHandle: str, itemType: nwItemType) -> bool: def checkType(self, tHandle: str, itemType: nwItemType) -> bool:
"""Check if item exists and is of the specified item type.""" """Check if item exists and is of the specified item type."""
if tItem := self.__getitem__(tHandle): if tItem := self[tHandle]:
return tItem.itemType == itemType return tItem.itemType == itemType
return False return False
@@ -414,8 +420,7 @@ class NWTree:
node = parent node = parent
else: else:
return path return path
else: logger.error("Max project tree depth reached")
logger.error("Max project tree depth reached")
return path return path
def subTree(self, tHandle: str) -> list[str]: def subTree(self, tHandle: str) -> list[str]:
+5 -1
View File
@@ -25,7 +25,8 @@ from __future__ import annotations
import logging import logging
from PyQt6.QtGui import QCloseEvent from typing import TYPE_CHECKING
from PyQt6.QtWidgets import ( from PyQt6.QtWidgets import (
QDialogButtonBox, QHBoxLayout, QLabel, QTextBrowser, QVBoxLayout, QWidget QDialogButtonBox, QHBoxLayout, QLabel, QTextBrowser, QVBoxLayout, QWidget
) )
@@ -37,6 +38,9 @@ from novelwriter.extensions.modified import NDialog
from novelwriter.extensions.versioninfo import VersionInfoWidget from novelwriter.extensions.versioninfo import VersionInfoWidget
from novelwriter.types import QtAlignRightTop, QtDialogClose, QtHexArgb from novelwriter.types import QtAlignRightTop, QtDialogClose, QtHexArgb
if TYPE_CHECKING:
from PyQt6.QtGui import QCloseEvent
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
+5 -5
View File
@@ -131,11 +131,11 @@ class GuiDocMerge(NDialog):
@classmethod @classmethod
def getData(cls, parent: QWidget, handle: str, items: list[str]) -> tuple[dict, bool]: def getData(cls, parent: QWidget, handle: str, items: list[str]) -> tuple[dict, bool]:
"""Pop the dialog and return the result.""" """Pop the dialog and return the result."""
cls = GuiDocMerge(parent, handle, items) dialog = cls(parent, handle, items)
cls.exec() dialog.exec()
data = cls.data() data = dialog.data()
accepted = cls.result() == QtAccepted accepted = dialog.result() == QtAccepted
cls.softDelete() dialog.softDelete()
return data, accepted return data, accepted
## ##
+5 -5
View File
@@ -178,11 +178,11 @@ class GuiDocSplit(NDialog):
@classmethod @classmethod
def getData(cls, parent: QWidget, handle: str) -> tuple[dict, list[str], bool]: def getData(cls, parent: QWidget, handle: str) -> tuple[dict, list[str], bool]:
"""Pop the dialog and return the result.""" """Pop the dialog and return the result."""
cls = GuiDocSplit(parent, handle) dialog = cls(parent, handle)
cls.exec() dialog.exec()
data, text = cls.data() data, text = dialog.data()
accepted = cls.result() == QtAccepted accepted = dialog.result() == QtAccepted
cls.softDelete() dialog.softDelete()
return data, text, accepted return data, text, accepted
## ##
+5 -5
View File
@@ -82,9 +82,9 @@ class GuiEditLabel(NDialog):
@classmethod @classmethod
def getLabel(cls, parent: QWidget, text: str) -> tuple[str, bool]: def getLabel(cls, parent: QWidget, text: str) -> tuple[str, bool]:
"""Pop the dialog and return the result.""" """Pop the dialog and return the result."""
cls = GuiEditLabel(parent, text=text) dialog = cls(parent, text=text)
cls.exec() dialog.exec()
label = cls.itemLabel label = dialog.itemLabel
accepted = cls.result() == QtAccepted accepted = dialog.result() == QtAccepted
cls.softDelete() dialog.softDelete()
return label, accepted return label, accepted
+3 -3
View File
@@ -432,7 +432,7 @@ class GuiPreferences(NDialog):
self.textWidth.setSingleStep(10) self.textWidth.setSingleStep(10)
self.textWidth.setValue(CONFIG.textWidth) self.textWidth.setValue(CONFIG.textWidth)
self.mainForm.addRow( self.mainForm.addRow(
self.tr("Maximum text width in \"Normal Mode\""), self.textWidth, self.tr('Maximum text width in "Normal Mode"'), self.textWidth,
self.tr("Set to 0 to disable this feature."), unit=self.tr("px") self.tr("Set to 0 to disable this feature."), unit=self.tr("px")
) )
@@ -443,7 +443,7 @@ class GuiPreferences(NDialog):
self.focusWidth.setSingleStep(10) self.focusWidth.setSingleStep(10)
self.focusWidth.setValue(CONFIG.focusWidth) self.focusWidth.setValue(CONFIG.focusWidth)
self.mainForm.addRow( self.mainForm.addRow(
self.tr("Maximum text width in \"Focus Mode\""), self.focusWidth, self.tr('Maximum text width in "Focus Mode"'), self.focusWidth,
self.tr("The maximum width cannot be disabled."), unit=self.tr("px") self.tr("The maximum width cannot be disabled."), unit=self.tr("px")
) )
@@ -451,7 +451,7 @@ class GuiPreferences(NDialog):
self.hideFocusFooter = NSwitch(self) self.hideFocusFooter = NSwitch(self)
self.hideFocusFooter.setChecked(CONFIG.hideFocusFooter) self.hideFocusFooter.setChecked(CONFIG.hideFocusFooter)
self.mainForm.addRow( self.mainForm.addRow(
self.tr("Hide document footer in \"Focus Mode\""), self.hideFocusFooter, self.tr('Hide document footer in "Focus Mode"'), self.hideFocusFooter,
self.tr("Hide the information bar in the document editor.") self.tr("Hide the information bar in the document editor.")
) )
+5 -5
View File
@@ -122,11 +122,11 @@ class GuiQuoteSelect(NDialog):
@classmethod @classmethod
def getQuote(cls, parent: QWidget, current: str = "") -> tuple[str, bool]: def getQuote(cls, parent: QWidget, current: str = "") -> tuple[str, bool]:
"""Pop the dialog and return the result.""" """Pop the dialog and return the result."""
cls = GuiQuoteSelect(parent, current=current) dialog = cls(parent, current=current)
cls.exec() dialog.exec()
quote = cls._selected quote = dialog._selected
accepted = cls.result() == QtAccepted accepted = dialog.result() == QtAccepted
cls.softDelete() dialog.softDelete()
return quote, accepted return quote, accepted
## ##
+8 -6
View File
@@ -26,9 +26,9 @@ from __future__ import annotations
import logging import logging
from pathlib import Path from pathlib import Path
from typing import TYPE_CHECKING
from PyQt6.QtCore import Qt, pyqtSignal, pyqtSlot from PyQt6.QtCore import Qt, pyqtSignal, pyqtSlot
from PyQt6.QtGui import QCloseEvent
from PyQt6.QtWidgets import ( from PyQt6.QtWidgets import (
QAbstractItemView, QApplication, QDialogButtonBox, QFileDialog, QAbstractItemView, QApplication, QDialogButtonBox, QFileDialog,
QHBoxLayout, QLineEdit, QListWidget, QVBoxLayout, QWidget QHBoxLayout, QLineEdit, QListWidget, QVBoxLayout, QWidget
@@ -41,6 +41,9 @@ from novelwriter.extensions.configlayout import NColorLabel
from novelwriter.extensions.modified import NDialog, NIconToolButton from novelwriter.extensions.modified import NDialog, NIconToolButton
from novelwriter.types import QtDialogClose, QtDialogSave from novelwriter.types import QtDialogClose, QtDialogSave
if TYPE_CHECKING:
from PyQt6.QtGui import QCloseEvent
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -242,8 +245,7 @@ class GuiWordList(NDialog):
def _listWords(self) -> list[str]: def _listWords(self) -> list[str]:
"""List all words in the list box.""" """List all words in the list box."""
result = [] return [
for i in range(self.listBox.count()): word for i in range(self.listBox.count())
if (item := self.listBox.item(i)) and (word := item.text().strip()): if (item := self.listBox.item(i)) and (word := item.text().strip())
result.append(word) ]
return result
+4 -4
View File
@@ -36,7 +36,7 @@ from PyQt6.QtWidgets import (
QPlainTextEdit, QStyle, QWidget QPlainTextEdit, QStyle, QWidget
) )
if TYPE_CHECKING: # pragma: no cover if TYPE_CHECKING:
from types import TracebackType from types import TracebackType
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -46,7 +46,7 @@ def logException() -> None:
"""Log the content of an exception message.""" """Log the content of an exception message."""
exType, exValue, _ = sys.exc_info() exType, exValue, _ = sys.exc_info()
if exType is not None: if exType is not None:
logger.error(f"{exType.__name__}: {str(exValue)}", stacklevel=2) logger.error(f"{exType.__name__}: {exValue!s}", stacklevel=2)
return return
@@ -54,7 +54,7 @@ def formatException(exc: BaseException) -> str:
"""Format an exception as a string the same way the default """Format an exception as a string the same way the default
exception handler does. exception handler does.
""" """
return f"{type(exc).__name__}: {str(exc)}" return f"{type(exc).__name__}: {exc!s}"
class NWErrorMessage(QDialog): class NWErrorMessage(QDialog):
@@ -151,7 +151,7 @@ class NWErrorMessage(QDialog):
f"Python: {sys.version.split()[0]} ({sys.hexversion:#x})\n" f"Python: {sys.version.split()[0]} ({sys.hexversion:#x})\n"
f"Qt: {QT_VERSION_STR}, PyQt: {PYQT_VERSION_STR}\n" f"Qt: {QT_VERSION_STR}, PyQt: {PYQT_VERSION_STR}\n"
f"enchant: {enchantVersion}\n\n" f"enchant: {enchantVersion}\n\n"
f"{exType.__name__}:\n{str(exValue)}\n\n" f"{exType.__name__}:\n{exValue!s}\n\n"
f"Traceback:\n{txtTrace}\n" f"Traceback:\n{txtTrace}\n"
) )
except Exception: except Exception:
+6 -2
View File
@@ -24,9 +24,13 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
from __future__ import annotations from __future__ import annotations
from typing import TYPE_CHECKING
from PyQt6.QtCore import QEvent, QObject from PyQt6.QtCore import QEvent, QObject
from PyQt6.QtGui import QStatusTipEvent, QWheelEvent from PyQt6.QtGui import QStatusTipEvent, QWheelEvent
from PyQt6.QtWidgets import QWidget
if TYPE_CHECKING:
from PyQt6.QtWidgets import QWidget
class WheelEventFilter(QObject): class WheelEventFilter(QObject):
@@ -40,7 +44,7 @@ class WheelEventFilter(QObject):
Reference: https://stackoverflow.com/a/17739995/5825851 Reference: https://stackoverflow.com/a/17739995/5825851
""" """
__slots__ = ("_parent", "_locked") __slots__ = ("_locked", "_parent")
def __init__(self, parent: QWidget) -> None: def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent) super().__init__(parent=parent)
+5 -3
View File
@@ -27,11 +27,9 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
from __future__ import annotations from __future__ import annotations
from enum import Enum
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from PyQt6.QtCore import QModelIndex, QSize, Qt, pyqtSignal, pyqtSlot from PyQt6.QtCore import QModelIndex, QSize, Qt, pyqtSignal, pyqtSlot
from PyQt6.QtGui import QMouseEvent, QWheelEvent
from PyQt6.QtWidgets import ( from PyQt6.QtWidgets import (
QApplication, QComboBox, QDialog, QDoubleSpinBox, QLabel, QSpinBox, QApplication, QComboBox, QDialog, QDoubleSpinBox, QLabel, QSpinBox,
QToolButton, QTreeView, QWidget QToolButton, QTreeView, QWidget
@@ -40,7 +38,11 @@ from PyQt6.QtWidgets import (
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
from novelwriter.types import QtMouseLeft, QtMouseMiddle from novelwriter.types import QtMouseLeft, QtMouseMiddle
if TYPE_CHECKING: # pragma: no cover if TYPE_CHECKING:
from enum import Enum
from PyQt6.QtGui import QMouseEvent, QWheelEvent
from novelwriter.guimain import GuiMain from novelwriter.guimain import GuiMain
+1 -1
View File
@@ -113,7 +113,7 @@ class NPagedSideBar(QToolBar):
class _PagedToolButton(QToolButton): class _PagedToolButton(QToolButton):
__slots__ = ("_bH", "_tM", "_aH") __slots__ = ("_aH", "_bH", "_tM")
def __init__(self, parent: QWidget) -> None: def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent) super().__init__(parent=parent)
+2 -2
View File
@@ -44,8 +44,8 @@ class NProgressCircle(QProgressBar):
""" """
__slots__ = ( __slots__ = (
"_text", "_point", "_dRect", "_cRect", "_dPen", "_dBrush", "_bPen", "_cPen", "_cRect", "_dBrush", "_dPen", "_dRect", "_point",
"_cPen", "_bPen", "_tColor" "_tColor", "_text",
) )
def __init__(self, parent: QWidget, size: int, point: int) -> None: def __init__(self, parent: QWidget, size: int, point: int) -> None:
+1 -1
View File
@@ -35,7 +35,7 @@ logger = logging.getLogger(__name__)
class StatusLED(QAbstractButton): class StatusLED(QAbstractButton):
__slots__ = ("_neutral", "_postitve", "_negative", "_color", "_state") __slots__ = ("_color", "_negative", "_neutral", "_postitve", "_state")
def __init__(self, sW: int, sH: int, parent: QWidget | None = None) -> None: def __init__(self, sW: int, sH: int, parent: QWidget | None = None) -> None:
super().__init__(parent=parent) super().__init__(parent=parent)
+1 -1
View File
@@ -33,7 +33,7 @@ from novelwriter.types import QtMouseLeft, QtNoPen, QtPaintAntiAlias, QtSizeFixe
class NSwitch(QAbstractButton): class NSwitch(QAbstractButton):
__slots__ = ("_xW", "_xH", "_xR", "_rH", "_rR", "_offset") __slots__ = ("_offset", "_rH", "_rR", "_xH", "_xR", "_xW")
def __init__(self, parent: QWidget, height: int = 0) -> None: def __init__(self, parent: QWidget, height: int = 0) -> None:
super().__init__(parent=parent) super().__init__(parent=parent)
+5 -1
View File
@@ -23,8 +23,9 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
from __future__ import annotations from __future__ import annotations
from typing import TYPE_CHECKING
from PyQt6.QtCore import pyqtSignal from PyQt6.QtCore import pyqtSignal
from PyQt6.QtGui import QIcon
from PyQt6.QtWidgets import QGridLayout, QLabel, QScrollArea, QWidget from PyQt6.QtWidgets import QGridLayout, QLabel, QScrollArea, QWidget
from novelwriter.extensions.switch import NSwitch from novelwriter.extensions.switch import NSwitch
@@ -33,6 +34,9 @@ from novelwriter.types import (
QtSizeMinimumExpanding QtSizeMinimumExpanding
) )
if TYPE_CHECKING:
from PyQt6.QtGui import QIcon
class NSwitchBox(QScrollArea): class NSwitchBox(QScrollArea):
"""Extension: Switch Box Widget """Extension: Switch Box Widget
+11 -7
View File
@@ -29,21 +29,25 @@ import re
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
from datetime import datetime from datetime import datetime
from pathlib import Path from typing import TYPE_CHECKING, NamedTuple
from typing import NamedTuple
from zipfile import ZIP_DEFLATED, ZipFile from zipfile import ZIP_DEFLATED, ZipFile
from PyQt6.QtCore import QMargins, QSize from PyQt6.QtCore import QMargins, QSize
from PyQt6.QtGui import QColor
from novelwriter import __version__ from novelwriter import __version__
from novelwriter.common import firstFloat, xmlElement, xmlSubElem from novelwriter.common import firstFloat, xmlElement, xmlSubElem
from novelwriter.constants import nwHeadFmt, nwStyles from novelwriter.constants import nwHeadFmt, nwStyles
from novelwriter.core.project import NWProject
from novelwriter.formats.shared import BlockFmt, BlockTyp, T_Formats, TextFmt from novelwriter.formats.shared import BlockFmt, BlockTyp, T_Formats, TextFmt
from novelwriter.formats.tokenizer import Tokenizer from novelwriter.formats.tokenizer import Tokenizer
from novelwriter.types import QtHexRgb from novelwriter.types import QtHexRgb
if TYPE_CHECKING:
from pathlib import Path
from PyQt6.QtGui import QColor
from novelwriter.core.project import NWProject
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# RegEx # RegEx
@@ -1052,9 +1056,9 @@ class ToDocX(Tokenizer):
class DocXParagraph: class DocXParagraph:
__slots__ = ( __slots__ = (
"_content", "_style", "_textAlign", "_bottomMargin", "_breakAfter", "_breakBefore", "_content",
"_topMargin", "_bottomMargin", "_leftMargin", "_rightMargin", "_footnoteRef", "_indentFirst", "_leftMargin", "_rightMargin",
"_indentFirst", "_breakBefore", "_breakAfter", "_footnoteRef", "_style", "_textAlign", "_topMargin",
) )
def __init__(self) -> None: def __init__(self) -> None:
+7 -5
View File
@@ -26,16 +26,20 @@ from __future__ import annotations
import json import json
import logging import logging
from pathlib import Path
from time import time from time import time
from typing import TYPE_CHECKING
from novelwriter.common import formatTimeStamp from novelwriter.common import formatTimeStamp
from novelwriter.constants import nwHtmlUnicode, nwStyles from novelwriter.constants import nwHtmlUnicode, nwStyles
from novelwriter.core.project import NWProject
from novelwriter.formats.shared import BlockFmt, BlockTyp, T_Formats, TextFmt, stripEscape from novelwriter.formats.shared import BlockFmt, BlockTyp, T_Formats, TextFmt, stripEscape
from novelwriter.formats.tokenizer import Tokenizer from novelwriter.formats.tokenizer import Tokenizer
from novelwriter.types import FONT_STYLE, FONT_WEIGHTS, QtHexRgb from novelwriter.types import FONT_STYLE, FONT_WEIGHTS, QtHexRgb
if TYPE_CHECKING:
from pathlib import Path
from novelwriter.core.project import NWProject
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# Each opener tag, with the id of its corresponding closer and tag format # Each opener tag, with the id of its corresponding closer and tag format
@@ -311,10 +315,8 @@ class ToHtml(Tokenizer):
def replaceTabs(self, nSpaces: int = 8, spaceChar: str = "&nbsp;") -> None: def replaceTabs(self, nSpaces: int = 8, spaceChar: str = "&nbsp;") -> None:
"""Replace tabs with spaces in the html.""" """Replace tabs with spaces in the html."""
pages = []
tabSpace = spaceChar*nSpaces tabSpace = spaceChar*nSpaces
for aLine in self._pages: pages = [aLine.replace("\t", tabSpace) for aLine in self._pages]
pages.append(aLine.replace("\t", tabSpace))
self._pages = pages self._pages = pages
return return
+12 -11
View File
@@ -28,8 +28,7 @@ import logging
import re import re
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from pathlib import Path from typing import TYPE_CHECKING, NamedTuple
from typing import NamedTuple
from PyQt6.QtCore import QLocale from PyQt6.QtCore import QLocale
from PyQt6.QtGui import QColor, QFont from PyQt6.QtGui import QColor, QFont
@@ -40,7 +39,6 @@ from novelwriter.constants import (
nwHeadFmt, nwKeyWords, nwLabels, nwShortcode, nwStats, nwStyles, nwUnicode, nwHeadFmt, nwKeyWords, nwLabels, nwShortcode, nwStats, nwStyles, nwUnicode,
trConst trConst
) )
from novelwriter.core.project import NWProject
from novelwriter.enum import nwComment, nwItemLayout from novelwriter.enum import nwComment, nwItemLayout
from novelwriter.formats.shared import ( from novelwriter.formats.shared import (
BlockFmt, BlockTyp, T_Block, T_Formats, T_Note, TextDocumentTheme, TextFmt BlockFmt, BlockTyp, T_Block, T_Formats, T_Note, TextDocumentTheme, TextFmt
@@ -48,6 +46,11 @@ from novelwriter.formats.shared import (
from novelwriter.text.comments import processComment from novelwriter.text.comments import processComment
from novelwriter.text.patterns import REGEX_PATTERNS, DialogParser from novelwriter.text.patterns import REGEX_PATTERNS, DialogParser
if TYPE_CHECKING:
from pathlib import Path
from novelwriter.core.project import NWProject
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -72,7 +75,7 @@ HEADINGS = [
BlockTyp.TITLE, BlockTyp.PART, BlockTyp.HEAD1, BlockTyp.TITLE, BlockTyp.PART, BlockTyp.HEAD1,
BlockTyp.HEAD2, BlockTyp.HEAD3, BlockTyp.HEAD4, BlockTyp.HEAD2, BlockTyp.HEAD3, BlockTyp.HEAD4,
] ]
SKIP_INDENT = HEADINGS + [BlockTyp.SEP, BlockTyp.SKIP] SKIP_INDENT = [*HEADINGS, BlockTyp.SEP, BlockTyp.SKIP]
B_EMPTY: T_Block = (BlockTyp.EMPTY, "", "", [], BlockFmt.NONE) B_EMPTY: T_Block = (BlockTyp.EMPTY, "", "", [], BlockFmt.NONE)
@@ -651,7 +654,7 @@ class Tokenizer(ABC):
tText = aLine[2:].strip() tText = aLine[2:].strip()
tType = BlockTyp.HEAD1 if isPlain else BlockTyp.TITLE tType = BlockTyp.HEAD1 if isPlain else BlockTyp.TITLE
sHide = self._hidePart if isPlain else False sHide = self._hidePart if isPlain else False
if not (isPlain or isNovel and sHide): if not (isPlain or (isNovel and sHide)):
tStyle |= self._titleStyle tStyle |= self._titleStyle
if isNovel: if isNovel:
tType = BlockTyp.PART if isPlain else BlockTyp.TITLE tType = BlockTyp.PART if isPlain else BlockTyp.TITLE
@@ -1124,12 +1127,10 @@ class Tokenizer(ABC):
temp.append((res.end(0), 0, TextFmt.HRF_E, "")) temp.append((res.end(0), 0, TextFmt.HRF_E, ""))
# Match Shortcodes # Match Shortcodes
for res in REGEX_PATTERNS.shortcodePlain.finditer(text): temp.extend(
temp.append(( (res.start(1), res.end(1), self._shortCodeFmt.get(res.group(1).lower(), 0), "")
res.start(1), res.end(1), for res in REGEX_PATTERNS.shortcodePlain.finditer(text)
self._shortCodeFmt.get(res.group(1).lower(), 0), )
"",
))
# Match Shortcode w/Values # Match Shortcode w/Values
tHandle = self._handle or "" tHandle = self._handle or ""
+6 -2
View File
@@ -25,13 +25,17 @@ from __future__ import annotations
import logging import logging
from pathlib import Path from typing import TYPE_CHECKING
from novelwriter.constants import nwUnicode from novelwriter.constants import nwUnicode
from novelwriter.core.project import NWProject
from novelwriter.formats.shared import BlockFmt, BlockTyp, T_Formats, TextFmt from novelwriter.formats.shared import BlockFmt, BlockTyp, T_Formats, TextFmt
from novelwriter.formats.tokenizer import Tokenizer from novelwriter.formats.tokenizer import Tokenizer
if TYPE_CHECKING:
from pathlib import Path
from novelwriter.core.project import NWProject
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
+22 -18
View File
@@ -29,10 +29,9 @@ from __future__ import annotations
import logging import logging
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
from collections.abc import Sequence
from datetime import datetime from datetime import datetime
from hashlib import sha256 from hashlib import sha256
from pathlib import Path from typing import TYPE_CHECKING, Final
from zipfile import ZIP_DEFLATED, ZipFile from zipfile import ZIP_DEFLATED, ZipFile
from PyQt6.QtGui import QColor, QFont from PyQt6.QtGui import QColor, QFont
@@ -40,11 +39,16 @@ from PyQt6.QtGui import QColor, QFont
from novelwriter import __version__ from novelwriter import __version__
from novelwriter.common import xmlElement, xmlIndent, xmlSubElem from novelwriter.common import xmlElement, xmlIndent, xmlSubElem
from novelwriter.constants import nwHeadFmt, nwStyles from novelwriter.constants import nwHeadFmt, nwStyles
from novelwriter.core.project import NWProject
from novelwriter.formats.shared import BlockFmt, BlockTyp, TextFmt, stripEscape from novelwriter.formats.shared import BlockFmt, BlockTyp, TextFmt, stripEscape
from novelwriter.formats.tokenizer import Tokenizer from novelwriter.formats.tokenizer import Tokenizer
from novelwriter.types import FONT_STYLE, QtHexRgb from novelwriter.types import FONT_STYLE, QtHexRgb
if TYPE_CHECKING:
from collections.abc import Sequence
from pathlib import Path
from novelwriter.core.project import NWProject
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# Main XML NameSpaces # Main XML NameSpaces
@@ -1000,11 +1004,11 @@ class ODTParagraphStyle:
exporter. Only the used settings are exposed here to keep the class exporter. Only the used settings are exposed here to keep the class
minimal and fast. minimal and fast.
""" """
VALID_ALIGN = ["start", "center", "end", "justify", "inside", "outside", "left", "right"] VALID_ALIGN: Final[list[str]] = ["start", "center", "end", "justify", "left", "right"]
VALID_BREAK = ["auto", "column", "page", "even-page", "odd-page", "inherit"] VALID_BREAK: Final[list[str]] = ["auto", "page", "even-page", "odd-page", "inherit"]
VALID_LEVEL = ["1", "2", "3", "4"] VALID_LEVEL: Final[list[str]] = ["1", "2", "3", "4"]
VALID_CLASS = ["text", "chapter", "extra"] VALID_CLASS: Final[list[str]] = ["text", "chapter", "extra"]
VALID_WEIGHT = ["normal", "bold"] + FONT_WEIGHT_NUM VALID_WEIGHT: Final[list[str]] = ["normal", "bold", *FONT_WEIGHT_NUM]
def __init__(self, name: str) -> None: def __init__(self, name: str) -> None:
@@ -1207,9 +1211,9 @@ class ODTParagraphStyle:
def getID(self) -> str: def getID(self) -> str:
"""Generate a unique ID from the settings.""" """Generate a unique ID from the settings."""
return sha256(( return sha256((
f"Paragraph:Main:{str(self._mAttr)}:" f"Paragraph:Main:{self._mAttr!s}:"
f"Paragraph:Para:{str(self._pAttr)}:" f"Paragraph:Para:{self._pAttr!s}:"
f"Paragraph:Text:{str(self._tAttr)}:" f"Paragraph:Text:{self._tAttr!s}:"
).encode()).hexdigest() ).encode()).hexdigest()
def packXML(self, xParent: ET.Element) -> None: def packXML(self, xParent: ET.Element) -> None:
@@ -1237,13 +1241,13 @@ class ODTTextStyle:
Only the used settings are exposed here to keep the class minimal Only the used settings are exposed here to keep the class minimal
and fast. and fast.
""" """
VALID_WEIGHT = ["normal", "bold"] + FONT_WEIGHT_NUM VALID_WEIGHT: Final[list[str]] = ["normal", "bold", *FONT_WEIGHT_NUM]
VALID_STYLE = ["normal", "italic", "oblique"] VALID_STYLE: Final[list[str]] = ["normal", "italic", "oblique"]
VALID_POS = ["super", "sub"] VALID_POS: Final[list[str]] = ["super", "sub"]
VALID_LSTYLE = ["none", "solid"] VALID_LSTYLE: Final[list[str]] = ["none", "solid"]
VALID_LTYPE = ["single", "double"] VALID_LTYPE: Final[list[str]] = ["single", "double"]
VALID_LWIDTH = ["auto"] VALID_LWIDTH: Final[list[str]] = ["auto"]
VALID_LCOL = ["font-color"] VALID_LCOL: Final[list[str]] = ["font-color"]
def __init__(self, name: str) -> None: def __init__(self, name: str) -> None:
self._name = name self._name = name
+6 -2
View File
@@ -25,7 +25,7 @@ from __future__ import annotations
import logging import logging
from pathlib import Path from typing import TYPE_CHECKING
from PyQt6.QtCore import QMarginsF, QSizeF from PyQt6.QtCore import QMarginsF, QSizeF
from PyQt6.QtGui import ( from PyQt6.QtGui import (
@@ -36,7 +36,6 @@ from PyQt6.QtPrintSupport import QPrinter
from novelwriter import __version__ from novelwriter import __version__
from novelwriter.constants import nwStyles, nwUnicode from novelwriter.constants import nwStyles, nwUnicode
from novelwriter.core.project import NWProject
from novelwriter.formats.shared import BlockFmt, BlockTyp, T_Formats, TextFmt from novelwriter.formats.shared import BlockFmt, BlockTyp, T_Formats, TextFmt
from novelwriter.formats.tokenizer import HEADINGS, Tokenizer from novelwriter.formats.tokenizer import HEADINGS, Tokenizer
from novelwriter.types import ( from novelwriter.types import (
@@ -45,6 +44,11 @@ from novelwriter.types import (
QtPropLineHeight, QtTransparent, QtVAlignNormal, QtVAlignSub, QtVAlignSuper QtPropLineHeight, QtTransparent, QtVAlignNormal, QtVAlignSub, QtVAlignSuper
) )
if TYPE_CHECKING:
from pathlib import Path
from novelwriter.core.project import NWProject
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
T_TextStyle = tuple[QTextBlockFormat, QTextCharFormat] T_TextStyle = tuple[QTextBlockFormat, QTextCharFormat]
+6 -2
View File
@@ -26,13 +26,17 @@ from __future__ import annotations
import json import json
import logging import logging
from pathlib import Path
from time import time from time import time
from typing import TYPE_CHECKING
from novelwriter.common import formatTimeStamp from novelwriter.common import formatTimeStamp
from novelwriter.core.project import NWProject
from novelwriter.formats.tokenizer import Tokenizer from novelwriter.formats.tokenizer import Tokenizer
if TYPE_CHECKING:
from pathlib import Path
from novelwriter.core.project import NWProject
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
+10 -10
View File
@@ -97,10 +97,10 @@ class GuiDocEditor(QPlainTextEdit):
"""Gui Widget: Main Document Editor""" """Gui Widget: Main Document Editor"""
__slots__ = ( __slots__ = (
"_nwDocument", "_nwItem", "_docChanged", "_docHandle", "_vpMargin", "_autoReplace", "_completer", "_doReplace", "_docChanged", "_docHandle", "_followTag1",
"_lastEdit", "_lastActive", "_lastFind", "_doReplace", "_autoReplace", "_followTag2", "_keyContext", "_lastActive", "_lastEdit", "_lastFind", "_nwDocument",
"_completer", "_qDocument", "_keyContext", "_followTag1", "_followTag2", "_nwItem", "_qDocument", "_timerDoc", "_timerSel", "_vpMargin", "_wCounterDoc",
"_timerDoc", "_wCounterDoc", "_timerSel", "_wCounterSel", "_wCounterSel",
) )
MOVE_KEYS = ( MOVE_KEYS = (
@@ -760,7 +760,7 @@ class GuiDocEditor(QPlainTextEdit):
elif action == nwDocAction.REPL_SNG: elif action == nwDocAction.REPL_SNG:
self._replaceQuotes("'", CONFIG.fmtSQuoteOpen, CONFIG.fmtSQuoteClose) self._replaceQuotes("'", CONFIG.fmtSQuoteOpen, CONFIG.fmtSQuoteClose)
elif action == nwDocAction.REPL_DBL: elif action == nwDocAction.REPL_DBL:
self._replaceQuotes("\"", CONFIG.fmtDQuoteOpen, CONFIG.fmtDQuoteClose) self._replaceQuotes('"', CONFIG.fmtDQuoteOpen, CONFIG.fmtDQuoteClose)
elif action == nwDocAction.RM_BREAKS: elif action == nwDocAction.RM_BREAKS:
self._removeInParLineBreaks() self._removeInParLineBreaks()
elif action == nwDocAction.ALIGN_L: elif action == nwDocAction.ALIGN_L:
@@ -2003,7 +2003,7 @@ class GuiDocEditor(QPlainTextEdit):
sPos = cPos - i - 1 sPos = cPos - i - 1
cOne = str(self._qDocument.characterAt(sPos)) cOne = str(self._qDocument.characterAt(sPos))
cTwo = str(self._qDocument.characterAt(sPos - 1)) cTwo = str(self._qDocument.characterAt(sPos - 1))
if not (cOne.isalnum() or cOne in apos and cTwo.isalnum()): if not (cOne.isalnum() or (cOne in apos and cTwo.isalnum())):
sPos += 1 sPos += 1
break break
@@ -2013,7 +2013,7 @@ class GuiDocEditor(QPlainTextEdit):
ePos = cPos + i ePos = cPos + i
cOne = str(self._qDocument.characterAt(ePos)) cOne = str(self._qDocument.characterAt(ePos))
cTwo = str(self._qDocument.characterAt(ePos + 1)) cTwo = str(self._qDocument.characterAt(ePos + 1))
if not (cOne.isalnum() or cOne in apos and cTwo.isalnum()): if not (cOne.isalnum() or (cOne in apos and cTwo.isalnum())):
break break
if ePos - sPos <= 0: if ePos - sPos <= 0:
@@ -2193,9 +2193,9 @@ class BackgroundWordCounterSignals(QObject):
class TextAutoReplace: class TextAutoReplace:
__slots__ = ( __slots__ = (
"_quoteSO", "_quoteSC", "_quoteDO", "_quoteDC", "_doPadAfter", "_doPadBefore", "_padAfter", "_padBefore", "_padChar",
"_replaceSQuote", "_replaceDQuote", "_replaceDash", "_replaceDots", "_quoteDC", "_quoteDO", "_quoteSC", "_quoteSO", "_replaceDQuote",
"_padChar", "_padBefore", "_padAfter", "_doPadBefore", "_doPadAfter", "_replaceDash", "_replaceDots", "_replaceSQuote",
) )
def __init__(self) -> None: def __init__(self) -> None:
+3 -3
View File
@@ -58,8 +58,8 @@ BLOCK_TITLE = 4
class GuiDocHighlighter(QSyntaxHighlighter): class GuiDocHighlighter(QSyntaxHighlighter):
__slots__ = ( __slots__ = (
"_tHandle", "_isNovel", "_isInactive", "_spellCheck", "_spellErr", "_cmnRules", "_dialogParser", "_hStyles", "_isInactive", "_isNovel",
"_hStyles", "_minRules", "_txtRules", "_cmnRules", "_dialogParser", "_minRules", "_spellCheck", "_spellErr", "_tHandle", "_txtRules",
) )
def __init__(self, document: QTextDocument) -> None: def __init__(self, document: QTextDocument) -> None:
@@ -466,7 +466,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
class TextBlockData(QTextBlockUserData): class TextBlockData(QTextBlockUserData):
__slots__ = ("_text", "_offset", "_metaData", "_spellErrors") __slots__ = ("_metaData", "_offset", "_spellErrors", "_text")
def __init__(self) -> None: def __init__(self) -> None:
super().__init__() super().__init__()
+4 -1
View File
@@ -26,6 +26,7 @@ from __future__ import annotations
import logging import logging
from enum import Enum from enum import Enum
from typing import TYPE_CHECKING
from PyQt6.QtCore import QModelIndex, Qt, pyqtSignal, pyqtSlot from PyQt6.QtCore import QModelIndex, Qt, pyqtSignal, pyqtSlot
from PyQt6.QtWidgets import ( from PyQt6.QtWidgets import (
@@ -36,12 +37,14 @@ from PyQt6.QtWidgets import (
from novelwriter import SHARED from novelwriter import SHARED
from novelwriter.common import checkInt, qtAddAction from novelwriter.common import checkInt, qtAddAction
from novelwriter.constants import nwLabels, nwLists, nwStyles, trConst from novelwriter.constants import nwLabels, nwLists, nwStyles, trConst
from novelwriter.core.indexdata import IndexHeading, IndexNode
from novelwriter.enum import nwChange, nwDocMode, nwItemClass from novelwriter.enum import nwChange, nwDocMode, nwItemClass
from novelwriter.extensions.modified import NIconToolButton from novelwriter.extensions.modified import NIconToolButton
from novelwriter.gui.theme import STYLES_FLAT_TABS, STYLES_MIN_TOOLBUTTON from novelwriter.gui.theme import STYLES_FLAT_TABS, STYLES_MIN_TOOLBUTTON
from novelwriter.types import QtDecoration, QtHeaderFixed, QtHeaderToContents, QtUserRole from novelwriter.types import QtDecoration, QtHeaderFixed, QtHeaderToContents, QtUserRole
if TYPE_CHECKING:
from novelwriter.core.indexdata import IndexHeading, IndexNode
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
+4 -1
View File
@@ -25,8 +25,8 @@ from __future__ import annotations
import logging import logging
from collections.abc import Iterable
from time import time from time import time
from typing import TYPE_CHECKING
from PyQt6.QtCore import QObject, pyqtSlot from PyQt6.QtCore import QObject, pyqtSlot
from PyQt6.QtGui import QTextBlock, QTextCursor, QTextDocument from PyQt6.QtGui import QTextBlock, QTextCursor, QTextDocument
@@ -35,6 +35,9 @@ from PyQt6.QtWidgets import QApplication, QPlainTextDocumentLayout
from novelwriter import SHARED from novelwriter import SHARED
from novelwriter.gui.dochighlight import GuiDocHighlighter, TextBlockData from novelwriter.gui.dochighlight import GuiDocHighlighter, TextBlockData
if TYPE_CHECKING:
from collections.abc import Iterable
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
+1 -1
View File
@@ -228,7 +228,7 @@ class GuiItemDetails(QWidget):
return return
self._handle = tHandle self._handle = tHandle
iPx = int(round(0.9*SHARED.theme.baseIconHeight)) iPx = round(0.9*SHARED.theme.baseIconHeight)
# Label # Label
# ===== # =====
+3 -3
View File
@@ -41,7 +41,7 @@ from novelwriter.constants import (
from novelwriter.enum import nwDocAction, nwDocInsert, nwFocus, nwView from novelwriter.enum import nwDocAction, nwDocInsert, nwFocus, nwView
from novelwriter.extensions.eventfilters import StatusTipFilter from novelwriter.extensions.eventfilters import StatusTipFilter
if TYPE_CHECKING: # pragma: no cover if TYPE_CHECKING:
from novelwriter.guimain import GuiMain from novelwriter.guimain import GuiMain
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -471,7 +471,7 @@ class GuiMainMenu(QMenuBar):
# Insert > Double Prime # Insert > Double Prime
self.aInsDPrime = qtAddAction(self.mInsPunct, self.tr("Double Prime")) self.aInsDPrime = qtAddAction(self.mInsPunct, self.tr("Double Prime"))
self.aInsDPrime.setShortcut("Ctrl+K, Ctrl+\"") self.aInsDPrime.setShortcut('Ctrl+K, Ctrl+"')
self.aInsDPrime.triggered.connect( self.aInsDPrime.triggered.connect(
lambda: self.requestDocInsertText.emit(nwUnicode.U_DPRIME) lambda: self.requestDocInsertText.emit(nwUnicode.U_DPRIME)
) )
@@ -680,7 +680,7 @@ class GuiMainMenu(QMenuBar):
# Format > Double Quotes # Format > Double Quotes
self.aFmtDQuote = qtAddAction(self.fmtMenu, self.tr("Wrap Double Quotes")) self.aFmtDQuote = qtAddAction(self.fmtMenu, self.tr("Wrap Double Quotes"))
self.aFmtDQuote.setShortcut("Ctrl+\"") self.aFmtDQuote.setShortcut('Ctrl+"')
self.aFmtDQuote.triggered.connect( self.aFmtDQuote.triggered.connect(
lambda: self.requestDocAction.emit(nwDocAction.D_QUOTE) lambda: self.requestDocAction.emit(nwDocAction.D_QUOTE)
) )
+6 -5
View File
@@ -32,6 +32,7 @@ import logging
from enum import Enum from enum import Enum
from time import time from time import time
from typing import Final
from PyQt6.QtCore import QT_TRANSLATE_NOOP, Qt, pyqtSignal, pyqtSlot from PyQt6.QtCore import QT_TRANSLATE_NOOP, Qt, pyqtSignal, pyqtSlot
from PyQt6.QtGui import QAction, QIcon from PyQt6.QtGui import QAction, QIcon
@@ -319,7 +320,7 @@ class GuiOutlineToolBar(QToolBar):
class GuiOutlineTree(QTreeWidget): class GuiOutlineTree(QTreeWidget):
DEF_WIDTH = { DEF_WIDTH: Final[dict[nwOutline, int]] = {
nwOutline.TITLE: 200, nwOutline.TITLE: 200,
nwOutline.LEVEL: 40, nwOutline.LEVEL: 40,
nwOutline.LABEL: 150, nwOutline.LABEL: 150,
@@ -342,7 +343,7 @@ class GuiOutlineTree(QTreeWidget):
nwOutline.SYNOP: 200, nwOutline.SYNOP: 200,
} }
DEF_HIDDEN = { DEF_HIDDEN: Final[dict[nwOutline, bool]] = {
nwOutline.TITLE: False, nwOutline.TITLE: False,
nwOutline.LEVEL: True, nwOutline.LEVEL: True,
nwOutline.LABEL: False, nwOutline.LABEL: False,
@@ -498,7 +499,7 @@ class GuiOutlineTree(QTreeWidget):
tree. tree.
""" """
# If it's the first time, we always build # If it's the first time, we always build
if self._firstView or self._firstView and overRide: if self._firstView or (self._firstView and overRide):
self._loadHeaderState() self._loadHeaderState()
self._populateTree(rootHandle) self._populateTree(rootHandle)
self._firstView = False self._firstView = False
@@ -558,7 +559,7 @@ class GuiOutlineTree(QTreeWidget):
logger.info("Writing CSV file: %s", path) logger.info("Writing CSV file: %s", path)
cols = [col for col in self._treeOrder if not self._colHidden[col]] cols = [col for col in self._treeOrder if not self._colHidden[col]]
order = [self._colIdx[col] for col in cols] order = [self._colIdx[col] for col in cols]
with open(path, mode="w", newline="") as csvFile: with open(path, mode="w", newline="", encoding="utf-8") as csvFile:
writer = csv.writer(csvFile, dialect="excel", quoting=csv.QUOTE_ALL) writer = csv.writer(csvFile, dialect="excel", quoting=csv.QUOTE_ALL)
writer.writerow([trConst(nwLabels.OUTLINE_COLS[col]) for col in cols]) writer.writerow([trConst(nwLabels.OUTLINE_COLS[col]) for col in cols])
for i in range(self.topLevelItemCount()): for i in range(self.topLevelItemCount()):
@@ -795,7 +796,7 @@ class GuiOutlineHeaderMenu(QMenu):
class GuiOutlineDetails(QScrollArea): class GuiOutlineDetails(QScrollArea):
LVL_MAP = { LVL_MAP: Final[dict[str, str]] = {
"H1": QT_TRANSLATE_NOOP("GuiOutlineDetails", "Title"), "H1": QT_TRANSLATE_NOOP("GuiOutlineDetails", "Title"),
"H2": QT_TRANSLATE_NOOP("GuiOutlineDetails", "Chapter"), "H2": QT_TRANSLATE_NOOP("GuiOutlineDetails", "Chapter"),
"H3": QT_TRANSLATE_NOOP("GuiOutlineDetails", "Scene"), "H3": QT_TRANSLATE_NOOP("GuiOutlineDetails", "Scene"),
+2 -2
View File
@@ -1099,7 +1099,7 @@ class _UpdatableMenu(QMenu):
class _TreeContextMenu(QMenu): class _TreeContextMenu(QMenu):
__slots__ = ("_tree", "_view", "_node", "_item", "_model", "_handle", "_indices", "_children") __slots__ = ("_children", "_handle", "_indices", "_item", "_model", "_node", "_tree", "_view")
def __init__( def __init__(
self, projTree: GuiProjectTree, model: ProjectModel, self, projTree: GuiProjectTree, model: ProjectModel,
@@ -1327,7 +1327,7 @@ class _TreeContextMenu(QMenu):
"""Add move to Trash action.""" """Add move to Trash action."""
if ( if (
self._model.trashSelection(self._indices) self._model.trashSelection(self._indices)
or len(self._indices) == 1 and self._item.isRootType() or (len(self._indices) == 1 and self._item.isRootType())
): ):
text = self.tr("Delete Permanently") text = self.tr("Delete Permanently")
else: else:
+4 -1
View File
@@ -26,6 +26,7 @@ from __future__ import annotations
import logging import logging
from time import time from time import time
from typing import TYPE_CHECKING
from PyQt6.QtCore import Qt, pyqtSignal, pyqtSlot from PyQt6.QtCore import Qt, pyqtSignal, pyqtSlot
from PyQt6.QtGui import QAction, QCursor, QKeyEvent, QPalette from PyQt6.QtGui import QAction, QCursor, QKeyEvent, QPalette
@@ -37,12 +38,14 @@ from PyQt6.QtWidgets import (
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
from novelwriter.common import checkInt, qtAddAction from novelwriter.common import checkInt, qtAddAction
from novelwriter.core.coretools import DocSearch from novelwriter.core.coretools import DocSearch
from novelwriter.core.item import NWItem
from novelwriter.types import ( from novelwriter.types import (
QtAlignMiddle, QtAlignRight, QtHeaderStretch, QtHeaderToContents, QtAlignMiddle, QtAlignRight, QtHeaderStretch, QtHeaderToContents,
QtHexArgb, QtUserRole QtHexArgb, QtUserRole
) )
if TYPE_CHECKING:
from novelwriter.core.item import NWItem
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
+1 -1
View File
@@ -37,7 +37,7 @@ from novelwriter.extensions.eventfilters import StatusTipFilter
from novelwriter.extensions.modified import NIconToolButton from novelwriter.extensions.modified import NIconToolButton
from novelwriter.gui.theme import STYLES_BIG_TOOLBUTTON from novelwriter.gui.theme import STYLES_BIG_TOOLBUTTON
if TYPE_CHECKING: # pragma: no cover if TYPE_CHECKING:
from novelwriter.guimain import GuiMain from novelwriter.guimain import GuiMain
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
+20 -22
View File
@@ -27,7 +27,7 @@ from __future__ import annotations
import logging import logging
from math import ceil from math import ceil
from pathlib import Path from typing import TYPE_CHECKING, Final
from PyQt6.QtCore import QSize, Qt from PyQt6.QtCore import QSize, Qt
from PyQt6.QtGui import ( from PyQt6.QtGui import (
@@ -44,6 +44,9 @@ from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
from novelwriter.error import logException from novelwriter.error import logException
from novelwriter.types import QtBlack, QtHexArgb, QtPaintAntiAlias, QtTransparent from novelwriter.types import QtBlack, QtHexArgb, QtPaintAntiAlias, QtTransparent
if TYPE_CHECKING:
from pathlib import Path
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
STYLES_FLAT_TABS = "flatTabWidget" STYLES_FLAT_TABS = "flatTabWidget"
@@ -93,18 +96,13 @@ class GuiTheme:
""" """
__slots__ = ( __slots__ = (
# Attributes "_availSyntax", "_availThemes", "_guiPalette", "_styleSheets", "_syntaxList", "_themeList",
"iconCache", "themeMeta", "isDarkTheme", "helpText", "fadedText", "errorText", "baseButtonHeight", "baseIconHeight", "baseIconSize", "buttonIconSize", "errorText",
"syntaxMeta", "syntaxTheme", "guiFont", "guiFontB", "guiFontBU", "guiFontSmall", "fadedText", "fontPixelSize", "fontPointSize", "getDecoration", "getHeaderDecoration",
"fontPointSize", "fontPixelSize", "baseIconHeight", "baseButtonHeight", "textNHeight", "getHeaderDecorationNarrow", "getIcon", "getIconColor", "getItemIcon", "getPixmap",
"textNWidth", "baseIconSize", "buttonIconSize", "guiFontFixed", "getToggleIcon", "guiFont", "guiFontB", "guiFontBU", "guiFontFixed", "guiFontSmall",
"helpText", "iconCache", "isDarkTheme", "syntaxMeta", "syntaxTheme", "textNHeight",
# Functions "textNWidth", "themeMeta",
"getIcon", "getPixmap", "getItemIcon", "getIconColor", "getToggleIcon", "getDecoration",
"getHeaderDecoration", "getHeaderDecorationNarrow",
# Internal
"_guiPalette", "_themeList", "_syntaxList", "_availThemes", "_availSyntax", "_styleSheets",
) )
def __init__(self) -> None: def __init__(self) -> None:
@@ -164,9 +162,9 @@ class GuiTheme:
fHeight = qMetric.height() fHeight = qMetric.height()
fAscent = qMetric.ascent() fAscent = qMetric.ascent()
self.fontPointSize = self.guiFont.pointSizeF() self.fontPointSize = self.guiFont.pointSizeF()
self.fontPixelSize = int(round(fHeight)) self.fontPixelSize = round(fHeight)
self.baseIconHeight = int(round(fAscent)) self.baseIconHeight = round(fAscent)
self.baseButtonHeight = int(round(1.35*fAscent)) self.baseButtonHeight = round(1.35*fAscent)
self.textNHeight = qMetric.boundingRect("N").height() self.textNHeight = qMetric.boundingRect("N").height()
self.textNWidth = qMetric.boundingRect("N").width() self.textNWidth = qMetric.boundingRect("N").width()
@@ -202,7 +200,7 @@ class GuiTheme:
qMetrics = QFontMetrics(font) qMetrics = QFontMetrics(font)
else: else:
qMetrics = QFontMetrics(self.guiFont) qMetrics = QFontMetrics(self.guiFont)
return int(ceil(qMetrics.boundingRect(text).width())) return ceil(qMetrics.boundingRect(text).width())
## ##
# Theme Methods # Theme Methods
@@ -567,16 +565,16 @@ class GuiIcons:
""" """
__slots__ = ( __slots__ = (
"mainTheme", "themeMeta", "_svgData", "_svgColors", "_qColors", "_availThemes", "_headerDec", "_headerDecNarrow", "_noIcon",
"_qIcons", "_headerDec", "_headerDecNarrow", "_availThemes", "_qColors", "_qIcons", "_svgColors", "_svgData", "_themeList",
"_themeList", "_noIcon", "mainTheme", "themeMeta",
) )
TOGGLE_ICON_KEYS: dict[str, tuple[str, str]] = { TOGGLE_ICON_KEYS: Final[dict[str, tuple[str, str]]] = {
"bullet": ("bullet-on", "bullet-off"), "bullet": ("bullet-on", "bullet-off"),
"unfold": ("unfold-show", "unfold-hide"), "unfold": ("unfold-show", "unfold-hide"),
} }
IMAGE_MAP: dict[str, tuple[str, str]] = { IMAGE_MAP: Final[dict[str, tuple[str, str]]] = {
"welcome": ("welcome-light.jpg", "welcome-dark.jpg"), "welcome": ("welcome-light.jpg", "welcome-dark.jpg"),
"nw-text": ("novelwriter-text-light.svg", "novelwriter-text-dark.svg"), "nw-text": ("novelwriter-text-light.svg", "novelwriter-text-dark.svg"),
} }
+1 -1
View File
@@ -1009,7 +1009,7 @@ class GuiMain(QMainWindow):
fN = self.novelView.treeHasFocus() fN = self.novelView.treeHasFocus()
self._changeView(nwView.EDITOR) self._changeView(nwView.EDITOR)
if (vM and (vP and fP or vN and not fN)) or (not vM and vN): if (vM and ((vP and fP) or (vN and not fN))) or (not vM and vN):
self._changeView(nwView.NOVEL) self._changeView(nwView.NOVEL)
self.novelView.setTreeFocus() self.novelView.setTreeFocus()
else: else:
+4 -4
View File
@@ -40,7 +40,7 @@ from novelwriter.constants import nwFiles
from novelwriter.core.spellcheck import NWSpellEnchant from novelwriter.core.spellcheck import NWSpellEnchant
from novelwriter.enum import nwChange, nwItemClass from novelwriter.enum import nwChange, nwItemClass
if TYPE_CHECKING: # pragma: no cover if TYPE_CHECKING:
from novelwriter.core.project import NWProject from novelwriter.core.project import NWProject
from novelwriter.core.status import T_StatusKind from novelwriter.core.status import T_StatusKind
from novelwriter.gui.theme import GuiTheme from novelwriter.gui.theme import GuiTheme
@@ -54,8 +54,8 @@ NWWidget = TypeVar("NWWidget", bound=QWidget)
class SharedData(QObject): class SharedData(QObject):
__slots__ = ( __slots__ = (
"_gui", "_theme", "_project", "_spelling", "_lockedBy", "_lastAlert", "_gui", "_idleRefTime", "_idleTime", "_lastAlert", "_lockedBy",
"_idleTime", "_idleRefTime", "_project", "_spelling", "_theme",
) )
focusModeChanged = pyqtSignal(bool) focusModeChanged = pyqtSignal(bool)
@@ -480,7 +480,7 @@ class _GuiAlert(QMessageBox):
def setException(self, exception: Exception) -> None: def setException(self, exception: Exception) -> None:
"""Add exception details.""" """Add exception details."""
info = self.informativeText() info = self.informativeText()
text = f"<b>{type(exception).__name__}</b>: {str(exception)}" text = f"<b>{type(exception).__name__}</b>: {exception!s}"
self.setInformativeText(f"{info}<br>{text}" if info else text) self.setInformativeText(f"{info}<br>{text}" if info else text)
return return
+2 -2
View File
@@ -128,8 +128,8 @@ REGEX_PATTERNS = RegExPatterns()
class DialogParser: class DialogParser:
__slots__ = ( __slots__ = (
"_quotes", "_dialog", "_alternate", "_enabled", "_alternate", "_breakD", "_breakQ", "_dialog", "_enabled", "_mode",
"_narrator", "_breakD", "_breakQ", "_mode", "_narrator", "_quotes",
) )
def __init__(self) -> None: def __init__(self) -> None:
+4 -4
View File
@@ -124,10 +124,10 @@ class GuiLipsum(NDialog):
@classmethod @classmethod
def getLipsum(cls, parent: QWidget) -> str: def getLipsum(cls, parent: QWidget) -> str:
"""Pop the dialog and return the lipsum text.""" """Pop the dialog and return the lipsum text."""
cls = GuiLipsum(parent) dialog = cls(parent)
cls.exec() dialog.exec()
text = cls.lipsumText text = dialog.lipsumText
cls.softDelete() dialog.softDelete()
return text return text
## ##
+6 -2
View File
@@ -26,9 +26,9 @@ from __future__ import annotations
import logging import logging
from pathlib import Path from pathlib import Path
from typing import TYPE_CHECKING
from PyQt6.QtCore import QTimer, pyqtSlot from PyQt6.QtCore import QTimer, pyqtSlot
from PyQt6.QtGui import QCloseEvent
from PyQt6.QtWidgets import ( from PyQt6.QtWidgets import (
QAbstractButton, QAbstractItemView, QDialogButtonBox, QFileDialog, QAbstractButton, QAbstractItemView, QDialogButtonBox, QFileDialog,
QGridLayout, QHBoxLayout, QLabel, QLineEdit, QListWidget, QListWidgetItem, QGridLayout, QHBoxLayout, QLabel, QLineEdit, QListWidget, QListWidgetItem,
@@ -38,7 +38,6 @@ from PyQt6.QtWidgets import (
from novelwriter import SHARED from novelwriter import SHARED
from novelwriter.common import makeFileNameSafe, openExternalPath from novelwriter.common import makeFileNameSafe, openExternalPath
from novelwriter.constants import nwLabels from novelwriter.constants import nwLabels
from novelwriter.core.buildsettings import BuildSettings
from novelwriter.core.docbuild import NWBuildDocument from novelwriter.core.docbuild import NWBuildDocument
from novelwriter.core.item import NWItem from novelwriter.core.item import NWItem
from novelwriter.enum import nwBuildFmt from novelwriter.enum import nwBuildFmt
@@ -46,6 +45,11 @@ from novelwriter.extensions.modified import NDialog, NIconToolButton
from novelwriter.extensions.progressbars import NProgressSimple from novelwriter.extensions.progressbars import NProgressSimple
from novelwriter.types import QtAlignCenter, QtDialogClose, QtRoleAction, QtRoleReject, QtUserRole from novelwriter.types import QtAlignCenter, QtDialogClose, QtRoleAction, QtRoleReject, QtUserRole
if TYPE_CHECKING:
from PyQt6.QtGui import QCloseEvent
from novelwriter.core.buildsettings import BuildSettings
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
+6 -6
View File
@@ -59,7 +59,7 @@ from novelwriter.types import (
QtUserRole QtUserRole
) )
if TYPE_CHECKING: # pragma: no cover if TYPE_CHECKING:
from novelwriter.guimain import GuiMain from novelwriter.guimain import GuiMain
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -427,10 +427,10 @@ class GuiManuscript(NToolDialog):
def _saveSettings(self) -> None: def _saveSettings(self) -> None:
"""Save the user GUI settings.""" """Save the user GUI settings."""
buildOrder = [] buildOrder = [
for i in range(self.buildList.count()): item.data(self.D_KEY) for i in range(self.buildList.count())
if item := self.buildList.item(i): if (item := self.buildList.item(i))
buildOrder.append(item.data(self.D_KEY)) ]
current = self.buildList.currentItem() current = self.buildList.currentItem()
lastBuild = current.data(self.D_KEY) if isinstance(current, QListWidgetItem) else "" lastBuild = current.data(self.D_KEY) if isinstance(current, QListWidgetItem) else ""
@@ -744,7 +744,7 @@ class _PreviewWidget(QTextBrowser):
document.setDocumentMargin(CONFIG.textMargin) document.setDocumentMargin(CONFIG.textMargin)
self.setPlaceholderText(self.tr( self.setPlaceholderText(self.tr(
"Press the \"Preview\" button to generate ..." 'Press the "Preview" button to generate ...'
)) ))
self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOn) self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOn)
+1 -1
View File
@@ -55,7 +55,7 @@ from novelwriter.types import (
QtUserRole QtUserRole
) )
if TYPE_CHECKING: # pragma: no cover if TYPE_CHECKING:
from novelwriter.guimain import GuiMain from novelwriter.guimain import GuiMain
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
+5 -1
View File
@@ -26,8 +26,9 @@ from __future__ import annotations
import logging import logging
import math import math
from typing import TYPE_CHECKING
from PyQt6.QtCore import pyqtSlot from PyQt6.QtCore import pyqtSlot
from PyQt6.QtGui import QCloseEvent
from PyQt6.QtWidgets import ( from PyQt6.QtWidgets import (
QAbstractItemView, QDialogButtonBox, QFormLayout, QGridLayout, QHBoxLayout, QAbstractItemView, QDialogButtonBox, QFormLayout, QGridLayout, QHBoxLayout,
QLabel, QSpinBox, QStackedWidget, QTreeWidget, QTreeWidgetItem, QLabel, QSpinBox, QStackedWidget, QTreeWidget, QTreeWidgetItem,
@@ -44,6 +45,9 @@ from novelwriter.extensions.pagedsidebar import NPagedSideBar
from novelwriter.extensions.switch import NSwitch from novelwriter.extensions.switch import NSwitch
from novelwriter.types import QtAlignRight, QtDecoration, QtDialogClose from novelwriter.types import QtAlignRight, QtDecoration, QtDialogClose
if TYPE_CHECKING:
from PyQt6.QtGui import QCloseEvent
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
+1 -1
View File
@@ -385,7 +385,7 @@ class _OpenProjectPage(QWidget):
class _ProjectListItem(QStyledItemDelegate): class _ProjectListItem(QStyledItemDelegate):
__slots__ = ("_pPx", "_hPx", "_tFont", "_dFont", "_dPen", "_icon") __slots__ = ("_dFont", "_dPen", "_hPx", "_icon", "_pPx", "_tFont")
def __init__(self, parent: QWidget) -> None: def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent) super().__init__(parent=parent)
+2 -2
View File
@@ -47,7 +47,7 @@ from novelwriter.types import (
QtDialogClose, QtRoleAction QtDialogClose, QtRoleAction
) )
if TYPE_CHECKING: # pragma: no cover if TYPE_CHECKING:
from novelwriter.guimain import GuiMain from novelwriter.guimain import GuiMain
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -124,7 +124,7 @@ class GuiWritingStats(NToolDialog):
self.listBox.setSortingEnabled(True) self.listBox.setSortingEnabled(True)
# Word Bar # Word Bar
self.barHeight = int(round(0.5*SHARED.theme.fontPixelSize)) self.barHeight = round(0.5*SHARED.theme.fontPixelSize)
self.barImage = QPixmap(self.barHeight, self.barHeight) self.barImage = QPixmap(self.barHeight, self.barHeight)
self.barImage.fill(self.palette().highlight().color()) self.barImage.fill(self.palette().highlight().color())
+39 -18
View File
@@ -60,30 +60,48 @@ line-length = 99
[tool.ruff.lint] [tool.ruff.lint]
preview = true preview = true
# Rules: https://docs.astral.sh/ruff/rules
select = [ select = [
"A", # flake8-builtins (A) "A", # flake8-builtins (A)
"B", # flake8-bugbear (B) "ANN", # flake8-annotations (ANN)
"E", # pycodestyle (E) "B", # flake8-bugbear (B)
"F", # Pyflakes (F) "E", # pycodestyle (E)
"W", # pycodestyle (W) "F", # Pyflakes (F)
"UP", # pyupgrade (UP) "FA", # flake8-future-annotations (FA)
"ANN", # flake8-annotations (ANN) "PERF", # Perflint (PERF)
"PLE", # Pylint Error (PLE) "PLC", # Pylint Convention (PLC)
"FA", # flake8-future-annotations (FA) "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)
] ]
ignore = [ ignore = [
"E221", # multiple-spaces-before-operator "ANN401", # any-type
"E226", # missing-whitespace-around-arithmetic-operator "E221", # multiple-spaces-before-operator
"E228", # missing-whitespace-around-modulo-operator "E226", # missing-whitespace-around-arithmetic-operator
"E241", # multiple-spaces-after-comma "E228", # missing-whitespace-around-modulo-operator
"E272", # multiple-spaces-before-keyword "E241", # multiple-spaces-after-comma
"ANN401", # any-type "E272", # multiple-spaces-before-keyword
"UP015", # redundant-open-modes "PLC0415", # import-outside-top-level
"UP030", # format-literals "PLC1901", # compare-to-empty-string
"PLW0108", # unnecessary-lambda
"PLW2901", # redefined-loop-name
"RUF001", # ambiguous-unicode-character-string
"RUF002", # ambiguous-unicode-character-docstring
"RUF015", # unnecessary-iterable-allocation-for-first-element
"UP015", # redundant-open-modes
"UP030", # format-literals
] ]
[tool.ruff.lint.per-file-ignores] [tool.ruff.lint.per-file-ignores]
"tests/*" = ["ANN"] "tests/*" = ["ANN", "SLF", "TC", "PLC2701"]
"utils/*" = ["ANN", "SLF", "TC"]
[tool.ruff.format] [tool.ruff.format]
quote-style = "double" quote-style = "double"
@@ -111,3 +129,6 @@ branch = false
[tool.coverage.report] [tool.coverage.report]
precision = 2 precision = 2
exclude_also = [
"if TYPE_CHECKING:"
]
+1 -1
View File
@@ -1,3 +1,3 @@
ruff
isort isort
pyright pyright
ruff
+4 -4
View File
@@ -33,10 +33,10 @@ from PyQt6.QtWidgets import QMessageBox
sys.path.insert(1, str(Path(__file__).parent.parent.absolute())) sys.path.insert(1, str(Path(__file__).parent.parent.absolute()))
from novelwriter import CONFIG, SHARED # noqa: E402 from novelwriter import CONFIG, SHARED
from tests.mocked import MockGuiMain, MockTheme # noqa: E402 from tests.mocked import MockGuiMain, MockTheme
from tests.tools import cleanProject # noqa: E402 from tests.tools import cleanProject
_TST_ROOT = Path(__file__).parent _TST_ROOT = Path(__file__).parent
_TMP_ROOT = _TST_ROOT / "temp" _TMP_ROOT = _TST_ROOT / "temp"
@@ -89,7 +89,7 @@ def functionFixture(qtbot):
shutil.rmtree(_TMP_CONF) shutil.rmtree(_TMP_CONF)
_TMP_CONF.mkdir() _TMP_CONF.mkdir()
CONFIG.__init__() CONFIG.__init__() # noqa: PLC2801
CONFIG.initConfig(confPath=_TMP_CONF, dataPath=_TMP_CONF) CONFIG.initConfig(confPath=_TMP_CONF, dataPath=_TMP_CONF)
resetConfigVars() resetConfigVars()
logging.getLogger("novelwriter").setLevel(logging.INFO) logging.getLogger("novelwriter").setLevel(logging.INFO)
+3 -2
View File
@@ -1,4 +1,5 @@
pytest>=6.0.0 coverage>=7.2.0
pytest-timeout
pytest-cov pytest-cov
pytest-qt pytest-qt
pytest-timeout
pytest>=6.0.0
+1 -1
View File
@@ -556,7 +556,7 @@ def testBaseCommon_jsonEncode():
# Correct types # Correct types
assert jsonEncode([1, 2]) == "[\n 1,\n 2\n]" assert jsonEncode([1, 2]) == "[\n 1,\n 2\n]"
assert jsonEncode((1, 2)) == "[\n 1,\n 2\n]" assert jsonEncode((1, 2)) == "[\n 1,\n 2\n]"
assert jsonEncode({1: 2}) == "{\n \"1\": 2\n}" assert jsonEncode({1: 2}) == '{\n "1": 2\n}'
tstDict = { tstDict = {
"null": None, "null": None,
+2 -2
View File
@@ -141,8 +141,8 @@ def testBaseConfig_InitLoadSave(monkeypatch, fncPath, tstPaths):
assert newConf.guiSyntax == "bar" assert newConf.guiSyntax == "bar"
# Test Correcting Quote Settings # Test Correcting Quote Settings
tstConf.fmtDQuoteOpen = "\"" tstConf.fmtDQuoteOpen = '"'
tstConf.fmtDQuoteClose = "\"" tstConf.fmtDQuoteClose = '"'
tstConf.fmtSQuoteOpen = "'" tstConf.fmtSQuoteOpen = "'"
tstConf.fmtSQuoteClose = "'" tstConf.fmtSQuoteClose = "'"
tstConf.doReplaceDQuote = True tstConf.doReplaceDQuote = True
+1 -1
View File
@@ -768,7 +768,7 @@ def testCoreTools_ProjectBuilderSample(monkeypatch, mockGUI, fncPath, tstPaths):
assert builder.buildProject(data) is False assert builder.buildProject(data) is False
# Create and open a defective zip file # Create and open a defective zip file
with open(dstSample, mode="w+") as outFile: with open(dstSample, mode="w+", encoding="utf-8") as outFile:
outFile.write("foo") outFile.write("foo")
assert builder.buildProject(data) is False assert builder.buildProject(data) is False
+8 -6
View File
@@ -42,6 +42,8 @@ from tests.tools import cmpFiles, writeFile
class MockProject: class MockProject:
"""Fake project object.""" """Fake project object."""
data: NWProjectData
def setProjectChanged(self, *a): def setProjectChanged(self, *a):
"""Fake project method.""" """Fake project method."""
pass pass
@@ -219,7 +221,7 @@ def testCoreProjectXML_ReadCurrent(monkeypatch, mockGUI, tstPaths, fncPath):
packedContent = [] packedContent = []
mockProject = MockProject() mockProject = MockProject()
mockProject.__setattr__("data", data) mockProject.data = data
for entry in content: for entry in content:
item = NWItem(mockProject, "0000000000000") # type: ignore item = NWItem(mockProject, "0000000000000") # type: ignore
item.unpack(entry) item.unpack(entry)
@@ -348,7 +350,7 @@ def testCoreProjectXML_ReadLegacy10(tstPaths, fncPath, mockGUI, mockRnd):
packedContent = [] packedContent = []
mockProject = MockProject() mockProject = MockProject()
mockProject.__setattr__("data", data) mockProject.data = data
status = {} status = {}
for entry in content: for entry in content:
item = NWItem(mockProject, "0000000000000") # type: ignore item = NWItem(mockProject, "0000000000000") # type: ignore
@@ -493,7 +495,7 @@ def testCoreProjectXML_ReadLegacy11(tstPaths, fncPath, mockGUI, mockRnd):
packedContent = [] packedContent = []
mockProject = MockProject() mockProject = MockProject()
mockProject.__setattr__("data", data) mockProject.data = data
status = {} status = {}
for entry in content: for entry in content:
item = NWItem(mockProject, "0000000000000") # type: ignore item = NWItem(mockProject, "0000000000000") # type: ignore
@@ -638,7 +640,7 @@ def testCoreProjectXML_ReadLegacy12(tstPaths, fncPath, mockGUI, mockRnd):
packedContent = [] packedContent = []
mockProject = MockProject() mockProject = MockProject()
mockProject.__setattr__("data", data) mockProject.data = data
status = {} status = {}
for entry in content: for entry in content:
item = NWItem(mockProject, "0000000000000") # type: ignore item = NWItem(mockProject, "0000000000000") # type: ignore
@@ -786,7 +788,7 @@ def testCoreProjectXML_ReadLegacy13(tstPaths, fncPath, mockGUI, mockRnd):
packedContent = [] packedContent = []
mockProject = MockProject() mockProject = MockProject()
mockProject.__setattr__("data", data) mockProject.data = data
status = {} status = {}
for entry in content: for entry in content:
item = NWItem(mockProject, "0000000000000") # type: ignore item = NWItem(mockProject, "0000000000000") # type: ignore
@@ -933,7 +935,7 @@ def testCoreProjectXML_ReadLegacy14(tstPaths, fncPath, mockGUI, mockRnd):
packedContent = [] packedContent = []
mockProject = MockProject() mockProject = MockProject()
mockProject.__setattr__("data", data) mockProject.data = data
status = {} status = {}
for entry in content: for entry in content:
item = NWItem(mockProject, "0000000000000") # type: ignore item = NWItem(mockProject, "0000000000000") # type: ignore
+10 -18
View File
@@ -982,7 +982,7 @@ def testFmtToOdt_SaveFull(mockGUI, fncPath, tstPaths, ipsumText):
def prettifyXml(inFile, outFile): def prettifyXml(inFile, outFile):
with open(outFile, mode="wb") as fStream: with open(outFile, mode="wb") as fStream:
xml = ET.parse(inFile) xml = ET.parse(inFile)
xmlIndent(xml) xmlIndent(xml.getroot())
xml.write(fStream, encoding="utf-8", xml_declaration=True) xml.write(fStream, encoding="utf-8", xml_declaration=True)
prettifyXml(maniOut, maniFile) prettifyXml(maniOut, maniFile)
@@ -1082,10 +1082,6 @@ def testFmtToOdt_ODTParagraphStyle():
assert parStyle._pAttr["text-align"] == ["fo", "end"] assert parStyle._pAttr["text-align"] == ["fo", "end"]
parStyle.setTextAlign("justify") parStyle.setTextAlign("justify")
assert parStyle._pAttr["text-align"] == ["fo", "justify"] assert parStyle._pAttr["text-align"] == ["fo", "justify"]
parStyle.setTextAlign("inside")
assert parStyle._pAttr["text-align"] == ["fo", "inside"]
parStyle.setTextAlign("outside")
assert parStyle._pAttr["text-align"] == ["fo", "outside"]
parStyle.setTextAlign("left") parStyle.setTextAlign("left")
assert parStyle._pAttr["text-align"] == ["fo", "left"] assert parStyle._pAttr["text-align"] == ["fo", "left"]
parStyle.setTextAlign("right") parStyle.setTextAlign("right")
@@ -1099,8 +1095,6 @@ def testFmtToOdt_ODTParagraphStyle():
assert parStyle._pAttr["break-before"] == ["fo", None] assert parStyle._pAttr["break-before"] == ["fo", None]
parStyle.setBreakBefore("auto") parStyle.setBreakBefore("auto")
assert parStyle._pAttr["break-before"] == ["fo", "auto"] assert parStyle._pAttr["break-before"] == ["fo", "auto"]
parStyle.setBreakBefore("column")
assert parStyle._pAttr["break-before"] == ["fo", "column"]
parStyle.setBreakBefore("page") parStyle.setBreakBefore("page")
assert parStyle._pAttr["break-before"] == ["fo", "page"] assert parStyle._pAttr["break-before"] == ["fo", "page"]
parStyle.setBreakBefore("even-page") parStyle.setBreakBefore("even-page")
@@ -1118,8 +1112,6 @@ def testFmtToOdt_ODTParagraphStyle():
assert parStyle._pAttr["break-after"] == ["fo", None] assert parStyle._pAttr["break-after"] == ["fo", None]
parStyle.setBreakAfter("auto") parStyle.setBreakAfter("auto")
assert parStyle._pAttr["break-after"] == ["fo", "auto"] assert parStyle._pAttr["break-after"] == ["fo", "auto"]
parStyle.setBreakAfter("column")
assert parStyle._pAttr["break-after"] == ["fo", "column"]
parStyle.setBreakAfter("page") parStyle.setBreakAfter("page")
assert parStyle._pAttr["break-after"] == ["fo", "page"] assert parStyle._pAttr["break-after"] == ["fo", "page"]
parStyle.setBreakAfter("even-page") parStyle.setBreakAfter("even-page")
@@ -1394,9 +1386,9 @@ def testFmtToOdt_XMLParagraph():
# Plain Text # Plain Text
xmlPar.appendText("Hello World") xmlPar.appendText("Hello World")
assert xmlToText(xRoot) == ( assert xmlToText(xRoot) == (
'<root>' "<root>"
'<text:p>Hello World</text:p>' "<text:p>Hello World</text:p>"
'</root>' "</root>"
) )
# Text Span # Text Span
@@ -1431,9 +1423,9 @@ def testFmtToOdt_XMLParagraph():
# Plain Text w/Line Break # Plain Text w/Line Break
xmlPar.appendText("Hello\nWorld\n!!") xmlPar.appendText("Hello\nWorld\n!!")
assert xmlToText(xRoot) == ( assert xmlToText(xRoot) == (
'<root>' "<root>"
'<text:p>Hello<text:line-break />World<text:line-break />!!</text:p>' "<text:p>Hello<text:line-break />World<text:line-break />!!</text:p>"
'</root>' "</root>"
) )
# Text Span w/Line Break # Text Span w/Line Break
@@ -1467,9 +1459,9 @@ def testFmtToOdt_XMLParagraph():
# Plain Text w/Line Break # Plain Text w/Line Break
xmlPar.appendText("Hello\tWorld\t!!") xmlPar.appendText("Hello\tWorld\t!!")
assert xmlToText(xRoot) == ( assert xmlToText(xRoot) == (
'<root>' "<root>"
'<text:p>Hello<text:tab />World<text:tab />!!</text:p>' "<text:p>Hello<text:tab />World<text:tab />!!</text:p>"
'</root>' "</root>"
) )
# Text Span w/Line Break # Text Span w/Line Break
+1 -1
View File
@@ -763,7 +763,7 @@ def testGuiEditor_Actions(qtbot, nwGUI, projPath, ipsumText, mockRnd):
assert docEditor.getText() == text.replace("consectetur", "\u2018consectetur\u2019") assert docEditor.getText() == text.replace("consectetur", "\u2018consectetur\u2019")
# Replace Double Quotes # Replace Double Quotes
repText = text.replace("consectetur", "\"consectetur\"") repText = text.replace("consectetur", '"consectetur"')
docEditor.replaceText(repText) docEditor.replaceText(repText)
assert docEditor.docAction(nwDocAction.SEL_ALL) is True assert docEditor.docAction(nwDocAction.SEL_ALL) is True
assert docEditor.docAction(nwDocAction.REPL_DBL) is True assert docEditor.docAction(nwDocAction.REPL_DBL) is True
+7 -6
View File
@@ -116,7 +116,8 @@ def testGuiMain_Launch(qtbot, monkeypatch, nwGUI, projPath):
# Check that project open dialog launches # Check that project open dialog launches
nwGUI.postLaunchTasks(None) nwGUI.postLaunchTasks(None)
qtbot.waitUntil(lambda: SHARED.findTopLevelWidget(GuiWelcome) is not None, timeout=1000) qtbot.waitUntil(lambda: SHARED.findTopLevelWidget(GuiWelcome) is not None, timeout=1000)
assert isinstance(welcome := SHARED.findTopLevelWidget(GuiWelcome), GuiWelcome) welcome = SHARED.findTopLevelWidget(GuiWelcome)
assert isinstance(welcome, GuiWelcome)
welcome.show() welcome.show()
welcome.close() welcome.close()
@@ -454,7 +455,7 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd):
# Dialogue # Dialogue
# ======== # ========
for c in "\"Full line double quoted text.\"": for c in '"Full line double quoted text."':
qtbot.keyClick(docEditor, c, delay=KEY_DELAY) qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY) qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY) qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
@@ -469,7 +470,7 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd):
CONFIG.fmtPadAfter = "\u201c" CONFIG.fmtPadAfter = "\u201c"
docEditor.initEditor() docEditor.initEditor()
for c in "Some \"double quoted text with spaces padded\".": for c in 'Some "double quoted text with spaces padded".':
qtbot.keyClick(docEditor, c, delay=KEY_DELAY) qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY) qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY) qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
@@ -533,17 +534,17 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd):
# ================ # ================
nwGUI._switchFocus(nwView.EDITOR) nwGUI._switchFocus(nwView.EDITOR)
for c in "\t\"Tab-indented text\"": for c in '\t"Tab-indented text"':
qtbot.keyClick(docEditor, c, delay=KEY_DELAY) qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY) qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY) qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
for c in ">\"Paragraph-indented text\"": for c in '>"Paragraph-indented text"':
qtbot.keyClick(docEditor, c, delay=KEY_DELAY) qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY) qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY) qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
for c in ">>\"Right-aligned text\"": for c in '>>"Right-aligned text"':
qtbot.keyClick(docEditor, c, delay=KEY_DELAY) qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY) qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY) qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
+4 -4
View File
@@ -279,7 +279,7 @@ def testGuiMainMenu_EditFormat(qtbot, monkeypatch, nwGUI, prjLipsum):
docEditor.setPlainText( docEditor.setPlainText(
"### New Text\n\n" "### New Text\n\n"
"Text with 'single' quotes and 'tricky stuff's'.\n\n" "Text with 'single' quotes and 'tricky stuff's'.\n\n"
"Also text with \"double\" quotes which are \"less tricky\".\n\n" 'Also text with "double" quotes which are "less tricky".\n\n'
) )
mainMenu.aSelectAll.activate(QAction.ActionEvent.Trigger) mainMenu.aSelectAll.activate(QAction.ActionEvent.Trigger)
@@ -287,7 +287,7 @@ def testGuiMainMenu_EditFormat(qtbot, monkeypatch, nwGUI, prjLipsum):
assert docEditor.getText() == ( assert docEditor.getText() == (
"### New Text\n\n" "### New Text\n\n"
"Text with single quotes and tricky stuffs.\n\n" "Text with single quotes and tricky stuffs.\n\n"
"Also text with \"double\" quotes which are \"less tricky\".\n\n" 'Also text with "double" quotes which are "less tricky".\n\n'
) )
mainMenu.aSelectAll.activate(QAction.ActionEvent.Trigger) mainMenu.aSelectAll.activate(QAction.ActionEvent.Trigger)
@@ -347,7 +347,7 @@ def testGuiMainMenu_EditFormat(qtbot, monkeypatch, nwGUI, prjLipsum):
"### New Text\n\n" "### New Text\n\n"
"@tag: Bod\n\n" "@tag: Bod\n\n"
"Text with 'single' quotes and 'tricky stuff's'.\n\n" "Text with 'single' quotes and 'tricky stuff's'.\n\n"
"Also text with \"double\" quotes which are \"less tricky\".\n\n" 'Also text with "double" quotes which are "less tricky".\n\n'
) )
# Cannot Format Tag # Cannot Format Tag
@@ -363,7 +363,7 @@ def testGuiMainMenu_EditFormat(qtbot, monkeypatch, nwGUI, prjLipsum):
"### New Text\n\n" "### New Text\n\n"
"@tag: Bod\n\n" "@tag: Bod\n\n"
"Text with 'single' quotes and 'tricky stuff's'.\n\n" "Text with 'single' quotes and 'tricky stuff's'.\n\n"
"Also text with \"double\" quotes which are \"less tricky\".\n\n" 'Also text with "double" quotes which are "less tricky".\n\n'
) )
# qtbot.stop() # qtbot.stop()
+14 -17
View File
@@ -29,15 +29,12 @@ from novelwriter.constants import nwUnicode
from novelwriter.text.patterns import REGEX_PATTERNS, DialogParser from novelwriter.text.patterns import REGEX_PATTERNS, DialogParser
def allMatches(regEx: re.Pattern, text: str) -> list[list[str]]: def allMatches(regEx: re.Pattern, text: str) -> list[list[tuple[str, int, int]]]:
"""Get all matches for a regex.""" """Get all matches for a regex."""
result = [] return [
for res in regEx.finditer(text): [(res.group(n), res.start(n), res.end(n)) for n in range((res.lastindex or 0) + 1)]
result.append([ for res in regEx.finditer(text)
(res.group(n), res.start(n), res.end(n)) ]
for n in range((res.lastindex or 0) + 1)
])
return result
@pytest.mark.core @pytest.mark.core
@@ -310,7 +307,7 @@ def testTextPatterns_DialogueStyle():
assert allMatches(regEx, "one 'two' three") == [] assert allMatches(regEx, "one 'two' three") == []
# Straight double quotes are ignored # Straight double quotes are ignored
assert allMatches(regEx, "one \"two\" three") == [] assert allMatches(regEx, 'one "two" three') == []
# Check with no whitespace, single quote # Check with no whitespace, single quote
assert allMatches(regEx, "one\u2018two\u2019three") == [ assert allMatches(regEx, "one\u2018two\u2019three") == [
@@ -370,19 +367,19 @@ def testTextPatterns_DialoguePlain():
# ====== # ======
# One double quoted string # One double quoted string
assert allMatches(regEx, "one \"two\" three") == [ assert allMatches(regEx, 'one "two" three') == [
[("\"two\"", 4, 9)] [('"two"', 4, 9)]
] ]
# Two double quoted strings # Two double quoted strings
assert allMatches(regEx, "one \"two\" three \"four\" five") == [ assert allMatches(regEx, 'one "two" three "four" five') == [
[("\"two\"", 4, 9)], [("\"four\"", 16, 22)], [('"two"', 4, 9)], [('"four"', 16, 22)],
] ]
# No space # No space
assert allMatches(regEx, "one\"two\" three") == [] assert allMatches(regEx, 'one"two" three') == []
assert allMatches(regEx, "one \"two\"three") == [] assert allMatches(regEx, 'one "two"three') == []
assert allMatches(regEx, "one\"two\"three") == [] assert allMatches(regEx, 'one"two"three') == []
# Single # Single
# ====== # ======
@@ -595,6 +592,6 @@ def testTextPatterns_DialogParserPolish():
] ]
assert parser( assert parser(
"And so on and so forth. However, \"text in quotation marks\" should not be " 'And so on and so forth. However, "text in quotation marks" should not be '
"highlighted at all, and if so, it should be highlighted differently." "highlighted at all, and if so, it should be highlighted differently."
) == [] ) == []
+1 -2
View File
@@ -83,8 +83,7 @@ def testToolManuscriptBuild_Main(
if item.data(manus.D_KEY) == fmt: if item.data(manus.D_KEY) == fmt:
manus.listFormats.setCurrentItem(item) manus.listFormats.setCurrentItem(item)
return return
else: raise ValueError("No such key in format list")
raise ValueError("No such key in format list")
# Build documents # Build documents
lastFmt = None lastFmt = None
+2 -2
View File
@@ -180,9 +180,9 @@ def updateTranslationSources(args: argparse.Namespace) -> None:
else: # Create an empty new language file else: # Create an empty new language file
langCode = item.name[3:-3] langCode = item.name[3:-3]
writeFile(item, ( writeFile(item, (
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" '<?xml version="1.0" encoding="utf-8"?>\n'
"<!DOCTYPE TS>\n" "<!DOCTYPE TS>\n"
f"<TS version=\"2.0\" language=\"{langCode}\" sourcelanguage=\"en_GB\"/>\n" f'<TS version="2.0" language="{langCode}" sourcelanguage="en_GB"/>\n'
)) ))
translations.append(item) translations.append(item)
print(f"Created: {item}") print(f"Created: {item}")
+3 -3
View File
@@ -134,10 +134,10 @@ def makeDebianPackage(
signArgs = [f"-k{signKey}"] signArgs = [f"-k{signKey}"]
if sourceBuild: if sourceBuild:
subprocess.call(["debuild", "-S"] + signArgs, cwd=outDir) subprocess.call(["debuild", "-S", *signArgs], cwd=outDir)
toUpload(bldDir / f"{bldPkg}.tar.xz") toUpload(bldDir / f"{bldPkg}.tar.xz")
else: else:
subprocess.call(["dpkg-buildpackage"] + signArgs, cwd=outDir) subprocess.call(["dpkg-buildpackage", *signArgs], cwd=outDir)
shutil.copyfile(bldDir / f"{bldPkg}.tar.xz", bldDir / f"{bldPkg}.debian.tar.xz") shutil.copyfile(bldDir / f"{bldPkg}.tar.xz", bldDir / f"{bldPkg}.debian.tar.xz")
toUpload(bldDir / f"{bldPkg}.debian.tar.xz") toUpload(bldDir / f"{bldPkg}.debian.tar.xz")
toUpload(bldDir / f"{bldPkg}_all.deb") toUpload(bldDir / f"{bldPkg}_all.deb")
@@ -195,7 +195,7 @@ def launchpad(args: argparse.Namespace) -> None:
signKey = SIGN_KEY if args.sign else None signKey = SIGN_KEY if args.sign else None
print(f"Sign Key: {str(signKey)}") print(f"Sign Key: {signKey!s}")
print("") print("")
dputCmd = [] dputCmd = []
+2 -2
View File
@@ -218,9 +218,9 @@ def main(args: argparse.Namespace) -> None:
"import sys\n" "import sys\n"
"\n" "\n"
"os.curdir = os.path.abspath(os.path.dirname(__file__))\n" "os.curdir = os.path.abspath(os.path.dirname(__file__))\n"
"sys.path.insert(0, os.path.join(os.curdir, \"lib\"))\n" 'sys.path.insert(0, os.path.join(os.curdir, "lib"))\n'
"\n" "\n"
"if __name__ == \"__main__\":\n" 'if __name__ == "__main__":\n'
" import novelwriter\n" " import novelwriter\n"
" novelwriter.main(sys.argv[1:])\n" " novelwriter.main(sys.argv[1:])\n"
)) ))
+1 -1
View File
@@ -137,7 +137,7 @@ def makeCheckSum(sumFile: str, cwd: Path | None = None) -> str:
shaFile = f"{sumFile}.sha256" shaFile = f"{sumFile}.sha256"
else: else:
shaFile = cwd / f"{sumFile}.sha256" shaFile = cwd / f"{sumFile}.sha256"
with open(shaFile, mode="w") as fOut: with open(shaFile, mode="w", encoding="utf-8") as fOut:
subprocess.call(["shasum", "-a", "256", sumFile], stdout=fOut, cwd=cwd) subprocess.call(["shasum", "-a", "256", sumFile], stdout=fOut, cwd=cwd)
print(f"SHA256 Sum: {shaFile}") print(f"SHA256 Sum: {shaFile}")
except Exception as exc: except Exception as exc: