From c29e9bb68d0a5909f3f1f4a322413f294594095f Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Mon, 25 Jan 2021 11:28:18 +0100 Subject: [PATCH 1/4] Project Details Content tree should not have selectable rows --- nw/gui/projdetails.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nw/gui/projdetails.py b/nw/gui/projdetails.py index 7b46b39d..496f0208 100644 --- a/nw/gui/projdetails.py +++ b/nw/gui/projdetails.py @@ -33,7 +33,7 @@ from PyQt5.QtCore import Qt, QSize from PyQt5.QtGui import QFont from PyQt5.QtWidgets import ( QWidget, QDialogButtonBox, QVBoxLayout, QTreeWidget, QTreeWidgetItem, - QLabel, QSpinBox, QGridLayout, QHBoxLayout, QLineEdit + QLabel, QSpinBox, QGridLayout, QHBoxLayout, QLineEdit, QAbstractItemView ) from nw.gui.custom import PagedDialog, QSwitch @@ -271,6 +271,7 @@ class GuiProjectDetailsContents(QWidget): self.tocTree.setIconSize(QSize(iPx, iPx)) self.tocTree.setIndentation(0) self.tocTree.setColumnCount(6) + self.tocTree.setSelectionMode(QAbstractItemView.NoSelection) self.tocTree.setHeaderLabels(["Title", "Words", "Pages", "Page", "Progress", ""]) treeHeadItem = self.tocTree.headerItem() From a420c1749a4839391006b5d19d480611e4f2a2e6 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Mon, 25 Jan 2021 17:09:39 +0100 Subject: [PATCH 2/4] Add more insert menu entries --- nw/constants/constants.py | 10 ++++++ nw/constants/enum.py | 6 ++++ nw/gui/doceditor.py | 16 ++++++++-- nw/gui/mainmenu.py | 66 +++++++++++++++++++++++++++++++++------ nw/guimain.py | 8 ++++- 5 files changed, 94 insertions(+), 12 deletions(-) diff --git a/nw/constants/constants.py b/nw/constants/constants.py index 36271e73..87ff02b7 100644 --- a/nw/constants/constants.py +++ b/nw/constants/constants.py @@ -268,10 +268,13 @@ class nwUnicode: U_RECQUO = "\u300f" # Right white corner bracket ## Punctuation + U_FGDASH = "\u2012" # Figure dash U_ENDASH = "\u2013" # Short dash U_EMDASH = "\u2014" # Long dash U_HELLIP = "\u2026" # Ellipsis U_MAPOSS = "\u02bc" # Modifier letter single apostrophe + U_PRIME = "\u2032" # Prime + U_DPRIME = "\u2033" # Double prime ## Spaces and Lines U_NBSP = "\u00a0" # Non-breaking space @@ -286,6 +289,8 @@ class nwUnicode: U_CHECK = "\u2714" # Heavy check mark U_MULT = "\u2715" # Multiplication x U_BULL = "\u2022" # List bullet + U_FLOWER = "\u2055" # Flower punctuation mark + U_PERMIL = "\u2030" # Per mille sign ## Arrows U_UTRI = "\u25b2" # Up-pointing triangle @@ -322,10 +327,13 @@ class nwUnicode: H_LWCQUO = "『" ## Punctuation + H_FGDASH = "‒" H_ENDASH = "–" H_EMDASH = "—" H_HELLIP = "…" H_MAPOSS = "ʼ" + H_PRIME = "′" + H_DPRIME = "″" ## Spaces H_NBSP = " " @@ -338,6 +346,8 @@ class nwUnicode: H_CHECK = "✔" H_MULT = "✕" H_BULL = "•" + H_FLOWER = "⁕" + H_PERMIL = "‰" ## Arrows H_UTRI = "▲" diff --git a/nw/constants/enum.py b/nw/constants/enum.py index 072eec6c..6ed97d39 100644 --- a/nw/constants/enum.py +++ b/nw/constants/enum.py @@ -112,6 +112,12 @@ class nwDocInsert(Enum): QUOTE_LD = 10 QUOTE_RD = 11 MODAPOS_S = 12 + FIGURE_DASH = 13 + LIST_BULLET = 14 + PER_MILLE = 15 + SINGLE_PRIME = 16 + DOUBLE_PRIME = 17 + FLOWER_MARK = 18 # END Enum nwDocInsert diff --git a/nw/gui/doceditor.py b/nw/gui/doceditor.py index 273fd5cd..784ea5d1 100644 --- a/nw/gui/doceditor.py +++ b/nw/gui/doceditor.py @@ -744,10 +744,16 @@ class GuiDocEditor(QTextEdit): theText = nwUnicode.U_ENDASH elif theInsert == nwDocInsert.LONG_DASH: theText = nwUnicode.U_EMDASH - elif theInsert == nwDocInsert.ELLIPSIS: - theText = nwUnicode.U_HELLIP + elif theInsert == nwDocInsert.FIGURE_DASH: + theText = nwUnicode.U_FGDASH elif theInsert == nwDocInsert.MODAPOS_S: theText = nwUnicode.U_MAPOSS + elif theInsert == nwDocInsert.ELLIPSIS: + theText = nwUnicode.U_HELLIP + elif theInsert == nwDocInsert.SINGLE_PRIME: + theText = nwUnicode.U_PRIME + elif theInsert == nwDocInsert.DOUBLE_PRIME: + theText = nwUnicode.U_DPRIME elif theInsert == nwDocInsert.QUOTE_LS: theText = self.typSQOpen elif theInsert == nwDocInsert.QUOTE_RS: @@ -756,6 +762,12 @@ class GuiDocEditor(QTextEdit): theText = self.typDQOpen elif theInsert == nwDocInsert.QUOTE_RD: theText = self.typDQClose + elif theInsert == nwDocInsert.LIST_BULLET: + theText = nwUnicode.U_BULL + elif theInsert == nwDocInsert.FLOWER_MARK: + theText = nwUnicode.U_FLOWER + elif theInsert == nwDocInsert.PER_MILLE: + theText = nwUnicode.U_PERMIL else: return False else: diff --git a/nw/gui/mainmenu.py b/nw/gui/mainmenu.py index 3a0868be..8ea2ae2a 100644 --- a/nw/gui/mainmenu.py +++ b/nw/gui/mainmenu.py @@ -511,8 +511,8 @@ class GuiMainMenu(QMenuBar): # Insert self.insertMenu = self.addMenu("&Insert") - # Insert > Dashes and Dots - self.mInsDashes = self.insertMenu.addMenu("Dashes and Dots") + # Insert > Dashes + self.mInsDashes = self.insertMenu.addMenu("Dashes") # Insert > Short Dash self.aInsENDash = QAction("Short Dash", self) @@ -528,12 +528,12 @@ class GuiMainMenu(QMenuBar): self.aInsEMDash.triggered.connect(lambda: self._docInsert(nwDocInsert.LONG_DASH)) self.mInsDashes.addAction(self.aInsEMDash) - # Insert > Ellipsis - self.aInsEllipsis = QAction("Ellipsis", self) - self.aInsEllipsis.setStatusTip("Insert ellipsis") - self.aInsEllipsis.setShortcut("Ctrl+K, .") - self.aInsEllipsis.triggered.connect(lambda: self._docInsert(nwDocInsert.ELLIPSIS)) - self.mInsDashes.addAction(self.aInsEllipsis) + # Insert > Figure Dash + self.aInsFigDash = QAction("Figure Dash", self) + self.aInsFigDash.setStatusTip("Insert figure dash (same width as a number character)") + self.aInsFigDash.setShortcut("Ctrl+K, ~") + self.aInsFigDash.triggered.connect(lambda: self._docInsert(nwDocInsert.FIGURE_DASH)) + self.mInsDashes.addAction(self.aInsFigDash) # Insert > Quote Marks self.mInsQuotes = self.insertMenu.addMenu("Quote Marks") @@ -568,11 +568,35 @@ class GuiMainMenu(QMenuBar): # Insert > Alternative Apostrophe self.aInsMSApos = QAction("Alternative Apostrophe", self) - self.aInsMSApos.setStatusTip("Insert unicode modifier letter single apostrophe") + self.aInsMSApos.setStatusTip("Insert modifier letter single apostrophe") self.aInsMSApos.setShortcut("Ctrl+K, '") self.aInsMSApos.triggered.connect(lambda: self._docInsert(nwDocInsert.MODAPOS_S)) self.mInsQuotes.addAction(self.aInsMSApos) + # Insert > Symbols + self.mInsPunct = self.insertMenu.addMenu("General Punctuation") + + # Insert > Ellipsis + self.aInsEllipsis = QAction("Ellipsis", self) + self.aInsEllipsis.setStatusTip("Insert ellipsis") + self.aInsEllipsis.setShortcut("Ctrl+K, .") + self.aInsEllipsis.triggered.connect(lambda: self._docInsert(nwDocInsert.ELLIPSIS)) + self.mInsPunct.addAction(self.aInsEllipsis) + + # Insert > Prime + self.aInsPrime = QAction("Prime", self) + self.aInsPrime.setStatusTip("Insert a prime symbol") + self.aInsPrime.setShortcut("Ctrl+K, Ctrl+'") + self.aInsPrime.triggered.connect(lambda: self._docInsert(nwDocInsert.SINGLE_PRIME)) + self.mInsPunct.addAction(self.aInsPrime) + + # Insert > Double Prime + self.aInsDPrime = QAction("Double Prime", self) + self.aInsDPrime.setStatusTip("Insert a double prime symbol") + self.aInsDPrime.setShortcut("Ctrl+K, Ctrl+\"") + self.aInsDPrime.triggered.connect(lambda: self._docInsert(nwDocInsert.DOUBLE_PRIME)) + self.mInsPunct.addAction(self.aInsDPrime) + # Insert > Breaks and Spaces self.mInsBreaks = self.insertMenu.addMenu("Breaks and Spaces") @@ -604,6 +628,30 @@ class GuiMainMenu(QMenuBar): self.aInsThinNBSpace.triggered.connect(lambda: self._docInsert(nwDocInsert.THIN_NB_SPACE)) self.mInsBreaks.addAction(self.aInsThinNBSpace) + # Insert > Symbols + self.mInsSymbol = self.insertMenu.addMenu("Other Symbols") + + # Insert > List Bullet + self.aInsBullet = QAction("List Bullet", self) + self.aInsBullet.setStatusTip("Insert a list bullet") + self.aInsBullet.setShortcut("Ctrl+K, *") + self.aInsBullet.triggered.connect(lambda: self._docInsert(nwDocInsert.LIST_BULLET)) + self.mInsSymbol.addAction(self.aInsBullet) + + # Insert > Flower Mark + self.aInsFlower = QAction("Flower Mark", self) + self.aInsFlower.setStatusTip("Insert a flower mark (alternative bullet)") + self.aInsFlower.setShortcut("Ctrl+K, Ctrl+*") + self.aInsFlower.triggered.connect(lambda: self._docInsert(nwDocInsert.FLOWER_MARK)) + self.mInsSymbol.addAction(self.aInsFlower) + + # Insert > Per Mille + self.aInsPerMille = QAction("Per Mille", self) + self.aInsPerMille.setStatusTip("Insert a per mille symbol") + self.aInsPerMille.setShortcut("Ctrl+K, %") + self.aInsPerMille.triggered.connect(lambda: self._docInsert(nwDocInsert.PER_MILLE)) + self.mInsSymbol.addAction(self.aInsPerMille) + # Insert > Separator self.insertMenu.addSeparator() diff --git a/nw/guimain.py b/nw/guimain.py index 64e0a2e0..1b0c7005 100644 --- a/nw/guimain.py +++ b/nw/guimain.py @@ -1240,16 +1240,22 @@ class GuiMain(QMainWindow): # Insert self.addAction(self.mainMenu.aInsENDash) self.addAction(self.mainMenu.aInsEMDash) - self.addAction(self.mainMenu.aInsEllipsis) + self.addAction(self.mainMenu.aInsFigDash) self.addAction(self.mainMenu.aInsQuoteLS) self.addAction(self.mainMenu.aInsQuoteRS) self.addAction(self.mainMenu.aInsQuoteLD) self.addAction(self.mainMenu.aInsQuoteRD) self.addAction(self.mainMenu.aInsMSApos) + self.addAction(self.mainMenu.aInsEllipsis) + self.addAction(self.mainMenu.aInsPrime) + self.addAction(self.mainMenu.aInsDPrime) self.addAction(self.mainMenu.aInsHardBreak) self.addAction(self.mainMenu.aInsNBSpace) self.addAction(self.mainMenu.aInsThinSpace) self.addAction(self.mainMenu.aInsThinNBSpace) + self.addAction(self.mainMenu.aInsBullet) + self.addAction(self.mainMenu.aInsFlower) + self.addAction(self.mainMenu.aInsPerMille) for mAction, _ in self.mainMenu.mInsKWItems.values(): self.addAction(mAction) From 907a371bd4b9ed0ee06fd40936a33b1e70506b1d Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Mon, 25 Jan 2021 17:11:05 +0100 Subject: [PATCH 3/4] Some minor improvements to the logging system --- nw/__init__.py | 33 +++++++++++++++---------------- nw/config.py | 3 --- nw/gui/build.py | 7 +------ tests/test_base/test_base_init.py | 5 ----- 4 files changed, 17 insertions(+), 31 deletions(-) diff --git a/nw/__init__.py b/nw/__init__.py index dd8d2579..a513224d 100644 --- a/nw/__init__.py +++ b/nw/__init__.py @@ -157,13 +157,13 @@ def main(sysArgs=None): ) # Defaults - debugLevel = logging.WARN - logFormat = "{levelname:8} {message:}" - confPath = None - dataPath = None - testMode = False - qtStyle = "Fusion" - cmdOpen = None + logLevel = logging.WARN + logFormat = "{levelname:8} {message:}" + confPath = None + dataPath = None + testMode = False + qtStyle = "Fusion" + cmdOpen = None # Parse Options try: @@ -186,12 +186,12 @@ def main(sysArgs=None): ) sys.exit(0) elif inOpt == "--info": - debugLevel = logging.INFO + logLevel = logging.INFO elif inOpt == "--debug": - debugLevel = logging.DEBUG + logLevel = logging.DEBUG logFormat = "[{asctime:}] {name:>22}:{lineno:<4d} {levelname:8} {message:}" elif inOpt == "--verbose": - debugLevel = VERBOSE + logLevel = VERBOSE logFormat = "[{asctime:}] {name:>22}:{lineno:<4d} {levelname:8} {message:}" elif inOpt == "--style": qtStyle = inArg @@ -203,17 +203,16 @@ def main(sysArgs=None): testMode = True # Set Config Options - CONFIG.debugInfo = debugLevel < logging.INFO - CONFIG.cmdOpen = cmdOpen + CONFIG.cmdOpen = cmdOpen # Set Logging - logFmt = logging.Formatter(fmt=logFormat, style="{") cHandle = logging.StreamHandler() - cHandle.setLevel(debugLevel) - cHandle.setFormatter(logFmt) - logger.addHandler(cHandle) + cHandle.setFormatter(logging.Formatter(fmt=logFormat, style="{")) + + pkgLogger = logging.getLogger(__package__) + pkgLogger.addHandler(cHandle) + pkgLogger.setLevel(logLevel) - logger.setLevel(debugLevel) logger.info("Starting novelWriter %s (%s) %s" % ( __version__, __hexversion__, __date__ )) diff --git a/nw/config.py b/nw/config.py index 22fc6844..d35582fb 100644 --- a/nw/config.py +++ b/nw/config.py @@ -55,9 +55,6 @@ class Config: self.appName = "novelWriter" self.appHandle = self.appName.lower() - # Debug Settings - self.debugInfo = False # True if log level is DEBUG or VERBOSE - # Config Error Handling self.hasError = False # True if the config class encountered an error self.errData = [] # List of error messages diff --git a/nw/gui/build.py b/nw/gui/build.py index c12be8b9..c24edb3e 100644 --- a/nw/gui/build.py +++ b/nw/gui/build.py @@ -968,11 +968,6 @@ class GuiBuildNovel(QDialog): """ buildCache = os.path.join(self.theProject.projCache, nwFiles.BUILD_CACHE) - if self.mainConf.debugInfo: - nIndent = 2 - else: - nIndent = None - logger.debug("Saving build cache") try: with open(buildCache, mode="w+", encoding="utf8") as outFile: @@ -981,7 +976,7 @@ class GuiBuildNovel(QDialog): "htmlStyle" : self.htmlStyle, "nwdText" : self.nwdText, "buildTime" : self.buildTime, - }, indent=nIndent)) + }, indent=2)) except Exception as e: logger.error("Failed to save build cache") logger.error(str(e)) diff --git a/tests/test_base/test_base_init.py b/tests/test_base/test_base_init.py index 9f53df36..923cfd4e 100644 --- a/tests/test_base/test_base_init.py +++ b/tests/test_base/test_base_init.py @@ -78,7 +78,6 @@ def testBaseInit_Options(monkeypatch, tmpDir): # Defaults w/None Args nwGUI = nw.main() assert nw.logger.getEffectiveLevel() == logging.WARNING - assert nw.CONFIG.debugInfo is False assert nwGUI.closeMain() == "closeMain" # Defaults @@ -86,7 +85,6 @@ def testBaseInit_Options(monkeypatch, tmpDir): ["--testmode", "--config=%s" % tmpDir, "--data=%s" % tmpDir, "--style=Fusion"] ) assert nw.logger.getEffectiveLevel() == logging.WARNING - assert nw.CONFIG.debugInfo is False assert nwGUI.closeMain() == "closeMain" # Log Levels @@ -94,21 +92,18 @@ def testBaseInit_Options(monkeypatch, tmpDir): ["--testmode", "--info", "--config=%s" % tmpDir, "--data=%s" % tmpDir] ) assert nw.logger.getEffectiveLevel() == logging.INFO - assert nw.CONFIG.debugInfo is False assert nwGUI.closeMain() == "closeMain" nwGUI = nw.main( ["--testmode", "--debug", "--config=%s" % tmpDir, "--data=%s" % tmpDir] ) assert nw.logger.getEffectiveLevel() == logging.DEBUG - assert nw.CONFIG.debugInfo is True assert nwGUI.closeMain() == "closeMain" nwGUI = nw.main( ["--testmode", "--verbose", "--config=%s" % tmpDir, "--data=%s" % tmpDir] ) assert nw.logger.getEffectiveLevel() == 5 - assert nw.CONFIG.debugInfo is True assert nwGUI.closeMain() == "closeMain" # Help and Version From 14584bef55b45dcf87a899ec18371e1059d89972 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Mon, 25 Jan 2021 17:15:12 +0100 Subject: [PATCH 4/4] Update docs and tests --- docs/source/int_interface.rst | 8 +++++++- tests/test_gui/test_gui_mainmenu.py | 28 ++++++++++++++++++++++++++-- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/docs/source/int_interface.rst b/docs/source/int_interface.rst index d8f91047..ceabf1e2 100644 --- a/docs/source/int_interface.rst +++ b/docs/source/int_interface.rst @@ -429,16 +429,22 @@ a key or key combination for the inserted content. ":kbd:`Ctrl`:kbd:`K`, :kbd:`-`", "Insert a short dash (en dash)." ":kbd:`Ctrl`:kbd:`K`, :kbd:`_`", "Insert a long dash (em dash)." - ":kbd:`Ctrl`:kbd:`K`, :kbd:`.`", "Insert an ellipsis." + ":kbd:`Ctrl`:kbd:`K`, :kbd:`~`", "Insert a figure dash (same width as a number)." ":kbd:`Ctrl`:kbd:`K`, :kbd:`1`", "Insert a left single quote." ":kbd:`Ctrl`:kbd:`K`, :kbd:`2`", "Insert a right single quote." ":kbd:`Ctrl`:kbd:`K`, :kbd:`3`", "Insert a left double quote." ":kbd:`Ctrl`:kbd:`K`, :kbd:`4`", "Insert a right double quote." ":kbd:`Ctrl`:kbd:`K`, :kbd:`'`", "Insert a modifier apostrophe." + ":kbd:`Ctrl`:kbd:`K`, :kbd:`.`", "Insert an ellipsis." + ":kbd:`Ctrl`:kbd:`K`, :kbd:`Ctrl`:kbd:`'`", "Insert a prime." + ":kbd:`Ctrl`:kbd:`K`, :kbd:`Ctrl`:kbd:`""`", "Insert a double prime." ":kbd:`Ctrl`:kbd:`K`, :kbd:`Return`", "Insert a hard line break." ":kbd:`Ctrl`:kbd:`K`, :kbd:`Space`", "Insert a non-breaking space." ":kbd:`Ctrl`:kbd:`K`, :kbd:`Shift`:kbd:`Space`", "Insert a thin space." ":kbd:`Ctrl`:kbd:`K`, :kbd:`Ctrl`:kbd:`Space`", "Insert a thin non-breaking space." + ":kbd:`Ctrl`:kbd:`K`, :kbd:`*`", "Insert a list bullet." + ":kbd:`Ctrl`:kbd:`K`, :kbd:`Ctrl`:kbd:`*`", "Insert a flower mark (alternative bullet)." + ":kbd:`Ctrl`:kbd:`K`, :kbd:`%`", "Insert a per mille symbol." ":kbd:`Ctrl`:kbd:`K`, :kbd:`G`", "Insert a ``@tag`` keyword." ":kbd:`Ctrl`:kbd:`K`, :kbd:`V`", "Insert a ``@pov`` keyword." ":kbd:`Ctrl`:kbd:`K`, :kbd:`C`", "Insert a ``@char`` keyword." diff --git a/tests/test_gui/test_gui_mainmenu.py b/tests/test_gui/test_gui_mainmenu.py index 325e5add..7d0bdb6b 100644 --- a/tests/test_gui/test_gui_mainmenu.py +++ b/tests/test_gui/test_gui_mainmenu.py @@ -392,8 +392,8 @@ def testGuiMenu_Insert(qtbot, monkeypatch, nwGUI, fncDir, fncProj): assert nwGUI.docEditor.getText() == nwUnicode.U_EMDASH nwGUI.docEditor.clear() - nwGUI.mainMenu.aInsEllipsis.activate(QAction.Trigger) - assert nwGUI.docEditor.getText() == nwUnicode.U_HELLIP + nwGUI.mainMenu.aInsFigDash.activate(QAction.Trigger) + assert nwGUI.docEditor.getText() == nwUnicode.U_FGDASH nwGUI.docEditor.clear() nwGUI.mainMenu.aInsQuoteLS.activate(QAction.Trigger) @@ -416,6 +416,30 @@ def testGuiMenu_Insert(qtbot, monkeypatch, nwGUI, fncDir, fncProj): assert nwGUI.docEditor.getText() == nwUnicode.U_MAPOSS nwGUI.docEditor.clear() + nwGUI.mainMenu.aInsEllipsis.activate(QAction.Trigger) + assert nwGUI.docEditor.getText() == nwUnicode.U_HELLIP + nwGUI.docEditor.clear() + + nwGUI.mainMenu.aInsPrime.activate(QAction.Trigger) + assert nwGUI.docEditor.getText() == nwUnicode.U_PRIME + nwGUI.docEditor.clear() + + nwGUI.mainMenu.aInsDPrime.activate(QAction.Trigger) + assert nwGUI.docEditor.getText() == nwUnicode.U_DPRIME + nwGUI.docEditor.clear() + + nwGUI.mainMenu.aInsBullet.activate(QAction.Trigger) + assert nwGUI.docEditor.getText() == nwUnicode.U_BULL + nwGUI.docEditor.clear() + + nwGUI.mainMenu.aInsFlower.activate(QAction.Trigger) + assert nwGUI.docEditor.getText() == nwUnicode.U_FLOWER + nwGUI.docEditor.clear() + + nwGUI.mainMenu.aInsPerMille.activate(QAction.Trigger) + assert nwGUI.docEditor.getText() == nwUnicode.U_PERMIL + nwGUI.docEditor.clear() + nwGUI.mainMenu.aInsHardBreak.activate(QAction.Trigger) assert nwGUI.docEditor.getText() == " \n" nwGUI.docEditor.clear()