Merge branch 'release'
This commit is contained in:
@@ -279,6 +279,16 @@ def simplified(text: str) -> str:
|
|||||||
return " ".join(str(text).strip().split())
|
return " ".join(str(text).strip().split())
|
||||||
|
|
||||||
|
|
||||||
|
def compact(text: str) -> str:
|
||||||
|
"""Compact a string by removing spaces."""
|
||||||
|
return "".join(str(text).split())
|
||||||
|
|
||||||
|
|
||||||
|
def uniqueCompact(text: str) -> str:
|
||||||
|
"""Return a unique, compact and sorted string."""
|
||||||
|
return "".join(sorted(set(compact(text))))
|
||||||
|
|
||||||
|
|
||||||
def elide(text: str, length: int) -> str:
|
def elide(text: str, length: int) -> str:
|
||||||
"""Elide a piece of text to a maximum length."""
|
"""Elide a piece of text to a maximum length."""
|
||||||
if len(text) > (cut := max(4, length)):
|
if len(text) > (cut := max(4, length)):
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ from PyQt5.QtWidgets import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from novelwriter import CONFIG, SHARED
|
from novelwriter import CONFIG, SHARED
|
||||||
from novelwriter.common import describeFont
|
from novelwriter.common import describeFont, uniqueCompact
|
||||||
from novelwriter.constants import nwUnicode
|
from novelwriter.constants import nwUnicode
|
||||||
from novelwriter.dialogs.quotes import GuiQuoteSelect
|
from novelwriter.dialogs.quotes import GuiQuoteSelect
|
||||||
from novelwriter.extensions.configlayout import NColourLabel, NScrollableForm
|
from novelwriter.extensions.configlayout import NColourLabel, NScrollableForm
|
||||||
@@ -952,8 +952,8 @@ class GuiPreferences(NDialog):
|
|||||||
# Text Highlighting
|
# Text Highlighting
|
||||||
dialogueStyle = self.dialogStyle.currentData()
|
dialogueStyle = self.dialogStyle.currentData()
|
||||||
allowOpenDial = self.allowOpenDial.isChecked()
|
allowOpenDial = self.allowOpenDial.isChecked()
|
||||||
narratorBreak = self.narratorBreak.text()
|
narratorBreak = self.narratorBreak.text().strip()
|
||||||
dialogueLine = self.dialogLine.text()
|
dialogueLine = self.dialogLine.text().strip()
|
||||||
altDialogOpen = self.altDialogOpen.text()
|
altDialogOpen = self.altDialogOpen.text()
|
||||||
altDialogClose = self.altDialogClose.text()
|
altDialogClose = self.altDialogClose.text()
|
||||||
highlightEmph = self.highlightEmph.isChecked()
|
highlightEmph = self.highlightEmph.isChecked()
|
||||||
@@ -983,8 +983,8 @@ class GuiPreferences(NDialog):
|
|||||||
CONFIG.doReplaceDQuote = self.doReplaceDQuote.isChecked()
|
CONFIG.doReplaceDQuote = self.doReplaceDQuote.isChecked()
|
||||||
CONFIG.doReplaceDash = self.doReplaceDash.isChecked()
|
CONFIG.doReplaceDash = self.doReplaceDash.isChecked()
|
||||||
CONFIG.doReplaceDots = self.doReplaceDots.isChecked()
|
CONFIG.doReplaceDots = self.doReplaceDots.isChecked()
|
||||||
CONFIG.fmtPadBefore = self.fmtPadBefore.text().strip()
|
CONFIG.fmtPadBefore = uniqueCompact(self.fmtPadBefore.text())
|
||||||
CONFIG.fmtPadAfter = self.fmtPadAfter.text().strip()
|
CONFIG.fmtPadAfter = uniqueCompact(self.fmtPadAfter.text())
|
||||||
CONFIG.fmtPadThin = self.fmtPadThin.isChecked()
|
CONFIG.fmtPadThin = self.fmtPadThin.isChecked()
|
||||||
|
|
||||||
# Quotation Style
|
# Quotation Style
|
||||||
|
|||||||
+16
-15
@@ -526,8 +526,14 @@ class GuiMain(QMainWindow):
|
|||||||
self.novelView.setActiveHandle(None)
|
self.novelView.setActiveHandle(None)
|
||||||
return
|
return
|
||||||
|
|
||||||
def openDocument(self, tHandle: str | None, tLine: int | None = None,
|
def openDocument(
|
||||||
changeFocus: bool = True, doScroll: bool = False) -> bool:
|
self,
|
||||||
|
tHandle: str | None,
|
||||||
|
tLine: int | None = None,
|
||||||
|
sTitle: str | None = None,
|
||||||
|
changeFocus: bool = True,
|
||||||
|
doScroll: bool = False
|
||||||
|
) -> bool:
|
||||||
"""Open a specific document, optionally at a given line."""
|
"""Open a specific document, optionally at a given line."""
|
||||||
if not SHARED.hasProject:
|
if not SHARED.hasProject:
|
||||||
logger.error("No project open")
|
logger.error("No project open")
|
||||||
@@ -537,9 +543,12 @@ class GuiMain(QMainWindow):
|
|||||||
logger.debug("Requested item '%s' is not a document", tHandle)
|
logger.debug("Requested item '%s' is not a document", tHandle)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
if sTitle and tLine is None:
|
||||||
|
if hItem := SHARED.project.index.getItemHeading(tHandle, sTitle):
|
||||||
|
tLine = hItem.line
|
||||||
|
|
||||||
self._changeView(nwView.EDITOR)
|
self._changeView(nwView.EDITOR)
|
||||||
cHandle = self.docEditor.docHandle
|
if tHandle == self.docEditor.docHandle:
|
||||||
if cHandle == tHandle:
|
|
||||||
self.docEditor.setCursorLine(tLine)
|
self.docEditor.setCursorLine(tLine)
|
||||||
if changeFocus:
|
if changeFocus:
|
||||||
self.docEditor.setFocus()
|
self.docEditor.setFocus()
|
||||||
@@ -711,7 +720,6 @@ class GuiMain(QMainWindow):
|
|||||||
if SHARED.hasProject:
|
if SHARED.hasProject:
|
||||||
tHandle = None
|
tHandle = None
|
||||||
sTitle = None
|
sTitle = None
|
||||||
tLine = None
|
|
||||||
if self.projView.treeHasFocus():
|
if self.projView.treeHasFocus():
|
||||||
tHandle = self.projView.getSelectedHandle()
|
tHandle = self.projView.getSelectedHandle()
|
||||||
elif self.novelView.treeHasFocus():
|
elif self.novelView.treeHasFocus():
|
||||||
@@ -722,11 +730,8 @@ class GuiMain(QMainWindow):
|
|||||||
logger.warning("No item selected")
|
logger.warning("No item selected")
|
||||||
return
|
return
|
||||||
|
|
||||||
if tHandle and sTitle:
|
|
||||||
if hItem := SHARED.project.index.getItemHeading(tHandle, sTitle):
|
|
||||||
tLine = hItem.line
|
|
||||||
if tHandle:
|
if tHandle:
|
||||||
self.openDocument(tHandle, tLine=tLine, changeFocus=False, doScroll=False)
|
self.openDocument(tHandle, sTitle=sTitle, changeFocus=False, doScroll=False)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -1118,7 +1123,7 @@ class GuiMain(QMainWindow):
|
|||||||
tHandle, sTitle = self._getTagSource(tag)
|
tHandle, sTitle = self._getTagSource(tag)
|
||||||
if tHandle is not None:
|
if tHandle is not None:
|
||||||
if mode == nwDocMode.EDIT:
|
if mode == nwDocMode.EDIT:
|
||||||
self.openDocument(tHandle)
|
self.openDocument(tHandle, sTitle=sTitle)
|
||||||
elif mode == nwDocMode.VIEW:
|
elif mode == nwDocMode.VIEW:
|
||||||
self.viewDocument(tHandle=tHandle, sTitle=sTitle)
|
self.viewDocument(tHandle=tHandle, sTitle=sTitle)
|
||||||
return
|
return
|
||||||
@@ -1137,11 +1142,7 @@ class GuiMain(QMainWindow):
|
|||||||
"""Handle an open document request."""
|
"""Handle an open document request."""
|
||||||
if tHandle is not None:
|
if tHandle is not None:
|
||||||
if mode == nwDocMode.EDIT:
|
if mode == nwDocMode.EDIT:
|
||||||
tLine = None
|
self.openDocument(tHandle, sTitle=sTitle, changeFocus=setFocus)
|
||||||
hItem = SHARED.project.index.getItemHeading(tHandle, sTitle)
|
|
||||||
if hItem is not None:
|
|
||||||
tLine = hItem.line
|
|
||||||
self.openDocument(tHandle, tLine=tLine, changeFocus=setFocus)
|
|
||||||
elif mode == nwDocMode.VIEW:
|
elif mode == nwDocMode.VIEW:
|
||||||
self.viewDocument(tHandle=tHandle, sTitle=sTitle)
|
self.viewDocument(tHandle=tHandle, sTitle=sTitle)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -32,12 +32,12 @@ from PyQt5.QtGui import QColor, QDesktopServices, QFontDatabase
|
|||||||
|
|
||||||
from novelwriter.common import (
|
from novelwriter.common import (
|
||||||
NWConfigParser, checkBool, checkFloat, checkInt, checkIntTuple, checkPath,
|
NWConfigParser, checkBool, checkFloat, checkInt, checkIntTuple, checkPath,
|
||||||
checkString, checkStringNone, checkUuid, cssCol, describeFont, elide,
|
checkString, checkStringNone, checkUuid, compact, cssCol, describeFont,
|
||||||
formatFileFilter, formatInt, formatTime, formatTimeStamp, formatVersion,
|
elide, formatFileFilter, formatInt, formatTime, formatTimeStamp,
|
||||||
fuzzyTime, getFileSize, hexToInt, isHandle, isItemClass, isItemLayout,
|
formatVersion, fuzzyTime, getFileSize, hexToInt, isHandle, isItemClass,
|
||||||
isItemType, isListInstance, isTitleTag, jsonEncode, makeFileNameSafe,
|
isItemLayout, isItemType, isListInstance, isTitleTag, jsonEncode,
|
||||||
minmax, numberToRoman, openExternalPath, readTextFile, simplified,
|
makeFileNameSafe, minmax, numberToRoman, openExternalPath, readTextFile,
|
||||||
transferCase, xmlIndent, yesNo
|
simplified, transferCase, uniqueCompact, xmlIndent, yesNo
|
||||||
)
|
)
|
||||||
|
|
||||||
from tests.mocked import causeOSError
|
from tests.mocked import causeOSError
|
||||||
@@ -346,6 +346,27 @@ def testBaseCommon_simplified():
|
|||||||
assert simplified("\tHello\n\r\tWorld") == "Hello World"
|
assert simplified("\tHello\n\r\tWorld") == "Hello World"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.base
|
||||||
|
def testBaseCommon_compact():
|
||||||
|
"""Test the compact function."""
|
||||||
|
assert compact("! ! !") == "!!!"
|
||||||
|
assert compact("1\t2\t3") == "123"
|
||||||
|
assert compact("1\n2\n3") == "123"
|
||||||
|
assert compact("1\r2\r3") == "123"
|
||||||
|
assert compact("1\u00a02\u00a03") == "123"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.base
|
||||||
|
def testBaseCommon_uniqueCompact():
|
||||||
|
"""Test the uniqueCompact function."""
|
||||||
|
assert uniqueCompact("! ! !") == "!"
|
||||||
|
assert uniqueCompact("1\t2\t3") == "123"
|
||||||
|
assert uniqueCompact("1\n2\n3") == "123"
|
||||||
|
assert uniqueCompact("1\r2\r3") == "123"
|
||||||
|
assert uniqueCompact("1\u00a02\u00a03") == "123"
|
||||||
|
assert uniqueCompact("3 2 1") == "123"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.base
|
@pytest.mark.base
|
||||||
def testBaseCommon_elide():
|
def testBaseCommon_elide():
|
||||||
"""Test the elide function."""
|
"""Test the elide function."""
|
||||||
|
|||||||
@@ -379,7 +379,7 @@ def testDlgPreferences_Settings(qtbot, monkeypatch, nwGUI, tstPaths):
|
|||||||
assert CONFIG.doReplaceDQuote is False
|
assert CONFIG.doReplaceDQuote is False
|
||||||
assert CONFIG.doReplaceDash is False
|
assert CONFIG.doReplaceDash is False
|
||||||
assert CONFIG.doReplaceDots is False
|
assert CONFIG.doReplaceDots is False
|
||||||
assert CONFIG.fmtPadBefore == "!?:"
|
assert CONFIG.fmtPadBefore == "!:?"
|
||||||
assert CONFIG.fmtPadAfter == "¡¿"
|
assert CONFIG.fmtPadAfter == "¡¿"
|
||||||
assert CONFIG.fmtPadThin is True
|
assert CONFIG.fmtPadThin is True
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ from novelwriter.gui.outline import GuiOutlineView
|
|||||||
from novelwriter.gui.projtree import GuiProjectTree
|
from novelwriter.gui.projtree import GuiProjectTree
|
||||||
from novelwriter.tools.welcome import GuiWelcome
|
from novelwriter.tools.welcome import GuiWelcome
|
||||||
|
|
||||||
|
from tests.mocked import causeOSError
|
||||||
from tests.tools import NWD_IGNORE, XML_IGNORE, C, buildTestProject, cmpFiles
|
from tests.tools import NWD_IGNORE, XML_IGNORE, C, buildTestProject, cmpFiles
|
||||||
|
|
||||||
KEY_DELAY = 1
|
KEY_DELAY = 1
|
||||||
@@ -651,7 +652,7 @@ def testGuiMain_Viewing(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
def testGuiMain_Features(qtbot, nwGUI, projPath, mockRnd):
|
def testGuiMain_Features(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
|
||||||
"""Test various features of the main window."""
|
"""Test various features of the main window."""
|
||||||
buildTestProject(nwGUI, projPath)
|
buildTestProject(nwGUI, projPath)
|
||||||
assert SHARED.focusMode is False
|
assert SHARED.focusMode is False
|
||||||
@@ -672,6 +673,7 @@ def testGuiMain_Features(qtbot, nwGUI, projPath, mockRnd):
|
|||||||
|
|
||||||
# Enable focus mode
|
# Enable focus mode
|
||||||
nwGUI.toggleFocusMode()
|
nwGUI.toggleFocusMode()
|
||||||
|
assert SHARED.focusMode is True
|
||||||
assert nwGUI.treePane.isVisible() is False
|
assert nwGUI.treePane.isVisible() is False
|
||||||
assert nwGUI.mainStatus.isVisible() is False
|
assert nwGUI.mainStatus.isVisible() is False
|
||||||
assert nwGUI.mainMenu.isVisible() is False
|
assert nwGUI.mainMenu.isVisible() is False
|
||||||
@@ -680,12 +682,19 @@ def testGuiMain_Features(qtbot, nwGUI, projPath, mockRnd):
|
|||||||
|
|
||||||
# Disable focus mode
|
# Disable focus mode
|
||||||
nwGUI.toggleFocusMode()
|
nwGUI.toggleFocusMode()
|
||||||
|
assert SHARED.focusMode is False
|
||||||
assert nwGUI.treePane.isVisible() is True
|
assert nwGUI.treePane.isVisible() is True
|
||||||
assert nwGUI.mainStatus.isVisible() is True
|
assert nwGUI.mainStatus.isVisible() is True
|
||||||
assert nwGUI.mainMenu.isVisible() is True
|
assert nwGUI.mainMenu.isVisible() is True
|
||||||
assert nwGUI.sideBar.isVisible() is True
|
assert nwGUI.sideBar.isVisible() is True
|
||||||
assert nwGUI.splitView.isVisible() is True
|
assert nwGUI.splitView.isVisible() is True
|
||||||
|
|
||||||
|
# Closing editor disables focus mode
|
||||||
|
nwGUI.toggleFocusMode()
|
||||||
|
assert SHARED.focusMode is True
|
||||||
|
nwGUI.closeDocument()
|
||||||
|
assert SHARED.focusMode is False
|
||||||
|
|
||||||
# Full Screen Mode
|
# Full Screen Mode
|
||||||
# ================
|
# ================
|
||||||
|
|
||||||
@@ -702,6 +711,17 @@ def testGuiMain_Features(qtbot, nwGUI, projPath, mockRnd):
|
|||||||
nwGUI.sideBar.mSettings.show()
|
nwGUI.sideBar.mSettings.show()
|
||||||
nwGUI.sideBar.mSettings.hide()
|
nwGUI.sideBar.mSettings.hide()
|
||||||
|
|
||||||
|
# Document Open Errors
|
||||||
|
# ====================
|
||||||
|
|
||||||
|
# Cannot edit a folder
|
||||||
|
assert nwGUI.openDocument(C.hChapterDir) is False
|
||||||
|
|
||||||
|
# Handle I/O error
|
||||||
|
with monkeypatch.context() as mp:
|
||||||
|
mp.setattr("builtins.open", causeOSError)
|
||||||
|
assert nwGUI.openDocument(C.hChapterDoc) is False
|
||||||
|
|
||||||
# qtbot.stop()
|
# qtbot.stop()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user