Merge branch 'main' into features/footnotes
This commit is contained in:
+13
-6
@@ -23,16 +23,21 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import getopt
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from PyQt5.QtWidgets import QApplication, QErrorMessage
|
||||
|
||||
from novelwriter.error import exceptionHandler, logException
|
||||
from novelwriter.config import Config
|
||||
from novelwriter.error import exceptionHandler, logException
|
||||
from novelwriter.shared import SharedData
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from novelwriter.guimain import GuiMain
|
||||
|
||||
# Package Meta
|
||||
# ============
|
||||
|
||||
@@ -42,9 +47,9 @@ __license__ = "GPLv3"
|
||||
__author__ = "Veronica Berglyd Olsen"
|
||||
__maintainer__ = "Veronica Berglyd Olsen"
|
||||
__email__ = "code@vkbo.net"
|
||||
__version__ = "2.4rc1"
|
||||
__hexversion__ = "0x020400c1"
|
||||
__date__ = "2024-04-06"
|
||||
__version__ = "2.5a1"
|
||||
__hexversion__ = "0x020500a1"
|
||||
__date__ = "2024-04-13"
|
||||
__status__ = "Stable"
|
||||
__domain__ = "novelwriter.io"
|
||||
|
||||
@@ -60,7 +65,7 @@ CONFIG = Config()
|
||||
SHARED = SharedData()
|
||||
|
||||
|
||||
def main(sysArgs: list | None = None):
|
||||
def main(sysArgs: list | None = None) -> GuiMain | None:
|
||||
"""Parse command line, set up logging, and launch main GUI."""
|
||||
if sysArgs is None:
|
||||
sysArgs = sys.argv[1:]
|
||||
@@ -239,4 +244,6 @@ def main(sysArgs: list | None = None):
|
||||
|
||||
sys.exit(nwApp.exec())
|
||||
|
||||
return None
|
||||
|
||||
# END Function main
|
||||
|
||||
@@ -449,7 +449,7 @@ def xmlIndent(tree: ET.Element | ET.ElementTree) -> None:
|
||||
|
||||
indentations = ["\n"]
|
||||
|
||||
def indentChildren(elem, level):
|
||||
def indentChildren(elem: ET.Element, level: int) -> None:
|
||||
chLevel = level + 1
|
||||
try:
|
||||
chIndent = indentations[chLevel]
|
||||
@@ -472,6 +472,8 @@ def xmlIndent(tree: ET.Element | ET.ElementTree) -> None:
|
||||
if last is not None:
|
||||
last.tail = indentations[level]
|
||||
|
||||
return
|
||||
|
||||
if len(tree):
|
||||
indentChildren(tree, 0)
|
||||
tree.tail = "\n"
|
||||
@@ -534,8 +536,9 @@ class NWConfigParser(ConfigParser):
|
||||
helper functions, and support for lists.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
return
|
||||
|
||||
def rdStr(self, section: str, option: str, default: str) -> str:
|
||||
"""Read string value."""
|
||||
|
||||
@@ -390,6 +390,10 @@ class Config:
|
||||
"""Un-scale fixed gui sizes by the screen scale factor."""
|
||||
return int(value/self.guiScale)
|
||||
|
||||
def homePath(self) -> Path:
|
||||
"""The user's home folder."""
|
||||
return self._homePath
|
||||
|
||||
def dataPath(self, target: str | None = None) -> Path:
|
||||
"""Return a path in the data folder."""
|
||||
if isinstance(target, str):
|
||||
|
||||
@@ -25,7 +25,7 @@ from __future__ import annotations
|
||||
|
||||
from PyQt5.QtCore import QCoreApplication, QT_TRANSLATE_NOOP
|
||||
|
||||
from novelwriter.enum import nwBuildFmt, nwItemClass, nwItemLayout, nwOutline
|
||||
from novelwriter.enum import nwBuildFmt, nwItemClass, nwItemLayout, nwOutline, nwStatusShape
|
||||
|
||||
|
||||
def trConst(text: str) -> str:
|
||||
@@ -268,6 +268,34 @@ class nwLabels:
|
||||
nwBuildFmt.J_HTML: ".json",
|
||||
nwBuildFmt.J_NWD: ".json",
|
||||
}
|
||||
SHAPES_PLAIN = {
|
||||
nwStatusShape.SQUARE: QT_TRANSLATE_NOOP("Constant", "Square"),
|
||||
nwStatusShape.TRIANGLE: QT_TRANSLATE_NOOP("Constant", "Triangle"),
|
||||
nwStatusShape.NABLA: QT_TRANSLATE_NOOP("Constant", "Nabla"),
|
||||
nwStatusShape.DIAMOND: QT_TRANSLATE_NOOP("Constant", "Diamond"),
|
||||
nwStatusShape.PENTAGON: QT_TRANSLATE_NOOP("Constant", "Pentagon"),
|
||||
nwStatusShape.HEXAGON: QT_TRANSLATE_NOOP("Constant", "Hexagon"),
|
||||
nwStatusShape.STAR: QT_TRANSLATE_NOOP("Constant", "Star"),
|
||||
nwStatusShape.PACMAN: QT_TRANSLATE_NOOP("Constant", "Pacman"),
|
||||
}
|
||||
SHAPES_CIRCLE = {
|
||||
nwStatusShape.CIRCLE_Q: QT_TRANSLATE_NOOP("Constant", "1/4 Circle"),
|
||||
nwStatusShape.CIRCLE_H: QT_TRANSLATE_NOOP("Constant", "Half Circle"),
|
||||
nwStatusShape.CIRCLE_T: QT_TRANSLATE_NOOP("Constant", "3/4 Circle"),
|
||||
nwStatusShape.CIRCLE: QT_TRANSLATE_NOOP("Constant", "Full Circle"),
|
||||
}
|
||||
SHAPES_BARS = {
|
||||
nwStatusShape.BARS_1: QT_TRANSLATE_NOOP("Constant", "1 Bar"),
|
||||
nwStatusShape.BARS_2: QT_TRANSLATE_NOOP("Constant", "2 Bars"),
|
||||
nwStatusShape.BARS_3: QT_TRANSLATE_NOOP("Constant", "3 Bars"),
|
||||
nwStatusShape.BARS_4: QT_TRANSLATE_NOOP("Constant", "4 Bars"),
|
||||
}
|
||||
SHAPES_BLOCKS = {
|
||||
nwStatusShape.BLOCK_1: QT_TRANSLATE_NOOP("Constant", "1 Block"),
|
||||
nwStatusShape.BLOCK_2: QT_TRANSLATE_NOOP("Constant", "2 Blocks"),
|
||||
nwStatusShape.BLOCK_3: QT_TRANSLATE_NOOP("Constant", "3 Blocks"),
|
||||
nwStatusShape.BLOCK_4: QT_TRANSLATE_NOOP("Constant", "4 Blocks"),
|
||||
}
|
||||
FILE_FILTERS = {
|
||||
"*.txt": QT_TRANSLATE_NOOP("Constant", "Text files"),
|
||||
"*.md": QT_TRANSLATE_NOOP("Constant", "Markdown files"),
|
||||
|
||||
@@ -56,13 +56,13 @@ SETTINGS_TEMPLATE = {
|
||||
"headings.fmtChapter": (str, nwHeadFmt.TITLE),
|
||||
"headings.fmtUnnumbered": (str, nwHeadFmt.TITLE),
|
||||
"headings.fmtScene": (str, "* * *"),
|
||||
"headings.fmtHardScene": (str, ""),
|
||||
"headings.fmtAltScene": (str, ""),
|
||||
"headings.fmtSection": (str, ""),
|
||||
"headings.hideTitle": (bool, False),
|
||||
"headings.hideChapter": (bool, False),
|
||||
"headings.hideUnnumbered": (bool, False),
|
||||
"headings.hideScene": (bool, False),
|
||||
"headings.hideHardScene": (bool, False),
|
||||
"headings.hideAltScene": (bool, False),
|
||||
"headings.hideSection": (bool, True),
|
||||
"headings.centerTitle": (bool, True),
|
||||
"headings.centerChapter": (bool, False),
|
||||
@@ -110,7 +110,7 @@ SETTINGS_LABELS = {
|
||||
"headings.fmtChapter": QT_TRANSLATE_NOOP("Builds", "Chapter Format"),
|
||||
"headings.fmtUnnumbered": QT_TRANSLATE_NOOP("Builds", "Unnumbered Format"),
|
||||
"headings.fmtScene": QT_TRANSLATE_NOOP("Builds", "Scene Format"),
|
||||
"headings.fmtHardScene": QT_TRANSLATE_NOOP("Builds", "Hard Scene Format"),
|
||||
"headings.fmtAltScene": QT_TRANSLATE_NOOP("Builds", "Alt. Scene Format"),
|
||||
"headings.fmtSection": QT_TRANSLATE_NOOP("Builds", "Section Format"),
|
||||
|
||||
"text.grpContent": QT_TRANSLATE_NOOP("Builds", "Text Content"),
|
||||
@@ -178,7 +178,7 @@ class BuildSettings:
|
||||
def __init__(self) -> None:
|
||||
self._name = ""
|
||||
self._uuid = str(uuid.uuid4())
|
||||
self._path = Path.home()
|
||||
self._path = CONFIG.homePath()
|
||||
self._build = ""
|
||||
self._order = 0
|
||||
self._format = nwBuildFmt.ODT
|
||||
@@ -220,7 +220,7 @@ class BuildSettings:
|
||||
"""The last used build path."""
|
||||
if self._path.is_dir():
|
||||
return self._path
|
||||
return Path.home()
|
||||
return CONFIG.homePath()
|
||||
|
||||
@property
|
||||
def lastBuildName(self) -> str:
|
||||
@@ -297,7 +297,7 @@ class BuildSettings:
|
||||
if isinstance(path, Path) and path.is_dir():
|
||||
self._path = path
|
||||
else:
|
||||
self._path = Path.home()
|
||||
self._path = CONFIG.homePath()
|
||||
self._changed = True
|
||||
return
|
||||
|
||||
@@ -382,7 +382,7 @@ class BuildSettings:
|
||||
|
||||
postponed = []
|
||||
|
||||
def allowRoot(rHandle):
|
||||
def allowRoot(rHandle: str | None) -> None:
|
||||
if rHandle in postponed and rHandle in result and rHandle is not None:
|
||||
result[rHandle] = (True, FilterMode.ROOT)
|
||||
postponed.remove(rHandle)
|
||||
|
||||
@@ -104,7 +104,7 @@ class DocMerger:
|
||||
docText = self._project.storage.getDocumentText(srcHandle).rstrip("\n")
|
||||
if addComment:
|
||||
docInfo = srcItem.describeMe()
|
||||
docSt, _ = srcItem.getImportStatus(incIcon=False)
|
||||
docSt, _ = srcItem.getImportStatus()
|
||||
cmtLine = f"% {cmtPrefix} {docInfo}: {srcItem.itemName} [{docSt}]\n\n"
|
||||
docText = cmtLine + docText
|
||||
|
||||
|
||||
@@ -306,8 +306,8 @@ class NWBuildDocument:
|
||||
self._build.getBool("headings.hideScene")
|
||||
)
|
||||
bldObj.setHardSceneFormat(
|
||||
self._build.getStr("headings.fmtHardScene"),
|
||||
self._build.getBool("headings.hideHardScene")
|
||||
self._build.getStr("headings.fmtAltScene"),
|
||||
self._build.getBool("headings.hideAltScene")
|
||||
)
|
||||
bldObj.setSectionFormat(
|
||||
self._build.getStr("headings.fmtSection"),
|
||||
|
||||
@@ -25,7 +25,7 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from typing import TYPE_CHECKING, Any, Literal, overload
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from PyQt5.QtGui import QIcon
|
||||
|
||||
@@ -308,25 +308,15 @@ class NWItem:
|
||||
|
||||
return trConst(nwLabels.ITEM_DESCRIPTION.get(descKey, ""))
|
||||
|
||||
@overload # pragma: no cover
|
||||
def getImportStatus(self, incIcon: Literal[True] = True) -> tuple[str, QIcon]:
|
||||
pass
|
||||
|
||||
@overload # pragma: no cover
|
||||
def getImportStatus(self, incIcon: Literal[False]) -> tuple[str, None]:
|
||||
pass
|
||||
|
||||
def getImportStatus(self, incIcon=True):
|
||||
def getImportStatus(self) -> tuple[str, QIcon]:
|
||||
"""Return the relevant importance or status label and icon for
|
||||
the current item based on its class.
|
||||
"""
|
||||
if self.isNovelLike():
|
||||
stName = self._project.data.itemStatus.name(self._status)
|
||||
stIcon = self._project.data.itemStatus.icon(self._status) if incIcon else None
|
||||
entry = self._project.data.itemStatus[self._status]
|
||||
else:
|
||||
stName = self._project.data.itemImport.name(self._import)
|
||||
stIcon = self._project.data.itemImport.icon(self._import) if incIcon else None
|
||||
return stName, stIcon
|
||||
entry = self._project.data.itemImport[self._import]
|
||||
return entry.name, entry.icon
|
||||
|
||||
##
|
||||
# Checker Methods
|
||||
|
||||
+21
-52
@@ -26,33 +26,32 @@ from __future__ import annotations
|
||||
import json
|
||||
import logging
|
||||
|
||||
from collections.abc import Iterable
|
||||
from enum import Enum
|
||||
from functools import partial
|
||||
from pathlib import Path
|
||||
from time import time
|
||||
from typing import TYPE_CHECKING
|
||||
from pathlib import Path
|
||||
from functools import partial
|
||||
from collections.abc import Iterable
|
||||
|
||||
from PyQt5.QtCore import QCoreApplication
|
||||
|
||||
from novelwriter import CONFIG, SHARED, __version__, __hexversion__
|
||||
from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout
|
||||
from novelwriter.error import logException
|
||||
from novelwriter.constants import trConst, nwLabels
|
||||
from novelwriter.core.tree import NWTree
|
||||
from novelwriter.core.index import NWIndex
|
||||
from novelwriter.core.options import OptionState
|
||||
from novelwriter.core.storage import NWStorage, NWStorageOpen
|
||||
from novelwriter.core.sessions import NWSessionLog
|
||||
from novelwriter.core.projectxml import ProjectXMLReader, ProjectXMLWriter, XMLReadState
|
||||
from novelwriter.core.projectdata import NWProjectData
|
||||
from novelwriter.common import (
|
||||
checkStringNone, formatInt, formatTimeStamp, getFileSize, hexToInt, makeFileNameSafe, minmax
|
||||
)
|
||||
from novelwriter.constants import trConst, nwLabels
|
||||
from novelwriter.core.index import NWIndex
|
||||
from novelwriter.core.options import OptionState
|
||||
from novelwriter.core.projectdata import NWProjectData
|
||||
from novelwriter.core.projectxml import ProjectXMLReader, ProjectXMLWriter, XMLReadState
|
||||
from novelwriter.core.sessions import NWSessionLog
|
||||
from novelwriter.core.storage import NWStorage, NWStorageOpen
|
||||
from novelwriter.core.tree import NWTree
|
||||
from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout
|
||||
from novelwriter.error import logException
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from novelwriter.core.item import NWItem
|
||||
from novelwriter.core.status import NWStatus
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -461,14 +460,14 @@ class NWProject:
|
||||
|
||||
def setDefaultStatusImport(self) -> None:
|
||||
"""Set the default status and importance values."""
|
||||
self._data.itemStatus.write(None, self.tr("New"), (100, 100, 100))
|
||||
self._data.itemStatus.write(None, self.tr("Note"), (200, 50, 0))
|
||||
self._data.itemStatus.write(None, self.tr("Draft"), (200, 150, 0))
|
||||
self._data.itemStatus.write(None, self.tr("Finished"), (50, 200, 0))
|
||||
self._data.itemImport.write(None, self.tr("New"), (100, 100, 100))
|
||||
self._data.itemImport.write(None, self.tr("Minor"), (200, 50, 0))
|
||||
self._data.itemImport.write(None, self.tr("Major"), (200, 150, 0))
|
||||
self._data.itemImport.write(None, self.tr("Main"), (50, 200, 0))
|
||||
self._data.itemStatus.add(None, self.tr("New"), (100, 100, 100), "SQUARE", 0)
|
||||
self._data.itemStatus.add(None, self.tr("Note"), (200, 50, 0), "SQUARE", 0)
|
||||
self._data.itemStatus.add(None, self.tr("Draft"), (200, 150, 0), "SQUARE", 0)
|
||||
self._data.itemStatus.add(None, self.tr("Finished"), (50, 200, 0), "SQUARE", 0)
|
||||
self._data.itemImport.add(None, self.tr("New"), (100, 100, 100), "SQUARE", 0)
|
||||
self._data.itemImport.add(None, self.tr("Minor"), (200, 50, 0), "SQUARE", 0)
|
||||
self._data.itemImport.add(None, self.tr("Major"), (200, 150, 0), "SQUARE", 0)
|
||||
self._data.itemImport.add(None, self.tr("Main"), (50, 200, 0), "SQUARE", 0)
|
||||
return
|
||||
|
||||
def setProjectLang(self, language: str | None) -> None:
|
||||
@@ -491,14 +490,6 @@ class NWProject:
|
||||
self.setProjectChanged(True)
|
||||
return
|
||||
|
||||
def setStatusColours(self, new: list[dict], deleted: list[str]) -> bool:
|
||||
"""Update the list of novel file status flags."""
|
||||
return self._setStatusImport(new, deleted, self._data.itemStatus)
|
||||
|
||||
def setImportColours(self, new: list[dict], deleted: list[str]) -> bool:
|
||||
"""Update the list of note file importance flags."""
|
||||
return self._setStatusImport(new, deleted, self._data.itemImport)
|
||||
|
||||
def setProjectChanged(self, status: bool) -> bool:
|
||||
"""Toggle the project changed flag, and propagate the
|
||||
information to the GUI statusbar.
|
||||
@@ -584,28 +575,6 @@ class NWProject:
|
||||
# Internal Functions
|
||||
##
|
||||
|
||||
def _setStatusImport(self, new: list[dict], delete: list[str], target: NWStatus) -> bool:
|
||||
"""Update the list of novel file status or importance flags, and
|
||||
delete those that have been requested deleted.
|
||||
"""
|
||||
if not (new or delete):
|
||||
return False
|
||||
|
||||
order = []
|
||||
for entry in new:
|
||||
key = entry.get("key", None)
|
||||
name = entry.get("name", "")
|
||||
cols = entry.get("cols", (100, 100, 100))
|
||||
if name:
|
||||
order.append(target.write(key, name, cols))
|
||||
|
||||
for key in delete:
|
||||
target.remove(key)
|
||||
|
||||
target.reorder(order)
|
||||
|
||||
return True
|
||||
|
||||
def _loadProjectLocalisation(self) -> bool:
|
||||
"""Load the language data for the current project language."""
|
||||
if self._data.language is None or CONFIG._nwLangPath is None:
|
||||
|
||||
@@ -46,7 +46,7 @@ if TYPE_CHECKING: # pragma: no cover
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
FILE_VERSION = "1.5" # The current project file format version
|
||||
FILE_REVISION = "3" # The current project file format revision
|
||||
FILE_REVISION = "4" # The current project file format revision
|
||||
HEX_VERSION = 0x0105
|
||||
|
||||
NUM_VERSION = {
|
||||
@@ -109,6 +109,8 @@ class ProjectXMLReader:
|
||||
Rev 2: Drops the title node from project and adds the TEMPLATE
|
||||
class for items. 2.3 Beta 1.
|
||||
Rev 3: Added TEMPLATE class. 2.3.
|
||||
Rev 4: Added shape attribute to status and importance entry
|
||||
nodes. 2.5.
|
||||
"""
|
||||
|
||||
def __init__(self, path: str | Path) -> None:
|
||||
@@ -356,8 +358,8 @@ class ProjectXMLReader:
|
||||
logger.debug("Parsing <content> section (legacy format)")
|
||||
|
||||
# Create maps to look up name -> key for status and importance
|
||||
statusMap = {entry.get("name"): key for key, entry in data.itemStatus.items()}
|
||||
importMap = {entry.get("name"): key for key, entry in data.itemImport.items()}
|
||||
sMap: dict[str | None, str] = {e.name: k for k, e in data.itemStatus.iterItems()}
|
||||
iMap: dict[str | None, str] = {e.name: k for k, e in data.itemImport.iterItems()}
|
||||
|
||||
for xItem in xSection:
|
||||
if xItem.tag != "item":
|
||||
@@ -404,9 +406,9 @@ class ProjectXMLReader:
|
||||
|
||||
# Status was split into separate status/import with a key in 1.4
|
||||
if item.get("class", "") in ("NOVEL", "ARCHIVE"):
|
||||
name["status"] = statusMap.get(tmpStatus, None)
|
||||
name["status"] = sMap.get(tmpStatus, None)
|
||||
else:
|
||||
name["import"] = importMap.get(tmpStatus, None)
|
||||
name["import"] = iMap.get(tmpStatus, None)
|
||||
|
||||
# A number of layouts were removed in 1.3
|
||||
if item.get("layout", "") in (
|
||||
@@ -436,7 +438,8 @@ class ProjectXMLReader:
|
||||
green = checkInt(xEntry.attrib.get("green", 0), 0)
|
||||
blue = checkInt(xEntry.attrib.get("blue", 0), 0)
|
||||
count = checkInt(xEntry.attrib.get("count", 0), 0)
|
||||
sObject.write(key, xEntry.text or "", (red, green, blue), count)
|
||||
shape = xEntry.attrib.get("shape", "")
|
||||
sObject.add(key, xEntry.text or "", (red, green, blue), shape, count)
|
||||
return
|
||||
|
||||
def _parseDictKeyText(self, xItem: ET.Element) -> dict:
|
||||
@@ -449,7 +452,7 @@ class ProjectXMLReader:
|
||||
result[xEntry.attrib["key"]] = checkString(xEntry.text, "")
|
||||
return result
|
||||
|
||||
def _parseDictTagText(self, xItem) -> dict:
|
||||
def _parseDictTagText(self, xItem: ET.Element) -> dict:
|
||||
"""Parse a dictionary stored with key as the tag and the value
|
||||
as the text property.
|
||||
"""
|
||||
|
||||
@@ -74,7 +74,7 @@ class NWSpellEnchant:
|
||||
# Setters
|
||||
##
|
||||
|
||||
def setLanguage(self, language: str | None):
|
||||
def setLanguage(self, language: str | None) -> None:
|
||||
"""Load a dictionary for the language specified in the config.
|
||||
If that fails, we load a mock dictionary so that lookups don't
|
||||
crash. Note that enchant will allow loading an empty string as
|
||||
@@ -182,10 +182,10 @@ class FakeEnchant:
|
||||
def check(self, word: str) -> bool:
|
||||
return True
|
||||
|
||||
def suggest(self, word) -> list[str]:
|
||||
def suggest(self, word: str) -> list[str]:
|
||||
return []
|
||||
|
||||
def add_to_session(self, word: str):
|
||||
def add_to_session(self, word: str) -> None:
|
||||
return
|
||||
|
||||
# END Class FakeEnchant
|
||||
|
||||
+222
-169
@@ -24,17 +24,19 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import random
|
||||
import dataclasses
|
||||
import logging
|
||||
import random
|
||||
|
||||
from typing import TYPE_CHECKING, Literal
|
||||
from collections.abc import ItemsView, Iterable, Iterator, KeysView, ValuesView
|
||||
from collections.abc import Iterable
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from PyQt5.QtGui import QIcon, QPainter, QPainterPath, QPixmap, QColor
|
||||
from PyQt5.QtCore import QRectF
|
||||
from PyQt5.QtCore import QPointF, Qt
|
||||
from PyQt5.QtGui import QIcon, QPainter, QPainterPath, QPixmap, QColor, QPolygonF
|
||||
|
||||
from novelwriter import CONFIG
|
||||
from novelwriter.common import minmax, simplified
|
||||
from novelwriter import SHARED
|
||||
from novelwriter.common import simplified
|
||||
from novelwriter.enum import nwStatusShape
|
||||
from novelwriter.types import QtPaintAnitAlias, QtTransparent
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
@@ -43,83 +45,94 @@ if TYPE_CHECKING: # pragma: no cover
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class StatusEntry:
|
||||
|
||||
name: str
|
||||
color: QColor
|
||||
shape: nwStatusShape
|
||||
icon: QIcon
|
||||
count: int = 0
|
||||
|
||||
@classmethod
|
||||
def duplicate(cls, source: StatusEntry) -> StatusEntry:
|
||||
"""Create a deep copy of the source object."""
|
||||
cls = dataclasses.replace(source)
|
||||
cls.color = QColor(source.color)
|
||||
cls.icon = QIcon(source.icon)
|
||||
return cls
|
||||
|
||||
# END Class StatusEntry
|
||||
|
||||
|
||||
NO_ENTRY = StatusEntry("", QColor(0, 0, 0), nwStatusShape.SQUARE, QIcon(), 0)
|
||||
|
||||
|
||||
class NWStatus:
|
||||
|
||||
STATUS = 1
|
||||
IMPORT = 2
|
||||
STATUS = "s"
|
||||
IMPORT = "i"
|
||||
|
||||
def __init__(self, kind: Literal[1, 2]) -> None:
|
||||
__slots__ = ("_store", "_default", "_prefix", "_height")
|
||||
|
||||
self._type = kind
|
||||
self._store = {}
|
||||
def __init__(self, prefix: str) -> None:
|
||||
self._store: dict[str, StatusEntry] = {}
|
||||
self._default = None
|
||||
|
||||
self._iPX = CONFIG.pxInt(24)
|
||||
|
||||
pA = CONFIG.pxInt(2)
|
||||
pB = CONFIG.pxInt(20)
|
||||
pR = float(CONFIG.pxInt(4))
|
||||
self._iconPath = QPainterPath()
|
||||
self._iconPath.addRoundedRect(QRectF(pA, pA, pB, pB), pR, pR)
|
||||
|
||||
self._defaultIcon = self._createIcon(100, 100, 100)
|
||||
|
||||
if self._type == self.STATUS:
|
||||
self._prefix = "s"
|
||||
elif self._type == self.IMPORT:
|
||||
self._prefix = "i"
|
||||
else:
|
||||
raise Exception("This is a bug!")
|
||||
|
||||
self._prefix = prefix[:1]
|
||||
self._height = SHARED.theme.baseIconHeight
|
||||
return
|
||||
|
||||
def write(self, key: str | None, name: str, col: tuple, count: int | None = None) -> str:
|
||||
def __len__(self) -> int:
|
||||
return len(self._store)
|
||||
|
||||
def __getitem__(self, key: str | None) -> StatusEntry:
|
||||
"""Return the entry associated with a given key."""
|
||||
if key and key in self._store:
|
||||
return self._store[key]
|
||||
elif self._default is not None:
|
||||
return self._store[self._default]
|
||||
return NO_ENTRY
|
||||
|
||||
##
|
||||
# Methods
|
||||
##
|
||||
|
||||
def add(self, key: str | None, name: str, color: tuple[int, int, int],
|
||||
shape: str, count: int) -> str:
|
||||
"""Add or update a status entry. If the key is invalid, a new
|
||||
key is generated.
|
||||
"""
|
||||
if not self._isKey(key):
|
||||
key = self._newKey()
|
||||
if not isinstance(col, tuple):
|
||||
col = (100, 100, 100)
|
||||
if len(col) != 3:
|
||||
col = (100, 100, 100)
|
||||
if isinstance(color, tuple) and len(color) == 3:
|
||||
qColor = QColor(*color)
|
||||
else:
|
||||
qColor = QColor(100, 100, 100)
|
||||
|
||||
cR = minmax(col[0], 0, 255)
|
||||
cG = minmax(col[1], 0, 255)
|
||||
cB = minmax(col[2], 0, 255)
|
||||
try:
|
||||
iShape = nwStatusShape[shape]
|
||||
except KeyError:
|
||||
iShape = nwStatusShape.SQUARE
|
||||
|
||||
key = self._checkKey(key)
|
||||
name = simplified(name)
|
||||
if count is None:
|
||||
count = self._store.get(key, {}).get("count", 0)
|
||||
|
||||
self._store[key] = {
|
||||
"name": name,
|
||||
"icon": self._createIcon(cR, cG, cB),
|
||||
"cols": (cR, cG, cB),
|
||||
"count": count,
|
||||
}
|
||||
icon = self.createIcon(self._height, qColor, iShape)
|
||||
self._store[key] = StatusEntry(name, qColor, iShape, icon, count)
|
||||
|
||||
if self._default is None:
|
||||
self._default = key
|
||||
|
||||
return key
|
||||
|
||||
def remove(self, key: str) -> bool:
|
||||
"""Remove an entry in the list, except if the count > 0."""
|
||||
if key not in self._store:
|
||||
return False
|
||||
if self._store[key]["count"] > 0:
|
||||
return False
|
||||
def update(self, update: list[tuple[str | None, StatusEntry]]) -> None:
|
||||
"""Update the list of statuses, and from removed list."""
|
||||
self._store.clear()
|
||||
for key, entry in update:
|
||||
self._store[self._checkKey(key)] = entry
|
||||
|
||||
del self._store[key]
|
||||
# Check if we need a new default
|
||||
if self._default not in self._store:
|
||||
self._default = next(iter(self._store)) if self._store else None
|
||||
|
||||
keys = list(self._store.keys())
|
||||
if key == self._default:
|
||||
if len(keys) > 0:
|
||||
self._default = keys[0]
|
||||
else:
|
||||
self._default = None
|
||||
|
||||
return True
|
||||
return
|
||||
|
||||
def check(self, value: str) -> str:
|
||||
"""Check the key against the stored status names."""
|
||||
@@ -129,93 +142,51 @@ class NWStatus:
|
||||
return self._default
|
||||
return ""
|
||||
|
||||
def name(self, key: str | None) -> str:
|
||||
"""Return the name associated with a given key."""
|
||||
if key and key in self._store:
|
||||
return self._store[key]["name"]
|
||||
elif self._default is not None:
|
||||
return self._store[self._default]["name"]
|
||||
return ""
|
||||
|
||||
def cols(self, key: str | None) -> tuple[int, int, int]:
|
||||
"""Return the colours associated with a given key."""
|
||||
if key and key in self._store:
|
||||
return self._store[key]["cols"]
|
||||
elif self._default is not None:
|
||||
return self._store[self._default]["cols"]
|
||||
return 100, 100, 100
|
||||
|
||||
def count(self, key: str | None) -> int:
|
||||
"""Return the count associated with a given key."""
|
||||
if key and key in self._store:
|
||||
return self._store[key]["count"]
|
||||
elif self._default is not None:
|
||||
return self._store[self._default]["count"]
|
||||
return 0
|
||||
|
||||
def icon(self, key: str | None) -> QIcon:
|
||||
"""Return the icon associated with a given key."""
|
||||
if key and key in self._store:
|
||||
return self._store[key]["icon"]
|
||||
elif self._default is not None:
|
||||
return self._store[self._default]["icon"]
|
||||
return self._defaultIcon
|
||||
|
||||
def reorder(self, order: list[str]) -> bool:
|
||||
"""Reorder the items according to list."""
|
||||
if len(order) != len(self._store):
|
||||
logger.error("Length mismatch between new and old order")
|
||||
return False
|
||||
|
||||
if order == list(self._store.keys()):
|
||||
return False
|
||||
|
||||
store = {}
|
||||
for key in order:
|
||||
if key in self._store:
|
||||
store[key] = self._store[key]
|
||||
else:
|
||||
logger.error("Unknown key '%s' in order", key)
|
||||
return False
|
||||
|
||||
self._store = store
|
||||
|
||||
return True
|
||||
|
||||
def resetCounts(self) -> None:
|
||||
"""Clear the counts of references to the status entries."""
|
||||
for key in self._store:
|
||||
self._store[key]["count"] = 0
|
||||
for entry in self._store.values():
|
||||
entry.count = 0
|
||||
return
|
||||
|
||||
def increment(self, key: str | None) -> None:
|
||||
"""Increment the counter for a given entry."""
|
||||
if key and key in self._store:
|
||||
self._store[key]["count"] += 1
|
||||
self._store[key].count += 1
|
||||
return
|
||||
|
||||
def pack(self) -> Iterable[tuple[str, dict]]:
|
||||
"""Pack the status entries into a dictionary."""
|
||||
for key, data in self._store.items():
|
||||
yield (data["name"], {
|
||||
for key, entry in self._store.items():
|
||||
yield (entry.name, {
|
||||
"key": key,
|
||||
"count": str(data["count"]),
|
||||
"red": str(data["cols"][0]),
|
||||
"green": str(data["cols"][1]),
|
||||
"blue": str(data["cols"][2]),
|
||||
"count": str(entry.count),
|
||||
"red": str(entry.color.red()),
|
||||
"green": str(entry.color.green()),
|
||||
"blue": str(entry.color.blue()),
|
||||
"shape": entry.shape.name,
|
||||
})
|
||||
return
|
||||
|
||||
def unpack(self, data: dict) -> None:
|
||||
"""Unpack a data dictionary and set the class values."""
|
||||
self._store = {}
|
||||
self._default = None
|
||||
for key, entry in data.items():
|
||||
label = entry.get("label", "")
|
||||
colour = entry.get("colour", (100, 100, 100))
|
||||
count = entry.get("count", 0)
|
||||
self.write(key, label, colour, count)
|
||||
return
|
||||
def iterItems(self) -> Iterable[tuple[str, StatusEntry]]:
|
||||
"""Yield entries from the status icons."""
|
||||
yield from self._store.items()
|
||||
|
||||
@staticmethod
|
||||
def createIcon(height: int, color: QColor, shape: nwStatusShape) -> QIcon:
|
||||
"""Generate an icon for a status label."""
|
||||
pixmap = QPixmap(48, 48)
|
||||
pixmap.fill(QtTransparent)
|
||||
|
||||
painter = QPainter(pixmap)
|
||||
painter.setRenderHint(QtPaintAnitAlias)
|
||||
painter.fillPath(_SHAPES.getShape(shape), color)
|
||||
painter.end()
|
||||
|
||||
return QIcon(pixmap.scaled(
|
||||
height, height,
|
||||
Qt.AspectRatioMode.IgnoreAspectRatio,
|
||||
Qt.TransformationMode.SmoothTransformation
|
||||
))
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
@@ -246,38 +217,120 @@ class NWStatus:
|
||||
return False
|
||||
return True
|
||||
|
||||
def _createIcon(self, red: int, green: int, blue: int) -> QIcon:
|
||||
"""Generate an icon for a status label."""
|
||||
pixmap = QPixmap(self._iPX, self._iPX)
|
||||
pixmap.fill(QtTransparent)
|
||||
|
||||
painter = QPainter(pixmap)
|
||||
painter.setRenderHint(QtPaintAnitAlias)
|
||||
painter.fillPath(self._iconPath, QColor(red, green, blue))
|
||||
painter.end()
|
||||
|
||||
return QIcon(pixmap)
|
||||
|
||||
##
|
||||
# Iterator Bits
|
||||
##
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self._store)
|
||||
|
||||
def __getitem__(self, key: str) -> dict:
|
||||
return self._store[key]
|
||||
|
||||
def __iter__(self) -> Iterator[dict]:
|
||||
return iter(self._store)
|
||||
|
||||
def keys(self) -> KeysView[str]:
|
||||
return self._store.keys()
|
||||
|
||||
def items(self) -> ItemsView[str, dict]:
|
||||
return self._store.items()
|
||||
|
||||
def values(self) -> ValuesView[dict]:
|
||||
return self._store.values()
|
||||
def _checkKey(self, key: str | None) -> str:
|
||||
"""Check key is valid, and if not, generate one."""
|
||||
return key if self._isKey(key) else self._newKey()
|
||||
|
||||
# END Class NWStatus
|
||||
|
||||
|
||||
class _ShapeCache:
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._cache: dict[nwStatusShape, QPainterPath] = {}
|
||||
return
|
||||
|
||||
def getShape(self, shape: nwStatusShape) -> QPainterPath:
|
||||
"""Return a painter shape for an icon."""
|
||||
if shape in self._cache:
|
||||
return self._cache[shape]
|
||||
|
||||
path = QPainterPath()
|
||||
if shape == nwStatusShape.SQUARE:
|
||||
path.addRoundedRect(2.0, 2.0, 44.0, 44.0, 4.0, 4.0)
|
||||
elif shape == nwStatusShape.TRIANGLE:
|
||||
path.addPolygon(QPolygonF([
|
||||
QPointF(24.00, 3.00),
|
||||
QPointF(43.92, 37.50),
|
||||
QPointF(4.08, 37.50),
|
||||
]))
|
||||
elif shape == nwStatusShape.NABLA:
|
||||
path.addPolygon(QPolygonF([
|
||||
QPointF(24.00, 48.00),
|
||||
QPointF(4.08, 14.50),
|
||||
QPointF(43.92, 14.50),
|
||||
]))
|
||||
elif shape == nwStatusShape.DIAMOND:
|
||||
path.addPolygon(QPolygonF([
|
||||
QPointF(24.00, 2.00),
|
||||
QPointF(44.00, 24.00),
|
||||
QPointF(24.00, 46.00),
|
||||
QPointF(4.00, 24.00),
|
||||
]))
|
||||
elif shape == nwStatusShape.PENTAGON:
|
||||
path.addPolygon(QPolygonF([
|
||||
QPointF(24.00, 1.50),
|
||||
QPointF(45.87, 17.39),
|
||||
QPointF(37.52, 43.11),
|
||||
QPointF(10.48, 43.11),
|
||||
QPointF(2.13, 17.39),
|
||||
]))
|
||||
elif shape == nwStatusShape.HEXAGON:
|
||||
path.addPolygon(QPolygonF([
|
||||
QPointF(24.00, 1.50),
|
||||
QPointF(43.92, 13.00),
|
||||
QPointF(43.92, 36.00),
|
||||
QPointF(24.00, 47.50),
|
||||
QPointF(4.08, 36.00),
|
||||
QPointF(4.08, 13.00),
|
||||
]))
|
||||
elif shape == nwStatusShape.STAR:
|
||||
path.addPolygon(QPolygonF([
|
||||
QPointF(24.00, 0.50), QPointF(31.05, 14.79),
|
||||
QPointF(46.83, 17.08), QPointF(35.41, 28.21),
|
||||
QPointF(38.11, 43.92), QPointF(24.00, 36.50),
|
||||
QPointF(9.89, 43.92), QPointF(12.59, 28.21),
|
||||
QPointF(1.17, 17.08), QPointF(15.37, 16.16),
|
||||
]))
|
||||
elif shape == nwStatusShape.PACMAN:
|
||||
path.moveTo(24.0, 24.0)
|
||||
path.arcTo(2.0, 2.0, 44.0, 44.0, 40.0, 280.0)
|
||||
elif shape == nwStatusShape.CIRCLE_Q:
|
||||
path.moveTo(24.0, 24.0)
|
||||
path.arcTo(2.0, 2.0, 44.0, 44.0, 0.0, 90.0)
|
||||
elif shape == nwStatusShape.CIRCLE_H:
|
||||
path.moveTo(24.0, 24.0)
|
||||
path.arcTo(2.0, 2.0, 44.0, 44.0, -90.0, 180.0)
|
||||
elif shape == nwStatusShape.CIRCLE_T:
|
||||
path.moveTo(24.0, 24.0)
|
||||
path.arcTo(2.0, 2.0, 44.0, 44.0, -180.0, 270.0)
|
||||
elif shape == nwStatusShape.CIRCLE:
|
||||
path.addEllipse(2.0, 2.0, 44.0, 44.0)
|
||||
elif shape == nwStatusShape.BARS_1:
|
||||
path.addRoundedRect(2.0, 2.0, 8.0, 44.0, 4.0, 4.0)
|
||||
elif shape == nwStatusShape.BARS_2:
|
||||
path.addRoundedRect(2.0, 2.0, 8.0, 44.0, 4.0, 4.0)
|
||||
path.addRoundedRect(14.0, 2.0, 8.0, 44.0, 4.0, 4.0)
|
||||
elif shape == nwStatusShape.BARS_3:
|
||||
path.addRoundedRect(2.0, 2.0, 8.0, 44.0, 4.0, 4.0)
|
||||
path.addRoundedRect(14.0, 2.0, 8.0, 44.0, 4.0, 4.0)
|
||||
path.addRoundedRect(26.0, 2.0, 8.0, 44.0, 4.0, 4.0)
|
||||
elif shape == nwStatusShape.BARS_4:
|
||||
path.addRoundedRect(2.0, 2.0, 8.0, 44.0, 4.0, 4.0)
|
||||
path.addRoundedRect(14.0, 2.0, 8.0, 44.0, 4.0, 4.0)
|
||||
path.addRoundedRect(26.0, 2.0, 8.0, 44.0, 4.0, 4.0)
|
||||
path.addRoundedRect(38.0, 2.0, 8.0, 44.0, 4.0, 4.0)
|
||||
elif shape == nwStatusShape.BLOCK_1:
|
||||
path.addRoundedRect(2.0, 2.0, 20.0, 20.0, 4.0, 4.0)
|
||||
elif shape == nwStatusShape.BLOCK_2:
|
||||
path.addRoundedRect(2.0, 2.0, 20.0, 20.0, 4.0, 4.0)
|
||||
path.addRoundedRect(24.0, 2.0, 20.0, 20.0, 4.0, 4.0)
|
||||
elif shape == nwStatusShape.BLOCK_3:
|
||||
path.addRoundedRect(2.0, 2.0, 20.0, 20.0, 4.0, 4.0)
|
||||
path.addRoundedRect(2.0, 24.0, 20.0, 20.0, 4.0, 4.0)
|
||||
path.addRoundedRect(24.0, 2.0, 20.0, 20.0, 4.0, 4.0)
|
||||
elif shape == nwStatusShape.BLOCK_4:
|
||||
path.addRoundedRect(2.0, 2.0, 20.0, 20.0, 4.0, 4.0)
|
||||
path.addRoundedRect(2.0, 24.0, 20.0, 20.0, 4.0, 4.0)
|
||||
path.addRoundedRect(24.0, 2.0, 20.0, 20.0, 4.0, 4.0)
|
||||
path.addRoundedRect(24.0, 24.0, 20.0, 20.0, 4.0, 4.0)
|
||||
|
||||
self._cache[shape] = path
|
||||
|
||||
return path
|
||||
|
||||
# END Class _ShapeCache
|
||||
|
||||
|
||||
# Create Singleton
|
||||
_SHAPES = _ShapeCache()
|
||||
|
||||
@@ -49,7 +49,7 @@ ESCAPES = {r"\*": "*", r"\~": "~", r"\_": "_", r"\[": "[", r"\]": "]", r"\ ": ""
|
||||
RX_ESC = re.compile("|".join([re.escape(k) for k in ESCAPES.keys()]), flags=re.DOTALL)
|
||||
|
||||
|
||||
def stripEscape(text) -> str:
|
||||
def stripEscape(text: str) -> str:
|
||||
"""Strip escaped Markdown characters from paragraph text."""
|
||||
if "\\" in text:
|
||||
return RX_ESC.sub(lambda x: ESCAPES[x.group(0)], text)
|
||||
@@ -639,8 +639,8 @@ class Tokenizer(ABC):
|
||||
tmpMarkdown.append(f"{aLine}\n")
|
||||
|
||||
elif aLine.startswith(("### ", "###! ")):
|
||||
# (Hard) Scene Headings
|
||||
# =====================
|
||||
# (Alternative) Scene Headings
|
||||
# ============================
|
||||
# Scene headings in novel documents are treated as centred
|
||||
# separators if the formatting does not change the text. If the
|
||||
# format is empty, the scene can be hidden or a blank paragraph
|
||||
|
||||
@@ -548,7 +548,7 @@ class ToOdt(Tokenizer):
|
||||
oVers = _mkTag("office", "version")
|
||||
xSett = ET.Element(oRoot, attrib={oVers: X_VERS})
|
||||
|
||||
def putInZip(name, xObj, zipObj):
|
||||
def putInZip(name: str, xObj: ET.Element, zipObj: ZipFile) -> None:
|
||||
with zipObj.open(name, mode="w") as fObj:
|
||||
xml = ET.ElementTree(xObj)
|
||||
xml.write(fObj, encoding="utf-8", xml_declaration=True)
|
||||
|
||||
@@ -795,7 +795,7 @@ class GuiPreferences(QDialog):
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _selectTextFont(self):
|
||||
def _selectTextFont(self) -> None:
|
||||
"""Open the QFontDialog and set a font for the font style."""
|
||||
current = QFont()
|
||||
current.setFamily(CONFIG.textFont)
|
||||
|
||||
@@ -27,20 +27,26 @@ from __future__ import annotations
|
||||
import logging
|
||||
|
||||
from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot
|
||||
from PyQt5.QtGui import QCloseEvent, QColor, QIcon, QPixmap
|
||||
from PyQt5.QtGui import QCloseEvent, QColor
|
||||
from PyQt5.QtWidgets import (
|
||||
QApplication, QColorDialog, QDialog, QDialogButtonBox, QHBoxLayout,
|
||||
QLineEdit, QPushButton, QStackedWidget, QTreeWidget, QTreeWidgetItem,
|
||||
QVBoxLayout, QWidget
|
||||
QAbstractItemView, QApplication, QColorDialog, QDialog, QDialogButtonBox, QHBoxLayout,
|
||||
QLineEdit, QMenu, QStackedWidget, QToolButton, QTreeWidget,
|
||||
QTreeWidgetItem, QVBoxLayout, QWidget
|
||||
)
|
||||
|
||||
from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.common import simplified
|
||||
from novelwriter.constants import nwLabels, trConst
|
||||
from novelwriter.core.status import NWStatus, StatusEntry
|
||||
from novelwriter.enum import nwStatusShape
|
||||
from novelwriter.extensions.configlayout import NColourLabel, NFixedPage, NScrollableForm
|
||||
from novelwriter.extensions.modified import NComboBox, NIconToolButton
|
||||
from novelwriter.extensions.pagedsidebar import NPagedSideBar
|
||||
from novelwriter.extensions.switch import NSwitch
|
||||
from novelwriter.types import QtDialogCancel, QtDialogSave, QtUserRole
|
||||
from novelwriter.types import (
|
||||
QtDialogCancel, QtDialogSave, QtSizeMinimum, QtSizeMinimumExpanding,
|
||||
QtUserRole
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -179,19 +185,19 @@ class GuiProjectSettings(QDialog):
|
||||
|
||||
rebuildTrees = False
|
||||
|
||||
if self.statusPage.wasChanged:
|
||||
newList, delList = self.statusPage.getNewList()
|
||||
project.setStatusColours(newList, delList)
|
||||
if self.statusPage.changed:
|
||||
logger.debug("Updating status labels")
|
||||
project.data.itemStatus.update(self.statusPage.getNewList())
|
||||
rebuildTrees = True
|
||||
|
||||
if self.importPage.wasChanged:
|
||||
newList, delList = self.importPage.getNewList()
|
||||
project.setImportColours(newList, delList)
|
||||
if self.importPage.changed:
|
||||
logger.debug("Updating importance labels")
|
||||
project.data.itemImport.update(self.importPage.getNewList())
|
||||
rebuildTrees = True
|
||||
|
||||
if self.replacePage.wasChanged:
|
||||
newList = self.replacePage.getNewList()
|
||||
project.data.setAutoReplace(newList)
|
||||
if self.replacePage.changed:
|
||||
logger.debug("Updating auto-replace settings")
|
||||
project.data.setAutoReplace(self.replacePage.getNewList())
|
||||
|
||||
self.newProjectSettingsReady.emit(rebuildTrees)
|
||||
QApplication.processEvents()
|
||||
@@ -257,17 +263,17 @@ class _SettingsPage(NScrollableForm):
|
||||
)
|
||||
|
||||
# Project Language
|
||||
projLang = data.language or CONFIG.guiLocale
|
||||
self.projLang = NComboBox(self)
|
||||
self.projLang.setMinimumWidth(xW)
|
||||
for tag, language in CONFIG.listLanguages(CONFIG.LANG_PROJ):
|
||||
self.projLang.addItem(language, tag)
|
||||
self.projLang.setCurrentData(projLang, projLang)
|
||||
self.addRow(
|
||||
self.tr("Project language"), self.projLang,
|
||||
self.tr("Only used when building the manuscript."),
|
||||
stretch=(3, 2)
|
||||
)
|
||||
if (idx := self.projLang.findData(data.language)) != -1:
|
||||
self.projLang.setCurrentIndex(idx)
|
||||
|
||||
# Spell Check Language
|
||||
self.spellLang = NComboBox(self)
|
||||
@@ -301,12 +307,12 @@ class _SettingsPage(NScrollableForm):
|
||||
|
||||
class _StatusPage(NFixedPage):
|
||||
|
||||
COL_LABEL = 0
|
||||
COL_USAGE = 1
|
||||
C_DATA = 0
|
||||
C_LABEL = 0
|
||||
C_USAGE = 1
|
||||
|
||||
KEY_ROLE = QtUserRole
|
||||
COL_ROLE = QtUserRole + 1
|
||||
NUM_ROLE = QtUserRole + 2
|
||||
D_KEY = QtUserRole
|
||||
D_ENTRY = QtUserRole + 1
|
||||
|
||||
def __init__(self, parent: QWidget, isStatus: bool) -> None:
|
||||
super().__init__(parent=parent)
|
||||
@@ -325,12 +331,21 @@ class _StatusPage(NFixedPage):
|
||||
)
|
||||
|
||||
self._changed = False
|
||||
self._colDeleted = []
|
||||
self._selColour = QColor(100, 100, 100)
|
||||
self._color = QColor(100, 100, 100)
|
||||
self._shape = nwStatusShape.SQUARE
|
||||
self._icons = {}
|
||||
|
||||
self.iPx = SHARED.theme.baseIconHeight
|
||||
self._iPx = SHARED.theme.baseIconHeight
|
||||
iSz = SHARED.theme.baseIconSize
|
||||
bSz = SHARED.theme.buttonIconSize
|
||||
bPd = CONFIG.pxInt(4)
|
||||
|
||||
iColor = self.palette().text().color()
|
||||
|
||||
# Labels
|
||||
self.trCountNone = self.tr("Not in use")
|
||||
self.trCountOne = self.tr("Used once")
|
||||
self.trCountMore = self.tr("Used by {0} items")
|
||||
self.trSelColor = self.tr("Select Colour")
|
||||
|
||||
# Title
|
||||
self.pageTitle = NColourLabel(
|
||||
@@ -341,12 +356,14 @@ class _StatusPage(NFixedPage):
|
||||
# List Box
|
||||
self.listBox = QTreeWidget(self)
|
||||
self.listBox.setHeaderLabels([self.tr("Label"), self.tr("Usage")])
|
||||
self.listBox.itemSelectionChanged.connect(self._selectedItem)
|
||||
self.listBox.setColumnWidth(self.COL_LABEL, wCol0)
|
||||
self.listBox.setColumnWidth(self.C_LABEL, wCol0)
|
||||
self.listBox.setIndentation(0)
|
||||
self.listBox.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
|
||||
self.listBox.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
|
||||
self.listBox.itemSelectionChanged.connect(self._selectionChanged)
|
||||
|
||||
for key, entry in status.items():
|
||||
self._addItem(key, entry["name"], entry["cols"], entry["count"])
|
||||
for key, entry in status.iterItems():
|
||||
self._addItem(key, StatusEntry.duplicate(entry))
|
||||
|
||||
# List Controls
|
||||
self.addButton = NIconToolButton(self, iSz, "add")
|
||||
@@ -367,16 +384,43 @@ class _StatusPage(NFixedPage):
|
||||
self.editName.setPlaceholderText(self.tr("Select item to edit"))
|
||||
self.editName.setEnabled(False)
|
||||
|
||||
self.colPixmap = QPixmap(self.iPx, self.iPx)
|
||||
self.colPixmap.fill(QColor(100, 100, 100))
|
||||
self.colButton = QPushButton(QIcon(self.colPixmap), self.tr("Colour"), self)
|
||||
self.colButton.setIconSize(bSz)
|
||||
self.colButton.setEnabled(False)
|
||||
self.colButton.clicked.connect(self._selectColour)
|
||||
buttonStyle = (
|
||||
f"QToolButton {{padding: 0 {bPd}px;}} "
|
||||
"QToolButton::menu-indicator {image: none;}"
|
||||
)
|
||||
|
||||
self.saveButton = QPushButton(self.tr("Save"), self)
|
||||
self.saveButton.setEnabled(False)
|
||||
self.saveButton.clicked.connect(self._saveItem)
|
||||
self.colorButton = NIconToolButton(self, iSz)
|
||||
self.colorButton.setToolTip(self.tr("Colour"))
|
||||
self.colorButton.setSizePolicy(QtSizeMinimum, QtSizeMinimumExpanding)
|
||||
self.colorButton.setStyleSheet(buttonStyle)
|
||||
self.colorButton.setEnabled(False)
|
||||
self.colorButton.clicked.connect(self._selectColour)
|
||||
|
||||
def buildMenu(menu: QMenu, items: dict[nwStatusShape, str]) -> None:
|
||||
for shape, label in items.items():
|
||||
icon = NWStatus.createIcon(self._iPx, iColor, shape)
|
||||
action = menu.addAction(icon, trConst(label))
|
||||
action.triggered.connect(lambda _, shape=shape: self._selectShape(shape))
|
||||
self._icons[shape] = icon
|
||||
|
||||
self.shapeMenu = QMenu(self)
|
||||
buildMenu(self.shapeMenu, nwLabels.SHAPES_PLAIN)
|
||||
buildMenu(self.shapeMenu.addMenu(self.tr("Circles ...")), nwLabels.SHAPES_CIRCLE)
|
||||
buildMenu(self.shapeMenu.addMenu(self.tr("Bars ...")), nwLabels.SHAPES_BARS)
|
||||
buildMenu(self.shapeMenu.addMenu(self.tr("Blocks ...")), nwLabels.SHAPES_BLOCKS)
|
||||
|
||||
self.shapeButton = NIconToolButton(self, iSz)
|
||||
self.shapeButton.setMenu(self.shapeMenu)
|
||||
self.shapeButton.setToolTip(self.tr("Shape"))
|
||||
self.shapeButton.setSizePolicy(QtSizeMinimum, QtSizeMinimumExpanding)
|
||||
self.shapeButton.setStyleSheet(buttonStyle)
|
||||
self.shapeButton.setEnabled(False)
|
||||
|
||||
self.applyButton = QToolButton(self)
|
||||
self.applyButton.setText(self.tr("Apply"))
|
||||
self.applyButton.setSizePolicy(QtSizeMinimum, QtSizeMinimumExpanding)
|
||||
self.applyButton.setEnabled(False)
|
||||
self.applyButton.clicked.connect(self._applyChanges)
|
||||
|
||||
# Assemble
|
||||
self.listControls = QVBoxLayout()
|
||||
@@ -387,28 +431,30 @@ class _StatusPage(NFixedPage):
|
||||
self.listControls.addStretch(1)
|
||||
|
||||
self.editBox = QHBoxLayout()
|
||||
self.editBox.addWidget(self.editName)
|
||||
self.editBox.addWidget(self.colButton)
|
||||
self.editBox.addWidget(self.saveButton)
|
||||
self.editBox.addWidget(self.editName, 1)
|
||||
self.editBox.addWidget(self.colorButton, 0)
|
||||
self.editBox.addWidget(self.shapeButton, 0)
|
||||
self.editBox.addWidget(self.applyButton, 0)
|
||||
|
||||
self.mainBox = QVBoxLayout()
|
||||
self.mainBox.addWidget(self.listBox)
|
||||
self.mainBox.addLayout(self.editBox)
|
||||
self.mainBox.addWidget(self.listBox, 1)
|
||||
self.mainBox.addLayout(self.editBox, 0)
|
||||
|
||||
self.innerBox = QHBoxLayout()
|
||||
self.innerBox.addLayout(self.mainBox)
|
||||
self.innerBox.addLayout(self.listControls)
|
||||
self.innerBox.addLayout(self.mainBox, 1)
|
||||
self.innerBox.addLayout(self.listControls, 0)
|
||||
|
||||
self.outerBox = QVBoxLayout()
|
||||
self.outerBox.addWidget(self.pageTitle)
|
||||
self.outerBox.addLayout(self.innerBox)
|
||||
self.outerBox.addWidget(self.pageTitle, 0)
|
||||
self.outerBox.addLayout(self.innerBox, 1)
|
||||
|
||||
self.setCentralLayout(self.outerBox)
|
||||
self._setButtonIcons()
|
||||
|
||||
return
|
||||
|
||||
@property
|
||||
def wasChanged(self) -> bool:
|
||||
def changed(self) -> bool:
|
||||
"""The user changed these settings."""
|
||||
return self._changed
|
||||
|
||||
@@ -416,20 +462,17 @@ class _StatusPage(NFixedPage):
|
||||
# Methods
|
||||
##
|
||||
|
||||
def getNewList(self) -> tuple[list, list]:
|
||||
def getNewList(self) -> list[tuple[str | None, StatusEntry]]:
|
||||
"""Return list of entries."""
|
||||
if self._changed:
|
||||
newList = []
|
||||
update = []
|
||||
for n in range(self.listBox.topLevelItemCount()):
|
||||
item = self.listBox.topLevelItem(n)
|
||||
if item is not None:
|
||||
newList.append({
|
||||
"key": item.data(self.COL_LABEL, self.KEY_ROLE),
|
||||
"name": item.text(self.COL_LABEL),
|
||||
"cols": item.data(self.COL_LABEL, self.COL_ROLE),
|
||||
})
|
||||
return newList, self._colDeleted
|
||||
return [], []
|
||||
if item := self.listBox.topLevelItem(n):
|
||||
key = item.data(self.C_DATA, self.D_KEY)
|
||||
entry = item.data(self.C_DATA, self.D_ENTRY)
|
||||
update.append((key, entry))
|
||||
return update
|
||||
return []
|
||||
|
||||
def columnWidth(self) -> int:
|
||||
"""Return the size of the header column."""
|
||||
@@ -442,124 +485,119 @@ class _StatusPage(NFixedPage):
|
||||
@pyqtSlot()
|
||||
def _selectColour(self) -> None:
|
||||
"""Open a dialog to select the status icon colour."""
|
||||
if self._selColour is not None:
|
||||
newCol = QColorDialog.getColor(
|
||||
self._selColour, self, self.tr("Select Colour")
|
||||
)
|
||||
if newCol.isValid():
|
||||
self._selColour = newCol
|
||||
pixmap = QPixmap(self.iPx, self.iPx)
|
||||
pixmap.fill(newCol)
|
||||
self.colButton.setIcon(QIcon(pixmap))
|
||||
self.colButton.setIconSize(pixmap.rect().size())
|
||||
if (color := QColorDialog.getColor(self._color, self, self.trSelColor)).isValid():
|
||||
self._color = color
|
||||
self._setButtonIcons()
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _newItem(self) -> None:
|
||||
"""Create a new status item."""
|
||||
self._addItem(None, self.tr("New Item"), (100, 100, 100), 0)
|
||||
color = QColor(100, 100, 100)
|
||||
shape = nwStatusShape.SQUARE
|
||||
icon = NWStatus.createIcon(self._iPx, color, shape)
|
||||
self._addItem(None, StatusEntry(self.tr("New Item"), color, shape, icon, 0))
|
||||
self._changed = True
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _delItem(self) -> None:
|
||||
"""Delete a status item."""
|
||||
selItem = self._getSelectedItem()
|
||||
if isinstance(selItem, QTreeWidgetItem):
|
||||
iRow = self.listBox.indexOfTopLevelItem(selItem)
|
||||
if selItem.data(self.COL_LABEL, self.NUM_ROLE) > 0:
|
||||
if item := self._getSelectedItem():
|
||||
iRow = self.listBox.indexOfTopLevelItem(item)
|
||||
entry: StatusEntry = item.data(self.C_DATA, self.D_ENTRY)
|
||||
if entry.count > 0:
|
||||
SHARED.error(self.tr("Cannot delete a status item that is in use."))
|
||||
else:
|
||||
self.listBox.takeTopLevelItem(iRow)
|
||||
self._colDeleted.append(selItem.data(self.COL_LABEL, self.KEY_ROLE))
|
||||
self._changed = True
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _saveItem(self) -> None:
|
||||
def _applyChanges(self) -> None:
|
||||
"""Save changes made to a status item."""
|
||||
selItem = self._getSelectedItem()
|
||||
if isinstance(selItem, QTreeWidgetItem):
|
||||
selItem.setText(self.COL_LABEL, simplified(self.editName.text()))
|
||||
selItem.setIcon(self.COL_LABEL, self.colButton.icon())
|
||||
selItem.setData(self.COL_LABEL, self.COL_ROLE, (
|
||||
self._selColour.red(), self._selColour.green(), self._selColour.blue()
|
||||
))
|
||||
if item := self._getSelectedItem():
|
||||
entry: StatusEntry = item.data(self.C_DATA, self.D_ENTRY)
|
||||
|
||||
name = simplified(self.editName.text())
|
||||
icon = NWStatus.createIcon(self._iPx, self._color, self._shape)
|
||||
|
||||
entry.name = name
|
||||
entry.color = self._color
|
||||
entry.shape = self._shape
|
||||
entry.icon = icon
|
||||
|
||||
item.setText(self.C_LABEL, name)
|
||||
item.setIcon(self.C_LABEL, icon)
|
||||
|
||||
self._changed = True
|
||||
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _selectedItem(self) -> None:
|
||||
def _selectionChanged(self) -> None:
|
||||
"""Extract the info of a selected item and populate the settings
|
||||
boxes and button. If no item is selected, clear the form.
|
||||
"""
|
||||
selItem = self._getSelectedItem()
|
||||
if isinstance(selItem, QTreeWidgetItem):
|
||||
cols = selItem.data(self.COL_LABEL, self.COL_ROLE)
|
||||
name = selItem.text(self.COL_LABEL)
|
||||
pixmap = QPixmap(self.iPx, self.iPx)
|
||||
pixmap.fill(QColor(*cols))
|
||||
self._selColour = QColor(*cols)
|
||||
self.editName.setText(name)
|
||||
self.colButton.setIcon(QIcon(pixmap))
|
||||
if item := self._getSelectedItem():
|
||||
entry: StatusEntry = item.data(self.C_DATA, self.D_ENTRY)
|
||||
self._color = entry.color
|
||||
self._shape = entry.shape
|
||||
self._setButtonIcons()
|
||||
|
||||
self.editName.setText(entry.name)
|
||||
self.editName.selectAll()
|
||||
self.editName.setFocus()
|
||||
|
||||
self.editName.setEnabled(True)
|
||||
self.colButton.setEnabled(True)
|
||||
self.saveButton.setEnabled(True)
|
||||
self.colorButton.setEnabled(True)
|
||||
self.shapeButton.setEnabled(True)
|
||||
self.applyButton.setEnabled(True)
|
||||
|
||||
else:
|
||||
pixmap = QPixmap(self.iPx, self.iPx)
|
||||
pixmap.fill(QColor(100, 100, 100))
|
||||
self._selColour = QColor(100, 100, 100)
|
||||
self._color = QColor(100, 100, 100)
|
||||
self._shape = nwStatusShape.SQUARE
|
||||
self._setButtonIcons()
|
||||
self.editName.setText("")
|
||||
self.colButton.setIcon(QIcon(pixmap))
|
||||
|
||||
self.editName.setEnabled(False)
|
||||
self.colButton.setEnabled(False)
|
||||
self.saveButton.setEnabled(False)
|
||||
self.colorButton.setEnabled(False)
|
||||
self.shapeButton.setEnabled(False)
|
||||
self.applyButton.setEnabled(False)
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
##
|
||||
|
||||
def _addItem(self, key: str | None, name: str,
|
||||
colour: tuple[int, int, int], count: int) -> None:
|
||||
def _selectShape(self, shape: nwStatusShape) -> None:
|
||||
"""Set the current shape."""
|
||||
self._shape = shape
|
||||
self._setButtonIcons()
|
||||
return
|
||||
|
||||
def _addItem(self, key: str | None, entry: StatusEntry) -> None:
|
||||
"""Add a status item to the list."""
|
||||
pixmap = QPixmap(self.iPx, self.iPx)
|
||||
pixmap.fill(QColor(*colour))
|
||||
|
||||
item = QTreeWidgetItem()
|
||||
item.setText(self.COL_LABEL, name)
|
||||
item.setIcon(self.COL_LABEL, QIcon(pixmap))
|
||||
item.setData(self.COL_LABEL, self.KEY_ROLE, key)
|
||||
item.setData(self.COL_LABEL, self.COL_ROLE, colour)
|
||||
item.setData(self.COL_LABEL, self.NUM_ROLE, count)
|
||||
item.setText(self.COL_USAGE, self._usageString(count))
|
||||
|
||||
item.setText(self.C_LABEL, entry.name)
|
||||
item.setIcon(self.C_LABEL, entry.icon)
|
||||
item.setText(self.C_USAGE, self._usageString(entry.count))
|
||||
item.setData(self.C_DATA, self.D_KEY, key)
|
||||
item.setData(self.C_DATA, self.D_ENTRY, entry)
|
||||
self.listBox.addTopLevelItem(item)
|
||||
|
||||
return
|
||||
|
||||
def _moveItem(self, step: int) -> None:
|
||||
"""Move and item up or down step."""
|
||||
selItem = self._getSelectedItem()
|
||||
if selItem is None:
|
||||
return
|
||||
|
||||
tIndex = self.listBox.indexOfTopLevelItem(selItem)
|
||||
nChild = self.listBox.topLevelItemCount()
|
||||
nIndex = tIndex + step
|
||||
if nIndex < 0 or nIndex >= nChild:
|
||||
return
|
||||
|
||||
cItem = self.listBox.takeTopLevelItem(tIndex)
|
||||
self.listBox.insertTopLevelItem(nIndex, cItem)
|
||||
self.listBox.clearSelection()
|
||||
|
||||
if cItem is not None:
|
||||
cItem.setSelected(True)
|
||||
self._changed = True
|
||||
|
||||
if item := self._getSelectedItem():
|
||||
tIdx = self.listBox.indexOfTopLevelItem(item)
|
||||
nItm = self.listBox.topLevelItemCount()
|
||||
nIdx = tIdx + step
|
||||
if (0 <= nIdx < nItm) and (cItem := self.listBox.takeTopLevelItem(tIdx)):
|
||||
self.listBox.insertTopLevelItem(nIdx, cItem)
|
||||
self.listBox.clearSelection()
|
||||
cItem.setSelected(True)
|
||||
self._changed = True
|
||||
return
|
||||
|
||||
def _getSelectedItem(self) -> QTreeWidgetItem | None:
|
||||
@@ -571,19 +609,26 @@ class _StatusPage(NFixedPage):
|
||||
def _usageString(self, count: int) -> str:
|
||||
"""Generate usage string."""
|
||||
if count == 0:
|
||||
return self.tr("Not in use")
|
||||
return self.trCountNone
|
||||
elif count == 1:
|
||||
return self.tr("Used once")
|
||||
return self.trCountOne
|
||||
else:
|
||||
return self.tr("Used by {0} items").format(count)
|
||||
return self.trCountMore.format(count)
|
||||
|
||||
def _setButtonIcons(self) -> None:
|
||||
"""Set the colour of the colour button."""
|
||||
icon = NWStatus.createIcon(self._iPx, self._color, nwStatusShape.SQUARE)
|
||||
self.colorButton.setIcon(icon)
|
||||
self.shapeButton.setIcon(self._icons[self._shape])
|
||||
return
|
||||
|
||||
# END Class _StatusPage
|
||||
|
||||
|
||||
class _ReplacePage(NFixedPage):
|
||||
|
||||
COL_KEY = 0
|
||||
COL_REPL = 1
|
||||
C_KEY = 0
|
||||
C_REPL = 1
|
||||
|
||||
def __init__(self, parent: QWidget) -> None:
|
||||
super().__init__(parent=parent)
|
||||
@@ -605,15 +650,17 @@ class _ReplacePage(NFixedPage):
|
||||
# List Box
|
||||
self.listBox = QTreeWidget(self)
|
||||
self.listBox.setHeaderLabels([self.tr("Keyword"), self.tr("Replace With")])
|
||||
self.listBox.setColumnWidth(self.COL_KEY, wCol0)
|
||||
self.listBox.setColumnWidth(self.C_KEY, wCol0)
|
||||
self.listBox.setIndentation(0)
|
||||
self.listBox.itemSelectionChanged.connect(self._selectedItem)
|
||||
self.listBox.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
|
||||
self.listBox.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
|
||||
self.listBox.itemSelectionChanged.connect(self._selectionChanged)
|
||||
|
||||
for aKey, aVal in SHARED.project.data.autoReplace.items():
|
||||
newItem = QTreeWidgetItem(["<%s>" % aKey, aVal])
|
||||
self.listBox.addTopLevelItem(newItem)
|
||||
|
||||
self.listBox.sortByColumn(self.COL_KEY, Qt.SortOrder.AscendingOrder)
|
||||
self.listBox.sortByColumn(self.C_KEY, Qt.SortOrder.AscendingOrder)
|
||||
self.listBox.setSortingEnabled(True)
|
||||
|
||||
# List Controls
|
||||
@@ -633,8 +680,10 @@ class _ReplacePage(NFixedPage):
|
||||
self.editValue.setEnabled(False)
|
||||
self.editValue.setMaxLength(80)
|
||||
|
||||
self.saveButton = QPushButton(self.tr("Save"), self)
|
||||
self.saveButton.clicked.connect(self._saveEntry)
|
||||
self.applyButton = QToolButton(self)
|
||||
self.applyButton.setText(self.tr("Apply"))
|
||||
self.applyButton.setSizePolicy(QtSizeMinimum, QtSizeMinimumExpanding)
|
||||
self.applyButton.clicked.connect(self._applyChanges)
|
||||
|
||||
# Assemble
|
||||
self.listControls = QVBoxLayout()
|
||||
@@ -645,11 +694,11 @@ class _ReplacePage(NFixedPage):
|
||||
self.editBox = QHBoxLayout()
|
||||
self.editBox.addWidget(self.editKey, 4)
|
||||
self.editBox.addWidget(self.editValue, 5)
|
||||
self.editBox.addWidget(self.saveButton, 0)
|
||||
self.editBox.addWidget(self.applyButton, 0)
|
||||
|
||||
self.mainBox = QVBoxLayout()
|
||||
self.mainBox.addWidget(self.listBox)
|
||||
self.mainBox.addLayout(self.editBox)
|
||||
self.mainBox.addWidget(self.listBox, 1)
|
||||
self.mainBox.addLayout(self.editBox, 0)
|
||||
|
||||
self.innerBox = QHBoxLayout()
|
||||
self.innerBox.addLayout(self.mainBox)
|
||||
@@ -664,7 +713,7 @@ class _ReplacePage(NFixedPage):
|
||||
return
|
||||
|
||||
@property
|
||||
def wasChanged(self) -> bool:
|
||||
def changed(self) -> bool:
|
||||
"""The user changed these settings."""
|
||||
return self._changed
|
||||
|
||||
@@ -672,15 +721,13 @@ class _ReplacePage(NFixedPage):
|
||||
# Methods
|
||||
##
|
||||
|
||||
def getNewList(self) -> dict:
|
||||
def getNewList(self) -> dict[str, str]:
|
||||
"""Extract the list from the widget."""
|
||||
new = {}
|
||||
for n in range(self.listBox.topLevelItemCount()):
|
||||
if tItem := self.listBox.topLevelItem(n):
|
||||
aKey = self._stripNotAllowed(tItem.text(0))
|
||||
aVal = tItem.text(1)
|
||||
if len(aKey) > 0:
|
||||
new[aKey] = aVal
|
||||
if item := self.listBox.topLevelItem(n):
|
||||
if key := self._stripNotAllowed(item.text(self.C_KEY)):
|
||||
new[key] = item.text(self.C_REPL)
|
||||
return new
|
||||
|
||||
def columnWidth(self) -> int:
|
||||
@@ -692,51 +739,48 @@ class _ReplacePage(NFixedPage):
|
||||
##
|
||||
|
||||
@pyqtSlot()
|
||||
def _selectedItem(self) -> None:
|
||||
def _selectionChanged(self) -> None:
|
||||
"""Extract the details from the selected item and populate the
|
||||
edit form.
|
||||
"""
|
||||
if selItem := self._getSelectedItem():
|
||||
editKey = self._stripNotAllowed(selItem.text(0))
|
||||
editVal = selItem.text(1)
|
||||
self.editKey.setText(editKey)
|
||||
self.editValue.setText(editVal)
|
||||
if item := self._getSelectedItem():
|
||||
self.editKey.setText(self._stripNotAllowed(item.text(self.C_KEY)))
|
||||
self.editValue.setText(item.text(self.C_REPL))
|
||||
self.editKey.setEnabled(True)
|
||||
self.editValue.setEnabled(True)
|
||||
self.editKey.selectAll()
|
||||
self.editKey.setFocus()
|
||||
else:
|
||||
self.editKey.setText("")
|
||||
self.editValue.setText("")
|
||||
self.editKey.setEnabled(False)
|
||||
self.editValue.setEnabled(False)
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _saveEntry(self) -> None:
|
||||
def _applyChanges(self) -> None:
|
||||
"""Save the form data into the list widget."""
|
||||
if selItem := self._getSelectedItem():
|
||||
newKey = self.editKey.text()
|
||||
newVal = self.editValue.text()
|
||||
saveKey = self._stripNotAllowed(newKey)
|
||||
if len(saveKey) > 0 and len(newVal) > 0:
|
||||
selItem.setText(self.COL_KEY, "<%s>" % saveKey)
|
||||
selItem.setText(self.COL_REPL, newVal)
|
||||
self.editKey.clear()
|
||||
self.editValue.clear()
|
||||
self.editKey.setEnabled(False)
|
||||
self.editValue.setEnabled(False)
|
||||
self.listBox.clearSelection()
|
||||
if item := self._getSelectedItem():
|
||||
key = self._stripNotAllowed(self.editKey.text())
|
||||
value = self.editValue.text()
|
||||
if key and value:
|
||||
item.setText(self.C_KEY, f"<{key}>")
|
||||
item.setText(self.C_REPL, value)
|
||||
self._changed = True
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _addEntry(self) -> None:
|
||||
"""Add a new list entry."""
|
||||
saveKey = "<keyword%d>" % (self.listBox.topLevelItemCount() + 1)
|
||||
self.listBox.addTopLevelItem(QTreeWidgetItem([saveKey, ""]))
|
||||
key = f"<keyword{self.listBox.topLevelItemCount() + 1:d}>"
|
||||
self.listBox.addTopLevelItem(QTreeWidgetItem([key, ""]))
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _delEntry(self) -> None:
|
||||
"""Delete the selected entry."""
|
||||
if selItem := self._getSelectedItem():
|
||||
self.listBox.takeTopLevelItem(self.listBox.indexOfTopLevelItem(selItem))
|
||||
if item := self._getSelectedItem():
|
||||
self.listBox.takeTopLevelItem(self.listBox.indexOfTopLevelItem(item))
|
||||
self._changed = True
|
||||
return
|
||||
|
||||
|
||||
@@ -190,7 +190,7 @@ class GuiWordList(QDialog):
|
||||
))
|
||||
ffilter = formatFileFilter(["*.txt", "*"])
|
||||
path, _ = QFileDialog.getOpenFileName(
|
||||
self, self.tr("Import File"), str(Path.home()), filter=ffilter
|
||||
self, self.tr("Import File"), str(CONFIG.homePath()), filter=ffilter
|
||||
)
|
||||
if path:
|
||||
try:
|
||||
@@ -207,7 +207,7 @@ class GuiWordList(QDialog):
|
||||
def _exportWords(self) -> None:
|
||||
"""Export words to file."""
|
||||
path, _ = QFileDialog.getSaveFileName(
|
||||
self, self.tr("Export File"), str(Path.home())
|
||||
self, self.tr("Export File"), str(CONFIG.homePath())
|
||||
)
|
||||
if path:
|
||||
try:
|
||||
|
||||
@@ -210,3 +210,29 @@ class nwBuildFmt(Enum):
|
||||
J_NWD = 7
|
||||
|
||||
# END Enum nwBuildFormat
|
||||
|
||||
|
||||
class nwStatusShape(Enum):
|
||||
|
||||
SQUARE = 0
|
||||
TRIANGLE = 1
|
||||
NABLA = 2
|
||||
DIAMOND = 3
|
||||
PENTAGON = 4
|
||||
HEXAGON = 5
|
||||
STAR = 6
|
||||
PACMAN = 7
|
||||
CIRCLE_Q = 8
|
||||
CIRCLE_H = 9
|
||||
CIRCLE_T = 10
|
||||
CIRCLE = 11
|
||||
BARS_1 = 12
|
||||
BARS_2 = 13
|
||||
BARS_3 = 14
|
||||
BARS_4 = 15
|
||||
BLOCK_1 = 16
|
||||
BLOCK_2 = 17
|
||||
BLOCK_3 = 18
|
||||
BLOCK_4 = 19
|
||||
|
||||
# END Enum nwStatusShape
|
||||
|
||||
@@ -54,7 +54,7 @@ def logException() -> None:
|
||||
return
|
||||
|
||||
|
||||
def formatException(exc) -> str:
|
||||
def formatException(exc: BaseException) -> str:
|
||||
"""Format an exception as a string the same way the default
|
||||
exception handler does.
|
||||
"""
|
||||
|
||||
@@ -27,10 +27,11 @@ from math import ceil
|
||||
|
||||
from PyQt5.QtCore import QRect
|
||||
from PyQt5.QtGui import QBrush, QColor, QPaintEvent, QPainter, QPen
|
||||
from PyQt5.QtWidgets import QProgressBar, QSizePolicy, QWidget
|
||||
from PyQt5.QtWidgets import QProgressBar, QWidget
|
||||
|
||||
from novelwriter.types import (
|
||||
QtPaintAnitAlias, QtAlignCenter, QtRoundCap, QtSolidLine, QtTransparent
|
||||
QtPaintAnitAlias, QtAlignCenter, QtRoundCap, QtSizeFixed, QtSolidLine,
|
||||
QtTransparent
|
||||
)
|
||||
|
||||
|
||||
@@ -59,7 +60,7 @@ class NProgressCircle(QProgressBar):
|
||||
bar=self.palette().highlight().color(),
|
||||
text=self.palette().text().color()
|
||||
)
|
||||
self.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
|
||||
self.setSizePolicy(QtSizeFixed, QtSizeFixed)
|
||||
self.setFixedWidth(size)
|
||||
self.setFixedHeight(size)
|
||||
return
|
||||
|
||||
@@ -25,6 +25,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
|
||||
from PyQt5.QtCore import QSize, Qt
|
||||
from PyQt5.QtGui import QWheelEvent
|
||||
from PyQt5.QtWidgets import QComboBox, QDoubleSpinBox, QSpinBox, QToolButton, QWidget
|
||||
@@ -46,7 +48,7 @@ class NComboBox(QComboBox):
|
||||
event.ignore()
|
||||
return
|
||||
|
||||
def setCurrentData(self, data: str, default: str) -> None:
|
||||
def setCurrentData(self, data: str | Enum, default: str | Enum) -> None:
|
||||
"""Set the current index from data, with a fallback."""
|
||||
idx = self.findData(data)
|
||||
self.setCurrentIndex(self.findData(default) if idx < 0 else idx)
|
||||
|
||||
@@ -28,11 +28,14 @@ from __future__ import annotations
|
||||
from PyQt5.QtGui import QColor, QPaintEvent, QPainter, QPolygon
|
||||
from PyQt5.QtCore import QPoint, QRectF, QSize, Qt, pyqtSignal, pyqtSlot
|
||||
from PyQt5.QtWidgets import (
|
||||
QAbstractButton, QAction, QButtonGroup, QLabel, QSizePolicy, QStyle,
|
||||
QAbstractButton, QAction, QButtonGroup, QLabel, QStyle,
|
||||
QStyleOptionToolButton, QToolBar, QToolButton, QWidget
|
||||
)
|
||||
|
||||
from novelwriter.types import QtPaintAnitAlias, QtAlignLeft, QtMouseOver, QtNoBrush, QtNoPen
|
||||
from novelwriter.types import (
|
||||
QtPaintAnitAlias, QtAlignLeft, QtMouseOver, QtNoBrush, QtNoPen,
|
||||
QtSizeExpanding, QtSizeFixed
|
||||
)
|
||||
|
||||
|
||||
class NPagedSideBar(QToolBar):
|
||||
@@ -59,7 +62,7 @@ class NPagedSideBar(QToolBar):
|
||||
self.setOrientation(Qt.Orientation.Vertical)
|
||||
|
||||
stretch = QWidget(self)
|
||||
stretch.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
||||
stretch.setSizePolicy(QtSizeExpanding, QtSizeExpanding)
|
||||
self._stretchAction = self.addWidget(stretch)
|
||||
|
||||
return
|
||||
@@ -119,7 +122,7 @@ class _NPagedToolButton(QToolButton):
|
||||
def __init__(self, parent: QWidget) -> None:
|
||||
super().__init__(parent=parent)
|
||||
|
||||
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
||||
self.setSizePolicy(QtSizeExpanding, QtSizeFixed)
|
||||
self.setCheckable(True)
|
||||
|
||||
fH = self.fontMetrics().height()
|
||||
@@ -197,7 +200,7 @@ class _NPagedToolLabel(QLabel):
|
||||
def __init__(self, parent: QWidget, textColor: QColor | None = None) -> None:
|
||||
super().__init__(parent=parent)
|
||||
|
||||
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
||||
self.setSizePolicy(QtSizeExpanding, QtSizeFixed)
|
||||
|
||||
fH = self.fontMetrics().height()
|
||||
self._bH = round(fH * 1.7)
|
||||
|
||||
@@ -25,10 +25,10 @@ from __future__ import annotations
|
||||
|
||||
from PyQt5.QtGui import QMouseEvent, QPainter, QPaintEvent, QResizeEvent
|
||||
from PyQt5.QtCore import QEvent, QPropertyAnimation, Qt, pyqtProperty
|
||||
from PyQt5.QtWidgets import QAbstractButton, QSizePolicy, QWidget
|
||||
from PyQt5.QtWidgets import QAbstractButton, QWidget
|
||||
|
||||
from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.types import QtPaintAnitAlias, QtMouseLeft, QtNoPen
|
||||
from novelwriter.types import QtPaintAnitAlias, QtMouseLeft, QtNoPen, QtSizeFixed
|
||||
|
||||
|
||||
class NSwitch(QAbstractButton):
|
||||
@@ -46,7 +46,7 @@ class NSwitch(QAbstractButton):
|
||||
self._rR = self._xR - self._rB
|
||||
|
||||
self.setCheckable(True)
|
||||
self.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
|
||||
self.setSizePolicy(QtSizeFixed, QtSizeFixed)
|
||||
self.setFixedWidth(self._xW)
|
||||
self.setFixedHeight(self._xH)
|
||||
self._offset = self._xR
|
||||
|
||||
@@ -25,10 +25,13 @@ from __future__ import annotations
|
||||
|
||||
from PyQt5.QtGui import QIcon
|
||||
from PyQt5.QtCore import pyqtSignal
|
||||
from PyQt5.QtWidgets import QGridLayout, QLabel, QScrollArea, QSizePolicy, QWidget
|
||||
from PyQt5.QtWidgets import QGridLayout, QLabel, QScrollArea, QWidget
|
||||
|
||||
from novelwriter.extensions.switch import NSwitch
|
||||
from novelwriter.types import QtAlignLeft, QtAlignRight, QtAlignRightMiddle
|
||||
from novelwriter.types import (
|
||||
QtAlignLeft, QtAlignRight, QtAlignRightMiddle, QtSizeMinimum,
|
||||
QtSizeMinimumExpanding
|
||||
)
|
||||
|
||||
|
||||
class NSwitchBox(QScrollArea):
|
||||
@@ -59,7 +62,7 @@ class NSwitchBox(QScrollArea):
|
||||
self._content.setColumnStretch(1, 1)
|
||||
|
||||
self._widget = QWidget(self)
|
||||
self._widget.setSizePolicy(QSizePolicy.Policy.MinimumExpanding, QSizePolicy.Policy.Minimum)
|
||||
self._widget.setSizePolicy(QtSizeMinimumExpanding, QtSizeMinimum)
|
||||
self._widget.setLayout(self._content)
|
||||
|
||||
self.setWidgetResizable(True)
|
||||
|
||||
@@ -381,7 +381,7 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
|
||||
return
|
||||
|
||||
def loadText(self, tHandle: str, tLine=None) -> bool:
|
||||
def loadText(self, tHandle: str, tLine: int | None = None) -> bool:
|
||||
"""Load text from a document into the editor. If we have an I/O
|
||||
error, we must handle this and clear the editor so that we don't
|
||||
risk overwriting the file if it exists. This can for instance
|
||||
@@ -497,6 +497,7 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
return False
|
||||
|
||||
self.setDocumentChanged(False)
|
||||
self.docTextChanged.emit(self._docHandle, self._lastEdit)
|
||||
|
||||
oldHeader = self._nwItem.mainHeading
|
||||
oldCount = SHARED.project.index.getHandleHeaderCount(tHandle)
|
||||
@@ -1080,7 +1081,7 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _cursorMoved(self):
|
||||
def _cursorMoved(self) -> None:
|
||||
"""Triggered when the cursor moved in the editor."""
|
||||
self.docFooter.updateLineCount(self.textCursor())
|
||||
return
|
||||
@@ -1470,7 +1471,7 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
# Internal Functions : Text Manipulation
|
||||
##
|
||||
|
||||
def _toggleFormat(self, fLen: int, fChar: str) -> bool:
|
||||
def _toggleFormat(self, fLen: int, fChar: str) -> None:
|
||||
"""Toggle the formatting of a specific type for a piece of text.
|
||||
If more than one block is selected, the formatting is applied to
|
||||
the first block.
|
||||
@@ -1488,12 +1489,12 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
|
||||
posS = cursor.selectionStart()
|
||||
posE = cursor.selectionEnd()
|
||||
if self._qDocument.characterAt(posO - 1) == fChar:
|
||||
if posS == posE and self._qDocument.characterAt(posO - 1) == fChar:
|
||||
logger.warning("Format repetition, cancelling action")
|
||||
cursor.clearSelection()
|
||||
cursor.setPosition(posO)
|
||||
self.setTextCursor(cursor)
|
||||
return False
|
||||
return
|
||||
|
||||
blockS = self._qDocument.findBlock(posS)
|
||||
blockE = self._qDocument.findBlock(posE)
|
||||
@@ -1519,7 +1520,6 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
break
|
||||
|
||||
if fLen == min(numA, numB):
|
||||
cursor.clearSelection()
|
||||
cursor.beginEditBlock()
|
||||
cursor.setPosition(posS)
|
||||
for i in range(fLen):
|
||||
@@ -1528,17 +1528,19 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
for i in range(fLen):
|
||||
cursor.deletePreviousChar()
|
||||
cursor.endEditBlock()
|
||||
cursor.clearSelection()
|
||||
cursor.setPosition(posO - fLen)
|
||||
self.setTextCursor(cursor)
|
||||
|
||||
if select != _SelectAction.KEEP_SELECTION:
|
||||
cursor.clearSelection()
|
||||
cursor.setPosition(posO - fLen)
|
||||
self.setTextCursor(cursor)
|
||||
|
||||
else:
|
||||
self._wrapSelection(fChar*fLen, pos=posO, select=select)
|
||||
|
||||
return True
|
||||
return
|
||||
|
||||
def _wrapSelection(self, before: str, after: str | None = None, pos: int | None = None,
|
||||
select: _SelectAction = _SelectAction.NO_DECISION) -> bool:
|
||||
select: _SelectAction = _SelectAction.NO_DECISION) -> None:
|
||||
"""Wrap the selected text in whatever is in tBefore and tAfter.
|
||||
If there is no selection, the autoSelect setting decides the
|
||||
action. AutoSelect will select the word under the cursor before
|
||||
@@ -1578,21 +1580,21 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
if select == _SelectAction.MOVE_AFTER:
|
||||
cursor.setPosition(posE + len(before + after))
|
||||
elif select == _SelectAction.KEEP_SELECTION:
|
||||
cursor.setPosition(posE + len(before), QtMoveAnchor)
|
||||
cursor.setPosition(posS + len(before), QtKeepAnchor)
|
||||
cursor.setPosition(posS + len(before), QtMoveAnchor)
|
||||
cursor.setPosition(posE + len(before), QtKeepAnchor)
|
||||
elif select == _SelectAction.KEEP_POSITION:
|
||||
cursor.setPosition(posO + len(before))
|
||||
|
||||
self.setTextCursor(cursor)
|
||||
|
||||
return True
|
||||
return
|
||||
|
||||
def _replaceQuotes(self, sQuote: str, oQuote: str, cQuote: str) -> bool:
|
||||
def _replaceQuotes(self, sQuote: str, oQuote: str, cQuote: str) -> None:
|
||||
"""Replace all straight quotes in the selected text."""
|
||||
cursor = self.textCursor()
|
||||
if not cursor.hasSelection():
|
||||
SHARED.error(self.tr("Please select some text before calling replace quotes."))
|
||||
return False
|
||||
return
|
||||
|
||||
posS = cursor.selectionStart()
|
||||
posE = cursor.selectionEnd()
|
||||
@@ -1632,7 +1634,7 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
|
||||
self._allowAutoReplace(True)
|
||||
|
||||
return True
|
||||
return
|
||||
|
||||
def _processBlockFormat(
|
||||
self, action: nwDocAction, text: str, toggle: bool = True
|
||||
@@ -2185,7 +2187,7 @@ class MetaCompleter(QMenu):
|
||||
# Internal Functions
|
||||
##
|
||||
|
||||
def _emitComplete(self, pos: int, length: int, value: str):
|
||||
def _emitComplete(self, pos: int, length: int, value: str) -> None:
|
||||
"""Emit the signal to indicate a selection has been made."""
|
||||
self.complete.emit(pos, length, value)
|
||||
return
|
||||
@@ -2966,7 +2968,7 @@ class GuiDocEditHeader(QWidget):
|
||||
# Events
|
||||
##
|
||||
|
||||
def mousePressEvent(self, event: QMouseEvent):
|
||||
def mousePressEvent(self, event: QMouseEvent) -> None:
|
||||
"""Capture a click on the title and ensure that the item is
|
||||
selected in the project tree.
|
||||
"""
|
||||
@@ -3134,7 +3136,7 @@ class GuiDocEditFooter(QWidget):
|
||||
sText = ""
|
||||
else:
|
||||
iPx = round(0.9*SHARED.theme.baseIconHeight)
|
||||
status, icon = self._tItem.getImportStatus(incIcon=True)
|
||||
status, icon = self._tItem.getImportStatus()
|
||||
sIcon = icon.pixmap(iPx, iPx)
|
||||
sText = f"{status} / {self._tItem.describeMe()}"
|
||||
|
||||
|
||||
@@ -328,7 +328,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
||||
self.setFormat(0, 3, self._hStyles["head2h"])
|
||||
self.setFormat(3, len(text), self._hStyles["header2"])
|
||||
|
||||
elif text.startswith("###! "): # Hard Scene
|
||||
elif text.startswith("###! "): # Alternative Scene
|
||||
self.setFormat(0, 4, self._hStyles["head3h"])
|
||||
self.setFormat(4, len(text), self._hStyles["header3"])
|
||||
|
||||
|
||||
@@ -450,7 +450,7 @@ class _ViewPanelKeyWords(QTreeWidget):
|
||||
nwItem.itemType, nwItem.itemClass,
|
||||
nwItem.itemLayout, nwItem.mainHeading
|
||||
)
|
||||
impLabel, impIcon = nwItem.getImportStatus(incIcon=True)
|
||||
impLabel, impIcon = nwItem.getImportStatus()
|
||||
iLevel = nwHeaders.H_LEVEL.get(hItem.level, 0) if nwItem.isDocumentLayout() else 5
|
||||
hDec = SHARED.theme.getHeaderDecorationNarrow(iLevel)
|
||||
|
||||
|
||||
@@ -253,7 +253,7 @@ class GuiItemDetails(QWidget):
|
||||
# Status
|
||||
# ======
|
||||
|
||||
status, icon = nwItem.getImportStatus(incIcon=True)
|
||||
status, icon = nwItem.getImportStatus()
|
||||
self.statusIcon.setPixmap(icon.pixmap(iPx, iPx))
|
||||
self.statusData.setText(status)
|
||||
|
||||
|
||||
@@ -742,8 +742,8 @@ class GuiMainMenu(QMenuBar):
|
||||
lambda: self.requestDocAction.emit(nwDocAction.BLOCK_UNN)
|
||||
)
|
||||
|
||||
# Format > Hard Scene
|
||||
self.aFmtHardSc = self.fmtMenu.addAction(self.tr("Hard Scene"))
|
||||
# Format > Alternative Scene
|
||||
self.aFmtHardSc = self.fmtMenu.addAction(self.tr("Alternative Scene"))
|
||||
self.aFmtHardSc.triggered.connect(
|
||||
lambda: self.requestDocAction.emit(nwDocAction.BLOCK_HSC)
|
||||
)
|
||||
|
||||
@@ -35,8 +35,8 @@ from PyQt5.QtCore import QModelIndex, QPoint, Qt, pyqtSlot, pyqtSignal
|
||||
from PyQt5.QtGui import QFocusEvent, QFont, QMouseEvent, QPalette, QResizeEvent
|
||||
from PyQt5.QtWidgets import (
|
||||
QAbstractItemView, QActionGroup, QFrame, QHBoxLayout, QHeaderView,
|
||||
QInputDialog, QMenu, QSizePolicy, QToolTip, QTreeWidget, QTreeWidgetItem,
|
||||
QVBoxLayout, QWidget
|
||||
QInputDialog, QMenu, QToolTip, QTreeWidget, QTreeWidgetItem, QVBoxLayout,
|
||||
QWidget
|
||||
)
|
||||
|
||||
from novelwriter import CONFIG, SHARED
|
||||
@@ -47,7 +47,10 @@ from novelwriter.enum import nwDocMode, nwItemClass, nwOutline
|
||||
from novelwriter.extensions.modified import NIconToolButton
|
||||
from novelwriter.extensions.novelselector import NovelSelector
|
||||
from novelwriter.gui.theme import STYLES_MIN_TOOLBUTTON
|
||||
from novelwriter.types import QtAlignRight, QtDecoration, QtMouseLeft, QtMouseMiddle, QtUserRole
|
||||
from novelwriter.types import (
|
||||
QtAlignRight, QtDecoration, QtMouseLeft, QtMouseMiddle, QtSizeExpanding,
|
||||
QtUserRole
|
||||
)
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from novelwriter.guimain import GuiMain
|
||||
@@ -215,7 +218,7 @@ class GuiNovelToolBar(QWidget):
|
||||
self.novelValue.setFont(selFont)
|
||||
self.novelValue.setListFormat(self.tr("Outline of {0}"))
|
||||
self.novelValue.setMinimumWidth(CONFIG.pxInt(150))
|
||||
self.novelValue.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
||||
self.novelValue.setSizePolicy(QtSizeExpanding, QtSizeExpanding)
|
||||
self.novelValue.novelSelectionChanged.connect(self.setCurrentRoot)
|
||||
|
||||
self.tbNovel = NIconToolButton(self, iSz)
|
||||
@@ -344,7 +347,7 @@ class GuiNovelToolBar(QWidget):
|
||||
# Internal Functions
|
||||
##
|
||||
|
||||
def _addLastColAction(self, colType, actionLabel) -> None:
|
||||
def _addLastColAction(self, colType: NovelTreeColumn, actionLabel: str) -> None:
|
||||
"""Add a column selection entry to the last column menu."""
|
||||
aLast = self.mLastCol.addAction(actionLabel)
|
||||
aLast.setCheckable(True)
|
||||
|
||||
+10
-11
@@ -36,20 +36,19 @@ from enum import Enum
|
||||
from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot, QT_TRANSLATE_NOOP
|
||||
from PyQt5.QtWidgets import (
|
||||
QAbstractItemView, QAction, QFileDialog, QFrame, QGridLayout, QGroupBox,
|
||||
QHBoxLayout, QLabel, QMenu, QScrollArea, QSizePolicy, QSplitter, QToolBar,
|
||||
QToolButton, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget
|
||||
QHBoxLayout, QLabel, QMenu, QScrollArea, QSplitter, QToolBar, QToolButton,
|
||||
QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget
|
||||
)
|
||||
|
||||
from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.enum import (
|
||||
nwDocMode, nwItemClass, nwItemLayout, nwItemType, nwOutline
|
||||
)
|
||||
from novelwriter.enum import nwDocMode, nwItemClass, nwItemLayout, nwItemType, nwOutline
|
||||
from novelwriter.error import logException
|
||||
from novelwriter.common import checkInt, formatFileFilter, makeFileNameSafe
|
||||
from novelwriter.constants import nwHeaders, trConst, nwKeyWords, nwLabels
|
||||
from novelwriter.extensions.novelselector import NovelSelector
|
||||
from novelwriter.types import (
|
||||
QtAlignLeftTop, QtAlignRight, QtAlignRightTop, QtDecoration, QtUserRole
|
||||
QtAlignLeftTop, QtAlignRight, QtAlignRightTop, QtDecoration,
|
||||
QtSizeExpanding, QtUserRole
|
||||
)
|
||||
|
||||
|
||||
@@ -190,7 +189,7 @@ class GuiOutlineView(QWidget):
|
||||
return
|
||||
|
||||
@pyqtSlot(str)
|
||||
def _rootItemChanged(self, tHandle) -> None:
|
||||
def _rootItemChanged(self, tHandle: str) -> None:
|
||||
"""Handle root novel changed or needs to be refreshed."""
|
||||
self.outlineTree.refreshTree(rootHandle=(tHandle or None), overRide=True)
|
||||
return
|
||||
@@ -217,7 +216,7 @@ class GuiOutlineToolBar(QToolBar):
|
||||
self.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
stretch = QWidget(self)
|
||||
stretch.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
||||
stretch.setSizePolicy(QtSizeExpanding, QtSizeExpanding)
|
||||
|
||||
# Novel Selector
|
||||
self.novelLabel = QLabel(self.tr("Outline of"), self)
|
||||
@@ -428,7 +427,7 @@ class GuiOutlineTree(QTreeWidget):
|
||||
##
|
||||
|
||||
@property
|
||||
def hiddenColumns(self):
|
||||
def hiddenColumns(self) -> dict[nwOutline, bool]:
|
||||
return self._colHidden
|
||||
|
||||
##
|
||||
@@ -586,7 +585,7 @@ class GuiOutlineTree(QTreeWidget):
|
||||
# Internal Functions
|
||||
##
|
||||
|
||||
def _loadHeaderState(self):
|
||||
def _loadHeaderState(self) -> None:
|
||||
"""Load the state of the main tree header, that is, column order
|
||||
and column width.
|
||||
"""
|
||||
@@ -1048,7 +1047,7 @@ class GuiOutlineDetails(QScrollArea):
|
||||
self.titleLabel.setText(self.tr(self.LVL_MAP.get(novIdx.level, "H1")))
|
||||
self.titleValue.setText(novIdx.title)
|
||||
|
||||
itemStatus, _ = nwItem.getImportStatus(incIcon=False)
|
||||
itemStatus, _ = nwItem.getImportStatus()
|
||||
|
||||
self.fileValue.setText(nwItem.itemName)
|
||||
self.itemValue.setText(itemStatus)
|
||||
|
||||
+15
-12
@@ -38,8 +38,8 @@ from PyQt5.QtGui import (
|
||||
)
|
||||
from PyQt5.QtWidgets import (
|
||||
QAbstractItemView, QAction, QDialog, QFrame, QHBoxLayout, QHeaderView,
|
||||
QLabel, QMenu, QShortcut, QSizePolicy, QTreeWidget, QTreeWidgetItem,
|
||||
QVBoxLayout, QWidget
|
||||
QLabel, QMenu, QShortcut, QTreeWidget, QTreeWidgetItem, QVBoxLayout,
|
||||
QWidget
|
||||
)
|
||||
|
||||
from novelwriter import CONFIG, SHARED
|
||||
@@ -54,7 +54,10 @@ from novelwriter.dialogs.projectsettings import GuiProjectSettings
|
||||
from novelwriter.enum import nwDocMode, nwItemType, nwItemClass, nwItemLayout
|
||||
from novelwriter.extensions.modified import NIconToolButton
|
||||
from novelwriter.gui.theme import STYLES_MIN_TOOLBUTTON
|
||||
from novelwriter.types import QtAlignLeft, QtAlignRight, QtMouseLeft, QtMouseMiddle, QtUserRole
|
||||
from novelwriter.types import (
|
||||
QtAlignLeft, QtAlignRight, QtMouseLeft, QtMouseMiddle, QtSizeExpanding,
|
||||
QtUserRole,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from novelwriter.guimain import GuiMain
|
||||
@@ -274,7 +277,7 @@ class GuiProjectToolBar(QWidget):
|
||||
self.viewLabel = QLabel(self.tr("Project Content"), self)
|
||||
self.viewLabel.setFont(SHARED.theme.guiFontB)
|
||||
self.viewLabel.setContentsMargins(0, 0, 0, 0)
|
||||
self.viewLabel.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
||||
self.viewLabel.setSizePolicy(QtSizeExpanding, QtSizeExpanding)
|
||||
|
||||
# Quick Links
|
||||
self.mQuick = QMenu(self)
|
||||
@@ -453,7 +456,7 @@ class GuiProjectToolBar(QWidget):
|
||||
|
||||
def _buildRootMenu(self) -> None:
|
||||
"""Build the rood folder menu."""
|
||||
def addClass(itemClass):
|
||||
def addClass(itemClass: nwItemClass) -> None:
|
||||
aNew = self.mAddRoot.addAction(trConst(nwLabels.CLASS_NAME[itemClass]))
|
||||
aNew.setIcon(SHARED.theme.getIcon(nwLabels.CLASS_ICON[itemClass]))
|
||||
aNew.triggered.connect(lambda: self.projTree.newTreeItem(nwItemType.ROOT, itemClass))
|
||||
@@ -1033,7 +1036,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
if trItem is None or nwItem is None:
|
||||
return
|
||||
|
||||
itemStatus, statusIcon = nwItem.getImportStatus(incIcon=True)
|
||||
itemStatus, statusIcon = nwItem.getImportStatus()
|
||||
hLevel = nwItem.mainHeading
|
||||
itemIcon = SHARED.theme.getItemIcon(
|
||||
nwItem.itemType, nwItem.itemClass, nwItem.itemLayout, hLevel
|
||||
@@ -1855,11 +1858,11 @@ class _TreeContextMenu(QMenu):
|
||||
if self._item.isNovelLike():
|
||||
menu = self.addMenu(self.tr("Set Status to ..."))
|
||||
current = self._item.itemStatus
|
||||
for n, (key, entry) in enumerate(SHARED.project.data.itemStatus.items()):
|
||||
name = entry["name"]
|
||||
for n, (key, entry) in enumerate(SHARED.project.data.itemStatus.iterItems()):
|
||||
name = entry.name
|
||||
if not multi and current == key:
|
||||
name += f" ({nwUnicode.U_CHECK})"
|
||||
action = menu.addAction(entry["icon"], name)
|
||||
action = menu.addAction(entry.icon, name)
|
||||
if multi:
|
||||
action.triggered.connect(lambda n, key=key: self._iterSetItemStatus(key))
|
||||
else:
|
||||
@@ -1872,11 +1875,11 @@ class _TreeContextMenu(QMenu):
|
||||
else:
|
||||
menu = self.addMenu(self.tr("Set Importance to ..."))
|
||||
current = self._item.itemImport
|
||||
for n, (key, entry) in enumerate(SHARED.project.data.itemImport.items()):
|
||||
name = entry["name"]
|
||||
for n, (key, entry) in enumerate(SHARED.project.data.itemImport.iterItems()):
|
||||
name = entry.name
|
||||
if not multi and current == key:
|
||||
name += f" ({nwUnicode.U_CHECK})"
|
||||
action = menu.addAction(entry["icon"], name)
|
||||
action = menu.addAction(entry.icon, name)
|
||||
if multi:
|
||||
action.triggered.connect(lambda n, key=key: self._iterSetItemImport(key))
|
||||
else:
|
||||
|
||||
@@ -74,9 +74,9 @@ class GuiTheme:
|
||||
self.isLightTheme = True
|
||||
|
||||
# GUI
|
||||
self.statNone = QColor(120, 120, 120)
|
||||
self.statUnsaved = QColor(200, 15, 39)
|
||||
self.statSaved = QColor(2, 133, 37)
|
||||
self.statNone = QColor(0, 0, 0)
|
||||
self.statUnsaved = QColor(0, 0, 0)
|
||||
self.statSaved = QColor(0, 0, 0)
|
||||
self.helpText = QColor(0, 0, 0)
|
||||
|
||||
# Loaded Syntax Settings
|
||||
@@ -227,6 +227,10 @@ class GuiTheme:
|
||||
logException()
|
||||
return False
|
||||
|
||||
# Reset Palette
|
||||
self._guiPalette = QApplication.style().standardPalette()
|
||||
self._resetGuiColors()
|
||||
|
||||
# Main
|
||||
sec = "Main"
|
||||
if parser.has_section(sec):
|
||||
@@ -256,8 +260,6 @@ class GuiTheme:
|
||||
self._setPalette(parser, sec, "highlightedtext", QPalette.ColorRole.HighlightedText)
|
||||
self._setPalette(parser, sec, "link", QPalette.ColorRole.Link)
|
||||
self._setPalette(parser, sec, "linkvisited", QPalette.ColorRole.LinkVisited)
|
||||
else:
|
||||
self._guiPalette = QApplication.style().standardPalette()
|
||||
|
||||
# GUI
|
||||
sec = "GUI"
|
||||
@@ -393,6 +395,14 @@ class GuiTheme:
|
||||
# Internal Functions
|
||||
##
|
||||
|
||||
def _resetGuiColors(self) -> None:
|
||||
"""Reset GUI colours to default values."""
|
||||
self.statNone = QColor(120, 120, 120)
|
||||
self.statUnsaved = QColor(200, 15, 39)
|
||||
self.statSaved = QColor(2, 133, 37)
|
||||
self.helpText = QColor(0, 0, 0)
|
||||
return
|
||||
|
||||
def _setGuiFont(self) -> None:
|
||||
"""Update the GUI's font style from settings."""
|
||||
font = QFont()
|
||||
|
||||
@@ -944,7 +944,7 @@ class GuiMain(QMainWindow):
|
||||
# Events
|
||||
##
|
||||
|
||||
def closeEvent(self, event: QCloseEvent):
|
||||
def closeEvent(self, event: QCloseEvent) -> None:
|
||||
"""Capture the closing event of the GUI and call the close
|
||||
function to handle all the close process steps.
|
||||
"""
|
||||
@@ -1201,7 +1201,7 @@ class GuiMain(QMainWindow):
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _toggleViewerPanelVisibility(self):
|
||||
def _toggleViewerPanelVisibility(self) -> None:
|
||||
"""Toggle the visibility of the document viewer panel."""
|
||||
CONFIG.showViewerPanel = not CONFIG.showViewerPanel
|
||||
self.docViewerPanel.setVisible(CONFIG.showViewerPanel)
|
||||
|
||||
@@ -57,6 +57,7 @@ def preProcessText(text: str, keepHeaders: bool = True) -> list[str]:
|
||||
continue
|
||||
if line[0] == ">":
|
||||
line = line.lstrip(">").lstrip(" ")
|
||||
if line: # Above block can return empty line (Issue #1816)
|
||||
if line[-1] == "<":
|
||||
line = line.rstrip("<").rstrip(" ")
|
||||
if "[" in line:
|
||||
|
||||
@@ -179,7 +179,7 @@ class GuiDictionaries(QDialog):
|
||||
##
|
||||
|
||||
@pyqtSlot()
|
||||
def _doBrowseHunspell(self):
|
||||
def _doBrowseHunspell(self) -> None:
|
||||
"""Browse for a Free/Libre Office dictionary."""
|
||||
ffilter = formatFileFilter([
|
||||
(self.tr("Free or Libre Office extension"), "*.sox *.oxt"), "*"
|
||||
@@ -193,7 +193,7 @@ class GuiDictionaries(QDialog):
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _doImportHunspell(self):
|
||||
def _doImportHunspell(self) -> None:
|
||||
"""Import a hunspell dictionary from .sox or .oxt file."""
|
||||
procErr = self.tr("Could not process dictionary file")
|
||||
if self._installPath:
|
||||
|
||||
@@ -60,7 +60,7 @@ class GuiManuscriptBuild(QDialog):
|
||||
|
||||
D_KEY = QtUserRole
|
||||
|
||||
def __init__(self, parent: QWidget, build: BuildSettings):
|
||||
def __init__(self, parent: QWidget, build: BuildSettings) -> None:
|
||||
super().__init__(parent=parent)
|
||||
|
||||
logger.debug("Create: GuiManuscriptBuild")
|
||||
@@ -260,7 +260,7 @@ class GuiManuscriptBuild(QDialog):
|
||||
##
|
||||
|
||||
@pyqtSlot("QAbstractButton*")
|
||||
def _dialogButtonClicked(self, button: QAbstractButton):
|
||||
def _dialogButtonClicked(self, button: QAbstractButton) -> None:
|
||||
"""Handle button clicks from the dialog button box."""
|
||||
role = self.dlgButtons.buttonRole(button)
|
||||
if role == QtRoleAction:
|
||||
@@ -273,7 +273,7 @@ class GuiManuscriptBuild(QDialog):
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _doSelectPath(self):
|
||||
def _doSelectPath(self) -> None:
|
||||
"""Select a folder for output."""
|
||||
bPath = Path(self.buildPath.text())
|
||||
bPath = bPath if bPath.is_dir() else self._build.lastPath
|
||||
@@ -285,7 +285,7 @@ class GuiManuscriptBuild(QDialog):
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _doResetBuildName(self):
|
||||
def _doResetBuildName(self) -> None:
|
||||
"""Generate a default build name."""
|
||||
bName = f"{SHARED.project.data.name} - {self._build.name}"
|
||||
self.buildName.setText(bName)
|
||||
@@ -293,7 +293,7 @@ class GuiManuscriptBuild(QDialog):
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _resetProgress(self):
|
||||
def _resetProgress(self) -> None:
|
||||
"""Set the progress bar back to 0."""
|
||||
self.buildProgress.setValue(0)
|
||||
return
|
||||
@@ -350,7 +350,7 @@ class GuiManuscriptBuild(QDialog):
|
||||
return items[0].data(self.D_KEY)
|
||||
return None
|
||||
|
||||
def _saveSettings(self):
|
||||
def _saveSettings(self) -> None:
|
||||
"""Save the user GUI settings."""
|
||||
winWidth = CONFIG.rpxInt(self.width())
|
||||
winHeight = CONFIG.rpxInt(self.height())
|
||||
@@ -369,7 +369,7 @@ class GuiManuscriptBuild(QDialog):
|
||||
|
||||
return
|
||||
|
||||
def _populateContentList(self):
|
||||
def _populateContentList(self) -> None:
|
||||
"""Build the content list."""
|
||||
rootMap = {}
|
||||
filtered = self._build.buildItemFilter(SHARED.project)
|
||||
@@ -398,7 +398,7 @@ class GuiManuscriptBuild(QDialog):
|
||||
|
||||
return
|
||||
|
||||
def _openOutputFolder(self):
|
||||
def _openOutputFolder(self) -> None:
|
||||
"""Open the build folder in the system's file explorer."""
|
||||
openExternalPath(Path(self.buildPath.text()))
|
||||
return
|
||||
|
||||
@@ -36,8 +36,8 @@ from PyQt5.QtPrintSupport import QPrintPreviewDialog, QPrinter
|
||||
from PyQt5.QtWidgets import (
|
||||
QAbstractItemView, QApplication, QDialog, QFormLayout, QGridLayout,
|
||||
QHBoxLayout, QLabel, QListWidget, QListWidgetItem, QPushButton,
|
||||
QSizePolicy, QSplitter, QStackedWidget, QTabWidget, QTextBrowser,
|
||||
QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget
|
||||
QSplitter, QStackedWidget, QTabWidget, QTextBrowser, QTreeWidget,
|
||||
QTreeWidgetItem, QVBoxLayout, QWidget
|
||||
)
|
||||
|
||||
from novelwriter import CONFIG, SHARED
|
||||
@@ -54,7 +54,7 @@ from novelwriter.tools.manusbuild import GuiManuscriptBuild
|
||||
from novelwriter.tools.manussettings import GuiBuildSettings
|
||||
from novelwriter.types import (
|
||||
QtAlignAbsolute, QtAlignCenter, QtAlignJustify, QtAlignRight, QtAlignTop,
|
||||
QtUserRole
|
||||
QtSizeExpanding, QtSizeIgnored, QtUserRole
|
||||
)
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
@@ -303,25 +303,21 @@ class GuiManuscript(QDialog):
|
||||
@pyqtSlot()
|
||||
def _editSelectedBuild(self) -> None:
|
||||
"""Edit the currently selected build settings entry."""
|
||||
build = self._getSelectedBuild()
|
||||
if build is not None:
|
||||
if build := self._getSelectedBuild():
|
||||
self._openSettingsDialog(build)
|
||||
return
|
||||
|
||||
@pyqtSlot("QListWidgetItem*", "QListWidgetItem*")
|
||||
def _updateBuildDetails(self, current: QListWidgetItem, previous: QListWidgetItem) -> None:
|
||||
"""Process change of build selection to update the details."""
|
||||
if isinstance(current, QListWidgetItem):
|
||||
build = self._builds.getBuild(current.data(self.D_KEY))
|
||||
if build is not None:
|
||||
self.buildDetails.updateInfo(build)
|
||||
if current and (build := self._builds.getBuild(current.data(self.D_KEY))):
|
||||
self.buildDetails.updateInfo(build)
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _deleteSelectedBuild(self) -> None:
|
||||
"""Delete the currently selected build settings entry."""
|
||||
build = self._getSelectedBuild()
|
||||
if build is not None:
|
||||
if build := self._getSelectedBuild():
|
||||
if SHARED.question(self.tr("Delete build '{0}'?".format(build.name))):
|
||||
self._builds.removeBuild(build.buildID)
|
||||
self._updateBuildsList()
|
||||
@@ -332,8 +328,7 @@ class GuiManuscript(QDialog):
|
||||
"""Process new build settings from the settings dialog."""
|
||||
self._builds.setBuild(build)
|
||||
self._updateBuildItem(build)
|
||||
current = self.buildList.currentItem()
|
||||
if isinstance(current, QListWidgetItem) and current.data(self.D_KEY) == build.buildID:
|
||||
if (current := self.buildList.currentItem()) and current.data(self.D_KEY) == build.buildID:
|
||||
self._updateBuildDetails(current, current)
|
||||
return
|
||||
|
||||
@@ -342,8 +337,7 @@ class GuiManuscript(QDialog):
|
||||
"""Run the document builder on the current build settings for
|
||||
the preview widget.
|
||||
"""
|
||||
build = self._getSelectedBuild()
|
||||
if build is None:
|
||||
if not (build := self._getSelectedBuild()):
|
||||
return
|
||||
|
||||
docBuild = NWBuildDocument(SHARED.project, build)
|
||||
@@ -383,8 +377,7 @@ class GuiManuscript(QDialog):
|
||||
@pyqtSlot()
|
||||
def _buildManuscript(self) -> None:
|
||||
"""Open the build dialog and build the manuscript."""
|
||||
build = self._getSelectedBuild()
|
||||
if isinstance(build, BuildSettings):
|
||||
if build := self._getSelectedBuild():
|
||||
dlgBuild = GuiManuscriptBuild(self, build)
|
||||
dlgBuild.exec()
|
||||
|
||||
@@ -627,7 +620,7 @@ class _DetailsWidget(QWidget):
|
||||
("headings.fmtChapter", "headings.hideChapter"),
|
||||
("headings.fmtUnnumbered", "headings.hideUnnumbered"),
|
||||
("headings.fmtScene", "headings.hideScene"),
|
||||
("headings.fmtHardScene", "headings.hideHardScene"),
|
||||
("headings.fmtAltScene", "headings.hideAltScene"),
|
||||
("headings.fmtSection", "headings.hideSection"),
|
||||
]:
|
||||
sub = QTreeWidgetItem()
|
||||
@@ -1030,16 +1023,14 @@ class _StatsWidget(QWidget):
|
||||
@pyqtSlot(bool)
|
||||
def _toggleView(self, state: bool) -> None:
|
||||
"""Toggle minimal or maximal view."""
|
||||
ignored = QSizePolicy.Policy.Ignored
|
||||
expanded = QSizePolicy.Policy.Expanding
|
||||
if state:
|
||||
self.mainStack.setCurrentWidget(self.maxWidget)
|
||||
self.maxWidget.setSizePolicy(expanded, expanded)
|
||||
self.minWidget.setSizePolicy(ignored, ignored)
|
||||
self.maxWidget.setSizePolicy(QtSizeExpanding, QtSizeExpanding)
|
||||
self.minWidget.setSizePolicy(QtSizeIgnored, QtSizeIgnored)
|
||||
else:
|
||||
self.mainStack.setCurrentWidget(self.minWidget)
|
||||
self.maxWidget.setSizePolicy(ignored, ignored)
|
||||
self.minWidget.setSizePolicy(expanded, expanded)
|
||||
self.maxWidget.setSizePolicy(QtSizeIgnored, QtSizeIgnored)
|
||||
self.minWidget.setSizePolicy(QtSizeExpanding, QtSizeExpanding)
|
||||
self.maxWidget.adjustSize()
|
||||
self.minWidget.adjustSize()
|
||||
self.mainStack.adjustSize()
|
||||
|
||||
@@ -669,20 +669,20 @@ class _HeadingsTab(NScrollablePage):
|
||||
self.formatBox.addWidget(self.swtScene, 3, 4)
|
||||
|
||||
# Alt Scene Heading
|
||||
self.lblHScene = QLabel(self._build.getLabel("headings.fmtHardScene"), self)
|
||||
self.fmtHScene = QLineEdit("", self)
|
||||
self.fmtHScene.setReadOnly(True)
|
||||
self.btnHScene = NIconToolButton(self, iSz, "edit")
|
||||
self.btnHScene.clicked.connect(lambda: self._editHeading(self.EDIT_HSCENE))
|
||||
self.hdeHScene = QLabel(trHide, self)
|
||||
self.hdeHScene.setIndent(bSp)
|
||||
self.swtHScene = NSwitch(self, height=iPx)
|
||||
self.lblAScene = QLabel(self._build.getLabel("headings.fmtAltScene"), self)
|
||||
self.fmtAScene = QLineEdit("", self)
|
||||
self.fmtAScene.setReadOnly(True)
|
||||
self.btnAScene = NIconToolButton(self, iSz, "edit")
|
||||
self.btnAScene.clicked.connect(lambda: self._editHeading(self.EDIT_HSCENE))
|
||||
self.hdeAScene = QLabel(trHide, self)
|
||||
self.hdeAScene.setIndent(bSp)
|
||||
self.swtAScene = NSwitch(self, height=iPx)
|
||||
|
||||
self.formatBox.addWidget(self.lblHScene, 4, 0)
|
||||
self.formatBox.addWidget(self.fmtHScene, 4, 1)
|
||||
self.formatBox.addWidget(self.btnHScene, 4, 2)
|
||||
self.formatBox.addWidget(self.hdeHScene, 4, 3)
|
||||
self.formatBox.addWidget(self.swtHScene, 4, 4)
|
||||
self.formatBox.addWidget(self.lblAScene, 4, 0)
|
||||
self.formatBox.addWidget(self.fmtAScene, 4, 1)
|
||||
self.formatBox.addWidget(self.btnAScene, 4, 2)
|
||||
self.formatBox.addWidget(self.hdeAScene, 4, 3)
|
||||
self.formatBox.addWidget(self.swtAScene, 4, 4)
|
||||
|
||||
# Section Heading
|
||||
self.lblSection = QLabel(self._build.getLabel("headings.fmtSection"), self)
|
||||
@@ -826,14 +826,14 @@ class _HeadingsTab(NScrollablePage):
|
||||
self.fmtChapter.setText(self._build.getStr("headings.fmtChapter"))
|
||||
self.fmtUnnumbered.setText(self._build.getStr("headings.fmtUnnumbered"))
|
||||
self.fmtScene.setText(self._build.getStr("headings.fmtScene"))
|
||||
self.fmtHScene.setText(self._build.getStr("headings.fmtHardScene"))
|
||||
self.fmtAScene.setText(self._build.getStr("headings.fmtAltScene"))
|
||||
self.fmtSection.setText(self._build.getStr("headings.fmtSection"))
|
||||
|
||||
self.swtTitle.setChecked(self._build.getBool("headings.hideTitle"))
|
||||
self.swtChapter.setChecked(self._build.getBool("headings.hideChapter"))
|
||||
self.swtUnnumbered.setChecked(self._build.getBool("headings.hideUnnumbered"))
|
||||
self.swtScene.setChecked(self._build.getBool("headings.hideScene"))
|
||||
self.swtHScene.setChecked(self._build.getBool("headings.hideHardScene"))
|
||||
self.swtAScene.setChecked(self._build.getBool("headings.hideAltScene"))
|
||||
self.swtSection.setChecked(self._build.getBool("headings.hideSection"))
|
||||
|
||||
self.centerTitle.setChecked(self._build.getBool("headings.centerTitle"))
|
||||
@@ -850,7 +850,7 @@ class _HeadingsTab(NScrollablePage):
|
||||
self._build.setValue("headings.hideChapter", self.swtChapter.isChecked())
|
||||
self._build.setValue("headings.hideUnnumbered", self.swtUnnumbered.isChecked())
|
||||
self._build.setValue("headings.hideScene", self.swtScene.isChecked())
|
||||
self._build.setValue("headings.hideHardScene", self.swtHScene.isChecked())
|
||||
self._build.setValue("headings.hideAltScene", self.swtAScene.isChecked())
|
||||
self._build.setValue("headings.hideSection", self.swtSection.isChecked())
|
||||
|
||||
self._build.setValue("headings.centerTitle", self.centerTitle.isChecked())
|
||||
@@ -890,8 +890,8 @@ class _HeadingsTab(NScrollablePage):
|
||||
text = self.fmtScene.text()
|
||||
label = self._build.getLabel("headings.fmtScene")
|
||||
elif heading == self.EDIT_HSCENE:
|
||||
text = self.fmtHScene.text()
|
||||
label = self._build.getLabel("headings.fmtHardScene")
|
||||
text = self.fmtAScene.text()
|
||||
label = self._build.getLabel("headings.fmtAltScene")
|
||||
elif heading == self.EDIT_SECTION:
|
||||
text = self.fmtSection.text()
|
||||
label = self._build.getLabel("headings.fmtSection")
|
||||
@@ -928,8 +928,8 @@ class _HeadingsTab(NScrollablePage):
|
||||
self.fmtScene.setText(text)
|
||||
self._build.setValue("headings.fmtScene", text)
|
||||
elif heading == self.EDIT_HSCENE:
|
||||
self.fmtHScene.setText(text)
|
||||
self._build.setValue("headings.fmtHardScene", text)
|
||||
self.fmtAScene.setText(text)
|
||||
self._build.setValue("headings.fmtAltScene", text)
|
||||
elif heading == self.EDIT_SECTION:
|
||||
self.fmtSection.setText(text)
|
||||
self._build.setValue("headings.fmtSection", text)
|
||||
@@ -1312,7 +1312,7 @@ class _FormatTab(NScrollableForm):
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _pageSizeValueChanged(self):
|
||||
def _pageSizeValueChanged(self) -> None:
|
||||
"""The user has changed the page size spin boxes, so we flip
|
||||
the page size box to Custom.
|
||||
"""
|
||||
|
||||
@@ -560,7 +560,7 @@ class _NewProjectForm(QWidget):
|
||||
def __init__(self, parent: QWidget) -> None:
|
||||
super().__init__(parent=parent)
|
||||
|
||||
self._basePath = CONFIG.lastPath()
|
||||
self._basePath = CONFIG.homePath()
|
||||
self._fillMode = self.FILL_BLANK
|
||||
self._copyPath = None
|
||||
|
||||
@@ -632,7 +632,7 @@ class _NewProjectForm(QWidget):
|
||||
|
||||
self.numChapters = NSpinBox(self)
|
||||
self.numChapters.setRange(0, 200)
|
||||
self.numChapters.setValue(5)
|
||||
self.numChapters.setValue(0)
|
||||
self.numChapters.setToolTip(self.tr("Set to 0 to only add scenes"))
|
||||
|
||||
self.chapterBox = NWrappedWidgetBox(
|
||||
@@ -642,7 +642,7 @@ class _NewProjectForm(QWidget):
|
||||
|
||||
self.numScenes = NSpinBox(self)
|
||||
self.numScenes.setRange(0, 200)
|
||||
self.numScenes.setValue(5)
|
||||
self.numScenes.setValue(0)
|
||||
|
||||
self.sceneBox = NWrappedWidgetBox(
|
||||
self.tr("Add {0} scene documents (to each chapter)"), self.numScenes
|
||||
@@ -742,7 +742,6 @@ class _NewProjectForm(QWidget):
|
||||
):
|
||||
self._basePath = Path(projDir)
|
||||
self._updateProjPath()
|
||||
CONFIG.setLastPath(self._basePath)
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
@@ -753,7 +752,7 @@ class _NewProjectForm(QWidget):
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _syncSwitches(self):
|
||||
def _syncSwitches(self) -> None:
|
||||
"""Check if the add notes option should also be switched off."""
|
||||
addPlot = self.addPlot.isChecked()
|
||||
addChar = self.addChar.isChecked()
|
||||
|
||||
@@ -25,7 +25,7 @@ from __future__ import annotations
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtGui import QColor, QPainter, QTextCursor
|
||||
from PyQt5.QtWidgets import QDialogButtonBox, QStyle
|
||||
from PyQt5.QtWidgets import QDialogButtonBox, QSizePolicy, QStyle
|
||||
|
||||
# Qt Alignment Flags
|
||||
|
||||
@@ -88,3 +88,11 @@ QtKeepAnchor = QTextCursor.MoveMode.KeepAnchor
|
||||
QtMoveAnchor = QTextCursor.MoveMode.MoveAnchor
|
||||
QtMoveLeft = QTextCursor.MoveOperation.Left
|
||||
QtMoveRight = QTextCursor.MoveOperation.Right
|
||||
|
||||
# Size Policy
|
||||
|
||||
QtSizeExpanding = QSizePolicy.Policy.Expanding
|
||||
QtSizeFixed = QSizePolicy.Policy.Fixed
|
||||
QtSizeIgnored = QSizePolicy.Policy.Ignored
|
||||
QtSizeMinimum = QSizePolicy.Policy.Minimum
|
||||
QtSizeMinimumExpanding = QSizePolicy.Policy.MinimumExpanding
|
||||
|
||||
Reference in New Issue
Block a user