From e236fb30dcb1487a1cf45333e936a7e38365a371 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Tue, 7 Nov 2023 17:47:59 +0100
Subject: [PATCH 01/10] Clean up document header signals
---
docs/source/usage_shortcuts.rst | 4 +-
novelwriter/gui/mainmenu.py | 4 +-
novelwriter/gui/sidebar.py | 1 -
novelwriter/guimain.py | 330 +++++++++++++++--------------
tests/test_gui/test_gui_guimain.py | 10 +-
5 files changed, 180 insertions(+), 169 deletions(-)
diff --git a/docs/source/usage_shortcuts.rst b/docs/source/usage_shortcuts.rst
index 86e60afa2..4e9313510 100644
--- a/docs/source/usage_shortcuts.rst
+++ b/docs/source/usage_shortcuts.rst
@@ -77,10 +77,10 @@ Text Search Shortcuts
":kbd:`F3`", "Find the next occurrence of the search word"
":kbd:`Ctrl+F`", "Open the search bar and search for the selected word, if any is selected"
- ":kbd:`Ctrl+G`", "Find next occurrence of search word in current document"
+ ":kbd:`Ctrl+G`", "Find the next occurrence of the search word"
":kbd:`Ctrl+H`", "Open the search tool and populate with the selected word (Mac :kbd:`Cmd+=`)"
":kbd:`Ctrl+Shift+1`", "Replace selected occurrence of the search word, and move to the next"
- ":kbd:`Ctrl+Shift+G`", "Find previous occurrence of the search word"
+ ":kbd:`Ctrl+Shift+G`", "Find the previous occurrence of the search word"
":kbd:`Shift+F3`", "Find the previous occurrence of the search word"
diff --git a/novelwriter/gui/mainmenu.py b/novelwriter/gui/mainmenu.py
index fa8b64add..6355ca63f 100644
--- a/novelwriter/gui/mainmenu.py
+++ b/novelwriter/gui/mainmenu.py
@@ -209,7 +209,7 @@ def _buildDocumentMenu(self) -> None:
# Document > Close
self.aCloseDoc = self.docuMenu.addAction(self.tr("Close Document"))
self.aCloseDoc.setShortcut("Ctrl+W")
- self.aCloseDoc.triggered.connect(lambda: self.mainGui.closeDocEditor())
+ self.aCloseDoc.triggered.connect(self.mainGui.closeDocEditor)
# Document > Separator
self.docuMenu.addSeparator()
@@ -330,7 +330,7 @@ def _buildViewMenu(self) -> None:
# View > Focus Mode
self.aFocusMode = self.viewMenu.addAction(self.tr("Focus Mode"))
self.aFocusMode.setShortcut("F8")
- self.aFocusMode.triggered.connect(lambda: self.mainGui.toggleFocusMode())
+ self.aFocusMode.triggered.connect(self.mainGui.toggleFocusMode)
# View > Toggle Full Screen
self.aFullScreen = self.viewMenu.addAction(self.tr("Full Screen Mode"))
diff --git a/novelwriter/gui/sidebar.py b/novelwriter/gui/sidebar.py
index 4b7b4e7bf..be719a42d 100644
--- a/novelwriter/gui/sidebar.py
+++ b/novelwriter/gui/sidebar.py
@@ -115,7 +115,6 @@ def __init__(self, mainGui: GuiMain) -> None:
self.outerBox.setSpacing(CONFIG.pxInt(4))
self.setLayout(self.outerBox)
-
self.updateTheme()
logger.debug("Ready: GuiSideBar")
diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py
index 4d324677f..adcebca8a 100644
--- a/novelwriter/guimain.py
+++ b/novelwriter/guimain.py
@@ -267,6 +267,8 @@ def __init__(self) -> None:
self.docEditor.novelItemMetaChanged.connect(self.novelView.updateNovelItemMeta)
self.docEditor.statusMessage.connect(self.mainStatus.setStatusMessage)
self.docEditor.spellCheckStateChanged.connect(self.mainMenu.setSpellCheckState)
+ self.docEditor.closeDocumentRequest.connect(self.closeDocEditor)
+ self.docEditor.toggleFocusModeRequest.connect(self.toggleFocusMode)
self.docViewer.loadDocumentTagRequest.connect(self._followTag)
@@ -1119,12 +1121,6 @@ def switchFocus(self, paneNo: nwWidget) -> None:
self.outlineView.setTreeFocus()
return
- def closeDocEditor(self) -> None:
- """Close the document editor. This does not hide the editor."""
- self.closeDocument()
- SHARED.project.data.setLastHandle(None, "editor")
- return
-
def closeDocViewer(self, byUser: bool = True) -> bool:
"""Close the document view panel."""
self.docViewer.clearViewer()
@@ -1139,13 +1135,44 @@ def closeDocViewer(self, byUser: bool = True) -> bool:
return not self.splitView.isVisible()
- def toggleFocusMode(self) -> bool:
+ def toggleFullScreenMode(self) -> None:
+ """Toggle full screen mode"""
+ self.setWindowState(self.windowState() ^ Qt.WindowFullScreen)
+ return
+
+ ##
+ # Events
+ ##
+
+ def closeEvent(self, event: QCloseEvent):
+ """Capture the closing event of the GUI and call the close
+ function to handle all the close process steps.
+ """
+ if self.closeMain():
+ event.accept()
+ else:
+ event.ignore()
+ return
+
+ ##
+ # Public Slots
+ ##
+
+ @pyqtSlot()
+ def closeDocEditor(self) -> None:
+ """Close the document editor. This does not hide the editor."""
+ self.closeDocument()
+ SHARED.project.data.setLastHandle(None, "editor")
+ return
+
+ @pyqtSlot()
+ def toggleFocusMode(self) -> None:
"""Handle toggle focus mode. The Main GUI Focus Mode hides tree,
view, statusbar and menu.
"""
if self.docEditor.docHandle is None:
logger.error("No document open, so not activating Focus Mode")
- return False
+ return
self.isFocusMode = not self.isFocusMode
if self.isFocusMode:
@@ -1169,11 +1196,141 @@ def toggleFocusMode(self) -> bool:
elif self.docViewer.docHandle is not None:
self.splitView.setVisible(True)
- return True
+ return
- def toggleFullScreenMode(self) -> None:
- """Toggle full screen mode"""
- self.setWindowState(self.windowState() ^ Qt.WindowFullScreen)
+ ##
+ # Private Slots
+ ##
+
+ @pyqtSlot(str, nwDocMode)
+ def _followTag(self, tag: str, mode: nwDocMode) -> None:
+ """Follow a tag after user interaction with a link."""
+ tHandle, sTitle = self._getTagSource(tag)
+ if tHandle is not None:
+ if mode == nwDocMode.EDIT:
+ self.openDocument(tHandle)
+ elif mode == nwDocMode.VIEW:
+ self.viewDocument(tHandle=tHandle, sTitle=sTitle)
+ return
+
+ @pyqtSlot(str, nwDocMode, str, bool)
+ def _openDocument(self, tHandle: str, mode: nwDocMode, sTitle: str, setFocus: bool) -> None:
+ """Handle an open document request."""
+ if tHandle is not None:
+ if mode == nwDocMode.EDIT:
+ tLine = None
+ hItem = SHARED.project.index.getItemHeader(tHandle, sTitle)
+ if hItem is not None:
+ tLine = hItem.line
+ self.openDocument(tHandle, tLine=tLine, changeFocus=setFocus)
+ elif mode == nwDocMode.VIEW:
+ self.viewDocument(tHandle=tHandle, sTitle=sTitle)
+ return
+
+ @pyqtSlot(nwView)
+ def _changeView(self, view: nwView) -> None:
+ """Handle the requested change of view from the GuiViewBar."""
+ if view == nwView.EDITOR:
+ # Only change the main stack, but not the project stack
+ self.mainStack.setCurrentWidget(self.splitMain)
+
+ elif view == nwView.PROJECT:
+ self.mainStack.setCurrentWidget(self.splitMain)
+ self.projStack.setCurrentWidget(self.projView)
+
+ elif view == nwView.NOVEL:
+ self.mainStack.setCurrentWidget(self.splitMain)
+ self.projStack.setCurrentWidget(self.novelView)
+
+ elif view == nwView.OUTLINE:
+ self.mainStack.setCurrentWidget(self.outlineView)
+
+ return
+
+ @pyqtSlot()
+ def _timeTick(self) -> None:
+ """Process time tick of the main timer."""
+ if not SHARED.hasProject:
+ return
+ currTime = time()
+ editIdle = currTime - self.docEditor.lastActive > CONFIG.userIdleTime
+ userIdle = qApp.applicationState() != Qt.ApplicationActive
+ self.mainStatus.setUserIdle(editIdle or userIdle)
+ SHARED.updateIdleTime(currTime, editIdle or userIdle)
+ self.mainStatus.updateTime(idleTime=SHARED.projectIdleTime)
+ return
+
+ @pyqtSlot()
+ def _autoSaveProject(self) -> None:
+ """Autosave of the project. This is a timer-activated slot."""
+ doSave = SHARED.hasProject
+ doSave &= SHARED.project.projChanged
+ doSave &= SHARED.project.storage.isOpen()
+ if doSave:
+ logger.debug("Autosaving project")
+ self.saveProject(autoSave=True)
+ return
+
+ @pyqtSlot()
+ def _autoSaveDocument(self) -> None:
+ """Autosave of the document. This is a timer-activated slot."""
+ if SHARED.hasProject and self.docEditor.docChanged:
+ logger.debug("Autosaving document")
+ self.saveDocument()
+ return
+
+ @pyqtSlot()
+ def _updateStatusWordCount(self) -> None:
+ """Update the word count on the status bar."""
+ if not SHARED.hasProject:
+ self.mainStatus.setProjectStats(0, 0)
+
+ SHARED.project.updateWordCounts()
+ if CONFIG.incNotesWCount:
+ iTotal = sum(SHARED.project.data.initCounts)
+ cTotal = sum(SHARED.project.data.currCounts)
+ self.mainStatus.setProjectStats(cTotal, cTotal - iTotal)
+ else:
+ iNovel, _ = SHARED.project.data.initCounts
+ cNovel, _ = SHARED.project.data.currCounts
+ self.mainStatus.setProjectStats(cNovel, cNovel - iNovel)
+
+ return
+
+ @pyqtSlot()
+ def _keyPressReturn(self) -> None:
+ """Forward the return/enter keypress to the function that opens
+ the currently selected item.
+ """
+ self.openSelectedItem()
+ return
+
+ @pyqtSlot()
+ def _keyPressEscape(self) -> None:
+ """Process escape keypress in the main window."""
+ if self.docEditor.docSearch.isVisible():
+ self.docEditor.closeSearch()
+ elif self.isFocusMode:
+ self.toggleFocusMode()
+ return
+
+ @pyqtSlot(int)
+ def _mainStackChanged(self, index: int) -> None:
+ """Process main window tab change."""
+ if index == self.idxOutlineView:
+ if SHARED.hasProject:
+ self.outlineView.refreshTree()
+ return
+
+ @pyqtSlot(int)
+ def _projStackChanged(self, index: int) -> None:
+ """Process project view tab change."""
+ sHandle = None
+ if index == self.idxProjView:
+ sHandle = self.projView.getSelectedHandle()
+ elif index == self.idxNovelView:
+ sHandle, _ = self.novelView.getSelectedHandle()
+ self.itemDetails.updateViewBox(sHandle)
return
##
@@ -1328,153 +1485,4 @@ def _getTagSource(self, tag: str) -> tuple[str | None, str | None]:
return None, None
return tHandle, sTitle
- ##
- # Events
- ##
-
- def closeEvent(self, event: QCloseEvent):
- """Capture the closing event of the GUI and call the close
- function to handle all the close process steps.
- """
- if self.closeMain():
- event.accept()
- else:
- event.ignore()
- return
-
- ##
- # Private Slots
- ##
-
- @pyqtSlot(str, nwDocMode)
- def _followTag(self, tag: str, mode: nwDocMode) -> None:
- """Follow a tag after user interaction with a link."""
- tHandle, sTitle = self._getTagSource(tag)
- if tHandle is not None:
- if mode == nwDocMode.EDIT:
- self.openDocument(tHandle)
- elif mode == nwDocMode.VIEW:
- self.viewDocument(tHandle=tHandle, sTitle=sTitle)
- return
-
- @pyqtSlot(str, nwDocMode, str, bool)
- def _openDocument(self, tHandle: str, mode: nwDocMode, sTitle: str, setFocus: bool) -> None:
- """Handle an open document request."""
- if tHandle is not None:
- if mode == nwDocMode.EDIT:
- tLine = None
- hItem = SHARED.project.index.getItemHeader(tHandle, sTitle)
- if hItem is not None:
- tLine = hItem.line
- self.openDocument(tHandle, tLine=tLine, changeFocus=setFocus)
- elif mode == nwDocMode.VIEW:
- self.viewDocument(tHandle=tHandle, sTitle=sTitle)
- return
-
- @pyqtSlot(nwView)
- def _changeView(self, view: nwView) -> None:
- """Handle the requested change of view from the GuiViewBar."""
- if view == nwView.EDITOR:
- # Only change the main stack, but not the project stack
- self.mainStack.setCurrentWidget(self.splitMain)
-
- elif view == nwView.PROJECT:
- self.mainStack.setCurrentWidget(self.splitMain)
- self.projStack.setCurrentWidget(self.projView)
-
- elif view == nwView.NOVEL:
- self.mainStack.setCurrentWidget(self.splitMain)
- self.projStack.setCurrentWidget(self.novelView)
-
- elif view == nwView.OUTLINE:
- self.mainStack.setCurrentWidget(self.outlineView)
-
- return
-
- @pyqtSlot()
- def _timeTick(self) -> None:
- """Process time tick of the main timer."""
- if not SHARED.hasProject:
- return
- currTime = time()
- editIdle = currTime - self.docEditor.lastActive > CONFIG.userIdleTime
- userIdle = qApp.applicationState() != Qt.ApplicationActive
- self.mainStatus.setUserIdle(editIdle or userIdle)
- SHARED.updateIdleTime(currTime, editIdle or userIdle)
- self.mainStatus.updateTime(idleTime=SHARED.projectIdleTime)
- return
-
- @pyqtSlot()
- def _autoSaveProject(self) -> None:
- """Autosave of the project. This is a timer-activated slot."""
- doSave = SHARED.hasProject
- doSave &= SHARED.project.projChanged
- doSave &= SHARED.project.storage.isOpen()
- if doSave:
- logger.debug("Autosaving project")
- self.saveProject(autoSave=True)
- return
-
- @pyqtSlot()
- def _autoSaveDocument(self) -> None:
- """Autosave of the document. This is a timer-activated slot."""
- if SHARED.hasProject and self.docEditor.docChanged:
- logger.debug("Autosaving document")
- self.saveDocument()
- return
-
- @pyqtSlot()
- def _updateStatusWordCount(self) -> None:
- """Update the word count on the status bar."""
- if not SHARED.hasProject:
- self.mainStatus.setProjectStats(0, 0)
-
- SHARED.project.updateWordCounts()
- if CONFIG.incNotesWCount:
- iTotal = sum(SHARED.project.data.initCounts)
- cTotal = sum(SHARED.project.data.currCounts)
- self.mainStatus.setProjectStats(cTotal, cTotal - iTotal)
- else:
- iNovel, _ = SHARED.project.data.initCounts
- cNovel, _ = SHARED.project.data.currCounts
- self.mainStatus.setProjectStats(cNovel, cNovel - iNovel)
-
- return
-
- @pyqtSlot()
- def _keyPressReturn(self) -> None:
- """Forward the return/enter keypress to the function that opens
- the currently selected item.
- """
- self.openSelectedItem()
- return
-
- @pyqtSlot()
- def _keyPressEscape(self) -> None:
- """Process escape keypress in the main window."""
- if self.docEditor.docSearch.isVisible():
- self.docEditor.closeSearch()
- elif self.isFocusMode:
- self.toggleFocusMode()
- return
-
- @pyqtSlot(int)
- def _mainStackChanged(self, index: int) -> None:
- """Process main window tab change."""
- if index == self.idxOutlineView:
- if SHARED.hasProject:
- self.outlineView.refreshTree()
- return
-
- @pyqtSlot(int)
- def _projStackChanged(self, index: int) -> None:
- """Process project view tab change."""
- sHandle = None
- if index == self.idxProjView:
- sHandle = self.projView.getSelectedHandle()
- elif index == self.idxNovelView:
- sHandle, _ = self.novelView.getSelectedHandle()
- self.itemDetails.updateViewBox(sHandle)
- return
-
# END Class GuiMain
diff --git a/tests/test_gui/test_gui_guimain.py b/tests/test_gui/test_gui_guimain.py
index 74e0e3522..52e2bc0fd 100644
--- a/tests/test_gui/test_gui_guimain.py
+++ b/tests/test_gui/test_gui_guimain.py
@@ -594,14 +594,18 @@ def testGuiMain_Features(qtbot, nwGUI, projPath, mockRnd):
# ==========
# No document open, so not allowing focus mode
- assert nwGUI.toggleFocusMode() is False
+ nwGUI.toggleFocusMode()
+ assert nwGUI.treePane.isVisible() is True
+ assert nwGUI.mainStatus.isVisible() is True
+ assert nwGUI.mainMenu.isVisible() is True
+ assert nwGUI.sideBar.isVisible() is True
# Open a file in editor and viewer
assert nwGUI.openDocument(C.hSceneDoc)
assert nwGUI.viewDocument(C.hSceneDoc)
# Enable focus mode
- assert nwGUI.toggleFocusMode() is True
+ nwGUI.toggleFocusMode()
assert nwGUI.treePane.isVisible() is False
assert nwGUI.mainStatus.isVisible() is False
assert nwGUI.mainMenu.isVisible() is False
@@ -609,7 +613,7 @@ def testGuiMain_Features(qtbot, nwGUI, projPath, mockRnd):
assert nwGUI.splitView.isVisible() is False
# Disable focus mode
- assert nwGUI.toggleFocusMode() is True
+ nwGUI.toggleFocusMode()
assert nwGUI.treePane.isVisible() is True
assert nwGUI.mainStatus.isVisible() is True
assert nwGUI.mainMenu.isVisible() is True
From 394cdd5dadbbcbf882afdb85699a0c61f277baae Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Tue, 7 Nov 2023 17:48:34 +0100
Subject: [PATCH 02/10] Add a basic document editor toolbar
---
novelwriter/gui/doceditor.py | 154 +++++++++++++++++++++++++----------
novelwriter/gui/docviewer.py | 8 +-
2 files changed, 117 insertions(+), 45 deletions(-)
diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py
index e9094c8b4..f12220e51 100644
--- a/novelwriter/gui/doceditor.py
+++ b/novelwriter/gui/doceditor.py
@@ -46,7 +46,7 @@
)
from PyQt5.QtWidgets import (
QAction, QFrame, QGridLayout, QHBoxLayout, QLabel, QLineEdit, QMenu,
- QPlainTextEdit, QPushButton, QShortcut, QToolBar, QToolButton, QWidget,
+ QPlainTextEdit, QPushButton, QShortcut, QToolBar, QToolButton, QVBoxLayout, QWidget,
qApp
)
@@ -83,6 +83,8 @@ class GuiDocEditor(QPlainTextEdit):
novelStructureChanged = pyqtSignal()
novelItemMetaChanged = pyqtSignal(str)
spellCheckStateChanged = pyqtSignal(bool)
+ closeDocumentRequest = pyqtSignal()
+ toggleFocusModeRequest = pyqtSignal()
def __init__(self, mainGui: GuiMain) -> None:
super().__init__(parent=mainGui)
@@ -135,6 +137,10 @@ def __init__(self, mainGui: GuiMain) -> None:
self.docHeader = GuiDocEditHeader(self)
self.docFooter = GuiDocEditFooter(self)
self.docSearch = GuiDocEditSearch(self)
+ self.docToolBar = GuiDocToolBar(self)
+
+ # Connect Signals
+ self.docHeader.closeDocumentRequest.connect(self._closeCurrentDocument)
# Context Menu
self.setContextMenuPolicy(Qt.CustomContextMenu)
@@ -248,6 +254,7 @@ def updateTheme(self) -> None:
self.docSearch.updateTheme()
self.docHeader.updateTheme()
self.docFooter.updateTheme()
+ self.docToolBar.updateTheme()
return
def updateSyntaxColours(self) -> None:
@@ -514,6 +521,7 @@ def updateDocMargins(self) -> None:
fY = wH - fH - tB - sH
self.docHeader.setGeometry(tB, tB, tW, tH)
self.docFooter.setGeometry(tB, fY, tW, fH)
+ self.docToolBar.move(0, tH)
rH = 0
if self.docSearch.isVisible():
@@ -857,6 +865,7 @@ def closeSearch(self) -> bool:
self.docSearch.closeSearch()
return self.docSearch.isVisible()
+ @pyqtSlot()
def toggleSearch(self) -> None:
"""Toggle the visibility of the search box."""
if self.docSearch.isVisible():
@@ -1179,6 +1188,12 @@ def _updateSelCounts(self, cCount: int, wCount: int, pCount: int) -> None:
return
+ @pyqtSlot()
+ def _closeCurrentDocument(self) -> None:
+ """Close the document. Forwarded to the main Gui."""
+ self.closeDocumentRequest.emit()
+ return
+
##
# Search & Replace
##
@@ -2072,6 +2087,77 @@ class BackgroundWordCounterSignals(QObject):
# END Class BackgroundWordCounterSignals
+# =============================================================================================== #
+# The Formatting and Options Fold Out Menu
+# Only used by DocEditor, and is opened by the first button in the header
+# =============================================================================================== #
+
+class GuiDocToolBar(QWidget):
+
+ def __init__(self, docEditor: GuiDocEditor) -> None:
+ super().__init__(parent=docEditor)
+
+ logger.debug("Create: GuiDocToolBar")
+
+ cM = CONFIG.pxInt(4)
+ tPx = int(0.8*SHARED.theme.fontPixelSize)
+ iconSize = QSize(tPx, tPx)
+ self.setContentsMargins(0, 0, 0, 0)
+
+ # General Buttons
+ # ===============
+
+ self.tbSticky = QToolButton(self)
+ self.tbSticky.setIconSize(iconSize)
+
+ self.tbEdit = QToolButton(self)
+ self.tbEdit.setIconSize(iconSize)
+
+ # Assemble
+ # ========
+
+ self.outerBox = QVBoxLayout()
+ self.outerBox.addWidget(self.tbSticky)
+ self.outerBox.addWidget(self.tbEdit)
+ self.outerBox.setContentsMargins(cM, cM, cM, cM)
+ self.outerBox.setSpacing(cM)
+
+ self.setLayout(self.outerBox)
+ self.updateTheme()
+
+ logger.debug("Ready: GuiDocToolBar")
+
+ return
+
+ def updateTheme(self) -> None:
+ """Initialise GUI elements that depend on specific settings."""
+ palette = QPalette()
+ palette.setColor(QPalette.Window, QColor(*SHARED.theme.colBack))
+ palette.setColor(QPalette.WindowText, QColor(*SHARED.theme.colText))
+ palette.setColor(QPalette.Text, QColor(*SHARED.theme.colText))
+
+ self.setPalette(palette)
+
+ # qPalette = self.palette()
+ # qPalette.setBrush(QPalette.Window, qPalette.base())
+ # self.setPalette(qPalette)
+
+ # fadeCol = qPalette.text().color()
+ # buttonStyle = (
+ # "QToolButton {{padding: {0}px; border: none; background: transparent;}} "
+ # "QToolButton:hover {{border: none; background: rgba({1},{2},{3},0.2);}}"
+ # ).format(CONFIG.pxInt(4), fadeCol.red(), fadeCol.green(), fadeCol.blue())
+ # buttonStyleMenu = f"{buttonStyle} QToolButton::menu-indicator {{image: none;}}"
+ # self.tbEdit.setStyleSheet(buttonStyle)
+
+ self.tbSticky.setIcon(SHARED.theme.getIcon("sticky-off"))
+ self.tbEdit.setIcon(SHARED.theme.getIcon("edit"))
+
+ return
+
+# END Class GuiDocToolBar
+
+
# =============================================================================================== #
# The Embedded Document Search/Replace Feature
# Only used by DocEditor, and is at a fixed position in the QTextEdit's viewport
@@ -2487,6 +2573,9 @@ def _alertSearchValid(self, isValid: bool) -> None:
class GuiDocEditHeader(QWidget):
+ toggleToolBarRequest = pyqtSignal()
+ closeDocumentRequest = pyqtSignal()
+
def __init__(self, docEditor: GuiDocEditor) -> None:
super().__init__(parent=docEditor)
@@ -2499,6 +2588,7 @@ def __init__(self, docEditor: GuiDocEditor) -> None:
fPx = int(0.9*SHARED.theme.fontPixelSize)
hSp = CONFIG.pxInt(6)
+ iconSize = QSize(fPx, fPx)
# Main Widget Settings
self.setAutoFillBackground(True)
@@ -2518,46 +2608,46 @@ def __init__(self, docEditor: GuiDocEditor) -> None:
self.itemTitle.setFont(lblFont)
# Buttons
- self.editButton = QToolButton(self)
- self.editButton.setContentsMargins(0, 0, 0, 0)
- self.editButton.setIconSize(QSize(fPx, fPx))
- self.editButton.setFixedSize(fPx, fPx)
- self.editButton.setToolButtonStyle(Qt.ToolButtonIconOnly)
- self.editButton.setVisible(False)
- self.editButton.setToolTip(self.tr("Edit document label"))
- self.editButton.clicked.connect(self._editDocument)
+ self.tbButton = QToolButton(self)
+ self.tbButton.setContentsMargins(0, 0, 0, 0)
+ self.tbButton.setIconSize(iconSize)
+ self.tbButton.setFixedSize(fPx, fPx)
+ self.tbButton.setToolButtonStyle(Qt.ToolButtonIconOnly)
+ self.tbButton.setVisible(False)
+ self.tbButton.setToolTip(self.tr("Toggle Tool Bar"))
+ self.tbButton.clicked.connect(lambda: self.toggleToolBarRequest.emit())
self.searchButton = QToolButton(self)
self.searchButton.setContentsMargins(0, 0, 0, 0)
- self.searchButton.setIconSize(QSize(fPx, fPx))
+ self.searchButton.setIconSize(iconSize)
self.searchButton.setFixedSize(fPx, fPx)
self.searchButton.setToolButtonStyle(Qt.ToolButtonIconOnly)
self.searchButton.setVisible(False)
- self.searchButton.setToolTip(self.tr("Search document"))
- self.searchButton.clicked.connect(self._searchDocument)
+ self.searchButton.setToolTip(self.tr("Search"))
+ self.searchButton.clicked.connect(self.docEditor.toggleSearch)
self.minmaxButton = QToolButton(self)
self.minmaxButton.setContentsMargins(0, 0, 0, 0)
- self.minmaxButton.setIconSize(QSize(fPx, fPx))
+ self.minmaxButton.setIconSize(iconSize)
self.minmaxButton.setFixedSize(fPx, fPx)
self.minmaxButton.setToolButtonStyle(Qt.ToolButtonIconOnly)
self.minmaxButton.setVisible(False)
self.minmaxButton.setToolTip(self.tr("Toggle Focus Mode"))
- self.minmaxButton.clicked.connect(self._minmaxDocument)
+ self.minmaxButton.clicked.connect(lambda: self.docEditor.toggleFocusModeRequest.emit())
self.closeButton = QToolButton(self)
self.closeButton.setContentsMargins(0, 0, 0, 0)
- self.closeButton.setIconSize(QSize(fPx, fPx))
+ self.closeButton.setIconSize(iconSize)
self.closeButton.setFixedSize(fPx, fPx)
self.closeButton.setToolButtonStyle(Qt.ToolButtonIconOnly)
self.closeButton.setVisible(False)
- self.closeButton.setToolTip(self.tr("Close the document"))
+ self.closeButton.setToolTip(self.tr("Close"))
self.closeButton.clicked.connect(self._closeDocument)
# Assemble Layout
self.outerBox = QHBoxLayout()
self.outerBox.setSpacing(hSp)
- self.outerBox.addWidget(self.editButton, 0)
+ self.outerBox.addWidget(self.tbButton, 0)
self.outerBox.addWidget(self.searchButton, 0)
self.outerBox.addWidget(self.itemTitle, 1)
self.outerBox.addWidget(self.minmaxButton, 0)
@@ -2583,7 +2673,7 @@ def __init__(self, docEditor: GuiDocEditor) -> None:
def updateTheme(self) -> None:
"""Update theme elements."""
- self.editButton.setIcon(SHARED.theme.getIcon("edit"))
+ self.tbButton.setIcon(SHARED.theme.getIcon("menu"))
self.searchButton.setIcon(SHARED.theme.getIcon("search"))
self.minmaxButton.setIcon(SHARED.theme.getIcon("maximise"))
self.closeButton.setIcon(SHARED.theme.getIcon("close"))
@@ -2593,7 +2683,7 @@ def updateTheme(self) -> None:
"QToolButton:hover {{border: none; background: rgba({0},{1},{2},0.2);}}"
).format(*SHARED.theme.colText)
- self.editButton.setStyleSheet(buttonStyle)
+ self.tbButton.setStyleSheet(buttonStyle)
self.searchButton.setStyleSheet(buttonStyle)
self.minmaxButton.setStyleSheet(buttonStyle)
self.closeButton.setStyleSheet(buttonStyle)
@@ -2623,7 +2713,7 @@ def setTitleFromHandle(self, tHandle: str | None) -> bool:
self._docHandle = tHandle
if tHandle is None:
self.itemTitle.setText("")
- self.editButton.setVisible(False)
+ self.tbButton.setVisible(False)
self.searchButton.setVisible(False)
self.closeButton.setVisible(False)
self.minmaxButton.setVisible(False)
@@ -2645,7 +2735,7 @@ def setTitleFromHandle(self, tHandle: str | None) -> bool:
return False
self.itemTitle.setText(nwItem.itemName)
- self.editButton.setVisible(True)
+ self.tbButton.setVisible(True)
self.searchButton.setVisible(True)
self.closeButton.setVisible(True)
self.minmaxButton.setVisible(True)
@@ -2667,34 +2757,16 @@ def updateFocusMode(self) -> None:
# Private Slots
##
- @pyqtSlot()
- def _editDocument(self) -> None:
- """Open the edit item dialog from the main GUI."""
- self.mainGui.editItemLabel(self._docHandle)
- return
-
- @pyqtSlot()
- def _searchDocument(self) -> None:
- """Toggle the visibility of the search box."""
- self.docEditor.toggleSearch()
- return
-
@pyqtSlot()
def _closeDocument(self) -> None:
"""Trigger the close editor on the main window."""
- self.mainGui.closeDocEditor()
- self.editButton.setVisible(False)
+ self.closeDocumentRequest.emit()
+ self.tbButton.setVisible(False)
self.searchButton.setVisible(False)
self.closeButton.setVisible(False)
self.minmaxButton.setVisible(False)
return
- @pyqtSlot()
- def _minmaxDocument(self) -> None:
- """Switch on or off Focus Mode."""
- self.mainGui.toggleFocusMode()
- return
-
##
# Events
##
diff --git a/novelwriter/gui/docviewer.py b/novelwriter/gui/docviewer.py
index 18dfdbc51..9aff4eee5 100644
--- a/novelwriter/gui/docviewer.py
+++ b/novelwriter/gui/docviewer.py
@@ -705,7 +705,7 @@ def __init__(self, docViewer):
self.backButton.setFixedSize(fPx, fPx)
self.backButton.setToolButtonStyle(Qt.ToolButtonIconOnly)
self.backButton.setVisible(False)
- self.backButton.setToolTip(self.tr("Go backward"))
+ self.backButton.setToolTip(self.tr("Go Backward"))
self.backButton.clicked.connect(self.docViewer.navBackward)
self.forwardButton = QToolButton(self)
@@ -714,7 +714,7 @@ def __init__(self, docViewer):
self.forwardButton.setFixedSize(fPx, fPx)
self.forwardButton.setToolButtonStyle(Qt.ToolButtonIconOnly)
self.forwardButton.setVisible(False)
- self.forwardButton.setToolTip(self.tr("Go forward"))
+ self.forwardButton.setToolTip(self.tr("Go Forward"))
self.forwardButton.clicked.connect(self.docViewer.navForward)
self.refreshButton = QToolButton(self)
@@ -723,7 +723,7 @@ def __init__(self, docViewer):
self.refreshButton.setFixedSize(fPx, fPx)
self.refreshButton.setToolButtonStyle(Qt.ToolButtonIconOnly)
self.refreshButton.setVisible(False)
- self.refreshButton.setToolTip(self.tr("Reload the document"))
+ self.refreshButton.setToolTip(self.tr("Reload"))
self.refreshButton.clicked.connect(self._refreshDocument)
self.closeButton = QToolButton(self)
@@ -732,7 +732,7 @@ def __init__(self, docViewer):
self.closeButton.setFixedSize(fPx, fPx)
self.closeButton.setToolButtonStyle(Qt.ToolButtonIconOnly)
self.closeButton.setVisible(False)
- self.closeButton.setToolTip(self.tr("Close the document"))
+ self.closeButton.setToolTip(self.tr("Close"))
self.closeButton.clicked.connect(self._closeDocument)
# Assemble Layout
From 9282b39f2a1decc35da35ad1159a1d90b229b1ac Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Wed, 8 Nov 2023 16:54:15 +0100
Subject: [PATCH 03/10] Change the menu icon to three dots
---
.../assets/icons/typicons_dark/icons.conf | 2 +-
.../icons/typicons_dark/typ_th-dot-menu.svg | 4 +
.../icons/typicons_dark/typ_th-menu.svg | 4 -
.../assets/icons/typicons_light/icons.conf | 2 +-
.../icons/typicons_light/typ_th-dot-menu.svg | 4 +
.../icons/typicons_light/typ_th-menu.svg | 4 -
novelwriter/core/options.py | 8 +-
novelwriter/gui/noveltree.py | 224 ++++++++----------
novelwriter/gui/outline.py | 1 +
novelwriter/gui/projtree.py | 7 +-
10 files changed, 120 insertions(+), 140 deletions(-)
create mode 100644 novelwriter/assets/icons/typicons_dark/typ_th-dot-menu.svg
delete mode 100644 novelwriter/assets/icons/typicons_dark/typ_th-menu.svg
create mode 100644 novelwriter/assets/icons/typicons_light/typ_th-dot-menu.svg
delete mode 100644 novelwriter/assets/icons/typicons_light/typ_th-menu.svg
diff --git a/novelwriter/assets/icons/typicons_dark/icons.conf b/novelwriter/assets/icons/typicons_dark/icons.conf
index d0b695725..c84461231 100644
--- a/novelwriter/assets/icons/typicons_dark/icons.conf
+++ b/novelwriter/assets/icons/typicons_dark/icons.conf
@@ -48,7 +48,7 @@ edit = typ_pencil.svg
export = typ_export.svg
forward = typ_chevron-right.svg
maximise = typ_arrow-maximise.svg
-menu = typ_th-menu.svg
+menu = typ_th-dot-menu.svg
minimise = typ_arrow-minimise.svg
noncheckable = mixed_input-none.svg
proj_chapter = mixed_document-chapter.svg
diff --git a/novelwriter/assets/icons/typicons_dark/typ_th-dot-menu.svg b/novelwriter/assets/icons/typicons_dark/typ_th-dot-menu.svg
new file mode 100644
index 000000000..19ec3bde0
--- /dev/null
+++ b/novelwriter/assets/icons/typicons_dark/typ_th-dot-menu.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/novelwriter/assets/icons/typicons_dark/typ_th-menu.svg b/novelwriter/assets/icons/typicons_dark/typ_th-menu.svg
deleted file mode 100644
index 4bf1c92fa..000000000
--- a/novelwriter/assets/icons/typicons_dark/typ_th-menu.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
diff --git a/novelwriter/assets/icons/typicons_light/icons.conf b/novelwriter/assets/icons/typicons_light/icons.conf
index 22fd94562..d9aa905c5 100644
--- a/novelwriter/assets/icons/typicons_light/icons.conf
+++ b/novelwriter/assets/icons/typicons_light/icons.conf
@@ -48,7 +48,7 @@ edit = typ_pencil.svg
export = typ_export.svg
forward = typ_chevron-right.svg
maximise = typ_arrow-maximise.svg
-menu = typ_th-menu.svg
+menu = typ_th-dot-menu.svg
minimise = typ_arrow-minimise.svg
noncheckable = mixed_input-none.svg
proj_chapter = mixed_document-chapter.svg
diff --git a/novelwriter/assets/icons/typicons_light/typ_th-dot-menu.svg b/novelwriter/assets/icons/typicons_light/typ_th-dot-menu.svg
new file mode 100644
index 000000000..3b5d6383c
--- /dev/null
+++ b/novelwriter/assets/icons/typicons_light/typ_th-dot-menu.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/novelwriter/assets/icons/typicons_light/typ_th-menu.svg b/novelwriter/assets/icons/typicons_light/typ_th-menu.svg
deleted file mode 100644
index c23c08ca1..000000000
--- a/novelwriter/assets/icons/typicons_light/typ_th-menu.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
diff --git a/novelwriter/core/options.py b/novelwriter/core/options.py
index 0dd62f066..48a0c594b 100644
--- a/novelwriter/core/options.py
+++ b/novelwriter/core/options.py
@@ -28,7 +28,7 @@
import logging
from enum import Enum
-from typing import TYPE_CHECKING, Any
+from typing import TYPE_CHECKING, Any, TypeVar
from pathlib import Path
from novelwriter.error import logException
@@ -40,6 +40,8 @@
logger = logging.getLogger(__name__)
+NWEnum = TypeVar("NWEnum", bound=Enum)
+
VALID_MAP = {
"GuiWritingStats": {
"winWidth", "winHeight", "widthCol0", "widthCol1", "widthCol2",
@@ -201,11 +203,11 @@ def getBool(self, group: str, name: str, default: bool) -> bool:
return checkBool(self._state[group].get(name, default), default)
return default
- def getEnum(self, group: str, name: str, lookup: type, default: Enum) -> Enum:
+ def getEnum(self, group: str, name: str, lookup: type, default: NWEnum) -> NWEnum:
"""Return the value mapped to an enum. Otherwise return the
default value.
"""
- if issubclass(lookup, Enum):
+ if issubclass(lookup, type(default)):
if group in self._state:
if name in self._state[group]:
value = self._state[group][name]
diff --git a/novelwriter/gui/noveltree.py b/novelwriter/gui/noveltree.py
index 0742ab548..246837333 100644
--- a/novelwriter/gui/noveltree.py
+++ b/novelwriter/gui/noveltree.py
@@ -29,9 +29,10 @@
from enum import Enum
from time import time
+from typing import TYPE_CHECKING
-from PyQt5.QtGui import QFont, QPalette
-from PyQt5.QtCore import Qt, QSize, pyqtSlot, pyqtSignal
+from PyQt5.QtGui import QFocusEvent, QFont, QMouseEvent, QPalette, QResizeEvent
+from PyQt5.QtCore import QModelIndex, QPoint, Qt, QSize, pyqtSlot, pyqtSignal
from PyQt5.QtWidgets import (
QAbstractItemView, QActionGroup, QFrame, QHBoxLayout, QHeaderView,
QInputDialog, QMenu, QSizePolicy, QToolButton, QToolTip, QTreeWidget,
@@ -42,8 +43,12 @@
from novelwriter.enum import nwDocMode, nwItemClass, nwOutline
from novelwriter.common import minmax
from novelwriter.constants import nwHeaders, nwKeyWords, nwLabels, trConst
+from novelwriter.core.index import IndexHeading
from novelwriter.extensions.novelselector import NovelSelector
+if TYPE_CHECKING: # pragma: no cover
+ from novelwriter.guimain import GuiMain
+
logger = logging.getLogger(__name__)
@@ -63,7 +68,7 @@ class GuiNovelView(QWidget):
selectedItemChanged = pyqtSignal(str)
openDocumentRequest = pyqtSignal(str, Enum, str, bool)
- def __init__(self, mainGui):
+ def __init__(self, mainGui: GuiMain) -> None:
super().__init__(parent=mainGui)
self.mainGui = mainGui
@@ -92,33 +97,29 @@ def __init__(self, mainGui):
# Methods
##
- def updateTheme(self):
- """Update theme elements.
- """
+ def updateTheme(self) -> None:
+ """Update theme elements."""
self.novelBar.updateTheme()
self.novelTree.updateTheme()
self.refreshTree()
return
- def initSettings(self):
- """Initialise GUI elements that depend on specific settings.
- """
+ def initSettings(self) -> None:
+ """Initialise GUI elements that depend on specific settings."""
self.novelTree.initSettings()
return
- def clearNovelView(self):
- """Clear project-related GUI content.
- """
+ def clearNovelView(self) -> None:
+ """Clear project-related GUI content."""
self.novelTree.clearContent()
self.novelBar.clearContent()
self.novelBar.setEnabled(False)
return
- def openProjectTasks(self):
- """Run open project tasks.
- """
+ def openProjectTasks(self) -> None:
+ """Run open project tasks."""
lastNovel = SHARED.project.data.getLastHandle("novelTree")
- if lastNovel not in SHARED.project.tree:
+ if lastNovel and lastNovel not in SHARED.project.tree:
lastNovel = SHARED.project.tree.findRoot(nwItemClass.NOVEL)
logger.debug("Setting novel tree to root item '%s'", lastNovel)
@@ -140,7 +141,7 @@ def openProjectTasks(self):
return
- def closeProjectTasks(self):
+ def closeProjectTasks(self) -> None:
"""Run closing project tasks."""
lastColType = self.novelTree.lastColType
lastColSize = self.novelTree.lastColSize
@@ -150,15 +151,13 @@ def closeProjectTasks(self):
self.clearNovelView()
return
- def setTreeFocus(self):
- """Set the focus to the tree widget.
- """
+ def setTreeFocus(self) -> None:
+ """Set the focus to the tree widget."""
self.novelTree.setFocus()
return
- def treeHasFocus(self):
- """Check if the novel tree has focus.
- """
+ def treeHasFocus(self) -> bool:
+ """Check if the novel tree has focus."""
return self.novelTree.hasFocus()
##
@@ -166,21 +165,19 @@ def treeHasFocus(self):
##
@pyqtSlot()
- def refreshTree(self):
- """Refresh the current tree.
- """
+ def refreshTree(self) -> None:
+ """Refresh the current tree."""
self.novelTree.refreshTree(rootHandle=SHARED.project.data.getLastHandle("novelTree"))
return
@pyqtSlot(str)
- def updateRootItem(self, tHandle):
- """If any root item changes, rebuild the novel root menu.
- """
+ def updateRootItem(self, tHandle: str) -> None:
+ """If any root item changes, rebuild the novel root menu."""
self.novelBar.buildNovelRootMenu()
return
@pyqtSlot(str)
- def updateNovelItemMeta(self, tHandle):
+ def updateNovelItemMeta(self, tHandle: str) -> None:
"""The meta data of a novel item has changed, and the tree item
needs to be refreshed.
"""
@@ -192,7 +189,7 @@ def updateNovelItemMeta(self, tHandle):
class GuiNovelToolBar(QWidget):
- def __init__(self, novelView):
+ def __init__(self, novelView: GuiNovelView) -> None:
super().__init__(parent=novelView)
logger.debug("Create: GuiNovelToolBar")
@@ -269,9 +266,8 @@ def __init__(self, novelView):
# Methods
##
- def updateTheme(self):
- """Update theme elements.
- """
+ def updateTheme(self) -> None:
+ """Update theme elements."""
# Icons
self.tbNovel.setIcon(SHARED.theme.getIcon("cls_novel"))
self.tbRefresh.setIcon(SHARED.theme.getIcon("refresh"))
@@ -287,10 +283,11 @@ def updateTheme(self):
"QToolButton {{padding: {0}px; border: none; background: transparent;}} "
"QToolButton:hover {{border: none; background: rgba({1},{2},{3},0.2);}}"
).format(CONFIG.pxInt(2), fadeCol.red(), fadeCol.green(), fadeCol.blue())
+ buttonStyleMenu = f"{buttonStyle} QToolButton::menu-indicator {{image: none;}}"
self.tbNovel.setStyleSheet(buttonStyle)
self.tbRefresh.setStyleSheet(buttonStyle)
- self.tbMore.setStyleSheet(buttonStyle)
+ self.tbMore.setStyleSheet(buttonStyleMenu)
self.novelValue.setStyleSheet(
"QComboBox {border-style: none; padding-left: 0;} "
@@ -301,30 +298,26 @@ def updateTheme(self):
return
- def clearContent(self):
- """Run clearing project tasks.
- """
+ def clearContent(self) -> None:
+ """Run clearing project tasks."""
self.novelValue.clear()
self.novelValue.setToolTip("")
return
- def buildNovelRootMenu(self):
- """Build the novel root menu.
- """
+ def buildNovelRootMenu(self) -> None:
+ """Build the novel root menu."""
self.novelValue.updateList(prefix=self.novelPrefix)
self.tbNovel.setVisible(self.novelValue.count() > 1)
return
- def setCurrentRoot(self, rootHandle):
- """Set the current active root handle.
- """
+ def setCurrentRoot(self, rootHandle: str | None) -> None:
+ """Set the current active root handle."""
self.novelValue.setHandle(rootHandle)
self.novelView.novelTree.refreshTree(rootHandle=rootHandle, overRide=True)
return
- def setLastColType(self, colType, doRefresh=True):
- """Set the last column type.
- """
+ def setLastColType(self, colType: NovelTreeColumn, doRefresh: bool = True) -> None:
+ """Set the last column type."""
self.aLastCol[colType].setChecked(True)
self.novelView.novelTree.setLastColType(colType, doRefresh=doRefresh)
return
@@ -334,24 +327,21 @@ def setLastColType(self, colType, doRefresh=True):
##
@pyqtSlot()
- def _openNovelSelector(self):
- """Trigger the dropdown list of the novel selector.
- """
+ def _openNovelSelector(self) -> None:
+ """Trigger the dropdown list of the novel selector."""
self.novelValue.showPopup()
return
@pyqtSlot()
- def _refreshNovelTree(self):
- """Rebuild the current tree.
- """
+ def _refreshNovelTree(self) -> None:
+ """Rebuild the current tree."""
rootHandle = SHARED.project.data.getLastHandle("novelTree")
self.novelView.novelTree.refreshTree(rootHandle=rootHandle, overRide=True)
return
@pyqtSlot()
- def _selectLastColumnSize(self):
- """Set the maximum width for the last column.
- """
+ def _selectLastColumnSize(self) -> None:
+ """Set the maximum width for the last column."""
oldSize = self.novelView.novelTree.lastColSize
newSize, isOk = QInputDialog.getInt(
self, self.tr("Column Size"), self.tr("Maximum column size in %"), oldSize, 15, 75, 5
@@ -365,9 +355,8 @@ def _selectLastColumnSize(self):
# Internal Functions
##
- def _addLastColAction(self, colType, actionLabel):
- """Add a column selection entry to the last column menu.
- """
+ def _addLastColAction(self, colType, actionLabel) -> None:
+ """Add a column selection entry to the last column menu."""
aLast = self.mLastCol.addAction(actionLabel)
aLast.setCheckable(True)
aLast.setActionGroup(self.gLastCol)
@@ -391,7 +380,7 @@ class GuiNovelTree(QTreeWidget):
D_KEY = Qt.ItemDataRole.UserRole + 2
D_EXTRA = Qt.ItemDataRole.UserRole + 3
- def __init__(self, novelView):
+ def __init__(self, novelView: GuiNovelView) -> None:
super().__init__(parent=novelView)
logger.debug("Create: GuiNovelTree")
@@ -461,9 +450,8 @@ def __init__(self, novelView):
return
- def initSettings(self):
- """Set or update tree widget settings.
- """
+ def initSettings(self) -> None:
+ """Set or update tree widget settings."""
# Scroll bars
if CONFIG.hideVScroll:
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
@@ -477,9 +465,8 @@ def initSettings(self):
return
- def updateTheme(self):
- """Update theme elements.
- """
+ def updateTheme(self) -> None:
+ """Update theme elements."""
iPx = SHARED.theme.baseIconSize
self._pMore = SHARED.theme.loadDecoration("deco_doc_more", pxH=iPx)
return
@@ -489,28 +476,26 @@ def updateTheme(self):
##
@property
- def lastColType(self):
+ def lastColType(self) -> NovelTreeColumn:
return self._lastCol
@property
- def lastColSize(self):
+ def lastColSize(self) -> int:
return int(self._lastColSize * 100)
##
# Class Methods
##
- def clearContent(self):
- """Clear the GUI content and the related maps.
- """
+ def clearContent(self) -> None:
+ """Clear the GUI content and the related maps."""
self.clear()
self._treeMap = {}
self._lastBuild = 0
return
- def refreshTree(self, rootHandle=None, overRide=False):
- """Refresh the tree if it has been changed.
- """
+ def refreshTree(self, rootHandle: str | None = None, overRide: bool = False) -> None:
+ """Refresh the tree if it has been changed."""
logger.debug("Requesting refresh of the novel tree")
if rootHandle is None:
rootHandle = SHARED.project.tree.findRoot(nwItemClass.NOVEL)
@@ -534,9 +519,8 @@ def refreshTree(self, rootHandle=None, overRide=False):
return
- def refreshHandle(self, tHandle):
- """Refresh the data for a given handle.
- """
+ def refreshHandle(self, tHandle: str) -> None:
+ """Refresh the data for a given handle."""
idxData = SHARED.project.index.getItemData(tHandle)
if idxData is None:
return
@@ -554,7 +538,7 @@ def refreshHandle(self, tHandle):
return
- def getSelectedHandle(self):
+ def getSelectedHandle(self) -> tuple[str | None, str | None]:
"""Get the currently selected or active handle. If multiple
items are selected, return the first.
"""
@@ -566,9 +550,8 @@ def getSelectedHandle(self):
return tHandle, sTitle
return None, None
- def setLastColType(self, colType, doRefresh=True):
- """Change the content type of the last column and rebuild.
- """
+ def setLastColType(self, colType: NovelTreeColumn, doRefresh: bool = True) -> None:
+ """Change the content type of the last column and rebuild."""
if self._lastCol != colType:
logger.debug("Changing last column to %s", colType.name)
self._lastCol = colType
@@ -578,15 +561,13 @@ def setLastColType(self, colType, doRefresh=True):
self.refreshTree(rootHandle=lastNovel, overRide=True)
return
- def setLastColSize(self, colSize):
- """Set the column size in integer values between 15 and 75.
- """
+ def setLastColSize(self, colSize: int) -> None:
+ """Set the column size in integer values between 15 and 75."""
self._lastColSize = minmax(colSize, 15, 75)/100.0
return
- def setActiveHandle(self, tHandle):
- """Highlight the rows associated with a given handle.
- """
+ def setActiveHandle(self, tHandle: str | None) -> None:
+ """Highlight the rows associated with a given handle."""
tStart = time()
self._actHandle = tHandle
@@ -612,20 +593,20 @@ def setActiveHandle(self, tHandle):
# Events
##
- def mousePressEvent(self, theEvent):
+ def mousePressEvent(self, event: QMouseEvent) -> None:
"""Overload mousePressEvent to clear selection if clicking the
mouse in a blank area of the tree view, and to load a document
for viewing if the user middle-clicked.
"""
- super().mousePressEvent(theEvent)
+ super().mousePressEvent(event)
- if theEvent.button() == Qt.LeftButton:
- selItem = self.indexAt(theEvent.pos())
+ if event.button() == Qt.LeftButton:
+ selItem = self.indexAt(event.pos())
if not selItem.isValid():
self.clearSelection()
- elif theEvent.button() == Qt.MiddleButton:
- selItem = self.itemAt(theEvent.pos())
+ elif event.button() == Qt.MiddleButton:
+ selItem = self.itemAt(event.pos())
if not isinstance(selItem, QTreeWidgetItem):
return
@@ -637,16 +618,14 @@ def mousePressEvent(self, theEvent):
return
- def focusOutEvent(self, theEvent):
- """Clear the selection when the tree no longer has focus.
- """
- super().focusOutEvent(theEvent)
+ def focusOutEvent(self, event: QFocusEvent) -> None:
+ """Clear the selection when the tree no longer has focus."""
+ super().focusOutEvent(event)
self.clearSelection()
return
- def resizeEvent(self, event):
- """Elide labels in the extra column.
- """
+ def resizeEvent(self, event: QResizeEvent) -> None:
+ """Elide labels in the extra column."""
super().resizeEvent(event)
newW = event.size().width()
oldW = event.oldSize().width()
@@ -665,18 +644,17 @@ def resizeEvent(self, event):
##
@pyqtSlot("QModelIndex")
- def _treeItemClicked(self, mIndex):
- """The user clicked on an item in the tree.
- """
- if mIndex.column() == self.C_MORE:
- tHandle = mIndex.siblingAtColumn(self.C_DATA).data(self.D_HANDLE)
- sTitle = mIndex.siblingAtColumn(self.C_DATA).data(self.D_TITLE)
- tipPos = self.mapToGlobal(self.visualRect(mIndex).topRight())
+ def _treeItemClicked(self, index: QModelIndex) -> None:
+ """The user clicked on an item in the tree."""
+ if index.column() == self.C_MORE:
+ tHandle = index.siblingAtColumn(self.C_DATA).data(self.D_HANDLE)
+ sTitle = index.siblingAtColumn(self.C_DATA).data(self.D_TITLE)
+ tipPos = self.mapToGlobal(self.visualRect(index).topRight())
self._popMetaBox(tipPos, tHandle, sTitle)
return
@pyqtSlot()
- def _treeSelectionChange(self):
+ def _treeSelectionChange(self) -> None:
"""Extract the handle and line number of the currently selected
title, and send it to the tree meta panel.
"""
@@ -686,7 +664,7 @@ def _treeSelectionChange(self):
return
@pyqtSlot("QTreeWidgetItem*", int)
- def _treeDoubleClick(self, tItem, colNo):
+ def _treeDoubleClick(self, item: QTreeWidgetItem, column: int) -> None:
"""Extract the handle and line number of the title double-
clicked, and send it to the main gui class for opening in the
document editor.
@@ -699,9 +677,8 @@ def _treeDoubleClick(self, tItem, colNo):
# Internal Functions
##
- def _populateTree(self, rootHandle):
- """Build the tree based on the project index.
- """
+ def _populateTree(self, rootHandle: str | None) -> None:
+ """Build the tree based on the project index."""
self.clearContent()
tStart = time()
logger.debug("Building novel tree for root item '%s'", rootHandle)
@@ -728,9 +705,9 @@ def _populateTree(self, rootHandle):
return
- def _updateTreeItemValues(self, trItem, idxItem, tHandle, sTitle):
- """Set the tree item values from the index entry.
- """
+ def _updateTreeItemValues(self, trItem: QTreeWidgetItem, idxItem: IndexHeading,
+ tHandle: str, sTitle: str) -> None:
+ """Set the tree item values from the index entry."""
iLevel = nwHeaders.H_LEVEL.get(idxItem.level, 0)
hDec = SHARED.theme.getHeaderDecoration(iLevel)
@@ -750,9 +727,8 @@ def _updateTreeItemValues(self, trItem, idxItem, tHandle, sTitle):
return
- def _getLastColumnText(self, tHandle, sTitle):
- """Generate the text for the last column based on user settings.
- """
+ def _getLastColumnText(self, tHandle: str, sTitle: str) -> tuple[str, str]:
+ """Generate text for the last column based on user settings."""
if self._lastCol == NovelTreeColumn.HIDDEN:
return "", ""
@@ -777,14 +753,15 @@ def _getLastColumnText(self, tHandle, sTitle):
return "", ""
- def _popMetaBox(self, qPos, tHandle, sTitle):
- """Show the novel meta data box.
- """
+ def _popMetaBox(self, qPos: QPoint, tHandle: str, sTitle: str) -> None:
+ """Show the novel meta data box."""
logger.debug("Generating meta data tooltip for '%s:%s'", tHandle, sTitle)
pIndex = SHARED.project.index
novIdx = pIndex.getItemHeader(tHandle, sTitle)
refTags = pIndex.getReferences(tHandle, sTitle)
+ if not novIdx:
+ return
synopText = novIdx.synopsis
if synopText:
@@ -814,9 +791,8 @@ def _popMetaBox(self, qPos, tHandle, sTitle):
return
@staticmethod
- def _appendMetaTag(refs, key, lines):
- """Generate a reference list for a given reference key.
- """
+ def _appendMetaTag(refs: dict, key: str, lines: list[str]) -> list[str]:
+ """Generate a reference list for a given reference key."""
tags = ", ".join(refs.get(key, []))
if tags:
lines.append(f"{trConst(nwLabels.KEY_NAME[key])}: {tags}")
diff --git a/novelwriter/gui/outline.py b/novelwriter/gui/outline.py
index 84234ccfe..596a422b0 100644
--- a/novelwriter/gui/outline.py
+++ b/novelwriter/gui/outline.py
@@ -261,6 +261,7 @@ def updateTheme(self) -> None:
self.novelValue.updateList(includeAll=True)
self.aRefresh.setIcon(SHARED.theme.getIcon("refresh"))
self.tbColumns.setIcon(SHARED.theme.getIcon("menu"))
+ self.tbColumns.setStyleSheet("QToolButton::menu-indicator {image: none;}")
return
def populateNovelList(self) -> None:
diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py
index 787f5671d..d1c2f903d 100644
--- a/novelwriter/gui/projtree.py
+++ b/novelwriter/gui/projtree.py
@@ -361,12 +361,13 @@ def updateTheme(self) -> None:
"QToolButton {{padding: {0}px; border: none; background: transparent;}} "
"QToolButton:hover {{border: none; background: rgba({1},{2},{3},0.2);}}"
).format(CONFIG.pxInt(2), fadeCol.red(), fadeCol.green(), fadeCol.blue())
+ buttonStyleMenu = f"{buttonStyle} QToolButton::menu-indicator {{image: none;}}"
- self.tbQuick.setStyleSheet(buttonStyle)
+ self.tbQuick.setStyleSheet(buttonStyleMenu)
self.tbMoveU.setStyleSheet(buttonStyle)
self.tbMoveD.setStyleSheet(buttonStyle)
- self.tbAdd.setStyleSheet(buttonStyle)
- self.tbMore.setStyleSheet(buttonStyle)
+ self.tbAdd.setStyleSheet(buttonStyleMenu)
+ self.tbMore.setStyleSheet(buttonStyleMenu)
self.tbQuick.setIcon(SHARED.theme.getIcon("bookmark"))
self.tbMoveU.setIcon(SHARED.theme.getIcon("up"))
From 1a6072be2c47b6728049595cebd399507a378287 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Wed, 8 Nov 2023 16:54:42 +0100
Subject: [PATCH 04/10] Add editor toolbar icons
---
.../assets/icons/typicons_dark/icons.conf | 8 ++++
.../assets/icons/typicons_dark/nw_tb-bold.svg | 4 ++
.../icons/typicons_dark/nw_tb-italic.svg | 4 ++
.../icons/typicons_dark/nw_tb-markdown.svg | 8 ++++
.../icons/typicons_dark/nw_tb-shortcode.svg | 8 ++++
.../icons/typicons_dark/nw_tb-strike.svg | 4 ++
.../icons/typicons_dark/nw_tb-subscript.svg | 5 ++
.../icons/typicons_dark/nw_tb-superscript.svg | 5 ++
.../icons/typicons_dark/nw_tb-underline.svg | 5 ++
.../assets/icons/typicons_light/icons.conf | 8 ++++
.../icons/typicons_light/nw_tb-bold.svg | 4 ++
.../icons/typicons_light/nw_tb-italic.svg | 4 ++
.../icons/typicons_light/nw_tb-markdown.svg | 8 ++++
.../icons/typicons_light/nw_tb-shortcode.svg | 8 ++++
.../icons/typicons_light/nw_tb-strike.svg | 4 ++
.../icons/typicons_light/nw_tb-underline.svg | 5 ++
novelwriter/gui/doceditor.py | 43 +++++++++++++----
novelwriter/gui/docviewer.py | 24 +++-------
novelwriter/gui/theme.py | 47 ++++++++++++++-----
19 files changed, 168 insertions(+), 38 deletions(-)
create mode 100644 novelwriter/assets/icons/typicons_dark/nw_tb-bold.svg
create mode 100644 novelwriter/assets/icons/typicons_dark/nw_tb-italic.svg
create mode 100644 novelwriter/assets/icons/typicons_dark/nw_tb-markdown.svg
create mode 100644 novelwriter/assets/icons/typicons_dark/nw_tb-shortcode.svg
create mode 100644 novelwriter/assets/icons/typicons_dark/nw_tb-strike.svg
create mode 100644 novelwriter/assets/icons/typicons_dark/nw_tb-subscript.svg
create mode 100644 novelwriter/assets/icons/typicons_dark/nw_tb-superscript.svg
create mode 100644 novelwriter/assets/icons/typicons_dark/nw_tb-underline.svg
create mode 100644 novelwriter/assets/icons/typicons_light/nw_tb-bold.svg
create mode 100644 novelwriter/assets/icons/typicons_light/nw_tb-italic.svg
create mode 100644 novelwriter/assets/icons/typicons_light/nw_tb-markdown.svg
create mode 100644 novelwriter/assets/icons/typicons_light/nw_tb-shortcode.svg
create mode 100644 novelwriter/assets/icons/typicons_light/nw_tb-strike.svg
create mode 100644 novelwriter/assets/icons/typicons_light/nw_tb-underline.svg
diff --git a/novelwriter/assets/icons/typicons_dark/icons.conf b/novelwriter/assets/icons/typicons_dark/icons.conf
index c84461231..94b4c6337 100644
--- a/novelwriter/assets/icons/typicons_dark/icons.conf
+++ b/novelwriter/assets/icons/typicons_dark/icons.conf
@@ -46,6 +46,14 @@ cross = typ_times.svg
down = typ_chevron-down.svg
edit = typ_pencil.svg
export = typ_export.svg
+fmt_bold = nw_tb-bold.svg
+fmt_italic = nw_tb-italic.svg
+fmt_mode-md = nw_tb-markdown.svg
+fmt_mode-sc = nw_tb-shortcode.svg
+fmt_strike = nw_tb-strike.svg
+fmt_subscript = nw_tb-subscript.svg
+fmt_superscript = nw_tb-superscript.svg
+fmt_underline = nw_tb-underline.svg
forward = typ_chevron-right.svg
maximise = typ_arrow-maximise.svg
menu = typ_th-dot-menu.svg
diff --git a/novelwriter/assets/icons/typicons_dark/nw_tb-bold.svg b/novelwriter/assets/icons/typicons_dark/nw_tb-bold.svg
new file mode 100644
index 000000000..2eaeb846c
--- /dev/null
+++ b/novelwriter/assets/icons/typicons_dark/nw_tb-bold.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/novelwriter/assets/icons/typicons_dark/nw_tb-italic.svg b/novelwriter/assets/icons/typicons_dark/nw_tb-italic.svg
new file mode 100644
index 000000000..0ec5fb2a1
--- /dev/null
+++ b/novelwriter/assets/icons/typicons_dark/nw_tb-italic.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/novelwriter/assets/icons/typicons_dark/nw_tb-markdown.svg b/novelwriter/assets/icons/typicons_dark/nw_tb-markdown.svg
new file mode 100644
index 000000000..027685965
--- /dev/null
+++ b/novelwriter/assets/icons/typicons_dark/nw_tb-markdown.svg
@@ -0,0 +1,8 @@
+
+
diff --git a/novelwriter/assets/icons/typicons_dark/nw_tb-shortcode.svg b/novelwriter/assets/icons/typicons_dark/nw_tb-shortcode.svg
new file mode 100644
index 000000000..4ea59ec38
--- /dev/null
+++ b/novelwriter/assets/icons/typicons_dark/nw_tb-shortcode.svg
@@ -0,0 +1,8 @@
+
+
diff --git a/novelwriter/assets/icons/typicons_dark/nw_tb-strike.svg b/novelwriter/assets/icons/typicons_dark/nw_tb-strike.svg
new file mode 100644
index 000000000..639dd6941
--- /dev/null
+++ b/novelwriter/assets/icons/typicons_dark/nw_tb-strike.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/novelwriter/assets/icons/typicons_dark/nw_tb-subscript.svg b/novelwriter/assets/icons/typicons_dark/nw_tb-subscript.svg
new file mode 100644
index 000000000..7566bdee5
--- /dev/null
+++ b/novelwriter/assets/icons/typicons_dark/nw_tb-subscript.svg
@@ -0,0 +1,5 @@
+
+
diff --git a/novelwriter/assets/icons/typicons_dark/nw_tb-superscript.svg b/novelwriter/assets/icons/typicons_dark/nw_tb-superscript.svg
new file mode 100644
index 000000000..0ac13bf39
--- /dev/null
+++ b/novelwriter/assets/icons/typicons_dark/nw_tb-superscript.svg
@@ -0,0 +1,5 @@
+
+
diff --git a/novelwriter/assets/icons/typicons_dark/nw_tb-underline.svg b/novelwriter/assets/icons/typicons_dark/nw_tb-underline.svg
new file mode 100644
index 000000000..3a249f4df
--- /dev/null
+++ b/novelwriter/assets/icons/typicons_dark/nw_tb-underline.svg
@@ -0,0 +1,5 @@
+
+
diff --git a/novelwriter/assets/icons/typicons_light/icons.conf b/novelwriter/assets/icons/typicons_light/icons.conf
index d9aa905c5..623145114 100644
--- a/novelwriter/assets/icons/typicons_light/icons.conf
+++ b/novelwriter/assets/icons/typicons_light/icons.conf
@@ -46,6 +46,14 @@ cross = typ_times.svg
down = typ_chevron-down.svg
edit = typ_pencil.svg
export = typ_export.svg
+fmt_bold = nw_tb-bold.svg
+fmt_italic = nw_tb-italic.svg
+fmt_mode-md = nw_tb-markdown.svg
+fmt_mode-sc = nw_tb-shortcode.svg
+fmt_strike = nw_tb-strike.svg
+fmt_subscript = nw_tb-subscript.svg
+fmt_superscript = nw_tb-superscript.svg
+fmt_underline = nw_tb-underline.svg
forward = typ_chevron-right.svg
maximise = typ_arrow-maximise.svg
menu = typ_th-dot-menu.svg
diff --git a/novelwriter/assets/icons/typicons_light/nw_tb-bold.svg b/novelwriter/assets/icons/typicons_light/nw_tb-bold.svg
new file mode 100644
index 000000000..3fbfbc881
--- /dev/null
+++ b/novelwriter/assets/icons/typicons_light/nw_tb-bold.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/novelwriter/assets/icons/typicons_light/nw_tb-italic.svg b/novelwriter/assets/icons/typicons_light/nw_tb-italic.svg
new file mode 100644
index 000000000..893d8930d
--- /dev/null
+++ b/novelwriter/assets/icons/typicons_light/nw_tb-italic.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/novelwriter/assets/icons/typicons_light/nw_tb-markdown.svg b/novelwriter/assets/icons/typicons_light/nw_tb-markdown.svg
new file mode 100644
index 000000000..24b3809d7
--- /dev/null
+++ b/novelwriter/assets/icons/typicons_light/nw_tb-markdown.svg
@@ -0,0 +1,8 @@
+
+
diff --git a/novelwriter/assets/icons/typicons_light/nw_tb-shortcode.svg b/novelwriter/assets/icons/typicons_light/nw_tb-shortcode.svg
new file mode 100644
index 000000000..fc19a690f
--- /dev/null
+++ b/novelwriter/assets/icons/typicons_light/nw_tb-shortcode.svg
@@ -0,0 +1,8 @@
+
+
diff --git a/novelwriter/assets/icons/typicons_light/nw_tb-strike.svg b/novelwriter/assets/icons/typicons_light/nw_tb-strike.svg
new file mode 100644
index 000000000..89f4b7ee5
--- /dev/null
+++ b/novelwriter/assets/icons/typicons_light/nw_tb-strike.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/novelwriter/assets/icons/typicons_light/nw_tb-underline.svg b/novelwriter/assets/icons/typicons_light/nw_tb-underline.svg
new file mode 100644
index 000000000..ca122269c
--- /dev/null
+++ b/novelwriter/assets/icons/typicons_light/nw_tb-underline.svg
@@ -0,0 +1,5 @@
+
+
diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py
index f12220e51..6e204219b 100644
--- a/novelwriter/gui/doceditor.py
+++ b/novelwriter/gui/doceditor.py
@@ -2107,18 +2107,39 @@ def __init__(self, docEditor: GuiDocEditor) -> None:
# General Buttons
# ===============
- self.tbSticky = QToolButton(self)
- self.tbSticky.setIconSize(iconSize)
+ self.tbMode = QToolButton(self)
+ self.tbMode.setIconSize(iconSize)
+ self.tbMode.setCheckable(True)
- self.tbEdit = QToolButton(self)
- self.tbEdit.setIconSize(iconSize)
+ self.tbBold = QToolButton(self)
+ self.tbBold.setIconSize(iconSize)
+
+ self.tbItalic = QToolButton(self)
+ self.tbItalic.setIconSize(iconSize)
+
+ self.tbStrike = QToolButton(self)
+ self.tbStrike.setIconSize(iconSize)
+
+ self.tbUnderline = QToolButton(self)
+ self.tbUnderline.setIconSize(iconSize)
+
+ self.tbSuperscript = QToolButton(self)
+ self.tbSuperscript.setIconSize(iconSize)
+
+ self.tbSubscript = QToolButton(self)
+ self.tbSubscript.setIconSize(iconSize)
# Assemble
# ========
self.outerBox = QVBoxLayout()
- self.outerBox.addWidget(self.tbSticky)
- self.outerBox.addWidget(self.tbEdit)
+ self.outerBox.addWidget(self.tbMode)
+ self.outerBox.addWidget(self.tbBold)
+ self.outerBox.addWidget(self.tbItalic)
+ self.outerBox.addWidget(self.tbStrike)
+ self.outerBox.addWidget(self.tbUnderline)
+ self.outerBox.addWidget(self.tbSuperscript)
+ self.outerBox.addWidget(self.tbSubscript)
self.outerBox.setContentsMargins(cM, cM, cM, cM)
self.outerBox.setSpacing(cM)
@@ -2150,8 +2171,14 @@ def updateTheme(self) -> None:
# buttonStyleMenu = f"{buttonStyle} QToolButton::menu-indicator {{image: none;}}"
# self.tbEdit.setStyleSheet(buttonStyle)
- self.tbSticky.setIcon(SHARED.theme.getIcon("sticky-off"))
- self.tbEdit.setIcon(SHARED.theme.getIcon("edit"))
+ tPx = int(0.8*SHARED.theme.fontPixelSize)
+ self.tbMode.setIcon(SHARED.theme.getToggleIcon("fmt_mode", (tPx, tPx)))
+ self.tbBold.setIcon(SHARED.theme.getIcon("fmt_bold"))
+ self.tbItalic.setIcon(SHARED.theme.getIcon("fmt_italic"))
+ self.tbStrike.setIcon(SHARED.theme.getIcon("fmt_strike"))
+ self.tbUnderline.setIcon(SHARED.theme.getIcon("fmt_underline"))
+ self.tbSuperscript.setIcon(SHARED.theme.getIcon("fmt_superscript"))
+ self.tbSubscript.setIcon(SHARED.theme.getIcon("fmt_subscript"))
return
diff --git a/novelwriter/gui/docviewer.py b/novelwriter/gui/docviewer.py
index 9aff4eee5..ff548fb68 100644
--- a/novelwriter/gui/docviewer.py
+++ b/novelwriter/gui/docviewer.py
@@ -34,8 +34,8 @@
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QPoint, QSize, Qt, QUrl
from PyQt5.QtGui import (
- QColor, QCursor, QFont, QIcon, QMouseEvent, QPalette, QResizeEvent,
- QTextCursor, QTextOption
+ QColor, QCursor, QFont, QMouseEvent, QPalette, QResizeEvent, QTextCursor,
+ QTextOption
)
from PyQt5.QtWidgets import (
QAction, qApp, QFrame, QHBoxLayout, QLabel, QMenu, QScrollArea,
@@ -1020,24 +1020,12 @@ def __init__(self, docViewer):
# Methods
##
- def updateTheme(self):
- """Update theme elements.
- """
+ def updateTheme(self) -> None:
+ """Update theme elements."""
# Icons
-
fPx = int(0.9*SHARED.theme.fontPixelSize)
-
- stickyOn = SHARED.theme.getPixmap("sticky-on", (fPx, fPx))
- stickyOff = SHARED.theme.getPixmap("sticky-off", (fPx, fPx))
- stickyIcon = QIcon()
- stickyIcon.addPixmap(stickyOn, QIcon.Normal, QIcon.On)
- stickyIcon.addPixmap(stickyOff, QIcon.Normal, QIcon.Off)
-
- bulletOn = SHARED.theme.getPixmap("bullet-on", (fPx, fPx))
- bulletOff = SHARED.theme.getPixmap("bullet-off", (fPx, fPx))
- bulletIcon = QIcon()
- bulletIcon.addPixmap(bulletOn, QIcon.Normal, QIcon.On)
- bulletIcon.addPixmap(bulletOff, QIcon.Normal, QIcon.Off)
+ stickyIcon = SHARED.theme.getToggleIcon("sticky", (fPx, fPx))
+ bulletIcon = SHARED.theme.getToggleIcon("bullet", (fPx, fPx))
self.showHide.setIcon(SHARED.theme.getIcon("reference"))
self.stickyRefs.setIcon(stickyIcon)
diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py
index da8f1be4f..22d8cf959 100644
--- a/novelwriter/gui/theme.py
+++ b/novelwriter/gui/theme.py
@@ -130,6 +130,7 @@ def __init__(self):
self.getIcon = self.iconCache.getIcon
self.getPixmap = self.iconCache.getPixmap
self.getItemIcon = self.iconCache.getItemIcon
+ self.getToggleIcon = self.iconCache.getToggleIcon
self.loadDecoration = self.iconCache.loadDecoration
self.getHeaderDecoration = self.iconCache.getHeaderDecoration
@@ -447,14 +448,22 @@ class GuiIcons:
ICON_KEYS = {
# Project and GUI Icons
"novelwriter", "alert_error", "alert_info", "alert_question", "alert_warn",
- "build_excluded", "build_filtered", "build_included", "cls_archive", "cls_character",
- "cls_custom", "cls_entity", "cls_none", "cls_novel", "cls_object", "cls_plot",
- "cls_timeline", "cls_trash", "cls_world", "proj_chapter", "proj_details", "proj_document",
- "proj_folder", "proj_note", "proj_nwx", "proj_section", "proj_scene", "proj_stats",
- "proj_title", "search_cancel", "search_case", "search_loop", "search_preserve",
- "search_project", "search_regex", "search_word", "status_idle", "status_lang",
- "status_lines", "status_stats", "status_time", "view_build", "view_editor", "view_novel",
- "view_outline",
+ "build_excluded", "build_filtered", "build_included", "proj_chapter", "proj_details",
+ "proj_document", "proj_folder", "proj_note", "proj_nwx", "proj_section", "proj_scene",
+ "proj_stats", "proj_title", "status_idle", "status_lang", "status_lines", "status_stats",
+ "status_time", "view_build", "view_editor", "view_novel", "view_outline",
+
+ # Class Icons
+ "cls_archive", "cls_character", "cls_custom", "cls_entity", "cls_none", "cls_novel",
+ "cls_object", "cls_plot", "cls_timeline", "cls_trash", "cls_world",
+
+ # Search Icons
+ "search_cancel", "search_case", "search_loop", "search_preserve", "search_project",
+ "search_regex", "search_word",
+
+ # Format Icons
+ "fmt_bold", "fmt_italic", "fmt_mode-md", "fmt_mode-sc", "fmt_strike", "fmt_subscript",
+ "fmt_superscript", "fmt_underline",
# General Button Icons
"add", "backward", "bookmark", "browse", "checked", "close", "cross", "down", "edit",
@@ -469,6 +478,12 @@ class GuiIcons:
"deco_doc_h0", "deco_doc_h1", "deco_doc_h2", "deco_doc_h3", "deco_doc_h4", "deco_doc_more",
}
+ TOGGLE_ICON_KEYS = {
+ "sticky": ("sticky-on", "sticky-off"),
+ "bullet": ("bullet-on", "bullet-off"),
+ "fmt_mode": ("fmt_mode-sc", "fmt_mode-md"),
+ }
+
IMAGE_MAP = {
"wiz-back": "wizard-back.jpg",
}
@@ -602,10 +617,7 @@ def loadDecoration(self, decoKey, pxW=None, pxH=None):
return theDeco
def getIcon(self, iconKey):
- """Return an icon from the icon buffer. If it doesn't exist,
- return, load it, and if it still doesn't exist, return an empty
- icon.
- """
+ """Return an icon from the icon buffer, or load it."""
if iconKey in self._qIcons:
return self._qIcons[iconKey]
else:
@@ -613,6 +625,17 @@ def getIcon(self, iconKey):
self._qIcons[iconKey] = qIcon
return qIcon
+ def getToggleIcon(self, iconKey: str, iconSize: tuple[int, int]) -> QIcon:
+ """Return a toggle icon from the icon buffer. or load it."""
+ if iconKey in self.TOGGLE_ICON_KEYS:
+ pixOne = self.getPixmap(self.TOGGLE_ICON_KEYS[iconKey][0], iconSize)
+ pixTwo = self.getPixmap(self.TOGGLE_ICON_KEYS[iconKey][1], iconSize)
+ qIcon = QIcon()
+ qIcon.addPixmap(pixOne, QIcon.Normal, QIcon.On)
+ qIcon.addPixmap(pixTwo, QIcon.Normal, QIcon.Off)
+ return qIcon
+ return QIcon()
+
def getPixmap(self, iconKey, iconSize):
"""Return an icon from the icon buffer as a QPixmap. If it
doesn't exist, return an empty QPixmap.
From 711766cf52b5b54ae6982933768e5bfc16244188 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Wed, 8 Nov 2023 18:00:43 +0100
Subject: [PATCH 05/10] Change main menu to emit signals
---
.../icons/typicons_light/nw_tb-subscript.svg | 5 +
.../typicons_light/nw_tb-superscript.svg | 5 +
novelwriter/config.py | 30 +-
novelwriter/gui/doceditor.py | 106 ++++++--
novelwriter/gui/mainmenu.py | 256 +++++++++++++-----
novelwriter/guimain.py | 45 ++-
6 files changed, 324 insertions(+), 123 deletions(-)
create mode 100644 novelwriter/assets/icons/typicons_light/nw_tb-subscript.svg
create mode 100644 novelwriter/assets/icons/typicons_light/nw_tb-superscript.svg
diff --git a/novelwriter/assets/icons/typicons_light/nw_tb-subscript.svg b/novelwriter/assets/icons/typicons_light/nw_tb-subscript.svg
new file mode 100644
index 000000000..db909aed7
--- /dev/null
+++ b/novelwriter/assets/icons/typicons_light/nw_tb-subscript.svg
@@ -0,0 +1,5 @@
+
+
diff --git a/novelwriter/assets/icons/typicons_light/nw_tb-superscript.svg b/novelwriter/assets/icons/typicons_light/nw_tb-superscript.svg
new file mode 100644
index 000000000..1634872e7
--- /dev/null
+++ b/novelwriter/assets/icons/typicons_light/nw_tb-superscript.svg
@@ -0,0 +1,5 @@
+
+
diff --git a/novelwriter/config.py b/novelwriter/config.py
index 50c8c1abb..ebd0d05f7 100644
--- a/novelwriter/config.py
+++ b/novelwriter/config.py
@@ -178,9 +178,11 @@ def __init__(self) -> None:
self.spellLanguage = "en"
# State
- self.showRefPanel = True # The reference panel for the viewer is visible
- self.viewComments = True # Comments are shown in the viewer
- self.viewSynopsis = True # Synopsis is shown in the viewer
+ self.showRefPanel = True # The reference panel for the viewer is visible
+ self.showEditToolBar = False # The document editor toolbar visibility
+ self.useShortcodes = False # Use shorcodes for basic formatting
+ self.viewComments = True # Comments are shown in the viewer
+ self.viewSynopsis = True # Synopsis is shown in the viewer
# Search Bar Switches
self.searchCase = False
@@ -598,15 +600,17 @@ def loadConfig(self) -> bool:
# State
sec = "State"
- self.showRefPanel = conf.rdBool(sec, "showrefpanel", self.showRefPanel)
- self.viewComments = conf.rdBool(sec, "viewcomments", self.viewComments)
- self.viewSynopsis = conf.rdBool(sec, "viewsynopsis", self.viewSynopsis)
- self.searchCase = conf.rdBool(sec, "searchcase", self.searchCase)
- self.searchWord = conf.rdBool(sec, "searchword", self.searchWord)
- self.searchRegEx = conf.rdBool(sec, "searchregex", self.searchRegEx)
- self.searchLoop = conf.rdBool(sec, "searchloop", self.searchLoop)
- self.searchNextFile = conf.rdBool(sec, "searchnextfile", self.searchNextFile)
- self.searchMatchCap = conf.rdBool(sec, "searchmatchcap", self.searchMatchCap)
+ self.showRefPanel = conf.rdBool(sec, "showrefpanel", self.showRefPanel)
+ self.showEditToolBar = conf.rdBool(sec, "showedittoolbar", self.showEditToolBar)
+ self.useShortcodes = conf.rdBool(sec, "useshortcodes", self.useShortcodes)
+ self.viewComments = conf.rdBool(sec, "viewcomments", self.viewComments)
+ self.viewSynopsis = conf.rdBool(sec, "viewsynopsis", self.viewSynopsis)
+ self.searchCase = conf.rdBool(sec, "searchcase", self.searchCase)
+ self.searchWord = conf.rdBool(sec, "searchword", self.searchWord)
+ self.searchRegEx = conf.rdBool(sec, "searchregex", self.searchRegEx)
+ self.searchLoop = conf.rdBool(sec, "searchloop", self.searchLoop)
+ self.searchNextFile = conf.rdBool(sec, "searchnextfile", self.searchNextFile)
+ self.searchMatchCap = conf.rdBool(sec, "searchmatchcap", self.searchMatchCap)
# Deprecated Settings or Locations as of 2.0
# ToDo: These will be loaded for a few minor releases until the users have converted them
@@ -721,6 +725,8 @@ def saveConfig(self) -> bool:
conf["State"] = {
"showrefpanel": str(self.showRefPanel),
+ "showedittoolbar": str(self.showEditToolBar),
+ "useshortcodes": str(self.useShortcodes),
"viewcomments": str(self.viewComments),
"viewsynopsis": str(self.viewSynopsis),
"searchcase": str(self.searchCase),
diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py
index 6e204219b..efccc3617 100644
--- a/novelwriter/gui/doceditor.py
+++ b/novelwriter/gui/doceditor.py
@@ -138,9 +138,12 @@ def __init__(self, mainGui: GuiMain) -> None:
self.docFooter = GuiDocEditFooter(self)
self.docSearch = GuiDocEditSearch(self)
self.docToolBar = GuiDocToolBar(self)
+ self.docToolBar.setVisible(CONFIG.showEditToolBar)
# Connect Signals
self.docHeader.closeDocumentRequest.connect(self._closeCurrentDocument)
+ self.docHeader.toggleToolBarRequest.connect(self._toggleToolBarVisibility)
+ self.docToolBar.requestDocAction.connect(self.docAction)
# Context Menu
self.setContextMenuPolicy(Qt.CustomContextMenu)
@@ -849,31 +852,11 @@ def insertNewBlock(self, text: str, defaultAfter: bool = True) -> bool:
return True
- def insertKeyWord(self, keyword: str) -> bool:
- """Insert a keyword in the text editor, at the cursor position.
- If the insert line is not blank, a new line is started.
- """
- if keyword not in nwKeyWords.VALID_KEYS:
- logger.error("Invalid keyword '%s'", keyword)
- return False
- logger.debug("Inserting keyword '%s'", keyword)
- state = self.insertNewBlock("%s: " % keyword)
- return state
-
def closeSearch(self) -> bool:
"""Close the search box."""
self.docSearch.closeSearch()
return self.docSearch.isVisible()
- @pyqtSlot()
- def toggleSearch(self) -> None:
- """Toggle the visibility of the search box."""
- if self.docSearch.isVisible():
- self.docSearch.closeSearch()
- else:
- self.beginSearch()
- return
-
##
# Document Events and Maintenance
##
@@ -968,6 +951,27 @@ def updateDocInfo(self, tHandle: str) -> None:
self.updateDocMargins()
return
+ @pyqtSlot(str)
+ def insertKeyWord(self, keyword: str) -> bool:
+ """Insert a keyword in the text editor, at the cursor position.
+ If the insert line is not blank, a new line is started.
+ """
+ if keyword not in nwKeyWords.VALID_KEYS:
+ logger.error("Invalid keyword '%s'", keyword)
+ return False
+ logger.debug("Inserting keyword '%s'", keyword)
+ state = self.insertNewBlock("%s: " % keyword)
+ return state
+
+ @pyqtSlot()
+ def toggleSearch(self) -> None:
+ """Toggle the visibility of the search box."""
+ if self.docSearch.isVisible():
+ self.docSearch.closeSearch()
+ else:
+ self.beginSearch()
+ return
+
##
# Private Slots
##
@@ -1194,6 +1198,14 @@ def _closeCurrentDocument(self) -> None:
self.closeDocumentRequest.emit()
return
+ @pyqtSlot()
+ def _toggleToolBarVisibility(self) -> None:
+ """Toggle the visibility of the tool bar."""
+ state = not self.docToolBar.isVisible()
+ self.docToolBar.setVisible(state)
+ CONFIG.showEditToolBar = state
+ return
+
##
# Search & Replace
##
@@ -2094,6 +2106,8 @@ class BackgroundWordCounterSignals(QObject):
class GuiDocToolBar(QWidget):
+ requestDocAction = pyqtSignal(nwDocAction)
+
def __init__(self, docEditor: GuiDocEditor) -> None:
super().__init__(parent=docEditor)
@@ -2108,26 +2122,41 @@ def __init__(self, docEditor: GuiDocEditor) -> None:
# ===============
self.tbMode = QToolButton(self)
+ self.tbMode.setToolTip(self.tr("Toggle Markdown or Shortcodes Mode"))
self.tbMode.setIconSize(iconSize)
self.tbMode.setCheckable(True)
+ self.tbMode.setChecked(CONFIG.useShortcodes)
+ self.tbMode.toggled.connect(self._toggleFormatMode)
self.tbBold = QToolButton(self)
self.tbBold.setIconSize(iconSize)
+ self.tbBold.clicked.connect(self._formatBold)
self.tbItalic = QToolButton(self)
self.tbItalic.setIconSize(iconSize)
+ self.tbItalic.clicked.connect(self._formatItalic)
self.tbStrike = QToolButton(self)
self.tbStrike.setIconSize(iconSize)
+ self.tbStrike.clicked.connect(self._formatStrike)
self.tbUnderline = QToolButton(self)
self.tbUnderline.setIconSize(iconSize)
+ self.tbUnderline.clicked.connect(
+ lambda: self.requestDocAction.emit(nwDocAction.SC_ULINE)
+ )
self.tbSuperscript = QToolButton(self)
self.tbSuperscript.setIconSize(iconSize)
+ self.tbSuperscript.clicked.connect(
+ lambda: self.requestDocAction.emit(nwDocAction.SC_SUP)
+ )
self.tbSubscript = QToolButton(self)
self.tbSubscript.setIconSize(iconSize)
+ self.tbSubscript.clicked.connect(
+ lambda: self.requestDocAction.emit(nwDocAction.SC_SUB)
+ )
# Assemble
# ========
@@ -2156,7 +2185,6 @@ def updateTheme(self) -> None:
palette.setColor(QPalette.Window, QColor(*SHARED.theme.colBack))
palette.setColor(QPalette.WindowText, QColor(*SHARED.theme.colText))
palette.setColor(QPalette.Text, QColor(*SHARED.theme.colText))
-
self.setPalette(palette)
# qPalette = self.palette()
@@ -2182,6 +2210,40 @@ def updateTheme(self) -> None:
return
+ ##
+ # Internal Slots
+ ##
+
+ @pyqtSlot(bool)
+ def _toggleFormatMode(self, checked: bool) -> None:
+ """Toggle the formatting mode."""
+ CONFIG.useShortcodes = checked
+ return
+
+ @pyqtSlot()
+ def _formatBold(self):
+ """Call the bold format action."""
+ self.requestDocAction.emit(
+ nwDocAction.SC_BOLD if self.tbMode.isChecked() else nwDocAction.STRONG
+ )
+ return
+
+ @pyqtSlot()
+ def _formatItalic(self):
+ """Call the italic format action."""
+ self.requestDocAction.emit(
+ nwDocAction.SC_ITALIC if self.tbMode.isChecked() else nwDocAction.EMPH
+ )
+ return
+
+ @pyqtSlot()
+ def _formatStrike(self):
+ """Call the strikethrough format action."""
+ self.requestDocAction.emit(
+ nwDocAction.SC_STRIKE if self.tbMode.isChecked() else nwDocAction.STRIKE
+ )
+ return
+
# END Class GuiDocToolBar
@@ -2600,8 +2662,8 @@ def _alertSearchValid(self, isValid: bool) -> None:
class GuiDocEditHeader(QWidget):
- toggleToolBarRequest = pyqtSignal()
closeDocumentRequest = pyqtSignal()
+ toggleToolBarRequest = pyqtSignal()
def __init__(self, docEditor: GuiDocEditor) -> None:
super().__init__(parent=docEditor)
diff --git a/novelwriter/gui/mainmenu.py b/novelwriter/gui/mainmenu.py
index 6355ca63f..e6453a823 100644
--- a/novelwriter/gui/mainmenu.py
+++ b/novelwriter/gui/mainmenu.py
@@ -30,7 +30,7 @@
from urllib.parse import urljoin
from urllib.request import pathname2url
-from PyQt5.QtCore import QUrl, pyqtSlot
+from PyQt5.QtCore import QUrl, pyqtSignal, pyqtSlot
from PyQt5.QtGui import QDesktopServices
from PyQt5.QtWidgets import QMenuBar, QAction
@@ -50,6 +50,11 @@ class GuiMainMenu(QMenuBar):
add them from this class.
"""
+ requestDocAction = pyqtSignal(nwDocAction)
+ requestDocInsert = pyqtSignal(nwDocInsert)
+ requestDocInsertText = pyqtSignal(str)
+ requestDocKeyWordInsert = pyqtSignal(str)
+
def __init__(self, mainGui: GuiMain) -> None:
super().__init__(parent=mainGui)
@@ -68,11 +73,6 @@ def __init__(self, mainGui: GuiMain) -> None:
self._buildToolsMenu()
self._buildHelpMenu()
- # Function Pointers
- self._docAction = self.mainGui.passDocumentAction
- self._docInsert = self.mainGui.docEditor.insertText
- self._insertKeyWord = self.mainGui.docEditor.insertKeyWord
-
logger.debug("Ready: GuiMainMenu")
return
@@ -246,12 +246,12 @@ def _buildEditMenu(self) -> None:
# Edit > Undo
self.aEditUndo = self.editMenu.addAction(self.tr("Undo"))
self.aEditUndo.setShortcut("Ctrl+Z")
- self.aEditUndo.triggered.connect(lambda: self._docAction(nwDocAction.UNDO))
+ self.aEditUndo.triggered.connect(lambda: self.requestDocAction.emit(nwDocAction.UNDO))
# Edit > Redo
self.aEditRedo = self.editMenu.addAction(self.tr("Redo"))
self.aEditRedo.setShortcut("Ctrl+Y")
- self.aEditRedo.triggered.connect(lambda: self._docAction(nwDocAction.REDO))
+ self.aEditRedo.triggered.connect(lambda: self.requestDocAction.emit(nwDocAction.REDO))
# Edit > Separator
self.editMenu.addSeparator()
@@ -259,17 +259,17 @@ def _buildEditMenu(self) -> None:
# Edit > Cut
self.aEditCut = self.editMenu.addAction(self.tr("Cut"))
self.aEditCut.setShortcut("Ctrl+X")
- self.aEditCut.triggered.connect(lambda: self._docAction(nwDocAction.CUT))
+ self.aEditCut.triggered.connect(lambda: self.requestDocAction.emit(nwDocAction.CUT))
# Edit > Copy
self.aEditCopy = self.editMenu.addAction(self.tr("Copy"))
self.aEditCopy.setShortcut("Ctrl+C")
- self.aEditCopy.triggered.connect(lambda: self._docAction(nwDocAction.COPY))
+ self.aEditCopy.triggered.connect(lambda: self.requestDocAction.emit(nwDocAction.COPY))
# Edit > Paste
self.aEditPaste = self.editMenu.addAction(self.tr("Paste"))
self.aEditPaste.setShortcut("Ctrl+V")
- self.aEditPaste.triggered.connect(lambda: self._docAction(nwDocAction.PASTE))
+ self.aEditPaste.triggered.connect(lambda: self.requestDocAction.emit(nwDocAction.PASTE))
# Edit > Separator
self.editMenu.addSeparator()
@@ -277,12 +277,12 @@ def _buildEditMenu(self) -> None:
# Edit > Select All
self.aSelectAll = self.editMenu.addAction(self.tr("Select All"))
self.aSelectAll.setShortcut("Ctrl+A")
- self.aSelectAll.triggered.connect(lambda: self._docAction(nwDocAction.SEL_ALL))
+ self.aSelectAll.triggered.connect(lambda: self.requestDocAction.emit(nwDocAction.SEL_ALL))
# Edit > Select Paragraph
self.aSelectPar = self.editMenu.addAction(self.tr("Select Paragraph"))
self.aSelectPar.setShortcut("Ctrl+Shift+A")
- self.aSelectPar.triggered.connect(lambda: self._docAction(nwDocAction.SEL_PARA))
+ self.aSelectPar.triggered.connect(lambda: self.requestDocAction.emit(nwDocAction.SEL_PARA))
return
@@ -350,22 +350,30 @@ def _buildInsertMenu(self) -> None:
# Insert > Short Dash
self.aInsENDash = self.mInsDashes.addAction(self.tr("Short Dash"))
self.aInsENDash.setShortcut("Ctrl+K, -")
- self.aInsENDash.triggered.connect(lambda: self._docInsert(nwUnicode.U_ENDASH))
+ self.aInsENDash.triggered.connect(
+ lambda: self.requestDocInsertText.emit(nwUnicode.U_ENDASH)
+ )
# Insert > Long Dash
self.aInsEMDash = self.mInsDashes.addAction(self.tr("Long Dash"))
self.aInsEMDash.setShortcut("Ctrl+K, _")
- self.aInsEMDash.triggered.connect(lambda: self._docInsert(nwUnicode.U_EMDASH))
+ self.aInsEMDash.triggered.connect(
+ lambda: self.requestDocInsertText.emit(nwUnicode.U_EMDASH)
+ )
# Insert > Long Dash
self.aInsHorBar = self.mInsDashes.addAction(self.tr("Horizontal Bar"))
self.aInsHorBar.setShortcut("Ctrl+K, Ctrl+_")
- self.aInsHorBar.triggered.connect(lambda: self._docInsert(nwUnicode.U_HBAR))
+ self.aInsHorBar.triggered.connect(
+ lambda: self.requestDocInsertText.emit(nwUnicode.U_HBAR)
+ )
# Insert > Figure Dash
self.aInsFigDash = self.mInsDashes.addAction(self.tr("Figure Dash"))
self.aInsFigDash.setShortcut("Ctrl+K, ~")
- self.aInsFigDash.triggered.connect(lambda: self._docInsert(nwUnicode.U_FGDASH))
+ self.aInsFigDash.triggered.connect(
+ lambda: self.requestDocInsertText.emit(nwUnicode.U_FGDASH)
+ )
# Insert > Quote Marks
self.mInsQuotes = self.insMenu.addMenu(self.tr("Quote Marks"))
@@ -373,27 +381,37 @@ def _buildInsertMenu(self) -> None:
# Insert > Left Single Quote
self.aInsQuoteLS = self.mInsQuotes.addAction(self.tr("Left Single Quote"))
self.aInsQuoteLS.setShortcut("Ctrl+K, 1")
- self.aInsQuoteLS.triggered.connect(lambda: self._docInsert(nwDocInsert.QUOTE_LS))
+ self.aInsQuoteLS.triggered.connect(
+ lambda: self.requestDocInsert.emit(nwDocInsert.QUOTE_LS)
+ )
# Insert > Right Single Quote
self.aInsQuoteRS = self.mInsQuotes.addAction(self.tr("Right Single Quote"))
self.aInsQuoteRS.setShortcut("Ctrl+K, 2")
- self.aInsQuoteRS.triggered.connect(lambda: self._docInsert(nwDocInsert.QUOTE_RS))
+ self.aInsQuoteRS.triggered.connect(
+ lambda: self.requestDocInsert.emit(nwDocInsert.QUOTE_RS)
+ )
# Insert > Left Double Quote
self.aInsQuoteLD = self.mInsQuotes.addAction(self.tr("Left Double Quote"))
self.aInsQuoteLD.setShortcut("Ctrl+K, 3")
- self.aInsQuoteLD.triggered.connect(lambda: self._docInsert(nwDocInsert.QUOTE_LD))
+ self.aInsQuoteLD.triggered.connect(
+ lambda: self.requestDocInsert.emit(nwDocInsert.QUOTE_LD)
+ )
# Insert > Right Double Quote
self.aInsQuoteRD = self.mInsQuotes.addAction(self.tr("Right Double Quote"))
self.aInsQuoteRD.setShortcut("Ctrl+K, 4")
- self.aInsQuoteRD.triggered.connect(lambda: self._docInsert(nwDocInsert.QUOTE_RD))
+ self.aInsQuoteRD.triggered.connect(
+ lambda: self.requestDocInsert.emit(nwDocInsert.QUOTE_RD)
+ )
# Insert > Alternative Apostrophe
self.aInsMSApos = self.mInsQuotes.addAction(self.tr("Alternative Apostrophe"))
self.aInsMSApos.setShortcut("Ctrl+K, '")
- self.aInsMSApos.triggered.connect(lambda: self._docInsert(nwUnicode.U_MAPOSS))
+ self.aInsMSApos.triggered.connect(
+ lambda: self.requestDocInsertText.emit(nwUnicode.U_MAPOSS)
+ )
# Insert > Symbols
self.mInsPunct = self.insMenu.addMenu(self.tr("General Punctuation"))
@@ -401,17 +419,23 @@ def _buildInsertMenu(self) -> None:
# Insert > Ellipsis
self.aInsEllipsis = self.mInsPunct.addAction(self.tr("Ellipsis"))
self.aInsEllipsis.setShortcut("Ctrl+K, .")
- self.aInsEllipsis.triggered.connect(lambda: self._docInsert(nwUnicode.U_HELLIP))
+ self.aInsEllipsis.triggered.connect(
+ lambda: self.requestDocInsertText.emit(nwUnicode.U_HELLIP)
+ )
# Insert > Prime
self.aInsPrime = self.mInsPunct.addAction(self.tr("Prime"))
self.aInsPrime.setShortcut("Ctrl+K, Ctrl+'")
- self.aInsPrime.triggered.connect(lambda: self._docInsert(nwUnicode.U_PRIME))
+ self.aInsPrime.triggered.connect(
+ lambda: self.requestDocInsertText.emit(nwUnicode.U_PRIME)
+ )
# Insert > Double Prime
self.aInsDPrime = self.mInsPunct.addAction(self.tr("Double Prime"))
self.aInsDPrime.setShortcut("Ctrl+K, Ctrl+\"")
- self.aInsDPrime.triggered.connect(lambda: self._docInsert(nwUnicode.U_DPRIME))
+ self.aInsDPrime.triggered.connect(
+ lambda: self.requestDocInsertText.emit(nwUnicode.U_DPRIME)
+ )
# Insert > White Spaces
self.mInsSpace = self.insMenu.addMenu(self.tr("White Spaces"))
@@ -419,17 +443,23 @@ def _buildInsertMenu(self) -> None:
# Insert > Non-Breaking Space
self.aInsNBSpace = self.mInsSpace.addAction(self.tr("Non-Breaking Space"))
self.aInsNBSpace.setShortcut("Ctrl+K, Space")
- self.aInsNBSpace.triggered.connect(lambda: self._docInsert(nwUnicode.U_NBSP))
+ self.aInsNBSpace.triggered.connect(
+ lambda: self.requestDocInsertText.emit(nwUnicode.U_NBSP)
+ )
# Insert > Thin Space
self.aInsThinSpace = self.mInsSpace.addAction(self.tr("Thin Space"))
self.aInsThinSpace.setShortcut("Ctrl+K, Shift+Space")
- self.aInsThinSpace.triggered.connect(lambda: self._docInsert(nwUnicode.U_THSP))
+ self.aInsThinSpace.triggered.connect(
+ lambda: self.requestDocInsertText.emit(nwUnicode.U_THSP)
+ )
# Insert > Thin Non-Breaking Space
self.aInsThinNBSpace = self.mInsSpace.addAction(self.tr("Thin Non-Breaking Space"))
self.aInsThinNBSpace.setShortcut("Ctrl+K, Ctrl+Space")
- self.aInsThinNBSpace.triggered.connect(lambda: self._docInsert(nwUnicode.U_THNBSP))
+ self.aInsThinNBSpace.triggered.connect(
+ lambda: self.requestDocInsertText.emit(nwUnicode.U_THNBSP)
+ )
# Insert > Symbols
self.mInsSymbol = self.insMenu.addMenu(self.tr("Other Symbols"))
@@ -437,42 +467,58 @@ def _buildInsertMenu(self) -> None:
# Insert > List Bullet
self.aInsBullet = self.mInsSymbol.addAction(self.tr("List Bullet"))
self.aInsBullet.setShortcut("Ctrl+K, *")
- self.aInsBullet.triggered.connect(lambda: self._docInsert(nwUnicode.U_BULL))
+ self.aInsBullet.triggered.connect(
+ lambda: self.requestDocInsertText.emit(nwUnicode.U_BULL)
+ )
# Insert > Hyphen Bullet
self.aInsHyBull = self.mInsSymbol.addAction(self.tr("Hyphen Bullet"))
self.aInsHyBull.setShortcut("Ctrl+K, Ctrl+-")
- self.aInsHyBull.triggered.connect(lambda: self._docInsert(nwUnicode.U_HYBULL))
+ self.aInsHyBull.triggered.connect(
+ lambda: self.requestDocInsertText.emit(nwUnicode.U_HYBULL)
+ )
# Insert > Flower Mark
self.aInsFlower = self.mInsSymbol.addAction(self.tr("Flower Mark"))
self.aInsFlower.setShortcut("Ctrl+K, Ctrl+*")
- self.aInsFlower.triggered.connect(lambda: self._docInsert(nwUnicode.U_FLOWER))
+ self.aInsFlower.triggered.connect(
+ lambda: self.requestDocInsertText.emit(nwUnicode.U_FLOWER)
+ )
# Insert > Per Mille
self.aInsPerMille = self.mInsSymbol.addAction(self.tr("Per Mille"))
self.aInsPerMille.setShortcut("Ctrl+K, %")
- self.aInsPerMille.triggered.connect(lambda: self._docInsert(nwUnicode.U_PERMIL))
+ self.aInsPerMille.triggered.connect(
+ lambda: self.requestDocInsertText.emit(nwUnicode.U_PERMIL)
+ )
# Insert > Degree Symbol
self.aInsDegree = self.mInsSymbol.addAction(self.tr("Degree Symbol"))
self.aInsDegree.setShortcut("Ctrl+K, Ctrl+O")
- self.aInsDegree.triggered.connect(lambda: self._docInsert(nwUnicode.U_DEGREE))
+ self.aInsDegree.triggered.connect(
+ lambda: self.requestDocInsertText.emit(nwUnicode.U_DEGREE)
+ )
# Insert > Minus Sign
self.aInsMinus = self.mInsSymbol.addAction(self.tr("Minus Sign"))
self.aInsMinus.setShortcut("Ctrl+K, Ctrl+M")
- self.aInsMinus.triggered.connect(lambda: self._docInsert(nwUnicode.U_MINUS))
+ self.aInsMinus.triggered.connect(
+ lambda: self.requestDocInsertText.emit(nwUnicode.U_MINUS)
+ )
# Insert > Times Sign
self.aInsTimes = self.mInsSymbol.addAction(self.tr("Times Sign"))
self.aInsTimes.setShortcut("Ctrl+K, Ctrl+X")
- self.aInsTimes.triggered.connect(lambda: self._docInsert(nwUnicode.U_TIMES))
+ self.aInsTimes.triggered.connect(
+ lambda: self.requestDocInsertText.emit(nwUnicode.U_TIMES)
+ )
# Insert > Division
self.aInsDivide = self.mInsSymbol.addAction(self.tr("Division Sign"))
self.aInsDivide.setShortcut("Ctrl+K, Ctrl+D")
- self.aInsDivide.triggered.connect(lambda: self._docInsert(nwUnicode.U_DIVIDE))
+ self.aInsDivide.triggered.connect(
+ lambda: self.requestDocInsertText.emit(nwUnicode.U_DIVIDE)
+ )
# Insert > Tags and References
self.mInsKeywords = self.insMenu.addMenu(self.tr("Tags and References"))
@@ -491,7 +537,7 @@ def _buildInsertMenu(self) -> None:
self.mInsKWItems[keyWord][0].setText(trConst(nwLabels.KEY_NAME[keyWord]))
self.mInsKWItems[keyWord][0].setShortcut(self.mInsKWItems[keyWord][1])
self.mInsKWItems[keyWord][0].triggered.connect(
- lambda n, keyWord=keyWord: self._insertKeyWord(keyWord)
+ lambda n, keyWord=keyWord: self.requestDocKeyWordInsert.emit(keyWord)
)
self.mInsKeywords.addAction(self.mInsKWItems[keyWord][0])
@@ -501,22 +547,30 @@ def _buildInsertMenu(self) -> None:
# Insert > Synopsis Comment
self.aInsSynopsis = self.mInsComments.addAction(self.tr("Synopsis Comment"))
self.aInsSynopsis.setShortcut("Ctrl+K, S")
- self.aInsSynopsis.triggered.connect(lambda: self._docInsert(nwDocInsert.SYNOPSIS))
+ self.aInsSynopsis.triggered.connect(
+ lambda: self.requestDocInsert.emit(nwDocInsert.SYNOPSIS)
+ )
# Insert > Symbols
self.mInsBreaks = self.insMenu.addMenu(self.tr("Page Break and Space"))
# Insert > New Page
self.aInsNewPage = self.mInsBreaks.addAction(self.tr("Page Break"))
- self.aInsNewPage.triggered.connect(lambda: self._docInsert(nwDocInsert.NEW_PAGE))
+ self.aInsNewPage.triggered.connect(
+ lambda: self.requestDocInsert.emit(nwDocInsert.NEW_PAGE)
+ )
# Insert > Vertical Space (Single)
self.aInsVSpaceS = self.mInsBreaks.addAction(self.tr("Vertical Space (Single)"))
- self.aInsVSpaceS.triggered.connect(lambda: self._docInsert(nwDocInsert.VSPACE_S))
+ self.aInsVSpaceS.triggered.connect(
+ lambda: self.requestDocInsert.emit(nwDocInsert.VSPACE_S)
+ )
# Insert > Vertical Space (Multi)
self.aInsVSpaceM = self.mInsBreaks.addAction(self.tr("Vertical Space (Multi)"))
- self.aInsVSpaceM.triggered.connect(lambda: self._docInsert(nwDocInsert.VSPACE_M))
+ self.aInsVSpaceM.triggered.connect(
+ lambda: self.requestDocInsert.emit(nwDocInsert.VSPACE_M)
+ )
# Insert > Placeholder Text
self.aLipsumText = self.mInsBreaks.addAction(self.tr("Placeholder Text"))
@@ -532,17 +586,23 @@ def _buildFormatMenu(self) -> None:
# Format > Emphasis
self.aFmtEmph = self.fmtMenu.addAction(self.tr("Emphasis"))
self.aFmtEmph.setShortcut("Ctrl+I")
- self.aFmtEmph.triggered.connect(lambda: self._docAction(nwDocAction.EMPH))
+ self.aFmtEmph.triggered.connect(
+ lambda: self.requestDocAction.emit(nwDocAction.EMPH)
+ )
# Format > Strong Emphasis
self.aFmtStrong = self.fmtMenu.addAction(self.tr("Strong Emphasis"))
self.aFmtStrong.setShortcut("Ctrl+B")
- self.aFmtStrong.triggered.connect(lambda: self._docAction(nwDocAction.STRONG))
+ self.aFmtStrong.triggered.connect(
+ lambda: self.requestDocAction.emit(nwDocAction.STRONG)
+ )
# Format > Strikethrough
self.aFmtStrike = self.fmtMenu.addAction(self.tr("Strikethrough"))
self.aFmtStrike.setShortcut("Ctrl+D")
- self.aFmtStrike.triggered.connect(lambda: self._docAction(nwDocAction.STRIKE))
+ self.aFmtStrike.triggered.connect(
+ lambda: self.requestDocAction.emit(nwDocAction.STRIKE)
+ )
# Edit > Separator
self.fmtMenu.addSeparator()
@@ -550,12 +610,16 @@ def _buildFormatMenu(self) -> None:
# Format > Double Quotes
self.aFmtDQuote = self.fmtMenu.addAction(self.tr("Wrap Double Quotes"))
self.aFmtDQuote.setShortcut("Ctrl+\"")
- self.aFmtDQuote.triggered.connect(lambda: self._docAction(nwDocAction.D_QUOTE))
+ self.aFmtDQuote.triggered.connect(
+ lambda: self.requestDocAction.emit(nwDocAction.D_QUOTE)
+ )
# Format > Single Quotes
self.aFmtSQuote = self.fmtMenu.addAction(self.tr("Wrap Single Quotes"))
self.aFmtSQuote.setShortcut("Ctrl+'")
- self.aFmtSQuote.triggered.connect(lambda: self._docAction(nwDocAction.S_QUOTE))
+ self.aFmtSQuote.triggered.connect(
+ lambda: self.requestDocAction.emit(nwDocAction.S_QUOTE)
+ )
# Format > Separator
self.fmtMenu.addSeparator()
@@ -564,28 +628,40 @@ def _buildFormatMenu(self) -> None:
self.mShortcodes = self.fmtMenu.addMenu(self.tr("More Formats ..."))
# Shortcode Italic
- self.aScItalic = self.mShortcodes.addAction(self.tr("Italics Shortcode"))
- self.aScItalic.triggered.connect(lambda: self._docAction(nwDocAction.SC_ITALIC))
+ self.aScItalic = self.mShortcodes.addAction(self.tr("Italics (Shortcode)"))
+ self.aScItalic.triggered.connect(
+ lambda: self.requestDocAction.emit(nwDocAction.SC_ITALIC)
+ )
# Shortcode Bold
- self.aScBold = self.mShortcodes.addAction(self.tr("Bold Shortcode"))
- self.aScBold.triggered.connect(lambda: self._docAction(nwDocAction.SC_BOLD))
-
- # Shortcode Underline
- self.aScULine = self.mShortcodes.addAction(self.tr("Underline Shortcode"))
- self.aScULine.triggered.connect(lambda: self._docAction(nwDocAction.SC_ULINE))
+ self.aScBold = self.mShortcodes.addAction(self.tr("Bold (Shortcode)"))
+ self.aScBold.triggered.connect(
+ lambda: self.requestDocAction.emit(nwDocAction.SC_BOLD)
+ )
# Shortcode Strikethrough
- self.aScStrike = self.mShortcodes.addAction(self.tr("Strikethrough Shortcode"))
- self.aScStrike.triggered.connect(lambda: self._docAction(nwDocAction.SC_STRIKE))
+ self.aScStrike = self.mShortcodes.addAction(self.tr("Strikethrough (Shortcode)"))
+ self.aScStrike.triggered.connect(
+ lambda: self.requestDocAction.emit(nwDocAction.SC_STRIKE)
+ )
+
+ # Shortcode Underline
+ self.aScULine = self.mShortcodes.addAction(self.tr("Underline"))
+ self.aScULine.triggered.connect(
+ lambda: self.requestDocAction.emit(nwDocAction.SC_ULINE)
+ )
# Shortcode Superscript
self.aScSuper = self.mShortcodes.addAction(self.tr("Superscript"))
- self.aScSuper.triggered.connect(lambda: self._docAction(nwDocAction.SC_SUP))
+ self.aScSuper.triggered.connect(
+ lambda: self.requestDocAction.emit(nwDocAction.SC_SUP)
+ )
# Shortcode Subscript
self.aScSub = self.mShortcodes.addAction(self.tr("Subscript"))
- self.aScSub.triggered.connect(lambda: self._docAction(nwDocAction.SC_SUB))
+ self.aScSub.triggered.connect(
+ lambda: self.requestDocAction.emit(nwDocAction.SC_SUB)
+ )
# Format > Separator
self.fmtMenu.addSeparator()
@@ -593,33 +669,45 @@ def _buildFormatMenu(self) -> None:
# Format > Header 1 (Partition)
self.aFmtHead1 = self.fmtMenu.addAction(self.tr("Header 1 (Partition)"))
self.aFmtHead1.setShortcut("Ctrl+1")
- self.aFmtHead1.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_H1))
+ self.aFmtHead1.triggered.connect(
+ lambda: self.requestDocAction.emit(nwDocAction.BLOCK_H1)
+ )
# Format > Header 2 (Chapter)
self.aFmtHead2 = self.fmtMenu.addAction(self.tr("Header 2 (Chapter)"))
self.aFmtHead2.setShortcut("Ctrl+2")
- self.aFmtHead2.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_H2))
+ self.aFmtHead2.triggered.connect(
+ lambda: self.requestDocAction.emit(nwDocAction.BLOCK_H2)
+ )
# Format > Header 3 (Scene)
self.aFmtHead3 = self.fmtMenu.addAction(self.tr("Header 3 (Scene)"))
self.aFmtHead3.setShortcut("Ctrl+3")
- self.aFmtHead3.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_H3))
+ self.aFmtHead3.triggered.connect(
+ lambda: self.requestDocAction.emit(nwDocAction.BLOCK_H3)
+ )
# Format > Header 4 (Section)
self.aFmtHead4 = self.fmtMenu.addAction(self.tr("Header 4 (Section)"))
self.aFmtHead4.setShortcut("Ctrl+4")
- self.aFmtHead4.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_H4))
+ self.aFmtHead4.triggered.connect(
+ lambda: self.requestDocAction.emit(nwDocAction.BLOCK_H4)
+ )
# Format > Separator
self.fmtMenu.addSeparator()
# Format > Novel Title
self.aFmtTitle = self.fmtMenu.addAction(self.tr("Novel Title"))
- self.aFmtTitle.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_TTL))
+ self.aFmtTitle.triggered.connect(
+ lambda: self.requestDocAction.emit(nwDocAction.BLOCK_TTL)
+ )
# Format > Unnumbered Chapter
self.aFmtUnNum = self.fmtMenu.addAction(self.tr("Unnumbered Chapter"))
- self.aFmtUnNum.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_UNN))
+ self.aFmtUnNum.triggered.connect(
+ lambda: self.requestDocAction.emit(nwDocAction.BLOCK_UNN)
+ )
# Format > Separator
self.fmtMenu.addSeparator()
@@ -627,17 +715,23 @@ def _buildFormatMenu(self) -> None:
# Format > Align Left
self.aFmtAlignLeft = self.fmtMenu.addAction(self.tr("Align Left"))
self.aFmtAlignLeft.setShortcut("Ctrl+5")
- self.aFmtAlignLeft.triggered.connect(lambda: self._docAction(nwDocAction.ALIGN_L))
+ self.aFmtAlignLeft.triggered.connect(
+ lambda: self.requestDocAction.emit(nwDocAction.ALIGN_L)
+ )
# Format > Align Centre
self.aFmtAlignCentre = self.fmtMenu.addAction(self.tr("Align Centre"))
self.aFmtAlignCentre.setShortcut("Ctrl+6")
- self.aFmtAlignCentre.triggered.connect(lambda: self._docAction(nwDocAction.ALIGN_C))
+ self.aFmtAlignCentre.triggered.connect(
+ lambda: self.requestDocAction.emit(nwDocAction.ALIGN_C)
+ )
# Format > Align Right
self.aFmtAlignRight = self.fmtMenu.addAction(self.tr("Align Right"))
self.aFmtAlignRight.setShortcut("Ctrl+7")
- self.aFmtAlignRight.triggered.connect(lambda: self._docAction(nwDocAction.ALIGN_R))
+ self.aFmtAlignRight.triggered.connect(
+ lambda: self.requestDocAction.emit(nwDocAction.ALIGN_R)
+ )
# Format > Separator
self.fmtMenu.addSeparator()
@@ -645,12 +739,16 @@ def _buildFormatMenu(self) -> None:
# Format > Indent Left
self.aFmtIndentLeft = self.fmtMenu.addAction(self.tr("Indent Left"))
self.aFmtIndentLeft.setShortcut("Ctrl+8")
- self.aFmtIndentLeft.triggered.connect(lambda: self._docAction(nwDocAction.INDENT_L))
+ self.aFmtIndentLeft.triggered.connect(
+ lambda: self.requestDocAction.emit(nwDocAction.INDENT_L)
+ )
# Format > Indent Right
self.aFmtIndentRight = self.fmtMenu.addAction(self.tr("Indent Right"))
self.aFmtIndentRight.setShortcut("Ctrl+9")
- self.aFmtIndentRight.triggered.connect(lambda: self._docAction(nwDocAction.INDENT_R))
+ self.aFmtIndentRight.triggered.connect(
+ lambda: self.requestDocAction.emit(nwDocAction.INDENT_R)
+ )
# Format > Separator
self.fmtMenu.addSeparator()
@@ -658,27 +756,37 @@ def _buildFormatMenu(self) -> None:
# Format > Comment
self.aFmtComment = self.fmtMenu.addAction(self.tr("Toggle Comment"))
self.aFmtComment.setShortcut("Ctrl+/")
- self.aFmtComment.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_COM))
+ self.aFmtComment.triggered.connect(
+ lambda: self.requestDocAction.emit(nwDocAction.BLOCK_COM)
+ )
# Format > Remove Block Format
self.aFmtNoFormat = self.fmtMenu.addAction(self.tr("Remove Block Format"))
self.aFmtNoFormat.setShortcuts(["Ctrl+0", "Ctrl+Shift+/"])
- self.aFmtNoFormat.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_TXT))
+ self.aFmtNoFormat.triggered.connect(
+ lambda: self.requestDocAction.emit(nwDocAction.BLOCK_TXT)
+ )
# Format > Separator
self.fmtMenu.addSeparator()
# Format > Replace Single Quotes
self.aFmtReplSng = self.fmtMenu.addAction(self.tr("Convert Single Quotes"))
- self.aFmtReplSng.triggered.connect(lambda: self._docAction(nwDocAction.REPL_SNG))
+ self.aFmtReplSng.triggered.connect(
+ lambda: self.requestDocAction.emit(nwDocAction.REPL_SNG)
+ )
# Format > Replace Double Quotes
self.aFmtReplDbl = self.fmtMenu.addAction(self.tr("Convert Double Quotes"))
- self.aFmtReplDbl.triggered.connect(lambda: self._docAction(nwDocAction.REPL_DBL))
+ self.aFmtReplDbl.triggered.connect(
+ lambda: self.requestDocAction.emit(nwDocAction.REPL_DBL)
+ )
# Format > Remove In-Paragraph Breaks
self.aFmtRmBreaks = self.fmtMenu.addAction(self.tr("Remove In-Paragraph Breaks"))
- self.aFmtRmBreaks.triggered.connect(lambda: self._docAction(nwDocAction.RM_BREAKS))
+ self.aFmtRmBreaks.triggered.connect(
+ lambda: self.requestDocAction.emit(nwDocAction.RM_BREAKS)
+ )
return
diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py
index adcebca8a..413014763 100644
--- a/novelwriter/guimain.py
+++ b/novelwriter/guimain.py
@@ -62,7 +62,7 @@
from novelwriter.core.coretools import ProjectBuilder
from novelwriter.enum import (
- nwDocAction, nwDocMode, nwItemType, nwItemClass, nwWidget, nwView
+ nwDocAction, nwDocInsert, nwDocMode, nwItemType, nwItemClass, nwWidget, nwView
)
from novelwriter.common import getGuiItem, hexToInt
from novelwriter.constants import nwFiles
@@ -243,6 +243,11 @@ def __init__(self) -> None:
SHARED.projectStatusMessage.connect(self.mainStatus.setStatusMessage)
SHARED.spellLanguageChanged.connect(self.mainStatus.setLanguage)
+ self.mainMenu.requestDocAction.connect(self._passDocumentAction)
+ self.mainMenu.requestDocInsert.connect(self._passDocumentInsert)
+ self.mainMenu.requestDocInsertText.connect(self._passDocumentInsert)
+ self.mainMenu.requestDocKeyWordInsert.connect(self.docEditor.insertKeyWord)
+
self.sideBar.viewChangeRequested.connect(self._changeView)
self.projView.selectedItemChanged.connect(self.itemDetails.updateViewBox)
@@ -735,20 +740,6 @@ def importDocument(self) -> bool:
return True
- def passDocumentAction(self, action: nwDocAction) -> None:
- """Pass on document action to the document viewer if it has
- focus, or pass it to the document editor if it or any of
- its child widgets have focus. If neither has focus, ignore the
- action.
- """
- if self.docViewer.hasFocus():
- self.docViewer.docAction(action)
- elif self.docEditor.hasFocus():
- self.docEditor.docAction(action)
- else:
- logger.debug("Action cancelled as neither editor nor viewer has focus")
- return
-
##
# Tree Item Actions
##
@@ -1247,6 +1238,30 @@ def _changeView(self, view: nwView) -> None:
return
+ @pyqtSlot(nwDocAction)
+ def _passDocumentAction(self, action: nwDocAction) -> None:
+ """Pass on a document action to the document viewer if it has
+ focus, or pass it to the document editor if it or any of its
+ child widgets have focus. If neither has focus, ignore it.
+ """
+ if self.docViewer.hasFocus():
+ self.docViewer.docAction(action)
+ elif self.docEditor.hasFocus():
+ self.docEditor.docAction(action)
+ else:
+ logger.debug("Action cancelled as neither editor nor viewer has focus")
+ return
+
+ @pyqtSlot(str)
+ @pyqtSlot(nwDocInsert)
+ def _passDocumentInsert(self, content: str | nwDocInsert) -> None:
+ """Pass on a document insert action to the document editor if it
+ has focus. If not, ignore it.
+ """
+ if self.docEditor.hasFocus():
+ self.docEditor.insertText(content)
+ return
+
@pyqtSlot()
def _timeTick(self) -> None:
"""Process time tick of the main timer."""
From c88fbf629d19b721363261d0fabf038bd44be07d Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Wed, 8 Nov 2023 18:06:19 +0100
Subject: [PATCH 06/10] Update tests
---
tests/reference/baseConfig_novelwriter.conf | 2 ++
tests/reference/guiPreferences_novelwriter.conf | 2 ++
tests/test_core/test_core_options.py | 2 +-
3 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/tests/reference/baseConfig_novelwriter.conf b/tests/reference/baseConfig_novelwriter.conf
index f3434415f..95df90720 100644
--- a/tests/reference/baseConfig_novelwriter.conf
+++ b/tests/reference/baseConfig_novelwriter.conf
@@ -68,6 +68,8 @@ useridletime = 300
[State]
showrefpanel = True
+showedittoolbar = False
+useshortcodes = False
viewcomments = True
viewsynopsis = True
searchcase = False
diff --git a/tests/reference/guiPreferences_novelwriter.conf b/tests/reference/guiPreferences_novelwriter.conf
index dfa3f3fd5..6ea3a0d53 100644
--- a/tests/reference/guiPreferences_novelwriter.conf
+++ b/tests/reference/guiPreferences_novelwriter.conf
@@ -68,6 +68,8 @@ useridletime = 300
[State]
showrefpanel = True
+showedittoolbar = False
+useshortcodes = False
viewcomments = True
viewsynopsis = True
searchcase = False
diff --git a/tests/test_core/test_core_options.py b/tests/test_core/test_core_options.py
index 747a8f73f..013456302 100644
--- a/tests/test_core/test_core_options.py
+++ b/tests/test_core/test_core_options.py
@@ -140,7 +140,7 @@ def testCoreOptions_SetGet(mockGUI):
assert theOpts.getFloat("GuiProjectDetails", "mockItem", None) is None
assert theOpts.getBool("GuiProjectDetails", "clearDouble", None) is True
assert theOpts.getBool("GuiProjectDetails", "mockItem", None) is None
- assert theOpts.getEnum("GuiNovelView", "lastCol", NovelTreeColumn, None) == nwColHidden
+ assert theOpts.getEnum("GuiNovelView", "lastCol", NovelTreeColumn, nwColHidden) == nwColHidden
# Get from non-existent groups
assert theOpts.getValue("SomeGroup", "mockItem", None) is None
From 5fbcca35a5ac31dec3dc90df3cc09211999b0092 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Wed, 8 Nov 2023 18:16:55 +0100
Subject: [PATCH 07/10] Make sure toolbar is hidden when document is closed
---
novelwriter/gui/doceditor.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py
index efccc3617..f0aa70416 100644
--- a/novelwriter/gui/doceditor.py
+++ b/novelwriter/gui/doceditor.py
@@ -138,7 +138,6 @@ def __init__(self, mainGui: GuiMain) -> None:
self.docFooter = GuiDocEditFooter(self)
self.docSearch = GuiDocEditSearch(self)
self.docToolBar = GuiDocToolBar(self)
- self.docToolBar.setVisible(CONFIG.showEditToolBar)
# Connect Signals
self.docHeader.closeDocumentRequest.connect(self._closeCurrentDocument)
@@ -410,6 +409,7 @@ def loadText(self, tHandle, tLine=None) -> bool:
qApp.processEvents()
self.setDocumentChanged(False)
self._qDocument.clearUndoRedoStacks()
+ self.docToolBar.setVisible(CONFIG.showEditToolBar)
qApp.restoreOverrideCursor()
@@ -1196,6 +1196,7 @@ def _updateSelCounts(self, cCount: int, wCount: int, pCount: int) -> None:
def _closeCurrentDocument(self) -> None:
"""Close the document. Forwarded to the main Gui."""
self.closeDocumentRequest.emit()
+ self.docToolBar.setVisible(False)
return
@pyqtSlot()
From 9a1947c0f5ac1dc486090d14a27ba55eb251886a Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Wed, 8 Nov 2023 21:26:19 +0100
Subject: [PATCH 08/10] Add colour to sub/superscript icons
---
novelwriter/assets/icons/typicons_dark/nw_tb-subscript.svg | 4 ++--
novelwriter/assets/icons/typicons_dark/nw_tb-superscript.svg | 4 ++--
novelwriter/assets/icons/typicons_light/nw_tb-subscript.svg | 4 ++--
novelwriter/assets/icons/typicons_light/nw_tb-superscript.svg | 4 ++--
4 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/novelwriter/assets/icons/typicons_dark/nw_tb-subscript.svg b/novelwriter/assets/icons/typicons_dark/nw_tb-subscript.svg
index 7566bdee5..e0ae3587e 100644
--- a/novelwriter/assets/icons/typicons_dark/nw_tb-subscript.svg
+++ b/novelwriter/assets/icons/typicons_dark/nw_tb-subscript.svg
@@ -1,5 +1,5 @@
diff --git a/novelwriter/assets/icons/typicons_dark/nw_tb-superscript.svg b/novelwriter/assets/icons/typicons_dark/nw_tb-superscript.svg
index 0ac13bf39..1d60aec27 100644
--- a/novelwriter/assets/icons/typicons_dark/nw_tb-superscript.svg
+++ b/novelwriter/assets/icons/typicons_dark/nw_tb-superscript.svg
@@ -1,5 +1,5 @@
diff --git a/novelwriter/assets/icons/typicons_light/nw_tb-subscript.svg b/novelwriter/assets/icons/typicons_light/nw_tb-subscript.svg
index db909aed7..2384f6eac 100644
--- a/novelwriter/assets/icons/typicons_light/nw_tb-subscript.svg
+++ b/novelwriter/assets/icons/typicons_light/nw_tb-subscript.svg
@@ -1,5 +1,5 @@
diff --git a/novelwriter/assets/icons/typicons_light/nw_tb-superscript.svg b/novelwriter/assets/icons/typicons_light/nw_tb-superscript.svg
index 1634872e7..df0aa35c1 100644
--- a/novelwriter/assets/icons/typicons_light/nw_tb-superscript.svg
+++ b/novelwriter/assets/icons/typicons_light/nw_tb-superscript.svg
@@ -1,5 +1,5 @@
From 003615c3bede4c399fb5554d07303246b3db6c35 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Wed, 8 Nov 2023 21:27:14 +0100
Subject: [PATCH 09/10] Make some minor modifications to the doc toolbar, and
add typings in themes
---
novelwriter/gui/doceditor.py | 14 +---
novelwriter/gui/noveltree.py | 2 +-
novelwriter/gui/theme.py | 133 +++++++++++++++----------------
tests/test_gui/test_gui_theme.py | 6 +-
4 files changed, 69 insertions(+), 86 deletions(-)
diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py
index f0aa70416..a3bc291c9 100644
--- a/novelwriter/gui/doceditor.py
+++ b/novelwriter/gui/doceditor.py
@@ -2188,18 +2188,6 @@ def updateTheme(self) -> None:
palette.setColor(QPalette.Text, QColor(*SHARED.theme.colText))
self.setPalette(palette)
- # qPalette = self.palette()
- # qPalette.setBrush(QPalette.Window, qPalette.base())
- # self.setPalette(qPalette)
-
- # fadeCol = qPalette.text().color()
- # buttonStyle = (
- # "QToolButton {{padding: {0}px; border: none; background: transparent;}} "
- # "QToolButton:hover {{border: none; background: rgba({1},{2},{3},0.2);}}"
- # ).format(CONFIG.pxInt(4), fadeCol.red(), fadeCol.green(), fadeCol.blue())
- # buttonStyleMenu = f"{buttonStyle} QToolButton::menu-indicator {{image: none;}}"
- # self.tbEdit.setStyleSheet(buttonStyle)
-
tPx = int(0.8*SHARED.theme.fontPixelSize)
self.tbMode.setIcon(SHARED.theme.getToggleIcon("fmt_mode", (tPx, tPx)))
self.tbBold.setIcon(SHARED.theme.getIcon("fmt_bold"))
@@ -2212,7 +2200,7 @@ def updateTheme(self) -> None:
return
##
- # Internal Slots
+ # Private Slots
##
@pyqtSlot(bool)
diff --git a/novelwriter/gui/noveltree.py b/novelwriter/gui/noveltree.py
index 246837333..40aae9efc 100644
--- a/novelwriter/gui/noveltree.py
+++ b/novelwriter/gui/noveltree.py
@@ -468,7 +468,7 @@ def initSettings(self) -> None:
def updateTheme(self) -> None:
"""Update theme elements."""
iPx = SHARED.theme.baseIconSize
- self._pMore = SHARED.theme.loadDecoration("deco_doc_more", pxH=iPx)
+ self._pMore = SHARED.theme.loadDecoration("deco_doc_more", h=iPx)
return
##
diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py
index 22d8cf959..6c4d7e767 100644
--- a/novelwriter/gui/theme.py
+++ b/novelwriter/gui/theme.py
@@ -36,7 +36,7 @@
)
from novelwriter import CONFIG
-from novelwriter.enum import nwItemLayout, nwItemType
+from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
from novelwriter.error import logException
from novelwriter.common import NWConfigParser, minmax
from novelwriter.constants import nwLabels
@@ -51,7 +51,7 @@
class GuiTheme:
- def __init__(self):
+ def __init__(self) -> None:
self.iconCache = GuiIcons(self)
@@ -183,7 +183,7 @@ def getTextWidth(self, text: str, font: QFont | None = None) -> int:
# Theme Methods
##
- def loadTheme(self):
+ def loadTheme(self) -> bool:
"""Load the currently specified GUI theme."""
guiTheme = CONFIG.guiTheme
if guiTheme not in self._availThemes:
@@ -270,7 +270,7 @@ def loadTheme(self):
return True
- def loadSyntax(self):
+ def loadSyntax(self) -> bool:
"""Load the currently specified syntax highlighter theme."""
guiSyntax = CONFIG.guiSyntax
if guiSyntax not in self._availSyntax:
@@ -364,7 +364,7 @@ def listSyntax(self) -> list[tuple[str, str]]:
# Internal Functions
##
- def _setGuiFont(self):
+ def _setGuiFont(self) -> None:
"""Update the GUI's font style from settings."""
theFont = QFont()
fontDB = QFontDatabase()
@@ -396,9 +396,7 @@ def _listConf(self, targetDict: dict, checkDir: Path) -> bool:
return True
- def _parseColour(
- self, parser: NWConfigParser, section: str, name: str
- ) -> list[int]:
+ def _parseColour(self, parser: NWConfigParser, section: str, name: str) -> list[int]:
"""Parse a colour value from a config string."""
if parser.has_option(section, name):
values = parser.get(section, name).split(",")
@@ -415,9 +413,8 @@ def _parseColour(
result = [0, 0, 0]
return result
- def _setPalette(
- self, parser: NWConfigParser, section: str, name: str, value: QPalette.ColorRole
- ):
+ def _setPalette(self, parser: NWConfigParser, section: str,
+ name: str, value: QPalette.ColorRole) -> None:
"""Set a palette colour value from a config string."""
self._guiPalette.setColor(
value, QColor(*self._parseColour(parser, section, name))
@@ -488,17 +485,17 @@ class GuiIcons:
"wiz-back": "wizard-back.jpg",
}
- def __init__(self, mainTheme):
+ def __init__(self, mainTheme: GuiTheme) -> None:
self.mainTheme = mainTheme
# Storage
- self._qIcons = {}
- self._themeMap = {}
- self._headerDec = []
- self._confName = "icons.conf"
+ self._qIcons: dict[str, QIcon] = {}
+ self._themeMap: dict[str, Path] = {}
+ self._headerDec: list[QPixmap] = []
# Icon Theme Path
+ self._confName = "icons.conf"
self._iconPath = CONFIG.assetPath("icons")
# Icon Theme Meta
@@ -516,7 +513,7 @@ def __init__(self, mainTheme):
# Actions
##
- def loadTheme(self, iconTheme):
+ def loadTheme(self, iconTheme: str) -> bool:
"""Update the theme map. This is more of an init, since many of
the GUI icons cannot really be replaced without writing specific
update functions for the classes where they're used.
@@ -590,60 +587,60 @@ def loadTheme(self, iconTheme):
# Access Functions
##
- def loadDecoration(self, decoKey, pxW=None, pxH=None):
+ def loadDecoration(self, name: str, w: int | None = None, h: int | None = None) -> QPixmap:
"""Load graphical decoration element based on the decoration
map or the icon map. This function always returns a QPixmap.
"""
- if decoKey in self._themeMap:
- imgPath = self._themeMap[decoKey]
- elif decoKey in self.IMAGE_MAP:
- imgPath = CONFIG.assetPath("images") / self.IMAGE_MAP[decoKey]
+ if name in self._themeMap:
+ imgPath = self._themeMap[name]
+ elif name in self.IMAGE_MAP:
+ imgPath = CONFIG.assetPath("images") / self.IMAGE_MAP[name]
else:
- logger.error("Decoration with name '%s' does not exist", decoKey)
+ logger.error("Decoration with name '%s' does not exist", name)
return QPixmap()
if not imgPath.is_file():
logger.error("Asset not found: %s", imgPath)
return QPixmap()
- theDeco = QPixmap(str(imgPath))
- if pxW is not None and pxH is not None:
- return theDeco.scaled(pxW, pxH, Qt.IgnoreAspectRatio, Qt.SmoothTransformation)
- elif pxW is None and pxH is not None:
- return theDeco.scaledToHeight(pxH, Qt.SmoothTransformation)
- elif pxW is not None and pxH is None:
- return theDeco.scaledToWidth(pxW, Qt.SmoothTransformation)
+ pixmap = QPixmap(str(imgPath))
+ if w is not None and h is not None:
+ return pixmap.scaled(w, h, Qt.IgnoreAspectRatio, Qt.SmoothTransformation)
+ elif w is None and h is not None:
+ return pixmap.scaledToHeight(h, Qt.SmoothTransformation)
+ elif w is not None and h is None:
+ return pixmap.scaledToWidth(w, Qt.SmoothTransformation)
- return theDeco
+ return pixmap
- def getIcon(self, iconKey):
+ def getIcon(self, name: str) -> QIcon:
"""Return an icon from the icon buffer, or load it."""
- if iconKey in self._qIcons:
- return self._qIcons[iconKey]
+ if name in self._qIcons:
+ return self._qIcons[name]
else:
- qIcon = self._loadIcon(iconKey)
- self._qIcons[iconKey] = qIcon
- return qIcon
+ icon = self._loadIcon(name)
+ self._qIcons[name] = icon
+ return icon
- def getToggleIcon(self, iconKey: str, iconSize: tuple[int, int]) -> QIcon:
+ def getToggleIcon(self, name: str, size: tuple[int, int]) -> QIcon:
"""Return a toggle icon from the icon buffer. or load it."""
- if iconKey in self.TOGGLE_ICON_KEYS:
- pixOne = self.getPixmap(self.TOGGLE_ICON_KEYS[iconKey][0], iconSize)
- pixTwo = self.getPixmap(self.TOGGLE_ICON_KEYS[iconKey][1], iconSize)
- qIcon = QIcon()
- qIcon.addPixmap(pixOne, QIcon.Normal, QIcon.On)
- qIcon.addPixmap(pixTwo, QIcon.Normal, QIcon.Off)
- return qIcon
+ if name in self.TOGGLE_ICON_KEYS:
+ pOne = self.getPixmap(self.TOGGLE_ICON_KEYS[name][0], size)
+ pTwo = self.getPixmap(self.TOGGLE_ICON_KEYS[name][1], size)
+ icon = QIcon()
+ icon.addPixmap(pOne, QIcon.Normal, QIcon.On)
+ icon.addPixmap(pTwo, QIcon.Normal, QIcon.Off)
+ return icon
return QIcon()
- def getPixmap(self, iconKey, iconSize):
+ def getPixmap(self, name: str, size: tuple[int, int]) -> QPixmap:
"""Return an icon from the icon buffer as a QPixmap. If it
doesn't exist, return an empty QPixmap.
"""
- qIcon = self.getIcon(iconKey)
- return qIcon.pixmap(iconSize[0], iconSize[1], QIcon.Normal)
+ return self.getIcon(name).pixmap(size[0], size[1], QIcon.Normal)
- def getItemIcon(self, tType, tClass, tLayout, hLevel="H0"):
+ def getItemIcon(self, tType: nwItemType, tClass: nwItemClass,
+ tLayout: nwItemLayout, hLevel: str = "H0") -> QIcon:
"""Get the correct icon for a project item based on type, class
and header level
"""
@@ -670,17 +667,16 @@ def getItemIcon(self, tType, tClass, tLayout, hLevel="H0"):
return self.getIcon(iconName)
- def getHeaderDecoration(self, hLevel):
- """Get the decoration for a specific header level.
- """
+ def getHeaderDecoration(self, hLevel: int) -> QPixmap:
+ """Get the decoration for a specific header level."""
if not self._headerDec:
iPx = self.mainTheme.baseIconSize
self._headerDec = [
- self.loadDecoration("deco_doc_h0", pxH=iPx),
- self.loadDecoration("deco_doc_h1", pxH=iPx),
- self.loadDecoration("deco_doc_h2", pxH=iPx),
- self.loadDecoration("deco_doc_h3", pxH=iPx),
- self.loadDecoration("deco_doc_h4", pxH=iPx),
+ self.loadDecoration("deco_doc_h0", h=iPx),
+ self.loadDecoration("deco_doc_h1", h=iPx),
+ self.loadDecoration("deco_doc_h2", h=iPx),
+ self.loadDecoration("deco_doc_h3", h=iPx),
+ self.loadDecoration("deco_doc_h4", h=iPx),
]
return self._headerDec[minmax(hLevel, 0, 4)]
@@ -688,27 +684,27 @@ def getHeaderDecoration(self, hLevel):
# Internal Functions
##
- def _loadIcon(self, iconKey):
+ def _loadIcon(self, name: str) -> QIcon:
"""Load an icon from the assets themes folder. Is guaranteed to
return a QIcon.
"""
- if iconKey not in self.ICON_KEYS:
- logger.error("Requested unknown icon name '%s'", iconKey)
+ if name not in self.ICON_KEYS:
+ logger.error("Requested unknown icon name '%s'", name)
return QIcon()
# If we just want the app icons, return right away
- if iconKey == "novelwriter":
+ if name == "novelwriter":
return QIcon(str(self._iconPath / "novelwriter.svg"))
- elif iconKey == "proj_nwx":
+ elif name == "proj_nwx":
return QIcon(str(self._iconPath / "x-novelwriter-project.svg"))
# Otherwise, we load from the theme folder
- if iconKey in self._themeMap:
- logger.debug("Loading: %s", self._themeMap[iconKey].name)
- return QIcon(str(self._themeMap[iconKey]))
+ if name in self._themeMap:
+ logger.debug("Loading: %s", self._themeMap[name].name)
+ return QIcon(str(self._themeMap[name]))
# If we didn't find one, give up and return an empty icon
- logger.warning("Did not load an icon for '%s'", iconKey)
+ logger.warning("Did not load an icon for '%s'", name)
return QIcon()
@@ -719,9 +715,8 @@ def _loadIcon(self, iconKey):
# Module Functions
# =============================================================================================== #
-def _loadInternalName(confParser, confFile):
- """Open a conf file and read the 'name' setting.
- """
+def _loadInternalName(confParser: NWConfigParser, confFile: str | Path) -> str:
+ """Open a conf file and read the 'name' setting."""
try:
with open(confFile, mode="r", encoding="utf-8") as inFile:
confParser.read_file(inFile)
diff --git a/tests/test_gui/test_gui_theme.py b/tests/test_gui/test_gui_theme.py
index f476ca94d..cfb16ab17 100644
--- a/tests/test_gui/test_gui_theme.py
+++ b/tests/test_gui/test_gui_theme.py
@@ -322,17 +322,17 @@ def testGuiTheme_Icons(qtbot, caplog, monkeypatch, nwGUI, tstPaths):
assert qPix.isNull() is True
# Test image sizes
- qPix = iconCache.loadDecoration("wiz-back", pxW=100, pxH=None)
+ qPix = iconCache.loadDecoration("wiz-back", w=100, h=None)
assert qPix.isNull() is False
assert qPix.width() == 100
assert qPix.height() > 100
- qPix = iconCache.loadDecoration("wiz-back", pxW=None, pxH=100)
+ qPix = iconCache.loadDecoration("wiz-back", w=None, h=100)
assert qPix.isNull() is False
assert qPix.width() < 100
assert qPix.height() == 100
- qPix = iconCache.loadDecoration("wiz-back", pxW=100, pxH=100)
+ qPix = iconCache.loadDecoration("wiz-back", w=100, h=100)
assert qPix.isNull() is False
assert qPix.width() == 100
assert qPix.height() == 100
From 6d411e17a6290371858f52a225f347c2fbe8df58 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Wed, 8 Nov 2023 23:12:39 +0100
Subject: [PATCH 10/10] Improve test coverage
---
tests/test_gui/test_gui_doceditor.py | 385 +++++++++++++++++++--------
1 file changed, 278 insertions(+), 107 deletions(-)
diff --git a/tests/test_gui/test_gui_doceditor.py b/tests/test_gui/test_gui_doceditor.py
index 9a28e5c2b..064fcf17b 100644
--- a/tests/test_gui/test_gui_doceditor.py
+++ b/tests/test_gui/test_gui_doceditor.py
@@ -24,15 +24,15 @@
from tools import C, buildTestProject
from mocked import causeOSError
-from PyQt5.QtCore import QThreadPool, Qt
from PyQt5.QtGui import QTextBlock, QTextCursor, QTextOption
+from PyQt5.QtCore import QThreadPool, Qt
from PyQt5.QtWidgets import QAction, qApp
from novelwriter import CONFIG, SHARED
from novelwriter.enum import nwDocAction, nwDocInsert, nwItemLayout, nwTrinary, nwWidget
from novelwriter.constants import nwKeyWords, nwUnicode
from novelwriter.core.index import countWords
-from novelwriter.gui.doceditor import GuiDocEditor
+from novelwriter.gui.doceditor import GuiDocEditor, GuiDocToolBar
KEY_DELAY = 1
@@ -214,9 +214,8 @@ def testGuiEditor_Actions(qtbot, nwGUI, projPath, ipsumText, mockRnd):
buildTestProject(nwGUI, projPath)
assert nwGUI.openDocument(C.hSceneDoc) is True
- theText = "### A Scene\n\n%s" % "\n\n".join(ipsumText)
- nwGUI.docEditor.replaceText(theText)
-
+ text = "### A Scene\n\n%s" % "\n\n".join(ipsumText)
+ nwGUI.docEditor.replaceText(text)
theDoc = nwGUI.docEditor.document()
# Select/Cut/Copy/Paste/Undo/Redo
@@ -228,7 +227,7 @@ def testGuiEditor_Actions(qtbot, nwGUI, projPath, ipsumText, mockRnd):
assert nwGUI.docEditor.docAction(nwDocAction.SEL_ALL) is True
theCursor = nwGUI.docEditor.textCursor()
assert theCursor.hasSelection() is True
- assert theCursor.selectedText() == theText.replace("\n", "\u2029")
+ assert theCursor.selectedText() == text.replace("\n", "\u2029")
theCursor.clearSelection()
# Select Paragraph
@@ -239,7 +238,7 @@ def testGuiEditor_Actions(qtbot, nwGUI, projPath, ipsumText, mockRnd):
assert theCursor.selectedText() == ipsumText[1]
# Cut Selected Text
- nwGUI.docEditor.replaceText(theText)
+ nwGUI.docEditor.replaceText(text)
nwGUI.docEditor.setCursorPosition(1000)
assert nwGUI.docEditor.docAction(nwDocAction.SEL_PARA) is True
assert nwGUI.docEditor.docAction(nwDocAction.CUT) is True
@@ -254,10 +253,10 @@ def testGuiEditor_Actions(qtbot, nwGUI, projPath, ipsumText, mockRnd):
# Paste Back In
assert nwGUI.docEditor.docAction(nwDocAction.PASTE) is True
- assert nwGUI.docEditor.getText() == theText
+ assert nwGUI.docEditor.getText() == text
# Copy Next Paragraph
- nwGUI.docEditor.replaceText(theText)
+ nwGUI.docEditor.replaceText(text)
nwGUI.docEditor.setCursorPosition(1500)
assert nwGUI.docEditor.docAction(nwDocAction.SEL_PARA) is True
assert nwGUI.docEditor.docAction(nwDocAction.COPY) is True
@@ -279,84 +278,132 @@ def testGuiEditor_Actions(qtbot, nwGUI, projPath, ipsumText, mockRnd):
# Emphasis/Undo/Redo
# ==================
- theText = "### A Scene\n\n%s" % ipsumText[0]
- nwGUI.docEditor.replaceText(theText)
+ text = "### A Scene\n\n%s" % ipsumText[0]
+ nwGUI.docEditor.replaceText(text)
# Emphasis
nwGUI.docEditor.setCursorPosition(50)
assert nwGUI.docEditor.docAction(nwDocAction.EMPH) is True
- assert nwGUI.docEditor.getText() == theText.replace("consectetur", "_consectetur_")
+ assert nwGUI.docEditor.getText() == text.replace("consectetur", "_consectetur_")
assert nwGUI.docEditor.docAction(nwDocAction.UNDO) is True
- assert nwGUI.docEditor.getText() == theText
+ assert nwGUI.docEditor.getText() == text
# Strong
nwGUI.docEditor.setCursorPosition(50)
assert nwGUI.docEditor.docAction(nwDocAction.STRONG) is True
- assert nwGUI.docEditor.getText() == theText.replace("consectetur", "**consectetur**")
+ assert nwGUI.docEditor.getText() == text.replace("consectetur", "**consectetur**")
assert nwGUI.docEditor.docAction(nwDocAction.UNDO) is True
- assert nwGUI.docEditor.getText() == theText
+ assert nwGUI.docEditor.getText() == text
# Strikeout
nwGUI.docEditor.setCursorPosition(50)
assert nwGUI.docEditor.docAction(nwDocAction.STRIKE) is True
- assert nwGUI.docEditor.getText() == theText.replace("consectetur", "~~consectetur~~")
+ assert nwGUI.docEditor.getText() == text.replace("consectetur", "~~consectetur~~")
assert nwGUI.docEditor.docAction(nwDocAction.UNDO) is True
- assert nwGUI.docEditor.getText() == theText
+ assert nwGUI.docEditor.getText() == text
# Redo
assert nwGUI.docEditor.docAction(nwDocAction.REDO) is True
- assert nwGUI.docEditor.getText() == theText.replace("consectetur", "~~consectetur~~")
+ assert nwGUI.docEditor.getText() == text.replace("consectetur", "~~consectetur~~")
+ assert nwGUI.docEditor.docAction(nwDocAction.UNDO) is True
+ assert nwGUI.docEditor.getText() == text
+
+ # Shortcodes
+ # ==========
+
+ text = "### A Scene\n\n%s" % ipsumText[0]
+ nwGUI.docEditor.replaceText(text)
+
+ # Italic
+ nwGUI.docEditor.setCursorPosition(46)
+ assert nwGUI.docEditor.docAction(nwDocAction.SC_ITALIC) is True
+ assert nwGUI.docEditor.getText() == text.replace("consectetur", "[i]consectetur[/i]")
+ assert nwGUI.docEditor.docAction(nwDocAction.UNDO) is True
+ assert nwGUI.docEditor.getText() == text
+
+ # Bold
+ nwGUI.docEditor.setCursorPosition(46)
+ assert nwGUI.docEditor.docAction(nwDocAction.SC_BOLD) is True
+ assert nwGUI.docEditor.getText() == text.replace("consectetur", "[b]consectetur[/b]")
+ assert nwGUI.docEditor.docAction(nwDocAction.UNDO) is True
+ assert nwGUI.docEditor.getText() == text
+
+ # Strikethrough
+ nwGUI.docEditor.setCursorPosition(46)
+ assert nwGUI.docEditor.docAction(nwDocAction.SC_STRIKE) is True
+ assert nwGUI.docEditor.getText() == text.replace("consectetur", "[s]consectetur[/s]")
+ assert nwGUI.docEditor.docAction(nwDocAction.UNDO) is True
+ assert nwGUI.docEditor.getText() == text
+
+ # Underline
+ nwGUI.docEditor.setCursorPosition(46)
+ assert nwGUI.docEditor.docAction(nwDocAction.SC_ULINE) is True
+ assert nwGUI.docEditor.getText() == text.replace("consectetur", "[u]consectetur[/u]")
+ assert nwGUI.docEditor.docAction(nwDocAction.UNDO) is True
+ assert nwGUI.docEditor.getText() == text
+
+ # Superscript
+ nwGUI.docEditor.setCursorPosition(46)
+ assert nwGUI.docEditor.docAction(nwDocAction.SC_SUP) is True
+ assert nwGUI.docEditor.getText() == text.replace("consectetur", "[sup]consectetur[/sup]")
+ assert nwGUI.docEditor.docAction(nwDocAction.UNDO) is True
+ assert nwGUI.docEditor.getText() == text
+
+ # Subscript
+ nwGUI.docEditor.setCursorPosition(46)
+ assert nwGUI.docEditor.docAction(nwDocAction.SC_SUB) is True
+ assert nwGUI.docEditor.getText() == text.replace("consectetur", "[sub]consectetur[/sub]")
assert nwGUI.docEditor.docAction(nwDocAction.UNDO) is True
- assert nwGUI.docEditor.getText() == theText
+ assert nwGUI.docEditor.getText() == text
# Quotes
# ======
- theText = "### A Scene\n\n%s" % ipsumText[0]
- nwGUI.docEditor.replaceText(theText)
+ text = "### A Scene\n\n%s" % ipsumText[0]
+ nwGUI.docEditor.replaceText(text)
# Add Single Quotes
nwGUI.docEditor.setCursorPosition(50)
assert nwGUI.docEditor.docAction(nwDocAction.S_QUOTE) is True
- assert nwGUI.docEditor.getText() == theText.replace("consectetur", "\u2018consectetur\u2019")
+ assert nwGUI.docEditor.getText() == text.replace("consectetur", "\u2018consectetur\u2019")
assert nwGUI.docEditor.docAction(nwDocAction.UNDO) is True
- assert nwGUI.docEditor.getText() == theText
+ assert nwGUI.docEditor.getText() == text
# Add Double Quotes
nwGUI.docEditor.setCursorPosition(50)
assert nwGUI.docEditor.docAction(nwDocAction.D_QUOTE) is True
- assert nwGUI.docEditor.getText() == theText.replace("consectetur", "\u201cconsectetur\u201d")
+ assert nwGUI.docEditor.getText() == text.replace("consectetur", "\u201cconsectetur\u201d")
assert nwGUI.docEditor.docAction(nwDocAction.UNDO) is True
- assert nwGUI.docEditor.getText() == theText
+ assert nwGUI.docEditor.getText() == text
# Replace Single Quotes
- repText = theText.replace("consectetur", "'consectetur'")
+ repText = text.replace("consectetur", "'consectetur'")
nwGUI.docEditor.replaceText(repText)
assert nwGUI.docEditor.docAction(nwDocAction.SEL_ALL) is True
assert nwGUI.docEditor.docAction(nwDocAction.REPL_SNG) is True
- assert nwGUI.docEditor.getText() == theText.replace("consectetur", "\u2018consectetur\u2019")
+ assert nwGUI.docEditor.getText() == text.replace("consectetur", "\u2018consectetur\u2019")
# Replace Double Quotes
- repText = theText.replace("consectetur", "\"consectetur\"")
+ repText = text.replace("consectetur", "\"consectetur\"")
nwGUI.docEditor.replaceText(repText)
assert nwGUI.docEditor.docAction(nwDocAction.SEL_ALL) is True
assert nwGUI.docEditor.docAction(nwDocAction.REPL_DBL) is True
- assert nwGUI.docEditor.getText() == theText.replace("consectetur", "\u201cconsectetur\u201d")
+ assert nwGUI.docEditor.getText() == text.replace("consectetur", "\u201cconsectetur\u201d")
# Remove Line Breaks
# ==================
- theText = "### A Scene\n\n%s" % ipsumText[0]
- repText = theText[:100] + theText[100:].replace(" ", "\n", 3)
+ text = "### A Scene\n\n%s" % ipsumText[0]
+ repText = text[:100] + text[100:].replace(" ", "\n", 3)
nwGUI.docEditor.replaceText(repText)
assert nwGUI.docEditor.docAction(nwDocAction.RM_BREAKS) is True
- assert nwGUI.docEditor.getText().strip() == theText.strip()
+ assert nwGUI.docEditor.getText().strip() == text.strip()
# Format Block
# ============
- theText = "## Scene Title\n\nScene text.\n\n"
- nwGUI.docEditor.replaceText(theText)
+ text = "## Scene Title\n\nScene text.\n\n"
+ nwGUI.docEditor.replaceText(text)
# Header 1
nwGUI.docEditor.setCursorPosition(0)
@@ -437,20 +484,125 @@ def testGuiEditor_Actions(qtbot, nwGUI, projPath, ipsumText, mockRnd):
# END Test testGuiEditor_Actions
+@pytest.mark.gui
+def testGuiEditor_ToolBar(qtbot, nwGUI, projPath, mockRnd):
+ """Test the document actions. This is not an extensive test of the
+ action features, just that the actions are actually called. The
+ various action features are tested when their respective functions
+ are tested.
+ """
+ buildTestProject(nwGUI, projPath)
+ assert nwGUI.openDocument(C.hSceneDoc) is True
+
+ docEditor: GuiDocEditor = nwGUI.docEditor
+ docToolBar: GuiDocToolBar = docEditor.docToolBar
+
+ text = (
+ "### A Scene\n\n"
+ "Text bold one\n\n"
+ "Text bold two\n\n"
+ "Text italic one\n\n"
+ "Text italic two\n\n"
+ "Text strikethrough one\n\n"
+ "Text strikethrough two\n\n"
+ "Text underline one\n\n"
+ "Text superscript one\n\n"
+ "Text subscript one\n\n"
+ )
+ length = len(text)
+ docEditor.replaceText(text)
+ assert len(docEditor.getText()) == length
+
+ # Show the ToolBar
+ assert docToolBar.isVisible() is False
+ docEditor._toggleToolBarVisibility()
+ assert docToolBar.isVisible() is True
+
+ # Markdown Mode
+ assert docToolBar.tbMode.isChecked() is False
+
+ # Click Bold
+ docEditor.setCursorPosition(20)
+ docToolBar.tbBold.click()
+ assert len(docEditor.getText()) == length + 4
+
+ # Click Italic
+ docEditor.setCursorPosition(54)
+ docToolBar.tbItalic.click()
+ assert len(docEditor.getText()) == length + 6
+
+ # Click Strikethrough
+ docEditor.setCursorPosition(90)
+ docToolBar.tbStrike.click()
+ assert len(docEditor.getText()) == length + 10
+
+ # Shortcode Mode
+ docToolBar.tbMode.click()
+ assert docToolBar.tbMode.isChecked() is True
+
+ # Click Bold
+ docEditor.setCursorPosition(39)
+ docToolBar.tbBold.click()
+ assert len(docEditor.getText()) == length + 17
+
+ # Click Italic
+ docEditor.setCursorPosition(80)
+ docToolBar.tbItalic.click()
+ assert len(docEditor.getText()) == length + 24
+
+ # Click Strikethrough
+ docEditor.setCursorPosition(132)
+ docToolBar.tbStrike.click()
+ assert len(docEditor.getText()) == length + 31
+
+ # Click Underline
+ docEditor.setCursorPosition(163)
+ docToolBar.tbUnderline.click()
+ assert len(docEditor.getText()) == length + 38
+
+ # Click Superscript
+ docEditor.setCursorPosition(190)
+ docToolBar.tbSuperscript.click()
+ assert len(docEditor.getText()) == length + 49
+
+ # Click Subscript
+ docEditor.setCursorPosition(223)
+ docToolBar.tbSubscript.click()
+ assert len(docEditor.getText()) == length + 60
+
+ # Check Result
+ assert docEditor.getText() == (
+ "### A Scene\n\n"
+ "Text **bold** one\n\n"
+ "Text [b]bold[/b] two\n\n"
+ "Text _italic_ one\n\n"
+ "Text [i]italic[/i] two\n\n"
+ "Text ~~strikethrough~~ one\n\n"
+ "Text [s]strikethrough[/s] two\n\n"
+ "Text [u]underline[/u] one\n\n"
+ "Text [sup]superscript[/sup] one\n\n"
+ "Text [sub]subscript[/sub] one\n\n"
+ )
+
+ # qtbot.stop()
+
+# END Test testGuiEditor_ToolBar
+
+
@pytest.mark.gui
def testGuiEditor_Insert(qtbot, monkeypatch, nwGUI, projPath, ipsumText, mockRnd):
"""Test the document insert functions."""
buildTestProject(nwGUI, projPath)
assert nwGUI.openDocument(C.hSceneDoc) is True
- theText = "### A Scene\n\n%s" % "\n\n".join(ipsumText)
- nwGUI.docEditor.replaceText(theText)
+ text = "### A Scene\n\n%s" % "\n\n".join(ipsumText)
+ nwGUI.docEditor.replaceText(text)
# Insert Text
# ===========
- theText = "### A Scene\n\n%s" % ipsumText[0]
- nwGUI.docEditor.replaceText(theText)
+ text = "### A Scene\n\n%s" % ipsumText[0]
+ nwGUI.docEditor.replaceText(text)
# No Document Handle
nwGUI.docEditor._docHandle = None
@@ -461,23 +613,23 @@ def testGuiEditor_Insert(qtbot, monkeypatch, nwGUI, projPath, ipsumText, mockRnd
# Insert String
nwGUI.docEditor.setCursorPosition(24)
assert nwGUI.docEditor.insertText(", ipsumer,") is True
- assert nwGUI.docEditor.getText() == theText[:24] + ", ipsumer," + theText[24:]
+ assert nwGUI.docEditor.getText() == text[:24] + ", ipsumer," + text[24:]
# Single Quotes
- nwGUI.docEditor.replaceText(theText)
+ nwGUI.docEditor.replaceText(text)
nwGUI.docEditor.setCursorPosition(41)
assert nwGUI.docEditor.insertText(nwDocInsert.QUOTE_LS) is True
nwGUI.docEditor.setCursorPosition(53)
assert nwGUI.docEditor.insertText(nwDocInsert.QUOTE_RS) is True
- assert nwGUI.docEditor.getText() == theText.replace("consectetur", "\u2018consectetur\u2019")
+ assert nwGUI.docEditor.getText() == text.replace("consectetur", "\u2018consectetur\u2019")
# Double Quotes
- nwGUI.docEditor.replaceText(theText)
+ nwGUI.docEditor.replaceText(text)
nwGUI.docEditor.setCursorPosition(41)
assert nwGUI.docEditor.insertText(nwDocInsert.QUOTE_LD) is True
nwGUI.docEditor.setCursorPosition(53)
assert nwGUI.docEditor.insertText(nwDocInsert.QUOTE_RD) is True
- assert nwGUI.docEditor.getText() == theText.replace("consectetur", "\u201cconsectetur\u201d")
+ assert nwGUI.docEditor.getText() == text.replace("consectetur", "\u201cconsectetur\u201d")
# Invalid Inserts
assert nwGUI.docEditor.insertText(nwDocInsert.NO_INSERT) is False
@@ -486,18 +638,18 @@ def testGuiEditor_Insert(qtbot, monkeypatch, nwGUI, projPath, ipsumText, mockRnd
# Insert KeyWords
# ===============
- theText = "### A Scene\n\n\n%s" % ipsumText[0]
- nwGUI.docEditor.replaceText(theText)
+ text = "### A Scene\n\n\n%s" % ipsumText[0]
+ nwGUI.docEditor.replaceText(text)
nwGUI.docEditor.setCursorLine(3)
# Invalid Keyword
assert nwGUI.docEditor.insertKeyWord("stuff") is False
- assert nwGUI.docEditor.getText() == theText
+ assert nwGUI.docEditor.getText() == text
# Valid Keyword
assert nwGUI.docEditor.insertKeyWord(nwKeyWords.POV_KEY) is True
assert nwGUI.docEditor.insertText("Jane\n")
- assert nwGUI.docEditor.getText() == theText.replace(
+ assert nwGUI.docEditor.getText() == text.replace(
"\n\n\n", "\n\n@pov: Jane\n\n", 1
)
@@ -510,7 +662,7 @@ def testGuiEditor_Insert(qtbot, monkeypatch, nwGUI, projPath, ipsumText, mockRnd
nwGUI.docEditor.setCursorPosition(20)
assert nwGUI.docEditor.insertKeyWord(nwKeyWords.CHAR_KEY) is True
assert nwGUI.docEditor.insertText("John")
- assert nwGUI.docEditor.getText() == theText.replace(
+ assert nwGUI.docEditor.getText() == text.replace(
"\n\n\n", "\n\n@pov: Jane\n@char: John\n\n", 1
)
@@ -525,45 +677,45 @@ def testGuiEditor_TextManipulation(qtbot, monkeypatch, nwGUI, projPath, ipsumTex
buildTestProject(nwGUI, projPath)
assert nwGUI.openDocument(C.hSceneDoc) is True
- theText = "### A Scene\n\n%s" % "\n\n".join(ipsumText)
- nwGUI.docEditor.replaceText(theText)
+ text = "### A Scene\n\n%s" % "\n\n".join(ipsumText)
+ nwGUI.docEditor.replaceText(text)
# Clear Surrounding
# =================
# No Selection
- theText = "### A Scene\n\n%s" % ipsumText[0]
- nwGUI.docEditor.replaceText(theText)
+ text = "### A Scene\n\n%s" % ipsumText[0]
+ nwGUI.docEditor.replaceText(text)
nwGUI.docEditor.setCursorPosition(45)
- theCursor = nwGUI.docEditor.textCursor()
- assert nwGUI.docEditor._clearSurrounding(theCursor, 1) is False
+ cursor = nwGUI.docEditor.textCursor()
+ assert nwGUI.docEditor._clearSurrounding(cursor, 1) is False
# Clear Characters, 1 Layer
- repText = theText.replace("consectetur", "=consectetur=")
+ repText = text.replace("consectetur", "=consectetur=")
nwGUI.docEditor.replaceText(repText)
nwGUI.docEditor.setCursorPosition(45)
- theCursor = nwGUI.docEditor.textCursor()
- theCursor.select(QTextCursor.WordUnderCursor)
- assert nwGUI.docEditor._clearSurrounding(theCursor, 1) is True
- assert nwGUI.docEditor.getText() == theText
+ cursor = nwGUI.docEditor.textCursor()
+ cursor.select(QTextCursor.WordUnderCursor)
+ assert nwGUI.docEditor._clearSurrounding(cursor, 1) is True
+ assert nwGUI.docEditor.getText() == text
# Clear Characters, 2 Layers
- repText = theText.replace("consectetur", "==consectetur==")
+ repText = text.replace("consectetur", "==consectetur==")
nwGUI.docEditor.replaceText(repText)
nwGUI.docEditor.setCursorPosition(45)
- theCursor = nwGUI.docEditor.textCursor()
- theCursor.select(QTextCursor.WordUnderCursor)
- assert nwGUI.docEditor._clearSurrounding(theCursor, 2) is True
- assert nwGUI.docEditor.getText() == theText
+ cursor = nwGUI.docEditor.textCursor()
+ cursor.select(QTextCursor.WordUnderCursor)
+ assert nwGUI.docEditor._clearSurrounding(cursor, 2) is True
+ assert nwGUI.docEditor.getText() == text
# Wrap Selection
# ==============
- theText = "### A Scene\n\n%s" % "\n\n".join(ipsumText[0:2])
- nwGUI.docEditor.replaceText(theText)
+ text = "### A Scene\n\n%s" % "\n\n".join(ipsumText[0:2])
+ nwGUI.docEditor.replaceText(text)
nwGUI.docEditor.setCursorPosition(45)
# No Selection
@@ -572,23 +724,23 @@ def testGuiEditor_TextManipulation(qtbot, monkeypatch, nwGUI, projPath, ipsumTex
assert nwGUI.docEditor._wrapSelection("=", "=") is False
# Wrap Equal
- nwGUI.docEditor.replaceText(theText)
+ nwGUI.docEditor.replaceText(text)
nwGUI.docEditor.setCursorPosition(45)
assert nwGUI.docEditor._wrapSelection("=") is True
- assert nwGUI.docEditor.getText() == theText.replace("consectetur", "=consectetur=")
+ assert nwGUI.docEditor.getText() == text.replace("consectetur", "=consectetur=")
# Wrap Unequal
- nwGUI.docEditor.replaceText(theText)
+ nwGUI.docEditor.replaceText(text)
nwGUI.docEditor.setCursorPosition(45)
assert nwGUI.docEditor._wrapSelection("=", "*") is True
- assert nwGUI.docEditor.getText() == theText.replace("consectetur", "=consectetur*")
+ assert nwGUI.docEditor.getText() == text.replace("consectetur", "=consectetur*")
# Past Paragraph
- nwGUI.docEditor.replaceText(theText)
- theCursor = nwGUI.docEditor.textCursor()
- theCursor.setPosition(13, QTextCursor.MoveAnchor)
- theCursor.setPosition(1000, QTextCursor.KeepAnchor)
- nwGUI.docEditor.setTextCursor(theCursor)
+ nwGUI.docEditor.replaceText(text)
+ cursor = nwGUI.docEditor.textCursor()
+ cursor.setPosition(13, QTextCursor.MoveAnchor)
+ cursor.setPosition(1000, QTextCursor.KeepAnchor)
+ nwGUI.docEditor.setTextCursor(cursor)
assert nwGUI.docEditor._wrapSelection("=") is True
newText = nwGUI.docEditor.getText()
@@ -599,8 +751,8 @@ def testGuiEditor_TextManipulation(qtbot, monkeypatch, nwGUI, projPath, ipsumTex
# Toggle Format
# =============
- theText = "### A Scene\n\n%s" % "\n\n".join(ipsumText[0:2])
- nwGUI.docEditor.replaceText(theText)
+ text = "### A Scene\n\n%s" % "\n\n".join(ipsumText[0:2])
+ nwGUI.docEditor.replaceText(text)
nwGUI.docEditor.setCursorPosition(45)
# No Selection
@@ -609,17 +761,17 @@ def testGuiEditor_TextManipulation(qtbot, monkeypatch, nwGUI, projPath, ipsumTex
assert nwGUI.docEditor._toggleFormat(2, "=") is False
# Wrap Single Equal
- nwGUI.docEditor.replaceText(theText)
+ nwGUI.docEditor.replaceText(text)
nwGUI.docEditor.setCursorPosition(45)
assert nwGUI.docEditor._toggleFormat(1, "=") is True
- assert nwGUI.docEditor.getText() == theText.replace("consectetur", "=consectetur=")
+ assert nwGUI.docEditor.getText() == text.replace("consectetur", "=consectetur=")
# Past Paragraph
- nwGUI.docEditor.replaceText(theText)
- theCursor = nwGUI.docEditor.textCursor()
- theCursor.setPosition(13, QTextCursor.MoveAnchor)
- theCursor.setPosition(1000, QTextCursor.KeepAnchor)
- nwGUI.docEditor.setTextCursor(theCursor)
+ nwGUI.docEditor.replaceText(text)
+ cursor = nwGUI.docEditor.textCursor()
+ cursor.setPosition(13, QTextCursor.MoveAnchor)
+ cursor.setPosition(1000, QTextCursor.KeepAnchor)
+ nwGUI.docEditor.setTextCursor(cursor)
assert nwGUI.docEditor._toggleFormat(1, "=") is True
newText = nwGUI.docEditor.getText()
@@ -628,31 +780,31 @@ def testGuiEditor_TextManipulation(qtbot, monkeypatch, nwGUI, projPath, ipsumTex
assert newPara[2] == ipsumText[1]
# Wrap Double Equal
- nwGUI.docEditor.replaceText(theText)
+ nwGUI.docEditor.replaceText(text)
nwGUI.docEditor.setCursorPosition(45)
assert nwGUI.docEditor._toggleFormat(2, "=") is True
- assert nwGUI.docEditor.getText() == theText.replace("consectetur", "==consectetur==")
+ assert nwGUI.docEditor.getText() == text.replace("consectetur", "==consectetur==")
# Toggle Double Equal
- nwGUI.docEditor.replaceText(theText)
+ nwGUI.docEditor.replaceText(text)
nwGUI.docEditor.setCursorPosition(45)
assert nwGUI.docEditor._toggleFormat(2, "=") is True
assert nwGUI.docEditor._toggleFormat(2, "=") is True
- assert nwGUI.docEditor.getText() == theText
+ assert nwGUI.docEditor.getText() == text
# Toggle Triple+Double Equal
- nwGUI.docEditor.replaceText(theText)
+ nwGUI.docEditor.replaceText(text)
nwGUI.docEditor.setCursorPosition(45)
assert nwGUI.docEditor._toggleFormat(3, "=") is True
assert nwGUI.docEditor._toggleFormat(2, "=") is True
- assert nwGUI.docEditor.getText() == theText.replace("consectetur", "=consectetur=")
+ assert nwGUI.docEditor.getText() == text.replace("consectetur", "=consectetur=")
# Toggle Unequal
- repText = theText.replace("consectetur", "=consectetur==")
+ repText = text.replace("consectetur", "=consectetur==")
nwGUI.docEditor.replaceText(repText)
nwGUI.docEditor.setCursorPosition(45)
assert nwGUI.docEditor._toggleFormat(1, "=") is True
- assert nwGUI.docEditor.getText() == theText.replace("consectetur", "consectetur=")
+ assert nwGUI.docEditor.getText() == text.replace("consectetur", "consectetur=")
assert nwGUI.docEditor._toggleFormat(1, "=") is True
assert nwGUI.docEditor.getText() == repText
@@ -660,15 +812,15 @@ def testGuiEditor_TextManipulation(qtbot, monkeypatch, nwGUI, projPath, ipsumTex
# ==============
# No Selection
- theText = "### A Scene\n\n%s" % ipsumText[0].replace("consectetur", "=consectetur=")
- nwGUI.docEditor.replaceText(theText)
+ text = "### A Scene\n\n%s" % ipsumText[0].replace("consectetur", "=consectetur=")
+ nwGUI.docEditor.replaceText(text)
nwGUI.docEditor.setCursorPosition(45)
assert nwGUI.docEditor._replaceQuotes("=", "<", ">") is False
# First Paragraph Selected
# This should not replace anything in second paragraph
- theText = "### A Scene\n\n%s" % "\n\n".join(ipsumText[0:2]).replace("ipsum", "=ipsum=")
- nwGUI.docEditor.replaceText(theText)
+ text = "### A Scene\n\n%s" % "\n\n".join(ipsumText[0:2]).replace("ipsum", "=ipsum=")
+ nwGUI.docEditor.replaceText(text)
nwGUI.docEditor.setCursorPosition(45)
assert nwGUI.docEditor.docAction(nwDocAction.SEL_PARA)
assert nwGUI.docEditor._replaceQuotes("=", "<", ">") is True
@@ -679,12 +831,12 @@ def testGuiEditor_TextManipulation(qtbot, monkeypatch, nwGUI, projPath, ipsumTex
assert newPara[2] == ipsumText[1].replace("ipsum", "=ipsum=")
# Edge of Document
- theText = ipsumText[0].replace("Lorem", "=Lorem=")
- nwGUI.docEditor.replaceText(theText)
+ text = ipsumText[0].replace("Lorem", "=Lorem=")
+ nwGUI.docEditor.replaceText(text)
nwGUI.docEditor.setCursorPosition(45)
assert nwGUI.docEditor.docAction(nwDocAction.SEL_ALL)
assert nwGUI.docEditor._replaceQuotes("=", "<", ">") is True
- assert nwGUI.docEditor.getText() == theText.replace("=Lorem=", "")
+ assert nwGUI.docEditor.getText() == text.replace("=Lorem=", "")
# Remove Line Breaks
# ==================
@@ -693,20 +845,20 @@ def testGuiEditor_TextManipulation(qtbot, monkeypatch, nwGUI, projPath, ipsumTex
parTwo = ipsumText[1].replace(" ", "\n", 5)
# Remove All
- theText = "### A Scene\n\n%s\n\n%s" % (parOne, parTwo)
- nwGUI.docEditor.replaceText(theText)
+ text = "### A Scene\n\n%s\n\n%s" % (parOne, parTwo)
+ nwGUI.docEditor.replaceText(text)
nwGUI.docEditor.setCursorPosition(45)
nwGUI.docEditor._removeInParLineBreaks()
assert nwGUI.docEditor.getText() == "### A Scene\n\n%s\n" % "\n\n".join(ipsumText[0:2])
# Remove First Paragraph
# Second paragraphs should remain unchanged
- theText = "### A Scene\n\n%s\n\n%s" % (parOne, parTwo)
- nwGUI.docEditor.replaceText(theText)
- theCursor = nwGUI.docEditor.textCursor()
- theCursor.setPosition(16, QTextCursor.MoveAnchor)
- theCursor.setPosition(680, QTextCursor.KeepAnchor)
- nwGUI.docEditor.setTextCursor(theCursor)
+ text = "### A Scene\n\n%s\n\n%s" % (parOne, parTwo)
+ nwGUI.docEditor.replaceText(text)
+ cursor = nwGUI.docEditor.textCursor()
+ cursor.setPosition(16, QTextCursor.MoveAnchor)
+ cursor.setPosition(680, QTextCursor.KeepAnchor)
+ nwGUI.docEditor.setTextCursor(cursor)
nwGUI.docEditor._removeInParLineBreaks()
newText = nwGUI.docEditor.getText()
@@ -720,6 +872,25 @@ def testGuiEditor_TextManipulation(qtbot, monkeypatch, nwGUI, projPath, ipsumTex
assert newPara[6] == twoBits[4]
assert newPara[7] == " ".join(twoBits[5:])
+ # Key Press Events
+ # ================
+ text = "### A Scene\n\n%s\n\n%s" % (parOne, parTwo)
+ nwGUI.docEditor.replaceText(text)
+ assert nwGUI.docEditor.getText() == text
+
+ # Select All
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_A, modifier=Qt.ControlModifier, delay=KEY_DELAY)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Delete, delay=KEY_DELAY)
+ assert nwGUI.docEditor.getText() == ""
+
+ # Undo
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Z, modifier=Qt.ControlModifier, delay=KEY_DELAY)
+ assert nwGUI.docEditor.getText() == text
+
+ # Redo
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Y, modifier=Qt.ControlModifier, delay=KEY_DELAY)
+ assert nwGUI.docEditor.getText() == ""
+
# qtbot.stop()
# END Test testGuiEditor_TextManipulation