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 86e60afa..4e931351 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 fa8b64ad..6355ca63 100644
--- a/novelwriter/gui/mainmenu.py
+++ b/novelwriter/gui/mainmenu.py
@@ -209,7 +209,7 @@ class GuiMainMenu(QMenuBar):
# 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 @@ class GuiMainMenu(QMenuBar):
# 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 4b7b4e7b..be719a42 100644
--- a/novelwriter/gui/sidebar.py
+++ b/novelwriter/gui/sidebar.py
@@ -115,7 +115,6 @@ class GuiSideBar(QWidget):
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 4d324677..adcebca8 100644
--- a/novelwriter/guimain.py
+++ b/novelwriter/guimain.py
@@ -267,6 +267,8 @@ class GuiMain(QMainWindow):
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 @@ class GuiMain(QMainWindow):
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 @@ class GuiMain(QMainWindow):
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 @@ class GuiMain(QMainWindow):
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 @@ class GuiMain(QMainWindow):
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 74e0e352..52e2bc0f 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 e9094c8b..f12220e5 100644
--- a/novelwriter/gui/doceditor.py
+++ b/novelwriter/gui/doceditor.py
@@ -46,7 +46,7 @@ from PyQt5.QtGui import (
)
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 @@ class GuiDocEditor(QPlainTextEdit):
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 @@ class GuiDocEditor(QPlainTextEdit):
self.docSearch.updateTheme()
self.docHeader.updateTheme()
self.docFooter.updateTheme()
+ self.docToolBar.updateTheme()
return
def updateSyntaxColours(self) -> None:
@@ -514,6 +521,7 @@ class GuiDocEditor(QPlainTextEdit):
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 @@ class GuiDocEditor(QPlainTextEdit):
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 @@ class GuiDocEditor(QPlainTextEdit):
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 @@ class GuiDocEditSearch(QFrame):
class GuiDocEditHeader(QWidget):
+ toggleToolBarRequest = pyqtSignal()
+ closeDocumentRequest = pyqtSignal()
+
def __init__(self, docEditor: GuiDocEditor) -> None:
super().__init__(parent=docEditor)
@@ -2499,6 +2588,7 @@ class GuiDocEditHeader(QWidget):
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 @@ class GuiDocEditHeader(QWidget):
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 @@ class GuiDocEditHeader(QWidget):
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 @@ class GuiDocEditHeader(QWidget):
"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 @@ class GuiDocEditHeader(QWidget):
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 @@ class GuiDocEditHeader(QWidget):
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 @@ class GuiDocEditHeader(QWidget):
# 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 18dfdbc5..9aff4eee 100644
--- a/novelwriter/gui/docviewer.py
+++ b/novelwriter/gui/docviewer.py
@@ -705,7 +705,7 @@ class GuiDocViewHeader(QWidget):
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 @@ class GuiDocViewHeader(QWidget):
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 @@ class GuiDocViewHeader(QWidget):
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 @@ class GuiDocViewHeader(QWidget):
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 d0b69572..c8446123 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 00000000..19ec3bde
--- /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 4bf1c92f..00000000
--- 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 22fd9456..d9aa905c 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 00000000..3b5d6383
--- /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 c23c08ca..00000000
--- 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 0dd62f06..48a0c594 100644
--- a/novelwriter/core/options.py
+++ b/novelwriter/core/options.py
@@ -28,7 +28,7 @@ import json
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 @@ if TYPE_CHECKING: # pragma: no cover
logger = logging.getLogger(__name__)
+NWEnum = TypeVar("NWEnum", bound=Enum)
+
VALID_MAP = {
"GuiWritingStats": {
"winWidth", "winHeight", "widthCol0", "widthCol1", "widthCol2",
@@ -201,11 +203,11 @@ class OptionState:
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 0742ab54..24683733 100644
--- a/novelwriter/gui/noveltree.py
+++ b/novelwriter/gui/noveltree.py
@@ -29,9 +29,10 @@ import logging
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 import CONFIG, SHARED
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 @@ class GuiNovelView(QWidget):
# 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 @@ class GuiNovelView(QWidget):
return
- def closeProjectTasks(self):
+ def closeProjectTasks(self) -> None:
"""Run closing project tasks."""
lastColType = self.novelTree.lastColType
lastColSize = self.novelTree.lastColSize
@@ -150,15 +151,13 @@ class GuiNovelView(QWidget):
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 @@ class GuiNovelView(QWidget):
##
@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 @@ class GuiNovelView(QWidget):
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 @@ class GuiNovelToolBar(QWidget):
# 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 @@ class GuiNovelToolBar(QWidget):
"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 @@ class GuiNovelToolBar(QWidget):
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 @@ class GuiNovelToolBar(QWidget):
##
@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 @@ class GuiNovelToolBar(QWidget):
# 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 @@ class GuiNovelTree(QTreeWidget):
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 @@ class GuiNovelTree(QTreeWidget):
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 @@ class GuiNovelTree(QTreeWidget):
##
@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 @@ class GuiNovelTree(QTreeWidget):
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 @@ class GuiNovelTree(QTreeWidget):
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 @@ class GuiNovelTree(QTreeWidget):
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 @@ class GuiNovelTree(QTreeWidget):
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 @@ class GuiNovelTree(QTreeWidget):
# 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 @@ class GuiNovelTree(QTreeWidget):
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 @@ class GuiNovelTree(QTreeWidget):
##
@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 @@ class GuiNovelTree(QTreeWidget):
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 @@ class GuiNovelTree(QTreeWidget):
# 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 @@ class GuiNovelTree(QTreeWidget):
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 @@ class GuiNovelTree(QTreeWidget):
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 @@ class GuiNovelTree(QTreeWidget):
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 @@ class GuiNovelTree(QTreeWidget):
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 84234ccf..596a422b 100644
--- a/novelwriter/gui/outline.py
+++ b/novelwriter/gui/outline.py
@@ -261,6 +261,7 @@ class GuiOutlineToolBar(QToolBar):
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 787f5671..d1c2f903 100644
--- a/novelwriter/gui/projtree.py
+++ b/novelwriter/gui/projtree.py
@@ -361,12 +361,13 @@ class GuiProjectToolBar(QWidget):
"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 c8446123..94b4c633 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 00000000..2eaeb846
--- /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 00000000..0ec5fb2a
--- /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 00000000..02768596
--- /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 00000000..4ea59ec3
--- /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 00000000..639dd694
--- /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 00000000..7566bdee
--- /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 00000000..0ac13bf3
--- /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 00000000..3a249f4d
--- /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 d9aa905c..62314511 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 00000000..3fbfbc88
--- /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 00000000..893d8930
--- /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 00000000..24b3809d
--- /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 00000000..fc19a690
--- /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 00000000..89f4b7ee
--- /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 00000000..ca122269
--- /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 f12220e5..6e204219 100644
--- a/novelwriter/gui/doceditor.py
+++ b/novelwriter/gui/doceditor.py
@@ -2107,18 +2107,39 @@ class GuiDocToolBar(QWidget):
# 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 @@ class GuiDocToolBar(QWidget):
# 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 9aff4eee..ff548fb6 100644
--- a/novelwriter/gui/docviewer.py
+++ b/novelwriter/gui/docviewer.py
@@ -34,8 +34,8 @@ from typing import TYPE_CHECKING
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 @@ class GuiDocViewFooter(QWidget):
# 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 da8f1be4..22d8cf95 100644
--- a/novelwriter/gui/theme.py
+++ b/novelwriter/gui/theme.py
@@ -130,6 +130,7 @@ class GuiTheme:
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 @@ class GuiIcons:
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 @@ class GuiIcons:
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 00000000..db909aed
--- /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 00000000..1634872e
--- /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 50c8c1ab..ebd0d05f 100644
--- a/novelwriter/config.py
+++ b/novelwriter/config.py
@@ -178,9 +178,11 @@ class Config:
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 @@ class Config:
# 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 @@ class Config:
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 6e204219..efccc361 100644
--- a/novelwriter/gui/doceditor.py
+++ b/novelwriter/gui/doceditor.py
@@ -138,9 +138,12 @@ class GuiDocEditor(QPlainTextEdit):
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 @@ class GuiDocEditor(QPlainTextEdit):
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 @@ class GuiDocEditor(QPlainTextEdit):
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 @@ class GuiDocEditor(QPlainTextEdit):
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 @@ class GuiDocToolBar(QWidget):
# ===============
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 @@ class GuiDocToolBar(QWidget):
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 @@ class GuiDocToolBar(QWidget):
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 @@ class GuiDocEditSearch(QFrame):
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 6355ca63..e6453a82 100644
--- a/novelwriter/gui/mainmenu.py
+++ b/novelwriter/gui/mainmenu.py
@@ -30,7 +30,7 @@ from pathlib import Path
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 @@ class GuiMainMenu(QMenuBar):
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 @@ class GuiMainMenu(QMenuBar):
# 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 @@ class GuiMainMenu(QMenuBar):
# 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 @@ class GuiMainMenu(QMenuBar):
# 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 @@ class GuiMainMenu(QMenuBar):
# 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 @@ class GuiMainMenu(QMenuBar):
# 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 @@ class GuiMainMenu(QMenuBar):
# 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 @@ class GuiMainMenu(QMenuBar):
# 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 @@ class GuiMainMenu(QMenuBar):
# 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 @@ class GuiMainMenu(QMenuBar):
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 @@ class GuiMainMenu(QMenuBar):
# 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 @@ class GuiMainMenu(QMenuBar):
# 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 @@ class GuiMainMenu(QMenuBar):
# 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 @@ class GuiMainMenu(QMenuBar):
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 @@ class GuiMainMenu(QMenuBar):
# 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 @@ class GuiMainMenu(QMenuBar):
# 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 @@ class GuiMainMenu(QMenuBar):
# 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 @@ class GuiMainMenu(QMenuBar):
# 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 adcebca8..41301476 100644
--- a/novelwriter/guimain.py
+++ b/novelwriter/guimain.py
@@ -62,7 +62,7 @@ from novelwriter.tools.writingstats import GuiWritingStats
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 @@ class GuiMain(QMainWindow):
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 @@ class GuiMain(QMainWindow):
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 @@ class GuiMain(QMainWindow):
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 f3434415..95df9072 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 dfa3f3fd..6ea3a0d5 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 747a8f73..01345630 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 efccc361..f0aa7041 100644
--- a/novelwriter/gui/doceditor.py
+++ b/novelwriter/gui/doceditor.py
@@ -138,7 +138,6 @@ class GuiDocEditor(QPlainTextEdit):
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 @@ class GuiDocEditor(QPlainTextEdit):
qApp.processEvents()
self.setDocumentChanged(False)
self._qDocument.clearUndoRedoStacks()
+ self.docToolBar.setVisible(CONFIG.showEditToolBar)
qApp.restoreOverrideCursor()
@@ -1196,6 +1196,7 @@ class GuiDocEditor(QPlainTextEdit):
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 7566bdee..e0ae3587 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 0ac13bf3..1d60aec2 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 db909aed..2384f6ea 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 1634872e..df0aa35c 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 f0aa7041..a3bc291c 100644
--- a/novelwriter/gui/doceditor.py
+++ b/novelwriter/gui/doceditor.py
@@ -2188,18 +2188,6 @@ class GuiDocToolBar(QWidget):
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 @@ class GuiDocToolBar(QWidget):
return
##
- # Internal Slots
+ # Private Slots
##
@pyqtSlot(bool)
diff --git a/novelwriter/gui/noveltree.py b/novelwriter/gui/noveltree.py
index 24683733..40aae9ef 100644
--- a/novelwriter/gui/noveltree.py
+++ b/novelwriter/gui/noveltree.py
@@ -468,7 +468,7 @@ class GuiNovelTree(QTreeWidget):
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 22d8cf95..6c4d7e76 100644
--- a/novelwriter/gui/theme.py
+++ b/novelwriter/gui/theme.py
@@ -36,7 +36,7 @@ from PyQt5.QtGui import (
)
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 @@ logger = logging.getLogger(__name__)
class GuiTheme:
- def __init__(self):
+ def __init__(self) -> None:
self.iconCache = GuiIcons(self)
@@ -183,7 +183,7 @@ class GuiTheme:
# 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 @@ class GuiTheme:
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 @@ class GuiTheme:
# 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 @@ class GuiTheme:
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 @@ class GuiTheme:
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 @@ class GuiIcons:
# 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 @@ class GuiIcons:
# 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 @@ class GuiIcons:
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 @@ class GuiIcons:
# 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 @@ class GuiIcons:
# 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 f476ca94..cfb16ab1 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 9a28e5c2..064fcf17 100644
--- a/tests/test_gui/test_gui_doceditor.py
+++ b/tests/test_gui/test_gui_doceditor.py
@@ -24,15 +24,15 @@ import pytest
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() == theText
+ 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() == 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