diff --git a/novelwriter/core/buildsettings.py b/novelwriter/core/buildsettings.py index f5173bee..71662db4 100644 --- a/novelwriter/core/buildsettings.py +++ b/novelwriter/core/buildsettings.py @@ -45,29 +45,30 @@ logger = logging.getLogger(__name__) # The Settings Template # ===================== -# Each entry contains a tuple on the form: -# (type, default, [min value, max value]) +# Each entry contains a tuple on the form: (type, default) -SETTINGS_TEMPLATE = { +SETTINGS_TEMPLATE: dict[str, tuple[type, str | int | float | bool]] = { "filter.includeNovel": (bool, True), "filter.includeNotes": (bool, False), "filter.includeInactive": (bool, False), - "headings.fmtTitle": (str, nwHeadFmt.TITLE), + "headings.fmtPart": (str, nwHeadFmt.TITLE), "headings.fmtChapter": (str, nwHeadFmt.TITLE), "headings.fmtUnnumbered": (str, nwHeadFmt.TITLE), "headings.fmtScene": (str, "* * *"), "headings.fmtAltScene": (str, ""), "headings.fmtSection": (str, ""), - "headings.hideTitle": (bool, False), + "headings.hidePart": (bool, False), "headings.hideChapter": (bool, False), "headings.hideUnnumbered": (bool, False), "headings.hideScene": (bool, False), "headings.hideAltScene": (bool, False), "headings.hideSection": (bool, True), "headings.centerTitle": (bool, True), + "headings.centerPart": (bool, True), "headings.centerChapter": (bool, False), "headings.centerScene": (bool, False), "headings.breakTitle": (bool, True), + "headings.breakPart": (bool, True), "headings.breakChapter": (bool, True), "headings.breakScene": (bool, False), "text.includeSynopsis": (bool, False), @@ -77,7 +78,7 @@ SETTINGS_TEMPLATE = { "text.ignoredKeywords": (str, ""), "text.addNoteHeadings": (bool, True), "format.textFont": (str, CONFIG.textFont.toString()), - "format.lineHeight": (float, 1.15, 0.75, 3.0), + "format.lineHeight": (float, 1.15), "format.justifyText": (bool, False), "format.stripUnicode": (bool, False), "format.replaceTabs": (bool, False), @@ -94,9 +95,11 @@ SETTINGS_TEMPLATE = { "format.bottomMargin": (float, 2.0), "format.leftMargin": (float, 2.0), "format.rightMargin": (float, 2.0), - "odt.addColours": (bool, True), "odt.pageHeader": (str, nwHeadFmt.ODT_AUTO), "odt.pageCountOffset": (int, 0), + "odt.colorHeadings": (bool, True), + "odt.scaleHeadings": (bool, True), + "odt.boldHeadings": (bool, True), "html.addStyles": (bool, True), "html.preserveTabs": (bool, False), } @@ -108,12 +111,16 @@ SETTINGS_LABELS = { "filter.includeInactive": QT_TRANSLATE_NOOP("Builds", "Inactive Documents"), "headings": QT_TRANSLATE_NOOP("Builds", "Headings"), - "headings.fmtTitle": QT_TRANSLATE_NOOP("Builds", "Partition Format"), + "headings.fmtPart": QT_TRANSLATE_NOOP("Builds", "Partition Format"), "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.fmtAltScene": QT_TRANSLATE_NOOP("Builds", "Alt. Scene Format"), "headings.fmtSection": QT_TRANSLATE_NOOP("Builds", "Section Format"), + "headings.styleTitle": QT_TRANSLATE_NOOP("Builds", "Title Styling"), + "headings.stylePart": QT_TRANSLATE_NOOP("Builds", "Partition Styling"), + "headings.styleChapter": QT_TRANSLATE_NOOP("Builds", "Chapter Styling"), + "headings.styleScene": QT_TRANSLATE_NOOP("Builds", "Scene Styling"), "text.grpContent": QT_TRANSLATE_NOOP("Builds", "Text Content"), "text.includeSynopsis": QT_TRANSLATE_NOOP("Builds", "Include Synopsis"), @@ -147,12 +154,14 @@ SETTINGS_LABELS = { "format.leftMargin": QT_TRANSLATE_NOOP("Builds", "Left Margin"), "format.rightMargin": QT_TRANSLATE_NOOP("Builds", "Right Margin"), - "odt": QT_TRANSLATE_NOOP("Builds", "ODT Documents"), - "odt.addColours": QT_TRANSLATE_NOOP("Builds", "Add Highlight Colours"), + "odt": QT_TRANSLATE_NOOP("Builds", "Document Options"), "odt.pageHeader": QT_TRANSLATE_NOOP("Builds", "Page Header"), "odt.pageCountOffset": QT_TRANSLATE_NOOP("Builds", "Page Counter Offset"), + "odt.colorHeadings": QT_TRANSLATE_NOOP("Builds", "Add Colours to Headings"), + "odt.scaleHeadings": QT_TRANSLATE_NOOP("Builds", "Increase Size of Headings"), + "odt.boldHeadings": QT_TRANSLATE_NOOP("Builds", "Bold Headings"), - "html": QT_TRANSLATE_NOOP("Builds", "HTML Document"), + "html": QT_TRANSLATE_NOOP("Builds", "HTML Options"), "html.addStyles": QT_TRANSLATE_NOOP("Builds", "Add CSS Styles"), "html.preserveTabs": QT_TRANSLATE_NOOP("Builds", "Preserve Tab Characters"), } @@ -346,18 +355,12 @@ class BuildSettings: self._changed = True return - def setValue(self, key: str, value: str | int | bool | float) -> bool: + def setValue(self, key: str, value: str | int | float | bool) -> None: """Set a specific value for a build setting.""" - if key not in SETTINGS_TEMPLATE: - return False - definition = SETTINGS_TEMPLATE[key] - if not isinstance(value, definition[0]): - return False - if len(definition) == 4 and isinstance(value, (int, float)): - value = min(max(value, definition[2]), definition[3]) - self._changed = value != self._settings[key] - self._settings[key] = value - return True + if (d := SETTINGS_TEMPLATE.get(key)) and len(d) == 2 and isinstance(value, d[0]): + self._changed = value != self._settings[key] + self._settings[key] = value + return ## # Methods diff --git a/novelwriter/core/docbuild.py b/novelwriter/core/docbuild.py index 30189bdd..7fce8e92 100644 --- a/novelwriter/core/docbuild.py +++ b/novelwriter/core/docbuild.py @@ -35,13 +35,13 @@ from novelwriter.constants import nwLabels from novelwriter.core.buildsettings import BuildSettings from novelwriter.core.item import NWItem from novelwriter.core.project import NWProject -from novelwriter.core.tohtml import ToHtml -from novelwriter.core.tokenizer import Tokenizer -from novelwriter.core.tomarkdown import ToMarkdown -from novelwriter.core.toodt import ToOdt -from novelwriter.core.toqdoc import TextDocumentTheme, ToQTextDocument from novelwriter.enum import nwBuildFmt from novelwriter.error import formatException, logException +from novelwriter.formats.tohtml import ToHtml +from novelwriter.formats.tokenizer import Tokenizer +from novelwriter.formats.tomarkdown import ToMarkdown +from novelwriter.formats.toodt import ToOdt +from novelwriter.formats.toqdoc import TextDocumentTheme, ToQTextDocument logger = logging.getLogger(__name__) @@ -178,10 +178,7 @@ class NWBuildDocument: self._cache = makeObj try: - if isFlat: - makeObj.saveFlatXML(path) - else: - makeObj.saveOpenDocText(path) + makeObj.saveDocument(path) except Exception as exc: logException() self._error = formatException(exc) @@ -213,10 +210,7 @@ class NWBuildDocument: if isinstance(path, Path): try: - if asJson: - makeObj.saveHtmlJson(path) - else: - makeObj.saveHtml5(path) + makeObj.saveDocument(path, asJson=asJson) except Exception as exc: logException() self._error = formatException(exc) @@ -246,7 +240,7 @@ class NWBuildDocument: self._cache = makeObj try: - makeObj.saveMarkdown(path) + makeObj.saveDocument(path) except Exception as exc: logException() self._error = formatException(exc) @@ -276,10 +270,7 @@ class NWBuildDocument: if isinstance(path, Path): try: - if asJson: - makeObj.saveRawMarkdownJSON(path) - else: - makeObj.saveRawMarkdown(path) + makeObj.saveRawDocument(path, asJson=asJson) except Exception as exc: logException() self._error = formatException(exc) @@ -297,9 +288,9 @@ class NWBuildDocument: textFont.fromString(self._build.getStr("format.textFont")) bldObj.setFont(textFont) - bldObj.setTitleFormat( - self._build.getStr("headings.fmtTitle"), - self._build.getBool("headings.hideTitle") + bldObj.setPartitionFormat( + self._build.getStr("headings.fmtPart"), + self._build.getBool("headings.hidePart") ) bldObj.setChapterFormat( self._build.getStr("headings.fmtChapter"), @@ -322,8 +313,12 @@ class NWBuildDocument: self._build.getBool("headings.hideSection") ) bldObj.setTitleStyle( - self._build.getBool("headings.centerTitle"), - self._build.getBool("headings.breakTitle") + self._build.getBool("headings.centerPart"), + self._build.getBool("headings.breakPart") + ) + bldObj.setPartitionStyle( + self._build.getBool("headings.centerPart"), + self._build.getBool("headings.breakPart") ) bldObj.setChapterStyle( self._build.getBool("headings.centerChapter"), @@ -343,6 +338,11 @@ class NWBuildDocument: self._build.getFloat("format.firstIndentWidth"), self._build.getBool("format.indentFirstPar"), ) + bldObj.setHeadingStyles( + self._build.getBool("odt.colorHeadings"), + self._build.getBool("odt.scaleHeadings"), + self._build.getBool("odt.boldHeadings"), + ) bldObj.setBodyText(self._build.getBool("text.includeBodyText")) bldObj.setSynopsis(self._build.getBool("text.includeSynopsis")) @@ -355,10 +355,10 @@ class NWBuildDocument: bldObj.setReplaceUnicode(self._build.getBool("format.stripUnicode")) if isinstance(bldObj, ToOdt): - bldObj.setColourHeaders(self._build.getBool("odt.addColours")) bldObj.setLanguage(self._project.data.language) bldObj.setHeaderFormat( - self._build.getStr("odt.pageHeader"), self._build.getInt("odt.pageCountOffset") + self._build.getStr("odt.pageHeader"), + self._build.getInt("odt.pageCountOffset"), ) scale = nwLabels.UNIT_SCALE.get(self._build.getStr("format.pageUnit"), 1.0) diff --git a/novelwriter/core/tohtml.py b/novelwriter/formats/tohtml.py similarity index 91% rename from novelwriter/core/tohtml.py rename to novelwriter/formats/tohtml.py index d9221e29..78f359b9 100644 --- a/novelwriter/core/tohtml.py +++ b/novelwriter/formats/tohtml.py @@ -32,7 +32,7 @@ from time import time from novelwriter.common import formatTimeStamp from novelwriter.constants import nwHeadFmt, nwHtmlUnicode, nwKeyWords, nwLabels from novelwriter.core.project import NWProject -from novelwriter.core.tokenizer import T_Formats, Tokenizer, stripEscape +from novelwriter.formats.tokenizer import T_Formats, Tokenizer, stripEscape from novelwriter.types import FONT_STYLE, FONT_WEIGHTS logger = logging.getLogger(__name__) @@ -290,51 +290,51 @@ class ToHtml(Tokenizer): return - def saveHtml5(self, path: str | Path) -> None: + def saveDocument(self, path: str | Path, asJson: bool = False) -> None: """Save the data to an HTML file.""" - with open(path, mode="w", encoding="utf-8") as fObj: - fObj.write(( - "\n" - "\n" - "\n" - "\n" - "{title:s}\n" - "\n" - "\n" - "\n" - "
\n" - "{body:s}\n" - "
\n" - "\n" - "\n" - ).format( - title=self._project.data.name, - style="\n".join(self.getStyleSheet()), - body=("".join(self._fullHTML)).replace("\t", " ").rstrip(), - )) - logger.info("Wrote file: %s", path) - return - - def saveHtmlJson(self, path: str | Path) -> None: - """Save the data to a JSON file.""" - timeStamp = time() - data = { - "meta": { - "projectName": self._project.data.name, - "novelAuthor": self._project.data.author, - "buildTime": int(timeStamp), - "buildTimeStr": formatTimeStamp(timeStamp), - }, - "text": { - "css": self.getStyleSheet(), - "html": [t.replace("\t", " ").rstrip().split("\n") for t in self.fullHTML], + if asJson: + ts = time() + data = { + "meta": { + "projectName": self._project.data.name, + "novelAuthor": self._project.data.author, + "buildTime": int(ts), + "buildTimeStr": formatTimeStamp(ts), + }, + "text": { + "css": self.getStyleSheet(), + "html": [t.replace("\t", " ").rstrip().split("\n") for t in self.fullHTML], + } } - } - with open(path, mode="w", encoding="utf-8") as fObj: - json.dump(data, fObj, indent=2) + with open(path, mode="w", encoding="utf-8") as fObj: + json.dump(data, fObj, indent=2) + + else: + with open(path, mode="w", encoding="utf-8") as fObj: + fObj.write(( + "\n" + "\n" + "\n" + "\n" + "{title:s}\n" + "\n" + "\n" + "\n" + "
\n" + "{body:s}\n" + "
\n" + "\n" + "\n" + ).format( + title=self._project.data.name, + style="\n".join(self.getStyleSheet()), + body=("".join(self._fullHTML)).replace("\t", " ").rstrip(), + )) + logger.info("Wrote file: %s", path) + return def replaceTabs(self, nSpaces: int = 8, spaceChar: str = " ") -> None: diff --git a/novelwriter/core/tokenizer.py b/novelwriter/formats/tokenizer.py similarity index 93% rename from novelwriter/core/tokenizer.py rename to novelwriter/formats/tokenizer.py index d53db4aa..ea003b4c 100644 --- a/novelwriter/core/tokenizer.py +++ b/novelwriter/formats/tokenizer.py @@ -150,18 +150,21 @@ class Tokenizer(ABC): # User Settings self._textFont = QFont("Serif", 11) # Output text font - self._lineHeight = 1.15 # Line height in units of em - self._blockIndent = 4.00 # Block indent in units of em - self._firstIndent = False # Enable first line indent - self._firstWidth = 1.40 # First line indent in units of em - self._indentFirst = False # Indent first paragraph - self._doJustify = False # Justify text - self._doBodyText = True # Include body text - self._doSynopsis = False # Also process synopsis comments - self._doComments = False # Also process comments - self._doKeywords = False # Also process keywords like tags and references - self._skipKeywords = set() # Keywords to ignore - self._keepBreaks = True # Keep line breaks in paragraphs + self._lineHeight = 1.15 # Line height in units of em + self._colorHeads = True # Colourise headings + self._scaleHeads = True # Scale headings to larger font size + self._boldHeads = True # Bold headings + self._blockIndent = 4.00 # Block indent in units of em + self._firstIndent = False # Enable first line indent + self._firstWidth = 1.40 # First line indent in units of em + self._indentFirst = False # Indent first paragraph + self._doJustify = False # Justify text + self._doBodyText = True # Include body text + self._doSynopsis = False # Also process synopsis comments + self._doComments = False # Also process comments + self._doKeywords = False # Also process keywords like tags and references + self._skipKeywords = set() # Keywords to ignore + self._keepBreaks = True # Keep line breaks in paragraphs # Margins self._marginTitle = (1.417, 0.500) @@ -175,14 +178,14 @@ class Tokenizer(ABC): self._marginSep = (1.168, 1.168) # Title Formats - self._fmtTitle = nwHeadFmt.TITLE # Formatting for titles + self._fmtPart = nwHeadFmt.TITLE # Formatting for partitions self._fmtChapter = nwHeadFmt.TITLE # Formatting for numbered chapters self._fmtUnNum = nwHeadFmt.TITLE # Formatting for unnumbered chapters self._fmtScene = nwHeadFmt.TITLE # Formatting for scenes self._fmtHScene = nwHeadFmt.TITLE # Formatting for hard scenes self._fmtSection = nwHeadFmt.TITLE # Formatting for sections - self._hideTitle = False # Do not include title headings + self._hidePart = False # Do not include partition headings self._hideChapter = False # Do not include chapter headings self._hideUnNum = False # Do not include unnumbered headings self._hideScene = False # Do not include scene headings @@ -192,6 +195,7 @@ class Tokenizer(ABC): self._linkHeadings = False # Add an anchor before headings self._titleStyle = self.A_CENTRE | self.A_PBB + self._partStyle = self.A_CENTRE | self.A_PBB self._chapterStyle = self.A_PBB self._sceneStyle = self.A_NONE @@ -271,10 +275,10 @@ class Tokenizer(ABC): # Setters ## - def setTitleFormat(self, hFormat: str, hide: bool = False) -> None: - """Set the title format pattern.""" - self._fmtTitle = hFormat.strip() - self._hideTitle = hide + def setPartitionFormat(self, hFormat: str, hide: bool = False) -> None: + """Set the partition format pattern.""" + self._fmtPart = hFormat.strip() + self._hidePart = hide return def setChapterFormat(self, hFormat: str, hide: bool = False) -> None: @@ -314,6 +318,13 @@ class Tokenizer(ABC): ) return + def setPartitionStyle(self, center: bool, pageBreak: bool) -> None: + """Set the partition heading style.""" + self._partStyle = ( + (self.A_CENTRE if center else self.A_NONE) | (self.A_PBB if pageBreak else self.A_NONE) + ) + return + def setChapterStyle(self, center: bool, pageBreak: bool) -> None: """Set the chapter heading style.""" self._chapterStyle = ( @@ -338,6 +349,13 @@ class Tokenizer(ABC): self._lineHeight = min(max(float(height), 0.5), 5.0) return + def setHeadingStyles(self, color: bool, scale: bool, bold: bool) -> None: + """Set text style for headings.""" + self._colorHeads = color + self._scaleHeads = scale + self._boldHeads = bold + return + def setBlockIndent(self, indent: float) -> None: """Set the block indent between 0.0 and 10.0.""" self._blockIndent = min(max(float(indent), 0.0), 10.0) @@ -468,6 +486,10 @@ class Tokenizer(ABC): def doConvert(self) -> None: raise NotImplementedError + @abstractmethod + def saveDocument(self, path: str | Path) -> None: + raise NotImplementedError + def addRootHeading(self, tHandle: str) -> None: """Add a heading at the start of a new root folder.""" self._text = "" @@ -663,16 +685,16 @@ class Tokenizer(ABC): nHead += 1 tText = aLine[2:].strip() tType = self.T_HEAD1 if isPlain else self.T_TITLE - tStyle = self.A_NONE if isPlain else (self.A_PBB | self.A_CENTRE) - sHide = self._hideTitle if isPlain else False + tStyle = self.A_NONE if isPlain else self._titleStyle + sHide = self._hidePart if isPlain else False if self._isNovel: if sHide: tText = "" tType = self.T_EMPTY tStyle = self.A_NONE elif isPlain: - tText = self._hFormatter.apply(self._fmtTitle, tText, nHead) - tStyle = self._titleStyle + tText = self._hFormatter.apply(self._fmtPart, tText, nHead) + tStyle = self._partStyle if isPlain: self._hFormatter.resetScene() else: @@ -1069,29 +1091,31 @@ class Tokenizer(ABC): return - def saveRawMarkdown(self, path: str | Path) -> None: + def saveRawDocument(self, path: str | Path, asJson: bool = False) -> None: """Save the raw text to a plain text file.""" - with open(path, mode="w", encoding="utf-8") as outFile: - for nwdPage in self._markdown: - outFile.write(nwdPage) - return - - def saveRawMarkdownJSON(self, path: str | Path) -> None: - """Save the raw text to a JSON file.""" - timeStamp = time() - data = { - "meta": { - "projectName": self._project.data.name, - "novelAuthor": self._project.data.author, - "buildTime": int(timeStamp), - "buildTimeStr": formatTimeStamp(timeStamp), - }, - "text": { - "nwd": [page.rstrip("\n").split("\n") for page in self._markdown], + if asJson: + ts = time() + data = { + "meta": { + "projectName": self._project.data.name, + "novelAuthor": self._project.data.author, + "buildTime": int(ts), + "buildTimeStr": formatTimeStamp(ts), + }, + "text": { + "nwd": [page.rstrip("\n").split("\n") for page in self._markdown], + } } - } - with open(path, mode="w", encoding="utf-8") as fObj: - json.dump(data, fObj, indent=2) + with open(path, mode="w", encoding="utf-8") as fObj: + json.dump(data, fObj, indent=2) + + else: + with open(path, mode="w", encoding="utf-8") as outFile: + for nwdPage in self._markdown: + outFile.write(nwdPage) + + logger.info("Wrote file: %s", path) + return ## diff --git a/novelwriter/core/tomarkdown.py b/novelwriter/formats/tomarkdown.py similarity index 98% rename from novelwriter/core/tomarkdown.py rename to novelwriter/formats/tomarkdown.py index eccf2300..a738513c 100644 --- a/novelwriter/core/tomarkdown.py +++ b/novelwriter/formats/tomarkdown.py @@ -29,7 +29,7 @@ from pathlib import Path from novelwriter.constants import nwHeadFmt, nwLabels, nwUnicode from novelwriter.core.project import NWProject -from novelwriter.core.tokenizer import T_Formats, Tokenizer +from novelwriter.formats.tokenizer import T_Formats, Tokenizer logger = logging.getLogger(__name__) @@ -199,7 +199,7 @@ class ToMarkdown(Tokenizer): return - def saveMarkdown(self, path: str | Path) -> None: + def saveDocument(self, path: str | Path) -> None: """Save the data to a plain text file.""" with open(path, mode="w", encoding="utf-8") as outFile: outFile.write("".join(self._fullMD)) diff --git a/novelwriter/core/toodt.py b/novelwriter/formats/toodt.py similarity index 95% rename from novelwriter/core/toodt.py rename to novelwriter/formats/toodt.py index dbf75be3..8d3de780 100644 --- a/novelwriter/core/toodt.py +++ b/novelwriter/formats/toodt.py @@ -41,7 +41,7 @@ from novelwriter import __version__ from novelwriter.common import xmlIndent from novelwriter.constants import nwHeadFmt, nwKeyWords, nwLabels from novelwriter.core.project import NWProject -from novelwriter.core.tokenizer import T_Formats, Tokenizer, stripEscape +from novelwriter.formats.tokenizer import T_Formats, Tokenizer, stripEscape from novelwriter.types import FONT_STYLE, FONT_WEIGHTS logger = logging.getLogger(__name__) @@ -161,7 +161,7 @@ class ToOdt(Tokenizer): # Properties self._textFont = QFont("Liberation Serif", 12) - self._colourHead = False + self._headWeight = "bold" self._headerFormat = "" self._pageOffset = 0 @@ -224,10 +224,10 @@ class ToOdt(Tokenizer): self._opaHead12 = None self._colHead34 = None self._opaHead34 = None - self._colMetaTx = None - self._opaMetaTx = None self._colDialogM = None self._colDialogA = None + self._colMetaTx = "#813709" + self._opaMetaTx = "100%" self._markText = "#ffffa6" return @@ -244,11 +244,6 @@ class ToOdt(Tokenizer): self._dCountry = country or self._dCountry return - def setColourHeaders(self, state: bool) -> None: - """Enable/disable coloured headings and comments.""" - self._colourHead = state - return - def setPageLayout( self, width: int | float, height: int | float, top: int | float, bottom: int | float, left: int | float, right: int | float @@ -287,13 +282,15 @@ class ToOdt(Tokenizer): self._fontStyle = FONT_STYLE.get(self._textFont.style(), "normal") self._fontPitch = "fixed" if self._textFont.fixedPitch() else "variable" self._fontBold = FONT_WEIGHT_MAP.get(fontBold, fontBold) + self._headWeight = self._fontBold if self._boldHeads else None - self._fSizeTitle = f"{round(2.50 * self._fontSize):d}pt" - self._fSizeHead1 = f"{round(2.00 * self._fontSize):d}pt" - self._fSizeHead2 = f"{round(1.60 * self._fontSize):d}pt" - self._fSizeHead3 = f"{round(1.30 * self._fontSize):d}pt" - self._fSizeHead4 = f"{round(1.15 * self._fontSize):d}pt" - self._fSizeHead = f"{round(1.15 * self._fontSize):d}pt" + hScale = self._scaleHeads + self._fSizeTitle = f"{round((2.50 if hScale else 1.0) * self._fontSize):d}pt" + self._fSizeHead1 = f"{round((2.00 if hScale else 1.0) * self._fontSize):d}pt" + self._fSizeHead2 = f"{round((1.60 if hScale else 1.0) * self._fontSize):d}pt" + self._fSizeHead3 = f"{round((1.30 if hScale else 1.0) * self._fontSize):d}pt" + self._fSizeHead4 = f"{round((1.15 if hScale else 1.0) * self._fontSize):d}pt" + self._fSizeHead = f"{round((1.15 if hScale else 1.0) * self._fontSize):d}pt" self._fSizeText = f"{self._fontSize:d}pt" self._fSizeFoot = f"{round(0.8*self._fontSize):d}pt" @@ -322,13 +319,11 @@ class ToOdt(Tokenizer): self._mLeftFoot = self._emToCm(self._marginFoot[0]) self._mBotFoot = self._emToCm(self._marginFoot[1]) - if self._colourHead: + if self._colorHeads: self._colHead12 = "#2a6099" self._opaHead12 = "100%" self._colHead34 = "#444444" self._opaHead34 = "100%" - self._colMetaTx = "#813709" - self._opaMetaTx = "100%" if self._showDialog: self._colDialogM = "#2a6099" @@ -548,46 +543,44 @@ class ToOdt(Tokenizer): self._xText.insert(0, xFields) return - def saveFlatXML(self, path: str | Path) -> None: - """Save the data to an .fodt file.""" - with open(path, mode="wb") as fObj: - xml = ET.ElementTree(self._dFlat) - xmlIndent(xml) - xml.write(fObj, encoding="utf-8", xml_declaration=True) - logger.info("Wrote file: %s", path) - return - - def saveOpenDocText(self, path: str | Path) -> None: - """Save the data to an .odt file.""" - mMani = _mkTag("manifest", "manifest") - mVers = _mkTag("manifest", "version") - mPath = _mkTag("manifest", "full-path") - mType = _mkTag("manifest", "media-type") - mFile = _mkTag("manifest", "file-entry") - - xMani = ET.Element(mMani, attrib={mVers: X_VERS}) - ET.SubElement(xMani, mFile, attrib={mPath: "/", mVers: X_VERS, mType: X_MIME}) - ET.SubElement(xMani, mFile, attrib={mPath: "settings.xml", mType: "text/xml"}) - ET.SubElement(xMani, mFile, attrib={mPath: "content.xml", mType: "text/xml"}) - ET.SubElement(xMani, mFile, attrib={mPath: "meta.xml", mType: "text/xml"}) - ET.SubElement(xMani, mFile, attrib={mPath: "styles.xml", mType: "text/xml"}) - - oRoot = _mkTag("office", "document-settings") - oVers = _mkTag("office", "version") - xSett = ET.Element(oRoot, attrib={oVers: X_VERS}) - - def putInZip(name: str, xObj: ET.Element, zipObj: ZipFile) -> None: - with zipObj.open(name, mode="w") as fObj: - xml = ET.ElementTree(xObj) + def saveDocument(self, path: str | Path) -> None: + """Save the data to an .fodt or .odt file.""" + if self._isFlat: + with open(path, mode="wb") as fObj: + xml = ET.ElementTree(self._dFlat) + xmlIndent(xml) xml.write(fObj, encoding="utf-8", xml_declaration=True) - with ZipFile(path, mode="w") as outZip: - outZip.writestr("mimetype", X_MIME) - putInZip("META-INF/manifest.xml", xMani, outZip) - putInZip("settings.xml", xSett, outZip) - putInZip("content.xml", self._dCont, outZip) - putInZip("meta.xml", self._dMeta, outZip) - putInZip("styles.xml", self._dStyl, outZip) + else: + mMani = _mkTag("manifest", "manifest") + mVers = _mkTag("manifest", "version") + mPath = _mkTag("manifest", "full-path") + mType = _mkTag("manifest", "media-type") + mFile = _mkTag("manifest", "file-entry") + + xMani = ET.Element(mMani, attrib={mVers: X_VERS}) + ET.SubElement(xMani, mFile, attrib={mPath: "/", mVers: X_VERS, mType: X_MIME}) + ET.SubElement(xMani, mFile, attrib={mPath: "settings.xml", mType: "text/xml"}) + ET.SubElement(xMani, mFile, attrib={mPath: "content.xml", mType: "text/xml"}) + ET.SubElement(xMani, mFile, attrib={mPath: "meta.xml", mType: "text/xml"}) + ET.SubElement(xMani, mFile, attrib={mPath: "styles.xml", mType: "text/xml"}) + + oRoot = _mkTag("office", "document-settings") + oVers = _mkTag("office", "version") + xSett = ET.Element(oRoot, attrib={oVers: X_VERS}) + + 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) + + with ZipFile(path, mode="w") as outZip: + outZip.writestr("mimetype", X_MIME) + putInZip("META-INF/manifest.xml", xMani, outZip) + putInZip("settings.xml", xSett, outZip) + putInZip("content.xml", self._dCont, outZip) + putInZip("meta.xml", self._dMeta, outZip) + putInZip("styles.xml", self._dStyl, outZip) logger.info("Wrote file: %s", path) @@ -965,7 +958,7 @@ class ToOdt(Tokenizer): style.setFontName(self._fontFamily) style.setFontFamily(self._fontFamily) style.setFontSize(self._fSizeTitle) - style.setFontWeight(self._fontBold) + style.setFontWeight(self._headWeight) style.packXML(self._xStyl) self._mainPara[style.name] = style @@ -998,7 +991,7 @@ class ToOdt(Tokenizer): style.setFontName(self._fontFamily) style.setFontFamily(self._fontFamily) style.setFontSize(self._fSizeHead1) - style.setFontWeight(self._fontBold) + style.setFontWeight(self._headWeight) style.setColour(self._colHead12) style.setOpacity(self._opaHead12) style.packXML(self._xStyl) @@ -1016,7 +1009,7 @@ class ToOdt(Tokenizer): style.setFontName(self._fontFamily) style.setFontFamily(self._fontFamily) style.setFontSize(self._fSizeHead2) - style.setFontWeight(self._fontBold) + style.setFontWeight(self._headWeight) style.setColour(self._colHead12) style.setOpacity(self._opaHead12) style.packXML(self._xStyl) @@ -1034,7 +1027,7 @@ class ToOdt(Tokenizer): style.setFontName(self._fontFamily) style.setFontFamily(self._fontFamily) style.setFontSize(self._fSizeHead3) - style.setFontWeight(self._fontBold) + style.setFontWeight(self._headWeight) style.setColour(self._colHead34) style.setOpacity(self._opaHead34) style.packXML(self._xStyl) @@ -1052,7 +1045,7 @@ class ToOdt(Tokenizer): style.setFontName(self._fontFamily) style.setFontFamily(self._fontFamily) style.setFontSize(self._fSizeHead4) - style.setFontWeight(self._fontBold) + style.setFontWeight(self._headWeight) style.setColour(self._colHead34) style.setOpacity(self._opaHead34) style.packXML(self._xStyl) diff --git a/novelwriter/core/toqdoc.py b/novelwriter/formats/toqdoc.py similarity index 93% rename from novelwriter/core/toqdoc.py rename to novelwriter/formats/toqdoc.py index f8a092e6..43f10b69 100644 --- a/novelwriter/core/toqdoc.py +++ b/novelwriter/formats/toqdoc.py @@ -25,6 +25,8 @@ from __future__ import annotations import logging +from pathlib import Path + from PyQt5.QtGui import ( QColor, QFont, QFontMetricsF, QTextBlockFormat, QTextCharFormat, QTextCursor, QTextDocument @@ -32,7 +34,7 @@ from PyQt5.QtGui import ( from novelwriter.constants import nwHeaders, nwHeadFmt, nwKeyWords, nwLabels, nwUnicode from novelwriter.core.project import NWProject -from novelwriter.core.tokenizer import T_Formats, Tokenizer +from novelwriter.formats.tokenizer import T_Formats, Tokenizer from novelwriter.types import ( QtAlignAbsolute, QtAlignCenter, QtAlignJustify, QtAlignLeft, QtAlignRight, QtBlack, QtPageBreakAfter, QtPageBreakBefore, QtTransparent, @@ -114,12 +116,13 @@ class ToQTextDocument(Tokenizer): self.T_HEAD4: (mPx * self._marginHead4[0], mPx * self._marginHead4[1]), } + hScale = self._scaleHeads self._sHead = { - self.T_TITLE: nwHeaders.H_SIZES.get(0, 1.0) * fPt, - self.T_HEAD1: nwHeaders.H_SIZES.get(1, 1.0) * fPt, - self.T_HEAD2: nwHeaders.H_SIZES.get(2, 1.0) * fPt, - self.T_HEAD3: nwHeaders.H_SIZES.get(3, 1.0) * fPt, - self.T_HEAD4: nwHeaders.H_SIZES.get(4, 1.0) * fPt, + self.T_TITLE: (nwHeaders.H_SIZES.get(0, 1.0) * fPt) if hScale else fPt, + self.T_HEAD1: (nwHeaders.H_SIZES.get(1, 1.0) * fPt) if hScale else fPt, + self.T_HEAD2: (nwHeaders.H_SIZES.get(2, 1.0) * fPt) if hScale else fPt, + self.T_HEAD3: (nwHeaders.H_SIZES.get(3, 1.0) * fPt) if hScale else fPt, + self.T_HEAD4: (nwHeaders.H_SIZES.get(4, 1.0) * fPt) if hScale else fPt, } self._mText = (mPx * self._marginText[0], mPx * self._marginText[1]) @@ -147,9 +150,6 @@ class ToQTextDocument(Tokenizer): self._cText.setBackground(QtTransparent) self._cText.setForeground(self._theme.text) - self._cHead = QTextCharFormat(self._cText) - self._cHead.setForeground(self._theme.head) - self._cComment = QTextCharFormat(self._cText) self._cComment.setForeground(self._theme.comment) @@ -275,6 +275,10 @@ class ToQTextDocument(Tokenizer): return + def saveDocument(self, path: str | Path) -> None: + """Not implemented.""" + return + def appendFootnotes(self) -> None: """Append the footnotes in the buffer.""" if self._usedNotes: @@ -409,8 +413,13 @@ class ToQTextDocument(Tokenizer): bFmt.setTopMargin(mTop) bFmt.setBottomMargin(mBottom) - cFmt = QTextCharFormat(self._cText if hType == self.T_TITLE else self._cHead) - cFmt.setFontWeight(self._bold) + self._cTitle = QTextCharFormat(self._cText) + self._cTitle.setFontWeight(self._bold if self._boldHeads else self._normal) + + hCol = self._colorHeads and hType != self.T_TITLE + cFmt = QTextCharFormat(self._cText) + cFmt.setForeground(self._theme.head if hCol else self._theme.text) + cFmt.setFontWeight(self._bold if self._boldHeads else self._normal) cFmt.setFontPointSize(self._sHead.get(hType, 1.0)) if nHead >= 0: cFmt.setAnchorNames([f"{self._handle}:T{nHead:04d}"]) diff --git a/novelwriter/gui/docviewer.py b/novelwriter/gui/docviewer.py index 96c5df3d..665285fa 100644 --- a/novelwriter/gui/docviewer.py +++ b/novelwriter/gui/docviewer.py @@ -39,12 +39,12 @@ from PyQt5.QtWidgets import ( from novelwriter import CONFIG, SHARED from novelwriter.constants import nwHeaders, nwUnicode -from novelwriter.core.toqdoc import TextDocumentTheme, ToQTextDocument from novelwriter.enum import nwDocAction, nwDocMode, nwItemType from novelwriter.error import logException from novelwriter.extensions.configlayout import NColourLabel from novelwriter.extensions.eventfilters import WheelEventFilter from novelwriter.extensions.modified import NIconToolButton +from novelwriter.formats.toqdoc import TextDocumentTheme, ToQTextDocument from novelwriter.gui.theme import STYLES_MIN_TOOLBUTTON from novelwriter.types import ( QtAlignCenterTop, QtKeepAnchor, QtMouseLeft, QtMoveAnchor, diff --git a/novelwriter/tools/manuscript.py b/novelwriter/tools/manuscript.py index f13ef236..410a33fd 100644 --- a/novelwriter/tools/manuscript.py +++ b/novelwriter/tools/manuscript.py @@ -42,10 +42,10 @@ from novelwriter import CONFIG, SHARED from novelwriter.common import fuzzyTime from novelwriter.core.buildsettings import BuildCollection, BuildSettings from novelwriter.core.docbuild import NWBuildDocument -from novelwriter.core.tokenizer import HeadingFormatter -from novelwriter.core.toqdoc import TextDocumentTheme, ToQTextDocument from novelwriter.extensions.modified import NIconToggleButton, NIconToolButton, NToolDialog from novelwriter.extensions.progressbars import NProgressCircle +from novelwriter.formats.tokenizer import HeadingFormatter +from novelwriter.formats.toqdoc import TextDocumentTheme, ToQTextDocument from novelwriter.gui.theme import STYLES_FLAT_TABS, STYLES_MIN_TOOLBUTTON from novelwriter.tools.manusbuild import GuiManuscriptBuild from novelwriter.tools.manussettings import GuiBuildSettings @@ -593,7 +593,7 @@ class _DetailsWidget(QWidget): item.setText(1, "") self.listView.addTopLevelItem(item) for hFormat, hHide in [ - ("headings.fmtTitle", "headings.hideTitle"), + ("headings.fmtPart", "headings.hidePart"), ("headings.fmtChapter", "headings.hideChapter"), ("headings.fmtUnnumbered", "headings.hideUnnumbered"), ("headings.fmtScene", "headings.hideScene"), diff --git a/novelwriter/tools/manussettings.py b/novelwriter/tools/manussettings.py index 15b6d918..c0168301 100644 --- a/novelwriter/tools/manussettings.py +++ b/novelwriter/tools/manussettings.py @@ -50,8 +50,8 @@ from novelwriter.extensions.pagedsidebar import NPagedSideBar from novelwriter.extensions.switch import NSwitch from novelwriter.extensions.switchbox import NSwitchBox from novelwriter.types import ( - QtAlignLeft, QtDialogApply, QtDialogClose, QtDialogSave, QtRoleAccept, - QtRoleApply, QtRoleReject, QtUserRole + QtAlignCenter, QtAlignLeft, QtDialogApply, QtDialogClose, QtDialogSave, + QtRoleAccept, QtRoleApply, QtRoleReject, QtUserRole ) if TYPE_CHECKING: # pragma: no cover @@ -585,20 +585,20 @@ class _HeadingsTab(NScrollablePage): self.formatBox.setHorizontalSpacing(bSp) # Title Heading - self.lblTitle = QLabel(self._build.getLabel("headings.fmtTitle"), self) - self.fmtTitle = QLineEdit("", self) - self.fmtTitle.setReadOnly(True) - self.btnTitle = NIconToolButton(self, iSz, "edit") - self.btnTitle.clicked.connect(lambda: self._editHeading(self.EDIT_TITLE)) - self.hdeTitle = QLabel(trHide, self) - self.hdeTitle.setIndent(bSp) - self.swtTitle = NSwitch(self, height=iPx) + self.lblPart = QLabel(self._build.getLabel("headings.fmtPart"), self) + self.fmtPart = QLineEdit("", self) + self.fmtPart.setReadOnly(True) + self.btnPart = NIconToolButton(self, iSz, "edit") + self.btnPart.clicked.connect(lambda: self._editHeading(self.EDIT_TITLE)) + self.hdePart = QLabel(trHide, self) + self.hdePart.setIndent(bSp) + self.swtPart = NSwitch(self, height=iPx) - self.formatBox.addWidget(self.lblTitle, 0, 0) - self.formatBox.addWidget(self.fmtTitle, 0, 1) - self.formatBox.addWidget(self.btnTitle, 0, 2) - self.formatBox.addWidget(self.hdeTitle, 0, 3) - self.formatBox.addWidget(self.swtTitle, 0, 4) + self.formatBox.addWidget(self.lblPart, 0, 0) + self.formatBox.addWidget(self.fmtPart, 0, 1) + self.formatBox.addWidget(self.btnPart, 0, 2) + self.formatBox.addWidget(self.hdePart, 0, 3) + self.formatBox.addWidget(self.swtPart, 0, 4) # Chapter Heading self.lblChapter = QLabel(self._build.getLabel("headings.fmtChapter"), self) @@ -734,56 +734,46 @@ class _HeadingsTab(NScrollablePage): self.layoutMatrix.setVerticalSpacing(vSp) self.layoutMatrix.setHorizontalSpacing(vSp) - # Heading - self.layoutHeading = QLabel("{0}".format(self.tr("Additional Styling")), self) - self.layoutMatrix.addWidget(self.layoutHeading, 0, 0, 1, 5) + self.layoutMatrix.addWidget(QLabel(self.tr("Centre"), self), 0, 1) + self.layoutMatrix.addWidget(QLabel(self.tr("Page Break"), self), 0, 2) # Title Layout - self.mtxTitle = QLabel(self._build.getLabel("headings.fmtTitle"), self) + self.lblTitle = QLabel(self._build.getLabel("headings.styleTitle"), self) self.centerTitle = NSwitch(self, height=iPx) self.breakTitle = NSwitch(self, height=iPx) - lblCenterT = QLabel(self.tr("Centre"), self) - lblCenterT.setIndent(sSp) - lblBreakT = QLabel(self.tr("Page Break"), self) - lblBreakT.setIndent(sSp) - self.layoutMatrix.addWidget(self.mtxTitle, 1, 0) - self.layoutMatrix.addWidget(lblCenterT, 1, 1) - self.layoutMatrix.addWidget(self.centerTitle, 1, 2) - self.layoutMatrix.addWidget(lblBreakT, 1, 3) - self.layoutMatrix.addWidget(self.breakTitle, 1, 4) + self.layoutMatrix.addWidget(self.lblTitle, 1, 0) + self.layoutMatrix.addWidget(self.centerTitle, 1, 1, QtAlignCenter) + self.layoutMatrix.addWidget(self.breakTitle, 1, 2, QtAlignCenter) + + # Partition Layout + self.lblPart = QLabel(self._build.getLabel("headings.stylePart"), self) + self.centerPart = NSwitch(self, height=iPx) + self.breakPart = NSwitch(self, height=iPx) + + self.layoutMatrix.addWidget(self.lblPart, 2, 0) + self.layoutMatrix.addWidget(self.centerPart, 2, 1, QtAlignCenter) + self.layoutMatrix.addWidget(self.breakPart, 2, 2, QtAlignCenter) # Chapter Layout - self.mtxChapter = QLabel(self._build.getLabel("headings.fmtChapter"), self) + self.lblChapter = QLabel(self._build.getLabel("headings.styleChapter"), self) self.centerChapter = NSwitch(self, height=iPx) self.breakChapter = NSwitch(self, height=iPx) - lblCenterC = QLabel(self.tr("Centre"), self) - lblCenterC.setIndent(sSp) - lblBreakC = QLabel(self.tr("Page Break"), self) - lblBreakC.setIndent(sSp) - self.layoutMatrix.addWidget(self.mtxChapter, 2, 0) - self.layoutMatrix.addWidget(lblCenterC, 2, 1) - self.layoutMatrix.addWidget(self.centerChapter, 2, 2) - self.layoutMatrix.addWidget(lblBreakC, 2, 3) - self.layoutMatrix.addWidget(self.breakChapter, 2, 4) + self.layoutMatrix.addWidget(self.lblChapter, 3, 0) + self.layoutMatrix.addWidget(self.centerChapter, 3, 1, QtAlignCenter) + self.layoutMatrix.addWidget(self.breakChapter, 3, 2, QtAlignCenter) # Scene Layout - self.mtxScene = QLabel(self._build.getLabel("headings.fmtScene"), self) + self.lblScene = QLabel(self._build.getLabel("headings.styleScene"), self) self.centerScene = NSwitch(self, height=iPx) self.breakScene = NSwitch(self, height=iPx) - lblCenterS = QLabel(self.tr("Centre"), self) - lblCenterS.setIndent(sSp) - lblBreakS = QLabel(self.tr("Page Break"), self) - lblBreakS.setIndent(sSp) - self.layoutMatrix.addWidget(self.mtxScene, 3, 0) - self.layoutMatrix.addWidget(lblCenterS, 3, 1) - self.layoutMatrix.addWidget(self.centerScene, 3, 2) - self.layoutMatrix.addWidget(lblBreakS, 3, 3) - self.layoutMatrix.addWidget(self.breakScene, 3, 4) + self.layoutMatrix.addWidget(self.lblScene, 4, 0) + self.layoutMatrix.addWidget(self.centerScene, 4, 1, QtAlignCenter) + self.layoutMatrix.addWidget(self.breakScene, 4, 2, QtAlignCenter) - self.layoutMatrix.setColumnStretch(5, 1) + self.layoutMatrix.setColumnStretch(3, 1) # Assemble # ======== @@ -802,14 +792,14 @@ class _HeadingsTab(NScrollablePage): def loadContent(self) -> None: """Populate the widgets.""" - self.fmtTitle.setText(self._build.getStr("headings.fmtTitle")) + self.fmtPart.setText(self._build.getStr("headings.fmtPart")) 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.fmtAScene.setText(self._build.getStr("headings.fmtAltScene")) self.fmtSection.setText(self._build.getStr("headings.fmtSection")) - self.swtTitle.setChecked(self._build.getBool("headings.hideTitle")) + self.swtPart.setChecked(self._build.getBool("headings.hidePart")) self.swtChapter.setChecked(self._build.getBool("headings.hideChapter")) self.swtUnnumbered.setChecked(self._build.getBool("headings.hideUnnumbered")) self.swtScene.setChecked(self._build.getBool("headings.hideScene")) @@ -817,16 +807,19 @@ class _HeadingsTab(NScrollablePage): self.swtSection.setChecked(self._build.getBool("headings.hideSection")) self.centerTitle.setChecked(self._build.getBool("headings.centerTitle")) + self.centerPart.setChecked(self._build.getBool("headings.centerPart")) self.centerChapter.setChecked(self._build.getBool("headings.centerChapter")) self.centerScene.setChecked(self._build.getBool("headings.centerScene")) + self.breakTitle.setChecked(self._build.getBool("headings.breakTitle")) + self.breakPart.setChecked(self._build.getBool("headings.breakPart")) self.breakChapter.setChecked(self._build.getBool("headings.breakChapter")) self.breakScene.setChecked(self._build.getBool("headings.breakScene")) return def saveContent(self) -> None: """Save choices back into build object.""" - self._build.setValue("headings.hideTitle", self.swtTitle.isChecked()) + self._build.setValue("headings.hidePart", self.swtPart.isChecked()) self._build.setValue("headings.hideChapter", self.swtChapter.isChecked()) self._build.setValue("headings.hideUnnumbered", self.swtUnnumbered.isChecked()) self._build.setValue("headings.hideScene", self.swtScene.isChecked()) @@ -834,9 +827,12 @@ class _HeadingsTab(NScrollablePage): self._build.setValue("headings.hideSection", self.swtSection.isChecked()) self._build.setValue("headings.centerTitle", self.centerTitle.isChecked()) + self._build.setValue("headings.centerPart", self.centerPart.isChecked()) self._build.setValue("headings.centerChapter", self.centerChapter.isChecked()) self._build.setValue("headings.centerScene", self.centerScene.isChecked()) + self._build.setValue("headings.breakTitle", self.breakTitle.isChecked()) + self._build.setValue("headings.breakPart", self.breakPart.isChecked()) self._build.setValue("headings.breakChapter", self.breakChapter.isChecked()) self._build.setValue("headings.breakScene", self.breakScene.isChecked()) return @@ -858,8 +854,8 @@ class _HeadingsTab(NScrollablePage): self._editing = heading self.editTextBox.setEnabled(True) if heading == self.EDIT_TITLE: - text = self.fmtTitle.text() - label = self._build.getLabel("headings.fmtTitle") + text = self.fmtPart.text() + label = self._build.getLabel("headings.fmtPart") elif heading == self.EDIT_CHAPTER: text = self.fmtChapter.text() label = self._build.getLabel("headings.fmtChapter") @@ -896,8 +892,8 @@ class _HeadingsTab(NScrollablePage): heading = self._editing text = self.editTextBox.toPlainText().strip().replace("\n", nwHeadFmt.BR) if heading == self.EDIT_TITLE: - self.fmtTitle.setText(text) - self._build.setValue("headings.fmtTitle", text) + self.fmtPart.setText(text) + self._build.setValue("headings.fmtPart", text) elif heading == self.EDIT_CHAPTER: self.fmtChapter.setText(text) self._build.setValue("headings.fmtChapter", text) @@ -1126,9 +1122,7 @@ class _FormattingTab(NScrollableForm): self._sidebar.addButton(title, section) self.addGroupLabel(title, section) - self.odtAddColours = NSwitch(self, height=iPx) - self.addRow(self._build.getLabel("odt.addColours"), self.odtAddColours) - + # Header self.odtPageHeader = QLineEdit(self) self.odtPageHeader.setMinimumWidth(CONFIG.pxInt(200)) self.btnPageHeader = NIconToolButton(self, iSz, "revert") @@ -1145,6 +1139,15 @@ class _FormattingTab(NScrollableForm): self.odtPageCountOffset.setMinimumWidth(spW) self.addRow(self._build.getLabel("odt.pageCountOffset"), self.odtPageCountOffset) + # Headings + self.colorHeadings = NSwitch(self, height=iPx) + self.scaleHeadings = NSwitch(self, height=iPx) + self.boldHeadings = NSwitch(self, height=iPx) + + self.addRow(self._build.getLabel("odt.colorHeadings"), self.colorHeadings) + self.addRow(self._build.getLabel("odt.scaleHeadings"), self.scaleHeadings) + self.addRow(self._build.getLabel("odt.boldHeadings"), self.boldHeadings) + # HTML Document # ============= @@ -1229,7 +1232,9 @@ class _FormattingTab(NScrollableForm): # ODT Document # ============ - self.odtAddColours.setChecked(self._build.getBool("odt.addColours")) + self.colorHeadings.setChecked(self._build.getBool("odt.colorHeadings")) + self.scaleHeadings.setChecked(self._build.getBool("odt.scaleHeadings")) + self.boldHeadings.setChecked(self._build.getBool("odt.boldHeadings")) self.odtPageHeader.setText(self._build.getStr("odt.pageHeader")) self.odtPageCountOffset.setValue(self._build.getInt("odt.pageCountOffset")) self.odtPageHeader.setCursorPosition(0) @@ -1279,7 +1284,9 @@ class _FormattingTab(NScrollableForm): self._build.setValue("format.rightMargin", self.rightMargin.value()) # ODT Document - self._build.setValue("odt.addColours", self.odtAddColours.isChecked()) + self._build.setValue("odt.colorHeadings", self.colorHeadings.isChecked()) + self._build.setValue("odt.scaleHeadings", self.scaleHeadings.isChecked()) + self._build.setValue("odt.boldHeadings", self.boldHeadings.isChecked()) self._build.setValue("odt.pageHeader", self.odtPageHeader.text()) self._build.setValue("odt.pageCountOffset", self.odtPageCountOffset.value()) diff --git a/tests/reference/coreToOdt_SaveFull_styles.xml b/tests/reference/coreToOdt_SaveFull_styles.xml index 9b7b65fb..8130cbae 100644 --- a/tests/reference/coreToOdt_SaveFull_styles.xml +++ b/tests/reference/coreToOdt_SaveFull_styles.xml @@ -1,5 +1,5 @@ - + @@ -25,7 +25,7 @@ - + @@ -37,19 +37,19 @@ - + - + - + - + diff --git a/tests/reference/mBuildDocBuild_Extended_Markdown_Lorem_Ipsum.md b/tests/reference/mBuildDocBuild_Extended_Markdown_Lorem_Ipsum.md index c7c7d743..e18f6b6d 100644 --- a/tests/reference/mBuildDocBuild_Extended_Markdown_Lorem_Ipsum.md +++ b/tests/reference/mBuildDocBuild_Extended_Markdown_Lorem_Ipsum.md @@ -18,7 +18,7 @@ The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for t _Lorem Ipsum_ is simply dummy text[1] of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum. -# Title: Act One +# Part: Act One “Fusce maximus felis libero” diff --git a/tests/reference/mBuildDocBuild_HTML5_Lorem_Ipsum.htm b/tests/reference/mBuildDocBuild_HTML5_Lorem_Ipsum.htm index d5348e24..992581d7 100644 --- a/tests/reference/mBuildDocBuild_HTML5_Lorem_Ipsum.htm +++ b/tests/reference/mBuildDocBuild_HTML5_Lorem_Ipsum.htm @@ -34,7 +34,7 @@ mark {background: rgb(255, 255, 166);}

Prologue

Synopsis: Explanation from the lipsum.com website.

Lorem Ipsum is simply dummy text1 of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.

-

Title: Act One

+

Part: Act One

“Fusce maximus felis libero”

Chapter: Chapter One

Point of View: Bod

diff --git a/tests/reference/mBuildDocBuild_HTML5_Lorem_Ipsum.json b/tests/reference/mBuildDocBuild_HTML5_Lorem_Ipsum.json index ab8b2d8f..d8fcd492 100644 --- a/tests/reference/mBuildDocBuild_HTML5_Lorem_Ipsum.json +++ b/tests/reference/mBuildDocBuild_HTML5_Lorem_Ipsum.json @@ -2,8 +2,8 @@ "meta": { "projectName": "Lorem Ipsum", "novelAuthor": "lipsum.com", - "buildTime": 1718057434, - "buildTimeStr": "2024-06-11 00:10:34" + "buildTime": 1727777646, + "buildTimeStr": "2024-10-01 12:14:06" }, "text": { "css": [ @@ -42,7 +42,7 @@ "

Lorem Ipsum is simply dummy text1 of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.

" ], [ - "

Title: Act One

", + "

Part: Act One

", "

\u201cFusce maximus felis libero\u201d

" ], [ diff --git a/tests/reference/mBuildDocBuild_OpenDocument_Lorem_Ipsum.fodt b/tests/reference/mBuildDocBuild_OpenDocument_Lorem_Ipsum.fodt index b6870a6c..8608fbbc 100644 --- a/tests/reference/mBuildDocBuild_OpenDocument_Lorem_Ipsum.fodt +++ b/tests/reference/mBuildDocBuild_OpenDocument_Lorem_Ipsum.fodt @@ -1,13 +1,13 @@ - 2024-09-30T14:08:16 + 2024-10-01T12:11:50 novelWriter/2.6a0 lipsum.com 45 P0DT0H36M8S Lorem Ipsum - 2024-09-30T14:08:16 + 2024-10-01T12:11:50 lipsum.com @@ -122,12 +122,12 @@ - + - - + + - + Lorem Ipsum By lipsum.com @@ -144,7 +144,7 @@ Lorem ipsum is typically a corrupted version of De finibus bonorum et malorum, a 1st-century BC text by the Roman statesman and philosopher Cicero, with words altered, added, and removed to make it nonsensical and improper Latin. (Source: Wikipedia) of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum. - Title: Act One + Part: Act One “Fusce maximus felis libero” Chapter: Chapter One Point of View: Bod diff --git a/tests/reference/mBuildDocBuild_Standard_Markdown_Lorem_Ipsum.md b/tests/reference/mBuildDocBuild_Standard_Markdown_Lorem_Ipsum.md index c7c7d743..e18f6b6d 100644 --- a/tests/reference/mBuildDocBuild_Standard_Markdown_Lorem_Ipsum.md +++ b/tests/reference/mBuildDocBuild_Standard_Markdown_Lorem_Ipsum.md @@ -18,7 +18,7 @@ The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for t _Lorem Ipsum_ is simply dummy text[1] of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum. -# Title: Act One +# Part: Act One “Fusce maximus felis libero” diff --git a/tests/test_core/test_core_buildsettings.py b/tests/test_core/test_core_buildsettings.py index b1308ea7..72f34274 100644 --- a/tests/test_core/test_core_buildsettings.py +++ b/tests/test_core/test_core_buildsettings.py @@ -29,7 +29,7 @@ from pathlib import Path import pytest from novelwriter import CONFIG -from novelwriter.constants import nwFiles +from novelwriter.constants import nwFiles, nwHeadFmt from novelwriter.core.buildsettings import BuildCollection, BuildSettings, FilterMode from novelwriter.core.project import NWProject from novelwriter.enum import nwBuildFmt, nwItemClass @@ -147,49 +147,48 @@ def testCoreBuildSettings_BuildValues(): """Test BuildSettings get/set of build values.""" build = BuildSettings() - strSetting = "headings.fmtTitle" + strSetting = "headings.fmtPart" intSetting = "odt.pageCountOffset" boolSetting = "filter.includeNovel" floatSetting = "format.lineHeight" # Invalid setting - assert build.setValue("foo", "bar") is False + build.setValue("foo", "bar") + assert build.getStr("foo") == "None" # Value must be correct type - assert build.setValue(strSetting, 15) is False - assert build.setValue(intSetting, 15.0) is False - assert build.setValue(boolSetting, "string") is False - assert build.setValue(floatSetting, 15) is False - - # Check min/max range - assert build.setValue(floatSetting, 200.0) is True - assert build.getFloat(floatSetting) == 3.0 - assert build.setValue(floatSetting, 0.0) is True - assert build.getFloat(floatSetting) == 0.75 + build.setValue(strSetting, 15) + assert build.getStr(strSetting) == nwHeadFmt.TITLE + build.setValue(intSetting, 15.0) + assert build.getInt(intSetting) == 0 + build.setValue(floatSetting, 15) + assert build.getFloat(floatSetting) == 1.15 + build.setValue(boolSetting, "string") + assert build.getBool(boolSetting) is True # Check string values - assert build.setValue(strSetting, "foobar") is True + build.setValue(strSetting, "foobar") assert build.getStr(strSetting) == "foobar" assert build.getInt(strSetting) == 0 assert build.getBool(strSetting) is True assert build.getFloat(strSetting) == 0.0 # Check int values - assert build.setValue(intSetting, 42) is True + build.setValue(intSetting, 42) assert build.getStr(intSetting) == "42" assert build.getInt(intSetting) == 42 assert build.getBool(intSetting) is True assert build.getFloat(intSetting) == 42.0 # Check bool values - assert build.setValue(boolSetting, True) is True + build.setValue(boolSetting, True) assert build.getStr(boolSetting) == "True" assert build.getInt(boolSetting) == 1 assert build.getBool(boolSetting) is True assert build.getFloat(boolSetting) == 1.0 # Check float values - assert build.setValue(floatSetting, 2.5) is True + build.setValue(floatSetting, 2.5) assert build.getStr(floatSetting) == "2.5" assert build.getInt(floatSetting) == 2 assert build.getBool(floatSetting) is True @@ -334,7 +333,7 @@ def testCoreBuildSettings_Filters(mockGUI, fncPath: Path, mockRnd): hCharDoc: (True, FilterMode.INCLUDED), } - # Set everything back to filered + # Set everything back to filtered build.setValue("filter.includeNotes", False) build.setAllowRoot(C.hPlotRoot, False) build.setAllowRoot(C.hCharRoot, True) diff --git a/tests/test_core/test_core_docbuild.py b/tests/test_core/test_core_docbuild.py index 62461dd3..391cc2b6 100644 --- a/tests/test_core/test_core_docbuild.py +++ b/tests/test_core/test_core_docbuild.py @@ -30,10 +30,10 @@ import pytest from novelwriter.core.buildsettings import BuildSettings from novelwriter.core.docbuild import NWBuildDocument from novelwriter.core.project import NWProject -from novelwriter.core.tohtml import ToHtml -from novelwriter.core.tomarkdown import ToMarkdown -from novelwriter.core.toodt import ToOdt from novelwriter.enum import nwBuildFmt +from novelwriter.formats.tohtml import ToHtml +from novelwriter.formats.tomarkdown import ToMarkdown +from novelwriter.formats.toodt import ToOdt from tests.mocked import causeException, causeOSError from tests.tools import ODT_IGNORE, C, buildTestProject, cmpFiles @@ -45,7 +45,7 @@ BUILD_CONF = { "filter.includeNovel": True, "filter.includeNotes": True, "filter.includeInactive": True, - "headings.fmtTitle": "Title: {Title}", + "headings.fmtPart": "Part: {Title}", "headings.fmtChapter": "Chapter: {Title}", "headings.fmtUnnumbered": "{Title}", "headings.fmtScene": "Scene: {Title}", @@ -64,7 +64,7 @@ BUILD_CONF = { "format.stripUnicode": False, "format.replaceTabs": True, "format.firstLineIndent": True, - "odt.addColours": True, + "odt.colorHeadings": True, "html.addStyles": True, }, "content": { @@ -146,7 +146,7 @@ def testCoreDocBuild_OpenDocument(monkeypatch, mockGUI, prjLipsum, fncPath, tstP # ================== with monkeypatch.context() as mp: - mp.setattr("novelwriter.core.toodt.ToOdt.doConvert", causeException) + mp.setattr("novelwriter.formats.toodt.ToOdt.doConvert", causeException) assert len(docBuild) == 21 count = 0 diff --git a/tests/test_core/test_core_tohtml.py b/tests/test_formats/test_fmt_tohtml.py similarity index 97% rename from tests/test_core/test_core_tohtml.py rename to tests/test_formats/test_fmt_tohtml.py index 7974cef4..49e7bf63 100644 --- a/tests/test_core/test_core_tohtml.py +++ b/tests/test_formats/test_fmt_tohtml.py @@ -26,11 +26,11 @@ import pytest from novelwriter import CONFIG from novelwriter.core.project import NWProject -from novelwriter.core.tohtml import ToHtml +from novelwriter.formats.tohtml import ToHtml @pytest.mark.core -def testCoreToHtml_ConvertHeaders(mockGUI): +def testFmtToHtml_ConvertHeaders(mockGUI): """Test header formats in the ToHtml class.""" project = NWProject() html = ToHtml(project) @@ -132,7 +132,7 @@ def testCoreToHtml_ConvertHeaders(mockGUI): @pytest.mark.core -def testCoreToHtml_ConvertParagraphs(mockGUI): +def testFmtToHtml_ConvertParagraphs(mockGUI): """Test paragraph formats in the ToHtml class.""" project = NWProject() html = ToHtml(project) @@ -309,7 +309,7 @@ def testCoreToHtml_ConvertParagraphs(mockGUI): @pytest.mark.core -def testCoreToHtml_CloseTags(mockGUI): +def testFmtToHtml_CloseTags(mockGUI): """Test automatic closing of HTML tags for shortcodes.""" project = NWProject() html = ToHtml(project) @@ -345,7 +345,7 @@ def testCoreToHtml_CloseTags(mockGUI): @pytest.mark.core -def testCoreToHtml_ConvertDirect(mockGUI): +def testFmtToHtml_ConvertDirect(mockGUI): """Test the converter directly using the ToHtml class.""" project = NWProject() html = ToHtml(project) @@ -492,7 +492,7 @@ def testCoreToHtml_ConvertDirect(mockGUI): @pytest.mark.core -def testCoreToHtml_SpecialCases(mockGUI): +def testFmtToHtml_SpecialCases(mockGUI): """Test some special cases that have caused errors in the past.""" project = NWProject() html = ToHtml(project) @@ -564,7 +564,7 @@ def testCoreToHtml_SpecialCases(mockGUI): @pytest.mark.core -def testCoreToHtml_Save(mockGUI, fncPath): +def testFmtToHtml_Save(mockGUI, fncPath): """Test the save method of the ToHtml class.""" project = NWProject() html = ToHtml(project) @@ -644,12 +644,12 @@ def testCoreToHtml_Save(mockGUI, fncPath): ) saveFile = fncPath / "outFile.htm" - html.saveHtml5(saveFile) + html.saveDocument(saveFile, asJson=False) assert saveFile.read_text(encoding="utf-8") == htmlDoc # JSON + HTML saveFile = fncPath / "outFile.json" - html.saveHtmlJson(saveFile) + html.saveDocument(saveFile, asJson=True) data = json.loads(saveFile.read_text(encoding="utf-8")) assert data["meta"]["projectName"] == "" assert data["meta"]["novelAuthor"] == "" @@ -660,7 +660,7 @@ def testCoreToHtml_Save(mockGUI, fncPath): @pytest.mark.core -def testCoreToHtml_Methods(mockGUI): +def testFmtToHtml_Methods(mockGUI): """Test all the other methods of the ToHtml class.""" project = NWProject() html = ToHtml(project) @@ -707,7 +707,7 @@ def testCoreToHtml_Methods(mockGUI): @pytest.mark.core -def testCoreToHtml_Format(mockGUI): +def testFmtToHtml_Format(mockGUI): """Test all the formatters for the ToHtml class.""" project = NWProject() html = ToHtml(project) diff --git a/tests/test_core/test_core_tokenizer.py b/tests/test_formats/test_fmt_tokenizer.py similarity index 96% rename from tests/test_core/test_core_tokenizer.py rename to tests/test_formats/test_fmt_tokenizer.py index 31ced909..33531f02 100644 --- a/tests/test_core/test_core_tokenizer.py +++ b/tests/test_formats/test_fmt_tokenizer.py @@ -29,8 +29,8 @@ from PyQt5.QtGui import QFont from novelwriter import CONFIG from novelwriter.constants import nwHeadFmt from novelwriter.core.project import NWProject -from novelwriter.core.tokenizer import HeadingFormatter, Tokenizer, stripEscape -from novelwriter.core.tomarkdown import ToMarkdown +from novelwriter.formats.tokenizer import HeadingFormatter, Tokenizer, stripEscape +from novelwriter.formats.tomarkdown import ToMarkdown from tests.tools import C, buildTestProject, readFile @@ -39,15 +39,18 @@ class BareTokenizer(Tokenizer): def doConvert(self): super().doConvert() # type: ignore (deliberate check) + def saveDocument(self, path) -> None: + super().saveDocument(path) # type: ignore (deliberate check) + @pytest.mark.core -def testCoreToken_Setters(mockGUI): +def testFmtToken_Setters(mockGUI): """Test all the setters for the Tokenizer class.""" project = NWProject() tokens = BareTokenizer(project) # Verify defaults - assert tokens._fmtTitle == nwHeadFmt.TITLE + assert tokens._fmtPart == nwHeadFmt.TITLE assert tokens._fmtChapter == nwHeadFmt.TITLE assert tokens._fmtUnNum == nwHeadFmt.TITLE assert tokens._fmtScene == nwHeadFmt.TITLE @@ -65,7 +68,7 @@ def testCoreToken_Setters(mockGUI): assert tokens._marginText == (0.000, 0.584) assert tokens._marginMeta == (0.000, 0.584) assert tokens._marginSep == (1.168, 1.168) - assert tokens._hideTitle is False + assert tokens._hidePart is False assert tokens._hideChapter is False assert tokens._hideUnNum is False assert tokens._hideScene is False @@ -78,7 +81,7 @@ def testCoreToken_Setters(mockGUI): assert tokens._doKeywords is False # Set new values - tokens.setTitleFormat(f"T: {nwHeadFmt.TITLE}", True) + tokens.setPartitionFormat(f"T: {nwHeadFmt.TITLE}", True) tokens.setChapterFormat(f"C: {nwHeadFmt.TITLE}", True) tokens.setUnNumberedFormat(f"U: {nwHeadFmt.TITLE}", True) tokens.setSceneFormat(f"S: {nwHeadFmt.TITLE}", True) @@ -103,7 +106,7 @@ def testCoreToken_Setters(mockGUI): tokens.setKeywords(True) # Check new values - assert tokens._fmtTitle == f"T: {nwHeadFmt.TITLE}" + assert tokens._fmtPart == f"T: {nwHeadFmt.TITLE}" assert tokens._fmtChapter == f"C: {nwHeadFmt.TITLE}" assert tokens._fmtUnNum == f"U: {nwHeadFmt.TITLE}" assert tokens._fmtScene == f"S: {nwHeadFmt.TITLE}" @@ -121,7 +124,7 @@ def testCoreToken_Setters(mockGUI): assert tokens._marginText == (2.0, 2.0) assert tokens._marginMeta == (2.0, 2.0) assert tokens._marginSep == (2.0, 2.0) - assert tokens._hideTitle is True + assert tokens._hidePart is True assert tokens._hideChapter is True assert tokens._hideUnNum is True assert tokens._hideScene is True @@ -152,7 +155,7 @@ def testCoreToken_Setters(mockGUI): @pytest.mark.core -def testCoreToken_TextOps(monkeypatch, mockGUI, mockRnd, fncPath): +def testFmtToken_TextOps(monkeypatch, mockGUI, mockRnd, fncPath): """Test handling files and text in the Tokenizer class.""" project = NWProject() mockRnd.reset() @@ -219,12 +222,12 @@ def testCoreToken_TextOps(monkeypatch, mockGUI, mockRnd, fncPath): # Save File savePath = fncPath / "dump.nwd" - tokens.saveRawMarkdown(savePath) + tokens.saveRawDocument(savePath, asJson=False) assert readFile(savePath) == ( "#! Notes: Plot\n\n" "#! Notes: Plot\n\n" ) - tokens.saveRawMarkdownJSON(savePath) + tokens.saveRawDocument(savePath, asJson=True) assert json.loads(readFile(savePath))["text"] == { "nwd": [ ["#! Notes: Plot"], @@ -232,13 +235,16 @@ def testCoreToken_TextOps(monkeypatch, mockGUI, mockRnd, fncPath): ] } - # Check abstract method + # Check abstract methods with pytest.raises(NotImplementedError): tokens.doConvert() + with pytest.raises(NotImplementedError): + tokens.saveDocument(fncPath) + @pytest.mark.core -def testCoreToken_StripEscape(): +def testFmtToken_StripEscape(): """Test the stripEscape helper function.""" text1 = "This is text with escapes: \\** \\~~ \\__" text2 = "This is text with escapes: ** ~~ __" @@ -247,7 +253,7 @@ def testCoreToken_StripEscape(): @pytest.mark.core -def testCoreToken_HeaderFormat(mockGUI): +def testFmtToken_HeaderFormat(mockGUI): """Test the tokenization of header formats in the Tokenizer class.""" project = NWProject() tokens = BareTokenizer(project) @@ -422,7 +428,7 @@ def testCoreToken_HeaderFormat(mockGUI): @pytest.mark.core -def testCoreToken_HeaderStyle(mockGUI): +def testFmtToken_HeaderStyle(mockGUI): """Test the styling of headers in the Tokenizer class.""" project = NWProject() tokens = BareTokenizer(project) @@ -436,11 +442,11 @@ def testCoreToken_HeaderStyle(mockGUI): # No Styles # ========= - tokens.setTitleStyle(False, False) + tokens.setPartitionStyle(False, False) tokens.setChapterStyle(False, False) tokens.setSceneStyle(False, False) - assert tokens._titleStyle == Tokenizer.A_NONE + assert tokens._partStyle == Tokenizer.A_NONE assert tokens._chapterStyle == Tokenizer.A_NONE assert tokens._sceneStyle == Tokenizer.A_NONE @@ -485,11 +491,11 @@ def testCoreToken_HeaderStyle(mockGUI): # Center Headers # ============== - tokens.setTitleStyle(True, False) + tokens.setPartitionStyle(True, False) tokens.setChapterStyle(True, False) tokens.setSceneStyle(True, False) - assert tokens._titleStyle == Tokenizer.A_CENTRE + assert tokens._partStyle == Tokenizer.A_CENTRE assert tokens._chapterStyle == Tokenizer.A_CENTRE assert tokens._sceneStyle == Tokenizer.A_CENTRE @@ -534,11 +540,11 @@ def testCoreToken_HeaderStyle(mockGUI): # Page Break Headers # ================== - tokens.setTitleStyle(False, True) + tokens.setPartitionStyle(False, True) tokens.setChapterStyle(False, True) tokens.setSceneStyle(False, True) - assert tokens._titleStyle == Tokenizer.A_PBB + assert tokens._partStyle == Tokenizer.A_PBB assert tokens._chapterStyle == Tokenizer.A_PBB assert tokens._sceneStyle == Tokenizer.A_PBB @@ -583,11 +589,11 @@ def testCoreToken_HeaderStyle(mockGUI): # Page Break and Centre Headers # ============================= - tokens.setTitleStyle(True, True) + tokens.setPartitionStyle(True, True) tokens.setChapterStyle(True, True) tokens.setSceneStyle(True, True) - assert tokens._titleStyle == Tokenizer.A_CENTRE | Tokenizer.A_PBB + assert tokens._partStyle == Tokenizer.A_CENTRE | Tokenizer.A_PBB assert tokens._chapterStyle == Tokenizer.A_CENTRE | Tokenizer.A_PBB assert tokens._sceneStyle == Tokenizer.A_CENTRE | Tokenizer.A_PBB @@ -634,11 +640,11 @@ def testCoreToken_HeaderStyle(mockGUI): tokens._isNovel = True # Title Styles - tokens.setTitleStyle(True, True) + tokens.setPartitionStyle(True, True) tokens.setChapterStyle(False, False) tokens.setSceneStyle(False, False) - assert tokens._titleStyle == Tokenizer.A_CENTRE | Tokenizer.A_PBB + assert tokens._partStyle == Tokenizer.A_CENTRE | Tokenizer.A_PBB assert tokens._chapterStyle == Tokenizer.A_NONE assert tokens._sceneStyle == Tokenizer.A_NONE @@ -650,11 +656,11 @@ def testCoreToken_HeaderStyle(mockGUI): assert processStyle("##! Prologue\n", False) == Tokenizer.A_NONE # Chapter Styles - tokens.setTitleStyle(False, False) + tokens.setPartitionStyle(False, False) tokens.setChapterStyle(True, True) tokens.setSceneStyle(False, False) - assert tokens._titleStyle == Tokenizer.A_NONE + assert tokens._partStyle == Tokenizer.A_NONE assert tokens._chapterStyle == Tokenizer.A_CENTRE | Tokenizer.A_PBB assert tokens._sceneStyle == Tokenizer.A_NONE @@ -666,11 +672,11 @@ def testCoreToken_HeaderStyle(mockGUI): assert processStyle("##! Prologue\n", False) == Tokenizer.A_CENTRE | Tokenizer.A_PBB # Scene Styles - tokens.setTitleStyle(False, False) + tokens.setPartitionStyle(False, False) tokens.setChapterStyle(False, False) tokens.setSceneStyle(True, True) - assert tokens._titleStyle == Tokenizer.A_NONE + assert tokens._partStyle == Tokenizer.A_NONE assert tokens._chapterStyle == Tokenizer.A_NONE assert tokens._sceneStyle == Tokenizer.A_CENTRE | Tokenizer.A_PBB @@ -683,7 +689,7 @@ def testCoreToken_HeaderStyle(mockGUI): @pytest.mark.core -def testCoreToken_MetaFormat(mockGUI): +def testFmtToken_MetaFormat(mockGUI): """Test the tokenization of meta formats in the Tokenizer class.""" project = NWProject() tokens = BareTokenizer(project) @@ -771,7 +777,7 @@ def testCoreToken_MetaFormat(mockGUI): @pytest.mark.core -def testCoreToken_MarginFormat(mockGUI): +def testFmtToken_MarginFormat(mockGUI): """Test the tokenization of margin formats in the Tokenizer class.""" project = NWProject() tokens = BareTokenizer(project) @@ -814,7 +820,7 @@ def testCoreToken_MarginFormat(mockGUI): @pytest.mark.core -def testCoreToken_ExtractFormats(mockGUI): +def testFmtToken_ExtractFormats(mockGUI): """Test the extraction of formats in the Tokenizer class.""" project = NWProject() tokens = BareTokenizer(project) @@ -921,7 +927,7 @@ def testCoreToken_ExtractFormats(mockGUI): @pytest.mark.core -def testCoreToken_Paragraphs(mockGUI): +def testFmtToken_Paragraphs(mockGUI): """Test the splitting of paragraphs.""" project = NWProject() tokens = BareTokenizer(project) @@ -994,7 +1000,7 @@ def testCoreToken_Paragraphs(mockGUI): @pytest.mark.core -def testCoreToken_TextFormat(mockGUI): +def testFmtToken_TextFormat(mockGUI): """Test the tokenization of text formats in the Tokenizer class.""" project = NWProject() tokens = BareTokenizer(project) @@ -1085,7 +1091,7 @@ def testCoreToken_TextFormat(mockGUI): @pytest.mark.core -def testCoreToken_Dialogue(mockGUI): +def testFmtToken_Dialogue(mockGUI): """Test the tokenization of dialogue in the Tokenizer class.""" CONFIG.fmtDQuoteOpen = "\u201c" CONFIG.fmtDQuoteClose = "\u201d" @@ -1182,7 +1188,7 @@ def testCoreToken_Dialogue(mockGUI): @pytest.mark.core -def testCoreToken_SpecialFormat(mockGUI): +def testFmtToken_SpecialFormat(mockGUI): """Test the tokenization of special formats in the Tokenizer class.""" project = NWProject() tokens = BareTokenizer(project) @@ -1344,7 +1350,7 @@ def testCoreToken_SpecialFormat(mockGUI): @pytest.mark.core -def testCoreToken_TextIndent(mockGUI): +def testFmtToken_TextIndent(mockGUI): """Test the handling of text indent in the Tokenizer class.""" project = NWProject() tokens = BareTokenizer(project) @@ -1427,7 +1433,7 @@ def testCoreToken_TextIndent(mockGUI): @pytest.mark.core -def testCoreToken_ProcessHeaders(mockGUI): +def testFmtToken_ProcessHeaders(mockGUI): """Test the header and page parser of the Tokenizer class.""" project = NWProject() project.data.setLanguage("en") @@ -1441,7 +1447,7 @@ def testCoreToken_ProcessHeaders(mockGUI): # H1: Title, First Page assert tokens._isFirst is True tokens._text = "# Part One\n" - tokens.setTitleFormat(f"T: {nwHeadFmt.TITLE}") + tokens.setPartitionFormat(f"T: {nwHeadFmt.TITLE}") tokens.tokenizeText() assert tokens._tokens == [ (Tokenizer.T_HEAD1, 1, "T: Part One", [], Tokenizer.A_CENTRE), @@ -1450,7 +1456,7 @@ def testCoreToken_ProcessHeaders(mockGUI): # H1: Title, Not First Page assert tokens._isFirst is False tokens._text = "# Part One\n" - tokens.setTitleFormat(f"T: {nwHeadFmt.TITLE}") + tokens.setPartitionFormat(f"T: {nwHeadFmt.TITLE}") tokens.tokenizeText() assert tokens._tokens == [ (Tokenizer.T_HEAD1, 1, "T: Part One", [], Tokenizer.A_PBB | Tokenizer.A_CENTRE), @@ -1616,7 +1622,7 @@ def testCoreToken_ProcessHeaders(mockGUI): @pytest.mark.core -def testCoreToken_BuildOutline(mockGUI, ipsumText): +def testFmtToken_BuildOutline(mockGUI, ipsumText): """Test stats counter of the Tokenizer class.""" project = NWProject() project.data.setLanguage("en") @@ -1680,7 +1686,7 @@ def testCoreToken_BuildOutline(mockGUI, ipsumText): @pytest.mark.core -def testCoreToken_CountStats(mockGUI, ipsumText): +def testFmtToken_CountStats(mockGUI, ipsumText): """Test stats counter of the Tokenizer class.""" project = NWProject() project.data.setLanguage("en") @@ -1858,7 +1864,7 @@ def testCoreToken_CountStats(mockGUI, ipsumText): ) tokens._counts = {} - tokens.setTitleFormat(f"T: {nwHeadFmt.TITLE}") + tokens.setPartitionFormat(f"T: {nwHeadFmt.TITLE}") tokens.setChapterFormat(f"C {nwHeadFmt.CH_NUM}: {nwHeadFmt.TITLE}") tokens.setSceneFormat("* * *", False) tokens.setSynopsis(True) @@ -1876,7 +1882,7 @@ def testCoreToken_CountStats(mockGUI, ipsumText): @pytest.mark.core -def testCoreToken_SceneSeparators(mockGUI): +def testFmtToken_SceneSeparators(mockGUI): """Test the section and scene separators of the Tokenizer class.""" project = NWProject() project.data.setLanguage("en") @@ -1899,7 +1905,7 @@ def testCoreToken_SceneSeparators(mockGUI): "###! Scene Four\n\n" "Text\n\n" ) - md.setTitleFormat(f"T: {nwHeadFmt.TITLE}") + md.setPartitionFormat(f"T: {nwHeadFmt.TITLE}") md.setChapterFormat(f"C: {nwHeadFmt.TITLE}") md.setSectionFormat("", True) @@ -1953,7 +1959,7 @@ def testCoreToken_SceneSeparators(mockGUI): "###! Scene Four\n\n" "Text\n\n" ) - md.setTitleFormat(f"T: {nwHeadFmt.TITLE}") + md.setPartitionFormat(f"T: {nwHeadFmt.TITLE}") md.setChapterFormat(f"C: {nwHeadFmt.TITLE}") md.setSectionFormat("", True) @@ -2027,7 +2033,7 @@ def testCoreToken_SceneSeparators(mockGUI): @pytest.mark.core -def testCoreToken_HeaderVisibility(mockGUI): +def testFmtToken_HeaderVisibility(mockGUI): """Test the heading visibility settings of the Tokenizer class.""" project = NWProject() project.data.setLanguage("en") @@ -2058,7 +2064,7 @@ def testCoreToken_HeaderVisibility(mockGUI): md._isNovel = True # Show All - md.setTitleFormat(nwHeadFmt.TITLE, False) + md.setPartitionFormat(nwHeadFmt.TITLE, False) md.setChapterFormat(nwHeadFmt.TITLE, False) md.setUnNumberedFormat(nwHeadFmt.TITLE, False) md.setSceneFormat(nwHeadFmt.TITLE, False) @@ -2086,7 +2092,7 @@ def testCoreToken_HeaderVisibility(mockGUI): ) # Hide All - md.setTitleFormat(nwHeadFmt.TITLE, True) + md.setPartitionFormat(nwHeadFmt.TITLE, True) md.setChapterFormat(nwHeadFmt.TITLE, True) md.setUnNumberedFormat(nwHeadFmt.TITLE, True) md.setSceneFormat(nwHeadFmt.TITLE, True) @@ -2110,7 +2116,7 @@ def testCoreToken_HeaderVisibility(mockGUI): md._isNovel = False # Hide All - md.setTitleFormat(nwHeadFmt.TITLE, True) + md.setPartitionFormat(nwHeadFmt.TITLE, True) md.setChapterFormat(nwHeadFmt.TITLE, True) md.setUnNumberedFormat(nwHeadFmt.TITLE, True) md.setSceneFormat(nwHeadFmt.TITLE, True) @@ -2139,7 +2145,7 @@ def testCoreToken_HeaderVisibility(mockGUI): @pytest.mark.core -def testCoreToken_CounterHandling(mockGUI): +def testFmtToken_CounterHandling(mockGUI): """Test the heading counter of the Tokenizer class.""" project = NWProject() project.data.setLanguage("en") @@ -2179,7 +2185,7 @@ def testCoreToken_CounterHandling(mockGUI): "###! Scene Four\n\n" "Text\n\n" ) - md.setTitleFormat(f"T: {nwHeadFmt.TITLE}") + md.setPartitionFormat(f"T: {nwHeadFmt.TITLE}") md.setChapterFormat(f"C {nwHeadFmt.CH_NUM}: {nwHeadFmt.TITLE}") md.setUnNumberedFormat(f"U: {nwHeadFmt.TITLE}") md.setSceneFormat( @@ -2224,7 +2230,7 @@ def testCoreToken_CounterHandling(mockGUI): @pytest.mark.core -def testCoreToken_HeadingFormatter(fncPath, mockGUI, mockRnd): +def testFmtToken_HeadingFormatter(fncPath, mockGUI, mockRnd): """Check the HeadingFormatter class.""" project = NWProject() project.setProjectLang("en_GB") diff --git a/tests/test_core/test_core_tomarkdown.py b/tests/test_formats/test_fmt_tomarkdown.py similarity index 96% rename from tests/test_core/test_core_tomarkdown.py rename to tests/test_formats/test_fmt_tomarkdown.py index 8ef04caf..5f446699 100644 --- a/tests/test_core/test_core_tomarkdown.py +++ b/tests/test_formats/test_fmt_tomarkdown.py @@ -23,11 +23,11 @@ from __future__ import annotations import pytest from novelwriter.core.project import NWProject -from novelwriter.core.tomarkdown import ToMarkdown +from novelwriter.formats.tomarkdown import ToMarkdown @pytest.mark.core -def testCoreToMarkdown_ConvertHeaders(mockGUI): +def testFmtToMarkdown_ConvertHeaders(mockGUI): """Test header formats in the ToMarkdown class.""" project = NWProject() toMD = ToMarkdown(project) @@ -73,7 +73,7 @@ def testCoreToMarkdown_ConvertHeaders(mockGUI): @pytest.mark.core -def testCoreToMarkdown_ConvertParagraphs(mockGUI): +def testFmtToMarkdown_ConvertParagraphs(mockGUI): """Test paragraph formats in the ToMarkdown class.""" project = NWProject() toMD = ToMarkdown(project) @@ -209,7 +209,7 @@ def testCoreToMarkdown_ConvertParagraphs(mockGUI): @pytest.mark.core -def testCoreToMarkdown_ConvertDirect(mockGUI): +def testFmtToMarkdown_ConvertDirect(mockGUI): """Test the converter directly using the ToMarkdown class.""" project = NWProject() toMD = ToMarkdown(project) @@ -246,7 +246,7 @@ def testCoreToMarkdown_ConvertDirect(mockGUI): @pytest.mark.core -def testCoreToMarkdown_Save(mockGUI, fncPath): +def testFmtToMarkdown_Save(mockGUI, fncPath): """Test the save method of the ToMarkdown class.""" project = NWProject() toMD = ToMarkdown(project) @@ -293,12 +293,12 @@ def testCoreToMarkdown_Save(mockGUI, fncPath): # ========== saveFile = fncPath / "outFile.md" - toMD.saveMarkdown(saveFile) + toMD.saveDocument(saveFile) assert saveFile.read_text(encoding="utf-8") == "".join(resText) @pytest.mark.core -def testCoreToMarkdown_Format(mockGUI): +def testFmtToMarkdown_Format(mockGUI): """Test all the formatters for the ToMarkdown class.""" project = NWProject() toMD = ToMarkdown(project) diff --git a/tests/test_core/test_core_toodt.py b/tests/test_formats/test_fmt_toodt.py similarity index 98% rename from tests/test_core/test_core_toodt.py rename to tests/test_formats/test_fmt_toodt.py index 77f17a0d..3206b221 100644 --- a/tests/test_core/test_core_toodt.py +++ b/tests/test_formats/test_fmt_toodt.py @@ -30,7 +30,7 @@ import pytest from novelwriter.common import xmlIndent from novelwriter.constants import nwHeadFmt from novelwriter.core.project import NWProject -from novelwriter.core.toodt import ODTParagraphStyle, ODTTextStyle, ToOdt, XMLParagraph, _mkTag +from novelwriter.formats.toodt import ODTParagraphStyle, ODTTextStyle, ToOdt, XMLParagraph, _mkTag from tests.tools import ODT_IGNORE, cmpFiles @@ -54,7 +54,7 @@ def xmlToText(xElem): @pytest.mark.core -def testCoreToOdt_Init(mockGUI): +def testFmtToOdt_Init(mockGUI): """Test initialisation of the ODT document.""" project = NWProject() @@ -106,7 +106,7 @@ def testCoreToOdt_Init(mockGUI): @pytest.mark.core -def testCoreToOdt_TextFormatting(mockGUI): +def testFmtToOdt_TextFormatting(mockGUI): """Test formatting of paragraphs.""" project = NWProject() odt = ToOdt(project, isFlat=True) @@ -235,7 +235,7 @@ def testCoreToOdt_TextFormatting(mockGUI): @pytest.mark.core -def testCoreToOdt_DialogueFormatting(mockGUI): +def testFmtToOdt_DialogueFormatting(mockGUI): """Test formatting of dialogue.""" project = NWProject() odt = ToOdt(project, isFlat=True) @@ -271,7 +271,7 @@ def testCoreToOdt_DialogueFormatting(mockGUI): @pytest.mark.core -def testCoreToOdt_ConvertHeaders(mockGUI): +def testFmtToOdt_ConvertHeaders(mockGUI): """Test the converter of the ToOdt class.""" project = NWProject() odt = ToOdt(project, isFlat=True) @@ -358,7 +358,7 @@ def testCoreToOdt_ConvertHeaders(mockGUI): @pytest.mark.core -def testCoreToOdt_ConvertParagraphs(mockGUI): +def testFmtToOdt_ConvertParagraphs(mockGUI): """Test the converter of the ToOdt class.""" project = NWProject() odt = ToOdt(project, isFlat=True) @@ -706,7 +706,7 @@ def testCoreToOdt_ConvertParagraphs(mockGUI): @pytest.mark.core -def testCoreToOdt_ConvertDirect(mockGUI): +def testFmtToOdt_ConvertDirect(mockGUI): """Test the converter directly using the ToOdt class to reach some otherwise hard to reach conditions. """ @@ -757,7 +757,7 @@ def testCoreToOdt_ConvertDirect(mockGUI): @pytest.mark.core -def testCoreToOdt_SaveFlat(mockGUI, fncPath, tstPaths): +def testFmtToOdt_SaveFlat(mockGUI, fncPath, tstPaths): """Test the document save functions.""" project = NWProject() project.data.setAuthor("Jane Smith") @@ -772,8 +772,6 @@ def testCoreToOdt_SaveFlat(mockGUI, fncPath, tstPaths): assert odt._dLanguage == "" odt.setLanguage("nb_NO") assert odt._dLanguage == "nb" - odt.setColourHeaders(True) - assert odt._colourHead is True odt.setHeaderFormat(nwHeadFmt.ODT_AUTO, 1) assert odt._headerFormat == nwHeadFmt.ODT_AUTO odt.setFirstLineIndent(True, 1.4, False) @@ -804,7 +802,7 @@ def testCoreToOdt_SaveFlat(mockGUI, fncPath, tstPaths): testFile = tstPaths.outDir / "coreToOdt_SaveFlat_document.fodt" compFile = tstPaths.refDir / "coreToOdt_SaveFlat_document.fodt" - odt.saveFlatXML(flatFile) + odt.saveDocument(flatFile) assert flatFile.exists() copyfile(flatFile, testFile) @@ -812,7 +810,7 @@ def testCoreToOdt_SaveFlat(mockGUI, fncPath, tstPaths): @pytest.mark.core -def testCoreToOdt_SaveFull(mockGUI, fncPath, tstPaths): +def testFmtToOdt_SaveFull(mockGUI, fncPath, tstPaths): """Test the document save functions.""" project = NWProject() project.data.setAuthor("Jane Smith") @@ -839,7 +837,7 @@ def testCoreToOdt_SaveFull(mockGUI, fncPath, tstPaths): fullFile = fncPath / "document.odt" - odt.saveOpenDocText(fullFile) + odt.saveDocument(fullFile) assert fullFile.exists() assert zipfile.is_zipfile(fullFile) @@ -890,7 +888,7 @@ def testCoreToOdt_SaveFull(mockGUI, fncPath, tstPaths): @pytest.mark.core -def testCoreToOdt_SpecialFormats(mockGUI): +def testFmtToOdt_SpecialFormats(mockGUI): """Test the special formatters for the ToOdt class.""" project = NWProject() odt = ToOdt(project, isFlat=True) @@ -921,7 +919,7 @@ def testCoreToOdt_SpecialFormats(mockGUI): @pytest.mark.core -def testCoreToOdt_ODTParagraphStyle(): +def testFmtToOdt_ODTParagraphStyle(): """Test the ODTParagraphStyle class.""" parStyle = ODTParagraphStyle("test") @@ -1158,7 +1156,7 @@ def testCoreToOdt_ODTParagraphStyle(): @pytest.mark.core -def testCoreToOdt_ODTTextStyle(): +def testFmtToOdt_ODTTextStyle(): """Test the ODTTextStyle class.""" txtStyle = ODTTextStyle("test") @@ -1308,7 +1306,7 @@ def testCoreToOdt_ODTTextStyle(): @pytest.mark.core -def testCoreToOdt_XMLParagraph(): +def testFmtToOdt_XMLParagraph(): """Test XML encoding of paragraph.""" # Stage 1 : Text # ============== @@ -1497,7 +1495,7 @@ def testCoreToOdt_XMLParagraph(): @pytest.mark.core -def testCoreToOdt_MkTag(): +def testFmtToOdt_MkTag(): """Test the tag maker function.""" assert _mkTag("office", "text") == "{urn:oasis:names:tc:opendocument:xmlns:office:1.0}text" assert _mkTag("style", "text") == "{urn:oasis:names:tc:opendocument:xmlns:style:1.0}text" diff --git a/tests/test_core/test_core_toqdoc.py b/tests/test_formats/test_fmt_toqdoc.py similarity index 97% rename from tests/test_core/test_core_toqdoc.py rename to tests/test_formats/test_fmt_toqdoc.py index b83badbe..d6a65e7b 100644 --- a/tests/test_core/test_core_toqdoc.py +++ b/tests/test_formats/test_fmt_toqdoc.py @@ -27,7 +27,7 @@ from PyQt5.QtGui import QColor, QTextBlock, QTextCharFormat, QTextCursor from novelwriter import CONFIG from novelwriter.constants import nwUnicode from novelwriter.core.project import NWProject -from novelwriter.core.toqdoc import TextDocumentTheme, ToQTextDocument +from novelwriter.formats.toqdoc import TextDocumentTheme, ToQTextDocument from novelwriter.types import ( QtAlignAbsolute, QtAlignCenter, QtAlignJustify, QtAlignLeft, QtAlignRight, QtPageBreakAfter, QtTransparent, QtVAlignNormal, QtVAlignSub, QtVAlignSuper @@ -56,11 +56,12 @@ def charFmtInBlock(block: QTextBlock, pos: int) -> QTextCharFormat: @pytest.mark.core -def testCoreToQTextDocument_ConvertHeaders(mockGUI): +def testFmtToQTextDocument_ConvertHeaders(mockGUI): """Test header formats in the ToQTextDocument class.""" project = NWProject() qdoc = ToQTextDocument(project) qdoc.initDocument(CONFIG.textFont, THEME) + qdoc.saveDocument("") # Doesn't do anything for this format qdoc._isNovel = True qdoc._isFirst = True @@ -132,7 +133,7 @@ def testCoreToQTextDocument_ConvertHeaders(mockGUI): @pytest.mark.core -def testCoreToQTextDocument_SeparatorSkip(mockGUI): +def testFmtToQTextDocument_SeparatorSkip(mockGUI): """Test separator and skip in the ToQTextDocument class.""" project = NWProject() qdoc = ToQTextDocument(project) @@ -198,7 +199,7 @@ def testCoreToQTextDocument_SeparatorSkip(mockGUI): @pytest.mark.core -def testCoreToQTextDocument_NovelMeta(mockGUI): +def testFmtToQTextDocument_NovelMeta(mockGUI): """Test novel meta formats in the ToQTextDocument class.""" project = NWProject() qdoc = ToQTextDocument(project) @@ -275,7 +276,7 @@ def testCoreToQTextDocument_NovelMeta(mockGUI): @pytest.mark.core -def testCoreToQTextDocument_NoteMeta(mockGUI): +def testFmtToQTextDocument_NoteMeta(mockGUI): """Test note meta formats in the ToQTextDocument class.""" project = NWProject() qdoc = ToQTextDocument(project) @@ -332,7 +333,7 @@ def testCoreToQTextDocument_NoteMeta(mockGUI): @pytest.mark.core -def testCoreToQTextDocument_TextBlockFormats(mockGUI): +def testFmtToQTextDocument_TextBlockFormats(mockGUI): """Test text block formats in the ToQTextDocument class.""" project = NWProject() qdoc = ToQTextDocument(project) @@ -441,7 +442,7 @@ def testCoreToQTextDocument_TextBlockFormats(mockGUI): @pytest.mark.core -def testCoreToQTextDocument_TextCharFormats(mockGUI): +def testFmtToQTextDocument_TextCharFormats(mockGUI): """Test text char formats in the ToQTextDocument class.""" CONFIG.fmtDQuoteOpen = nwUnicode.U_LDQUO CONFIG.fmtDQuoteClose = nwUnicode.U_RDQUO @@ -576,7 +577,7 @@ def testCoreToQTextDocument_TextCharFormats(mockGUI): @pytest.mark.core -def testCoreToQTextDocument_Footnotes(mockGUI): +def testFmtToQTextDocument_Footnotes(mockGUI): """Test footnotes in the ToQTextDocument class.""" project = NWProject() qdoc = ToQTextDocument(project) diff --git a/tests/test_gui/test_gui_docviewer.py b/tests/test_gui/test_gui_docviewer.py index f3a39a45..34a559f3 100644 --- a/tests/test_gui/test_gui_docviewer.py +++ b/tests/test_gui/test_gui_docviewer.py @@ -27,8 +27,8 @@ from PyQt5.QtGui import QMouseEvent, QTextCursor from PyQt5.QtWidgets import QAction, QApplication, QMenu from novelwriter import CONFIG, SHARED -from novelwriter.core.toqdoc import ToQTextDocument from novelwriter.enum import nwDocAction +from novelwriter.formats.toqdoc import ToQTextDocument from novelwriter.gui.docviewer import GuiDocViewer from novelwriter.types import QtModNone, QtMouseLeft diff --git a/tests/test_tools/test_tools_manussettings.py b/tests/test_tools/test_tools_manussettings.py index 026b27ee..756a5eb1 100644 --- a/tests/test_tools/test_tools_manussettings.py +++ b/tests/test_tools/test_tools_manussettings.py @@ -311,14 +311,14 @@ def testToolBuildSettings_Headings(qtbot, nwGUI): """Test the Headings Tab of the GuiBuildSettings dialog.""" build = BuildSettings() - ttTitle = f"Title: {nwHeadFmt.TITLE}" + ttTitle = f"Part: {nwHeadFmt.TITLE}" chTitle = f"Chapter: {nwHeadFmt.TITLE}" unTitle = f"Interlude: {nwHeadFmt.TITLE}" scTitle = f"Scene: {nwHeadFmt.TITLE}" shTitle = f"Hard Scene: {nwHeadFmt.TITLE}" sxTitle = f"Section: {nwHeadFmt.TITLE}" - build.setValue("headings.fmtTitle", ttTitle) + build.setValue("headings.fmtPart", ttTitle) build.setValue("headings.fmtChapter", chTitle) build.setValue("headings.fmtUnnumbered", unTitle) build.setValue("headings.fmtScene", scTitle) @@ -337,7 +337,7 @@ def testToolBuildSettings_Headings(qtbot, nwGUI): assert bSettings.toolStack.currentWidget() is headTab # Check initial values - assert headTab.fmtTitle.text() == ttTitle + assert headTab.fmtPart.text() == ttTitle assert headTab.fmtChapter.text() == chTitle assert headTab.fmtUnnumbered.text() == unTitle assert headTab.fmtScene.text() == scTitle @@ -356,7 +356,7 @@ def testToolBuildSettings_Headings(qtbot, nwGUI): assert headTab.editTextBox.isEnabled() is False # Title - headTab.btnTitle.click() + headTab.btnPart.click() assert headTab._editing == headTab.EDIT_TITLE assert headTab.editTextBox.isEnabled() is True assert headTab.editTextBox.toPlainText() == ttTitle @@ -434,10 +434,10 @@ def testToolBuildSettings_Headings(qtbot, nwGUI): ) # Set all to plain title - headTab.btnTitle.click() + headTab.btnPart.click() headTab.editTextBox.setPlainText(nwHeadFmt.TITLE) headTab.btnApply.click() - assert build.getStr("headings.fmtTitle") == nwHeadFmt.TITLE + assert build.getStr("headings.fmtPart") == nwHeadFmt.TITLE headTab.btnChapter.click() headTab.editTextBox.setPlainText(nwHeadFmt.TITLE) @@ -707,9 +707,11 @@ def testToolBuildSettings_FormatOutput(qtbot, nwGUI): """Test the format-specific settings.""" build = BuildSettings() - build.setValue("odt.addColours", False) build.setValue("odt.pageHeader", nwHeadFmt.ODT_AUTO) build.setValue("odt.pageCountOffset", 0) + build.setValue("odt.colorHeadings", True) + build.setValue("odt.scaleHeadings", True) + build.setValue("odt.boldHeadings", True) build.setValue("html.addStyles", False) build.setValue("html.preserveTabs", False) @@ -724,28 +726,34 @@ def testToolBuildSettings_FormatOutput(qtbot, nwGUI): assert bSettings.toolStack.currentWidget() is fmtTab # Check initial values - assert fmtTab.odtAddColours.isChecked() is False assert fmtTab.odtPageHeader.text() == nwHeadFmt.ODT_AUTO assert fmtTab.odtPageCountOffset.value() == 0 + assert fmtTab.colorHeadings.isChecked() is True + assert fmtTab.scaleHeadings.isChecked() is True + assert fmtTab.boldHeadings.isChecked() is True assert fmtTab.htmlAddStyles.isChecked() is False assert fmtTab.htmlPreserveTabs.isChecked() is False - # Toggle all - fmtTab.odtAddColours.setChecked(True) - fmtTab.htmlAddStyles.setChecked(True) - fmtTab.htmlPreserveTabs.setChecked(True) - # Change Values fmtTab.odtPageCountOffset.setValue(1) fmtTab.odtPageHeader.setText("Stuff") + # Toggle all + fmtTab.colorHeadings.setChecked(False) + fmtTab.scaleHeadings.setChecked(False) + fmtTab.boldHeadings.setChecked(False) + fmtTab.htmlAddStyles.setChecked(True) + fmtTab.htmlPreserveTabs.setChecked(True) + # Save values fmtTab.saveContent() - assert build.getBool("odt.addColours") is True assert build.getStr("odt.pageHeader") == "Stuff" assert build.getInt("odt.pageCountOffset") == 1 + assert build.getBool("odt.colorHeadings") is False + assert build.getBool("odt.scaleHeadings") is False + assert build.getBool("odt.boldHeadings") is False assert build.getBool("html.addStyles") is True assert build.getBool("html.preserveTabs") is True