diff --git a/.github/workflows/test_mac.yml b/.github/workflows/test_mac.yml index 8465f75b..2070f157 100644 --- a/.github/workflows/test_mac.yml +++ b/.github/workflows/test_mac.yml @@ -12,7 +12,7 @@ on: jobs: testMac: - runs-on: macos-13 + runs-on: macos-latest steps: - name: Python Setup uses: actions/setup-python@v5 diff --git a/novelWriter.py b/novelWriter.py index 797dfe23..78c8db27 100755 --- a/novelWriter.py +++ b/novelWriter.py @@ -7,8 +7,8 @@ import os import sys try: - import PyQt6.QtCore - import PyQt6.QtGui + import PyQt6.QtCore # noqa: F401 + import PyQt6.QtGui # noqa: F401 import PyQt6.QtWidgets # noqa: F401 except Exception: print("ERROR: Failed to load dependency PyQt6") diff --git a/novelwriter/core/status.py b/novelwriter/core/status.py index fc8bf85c..9a3d3194 100644 --- a/novelwriter/core/status.py +++ b/novelwriter/core/status.py @@ -183,7 +183,7 @@ class NWStatus: icon = NWStatus.createIcon(self._height, color, shape) return StatusEntry(simplified(data[2]), color, theme, shape, icon) except Exception: - logger.error("Could not parse entry %s", str(data)) + logger.error("Could not parse entry %s", data) return None def refreshIcons(self) -> None: diff --git a/novelwriter/core/tree.py b/novelwriter/core/tree.py index 4ee4b626..2f151cc4 100644 --- a/novelwriter/core/tree.py +++ b/novelwriter/core/tree.py @@ -89,7 +89,7 @@ class NWTree: """ if tHandle and tHandle in self._items: return self._items[tHandle] - logger.error("No tree item with handle '%s'", str(tHandle)) + logger.error("No tree item with handle '%s'", tHandle) return None def __contains__(self, tHandle: str) -> bool: diff --git a/novelwriter/error.py b/novelwriter/error.py index 59fafdff..0f1ff8fd 100644 --- a/novelwriter/error.py +++ b/novelwriter/error.py @@ -171,7 +171,7 @@ def exceptionHandler(exType: type, exValue: BaseException, exTrace: TracebackTyp from PyQt6.QtWidgets import QApplication - logger.critical("%s: %s", exType.__name__, str(exValue)) + logger.critical("%s: %s", exType.__name__, exValue) print_tb(exTrace) try: diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index c6773f24..1496743f 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -77,7 +77,8 @@ from novelwriter.types import ( QtAlignCenterTop, QtAlignJustify, QtAlignLeft, QtAlignLeftTop, QtAlignRight, QtImCursorRectangle, QtKeepAnchor, QtModCtrl, QtModNone, QtModShift, QtMouseLeft, QtMoveAnchor, QtMoveLeft, QtMoveRight, - QtScrollAlwaysOff, QtScrollAsNeeded, QtTransparent + QtScrollAlwaysOff, QtScrollAsNeeded, QtSelectBlock, QtSelectDocument, + QtSelectWord, QtTransparent ) logger = logging.getLogger(__name__) @@ -672,7 +673,7 @@ class GuiDocEditor(QPlainTextEdit): self.spellCheckStateChanged.emit(state) self.spellCheckDocument() - logger.debug("Spell check is set to '%s'", str(state)) + logger.debug("Spell check is set to '%s'", state) def spellCheckDocument(self) -> None: """Rerun the highlighter to update spell checking status of the @@ -731,9 +732,9 @@ class GuiDocEditor(QPlainTextEdit): elif action == nwDocAction.D_QUOTE: self._wrapSelection(CONFIG.fmtDQuoteOpen, CONFIG.fmtDQuoteClose) elif action == nwDocAction.SEL_ALL: - self._makeSelection(QTextCursor.SelectionType.Document) + self._makeSelection(QtSelectDocument) elif action == nwDocAction.SEL_PARA: - self._makeSelection(QTextCursor.SelectionType.BlockUnderCursor) + self._makeSelection(QtSelectBlock) elif action == nwDocAction.BLOCK_H1: self._formatBlock(nwDocAction.BLOCK_H1) elif action == nwDocAction.BLOCK_H2: @@ -785,7 +786,7 @@ class GuiDocEditor(QPlainTextEdit): elif action == nwDocAction.SC_SUB: self._wrapSelection(nwShortcode.SUB_O, nwShortcode.SUB_C) else: - logger.debug("Unknown or unsupported document action '%s'", str(action)) + logger.debug("Unknown or unsupported document action '%s'", action) self._allowAutoReplace(True) return False @@ -1174,13 +1175,9 @@ class GuiDocEditor(QPlainTextEdit): action = qtAddAction(ctxMenu, self.tr("Select All")) action.triggered.connect(qtLambda(self.docAction, nwDocAction.SEL_ALL)) action = qtAddAction(ctxMenu, self.tr("Select Word")) - action.triggered.connect(qtLambda( - self._makePosSelection, QTextCursor.SelectionType.WordUnderCursor, pos, - )) + action.triggered.connect(qtLambda(self._makePosSelection, QtSelectWord, pos)) action = qtAddAction(ctxMenu, self.tr("Select Paragraph")) - action.triggered.connect(qtLambda( - self._makePosSelection, QTextCursor.SelectionType.BlockUnderCursor, pos - )) + action.triggered.connect(qtLambda(self._makePosSelection, QtSelectBlock, pos)) # Spell Checking if SHARED.project.data.spellCheck: @@ -1750,7 +1747,7 @@ class GuiDocEditor(QPlainTextEdit): elif action == nwDocAction.BLOCK_TXT: text = temp else: - logger.error("Unknown or unsupported block format requested: '%s'", str(action)) + logger.error("Unknown or unsupported block format requested: '%s'", action) return nwDocAction.NO_ACTION, "", 0 return action, text, offset @@ -1760,7 +1757,7 @@ class GuiDocEditor(QPlainTextEdit): cursor = self.textCursor() block = cursor.block() if not block.isValid(): - logger.debug("Invalid block selected for action '%s'", str(action)) + logger.debug("Invalid block selected for action '%s'", action) return False action, text, offset = self._processBlockFormat(action, block.text()) @@ -1770,7 +1767,7 @@ class GuiDocEditor(QPlainTextEdit): pos = cursor.position() cursor.beginEditBlock() - self._makeSelection(QTextCursor.SelectionType.BlockUnderCursor, cursor) + self._makeSelection(QtSelectBlock, cursor) cursor.insertText(text) cursor.endEditBlock() @@ -1798,7 +1795,7 @@ class GuiDocEditor(QPlainTextEdit): if pAction != nwDocAction.NO_ACTION and blockText.strip(): action = pAction # First block decides further actions cursor.setPosition(block.position()) - self._makeSelection(QTextCursor.SelectionType.BlockUnderCursor, cursor) + self._makeSelection(QtSelectBlock, cursor) cursor.insertText(text) toggle = False @@ -1818,7 +1815,7 @@ class GuiDocEditor(QPlainTextEdit): """Strip line breaks within paragraphs in the selected text.""" cursor = self.textCursor() if not cursor.hasSelection(): - cursor.select(QTextCursor.SelectionType.Document) + cursor.select(QtSelectDocument) rS = 0 rE = self._qDocument.characterCount() @@ -2035,10 +2032,10 @@ class GuiDocEditor(QPlainTextEdit): cursor.clearSelection() cursor.select(mode) - if mode == QTextCursor.SelectionType.WordUnderCursor: + if mode == QtSelectWord: cursor = self._autoSelect() - elif mode == QTextCursor.SelectionType.BlockUnderCursor: + elif mode == QtSelectBlock: # This selection mode also selects the preceding paragraph # separator, which we want to avoid. posS = cursor.selectionStart() diff --git a/novelwriter/gui/docviewer.py b/novelwriter/gui/docviewer.py index 10ef9c46..fc7bef81 100644 --- a/novelwriter/gui/docviewer.py +++ b/novelwriter/gui/docviewer.py @@ -53,7 +53,8 @@ from novelwriter.formats.toqdoc import ToQTextDocument from novelwriter.gui.theme import STYLES_MIN_TOOLBUTTON from novelwriter.types import ( QtAlignCenterTop, QtKeepAnchor, QtMouseLeft, QtMoveAnchor, - QtScrollAlwaysOff, QtScrollAsNeeded + QtScrollAlwaysOff, QtScrollAsNeeded, QtSelectBlock, QtSelectDocument, + QtSelectWord ) logger = logging.getLogger(__name__) @@ -287,11 +288,11 @@ class GuiDocViewer(QTextBrowser): elif action == nwDocAction.COPY: self.copy() elif action == nwDocAction.SEL_ALL: - self._makeSelection(QTextCursor.SelectionType.Document) + self._makeSelection(QtSelectDocument) elif action == nwDocAction.SEL_PARA: - self._makeSelection(QTextCursor.SelectionType.BlockUnderCursor) + self._makeSelection(QtSelectBlock) else: - logger.debug("Unknown or unsupported document action '%s'", str(action)) + logger.debug("Unknown or unsupported document action '%s'", action) return False return True @@ -400,14 +401,10 @@ class GuiDocViewer(QTextBrowser): action.triggered.connect(qtLambda(self.docAction, nwDocAction.SEL_ALL)) action = qtAddAction(ctxMenu, self.tr("Select Word")) - action.triggered.connect(qtLambda( - self._makePosSelection, QTextCursor.SelectionType.WordUnderCursor, point - )) + action.triggered.connect(qtLambda(self._makePosSelection, QtSelectWord, point)) action = qtAddAction(ctxMenu, self.tr("Select Paragraph")) - action.triggered.connect(qtLambda( - self._makePosSelection, QTextCursor.SelectionType.BlockUnderCursor, point - )) + action.triggered.connect(qtLambda(self._makePosSelection, QtSelectBlock, point)) # Open the context menu if viewport := self.viewport(): @@ -466,7 +463,7 @@ class GuiDocViewer(QTextBrowser): cursor.clearSelection() cursor.select(selType) - if selType == QTextCursor.SelectionType.BlockUnderCursor: + if selType == QtSelectBlock: # This selection mode also selects the preceding paragraph # separator, which we want to avoid. posS = cursor.selectionStart() diff --git a/novelwriter/gui/outline.py b/novelwriter/gui/outline.py index 3df01d3f..4a76712f 100644 --- a/novelwriter/gui/outline.py +++ b/novelwriter/gui/outline.py @@ -586,7 +586,7 @@ class GuiOutlineTree(QTreeWidget): try: for name, (hidden, width) in colState.items(): if name not in nwOutline.__members__: - logger.warning("Ignored unknown outline column '%s'", str(name)) + logger.warning("Ignored unknown outline column '%s'", name) continue tmpOrder.append(nwOutline[name]) tmpHidden[nwOutline[name]] = hidden diff --git a/novelwriter/types.py b/novelwriter/types.py index 0d5e64d8..f972e04f 100644 --- a/novelwriter/types.py +++ b/novelwriter/types.py @@ -109,9 +109,14 @@ QtRoleReject = QDialogButtonBox.ButtonRole.RejectRole QtKeepAnchor = QTextCursor.MoveMode.KeepAnchor QtMoveAnchor = QTextCursor.MoveMode.MoveAnchor + QtMoveLeft = QTextCursor.MoveOperation.Left QtMoveRight = QTextCursor.MoveOperation.Right +QtSelectWord = QTextCursor.SelectionType.WordUnderCursor +QtSelectBlock = QTextCursor.SelectionType.BlockUnderCursor +QtSelectDocument = QTextCursor.SelectionType.Document + QtImCursorRectangle = Qt.InputMethodQuery.ImCursorRectangle # Size Policy diff --git a/tests/test_gui/test_gui_doceditor.py b/tests/test_gui/test_gui_doceditor.py index ba03b103..4a3c97d9 100644 --- a/tests/test_gui/test_gui_doceditor.py +++ b/tests/test_gui/test_gui_doceditor.py @@ -42,7 +42,8 @@ from novelwriter.gui.dochighlight import TextBlockData from novelwriter.text.counting import standardCounter from novelwriter.types import ( QtAlignJustify, QtAlignLeft, QtKeepAnchor, QtModCtrl, QtModNone, - QtMouseLeft, QtMoveAnchor, QtMoveRight, QtScrollAlwaysOff, QtScrollAsNeeded + QtMouseLeft, QtMoveAnchor, QtMoveRight, QtScrollAlwaysOff, + QtScrollAsNeeded, QtSelectDocument, QtSelectWord ) from tests.mocked import causeOSError @@ -58,7 +59,7 @@ def getMenuForPos(editor: GuiDocEditor, pos: int, select: bool = False) -> QMenu cursor = editor.textCursor() cursor.setPosition(pos) if select: - cursor.select(QTextCursor.SelectionType.WordUnderCursor) + cursor.select(QtSelectWord) editor.setTextCursor(cursor) editor._openContextFromCursor() for obj in editor.children(): @@ -1239,7 +1240,7 @@ def testGuiEditor_TextManipulation(qtbot, nwGUI, projPath, ipsumText, mockRnd): docEditor.setCursorPosition(45) assert len(docEditor._selectedBlocks(cursor)) == 0 - cursor.select(QTextCursor.SelectionType.Document) + cursor.select(QtSelectDocument) assert len(docEditor._selectedBlocks(cursor)) == 15 # Remove All @@ -2093,7 +2094,7 @@ def testGuiEditor_Search(qtbot, monkeypatch, nwGUI, prjLipsum): # Select the Word "est" docEditor.setCursorPosition(663) - docEditor._makeSelection(QTextCursor.SelectionType.WordUnderCursor) + docEditor._makeSelection(QtSelectWord) cursor = docEditor.textCursor() assert cursor.selectedText() == "est" @@ -2223,7 +2224,7 @@ def testGuiEditor_Search(qtbot, monkeypatch, nwGUI, prjLipsum): # Close search and select "est" again docSearch.cancelSearch.activate(QAction.ActionEvent.Trigger) docEditor.setCursorPosition(663) - docEditor._makeSelection(QTextCursor.SelectionType.WordUnderCursor) + docEditor._makeSelection(QtSelectWord) cursor = docEditor.textCursor() assert cursor.selectedText() == "est" diff --git a/tests/test_gui/test_gui_docviewer.py b/tests/test_gui/test_gui_docviewer.py index ac16173b..06d97a80 100644 --- a/tests/test_gui/test_gui_docviewer.py +++ b/tests/test_gui/test_gui_docviewer.py @@ -27,7 +27,7 @@ import pytest from PyQt6.QtCore import QEvent, QMimeData, QPointF, Qt, QUrl from PyQt6.QtGui import ( QAction, QDesktopServices, QDragEnterEvent, QDragMoveEvent, QDropEvent, - QMouseEvent, QTextCursor + QMouseEvent ) from PyQt6.QtWidgets import QApplication, QMenu, QTextBrowser @@ -35,7 +35,7 @@ from novelwriter import CONFIG, SHARED from novelwriter.common import decodeMimeHandles from novelwriter.enum import nwChange, nwDocAction from novelwriter.formats.toqdoc import ToQTextDocument -from novelwriter.types import QtModNone, QtMouseLeft, QtMouseMiddle +from novelwriter.types import QtModNone, QtMouseLeft, QtMouseMiddle, QtSelectBlock, QtSelectWord from tests.mocked import causeException from tests.tools import C, buildTestProject @@ -89,7 +89,7 @@ def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, prjLipsum): cursor = docViewer.textCursor() cursor.setPosition(100) docViewer.setTextCursor(cursor) - docViewer._makeSelection(QTextCursor.SelectionType.WordUnderCursor) + docViewer._makeSelection(QtSelectWord) clipboard = QApplication.clipboard() assert clipboard is not None @@ -117,9 +117,7 @@ def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, prjLipsum): cursor.clearSelection() docViewer.setTextCursor(cursor) - docViewer._makePosSelection( - QTextCursor.SelectionType.BlockUnderCursor, docViewer.cursorRect().center() - ) + docViewer._makePosSelection(QtSelectBlock, docViewer.cursorRect().center()) cursor = docViewer.textCursor() assert cursor.selectedText() == ( "Synopsis: Aenean ut placerat velit. Etiam laoreet ullamcorper risus, " @@ -159,7 +157,7 @@ def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, prjLipsum): cursor = docViewer.textCursor() cursor.setPosition(27) docViewer.setTextCursor(cursor) - docViewer._makeSelection(QTextCursor.SelectionType.WordUnderCursor) + docViewer._makeSelection(QtSelectWord) with monkeypatch.context() as mp: mp.setattr(QMenu, "exec", mockExec) docViewer._openContextMenu(docViewer.cursorRect().center()) @@ -169,7 +167,7 @@ def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, prjLipsum): cursor = docViewer.textCursor() cursor.setPosition(27) docViewer.setTextCursor(cursor) - docViewer._makeSelection(QTextCursor.SelectionType.WordUnderCursor) + docViewer._makeSelection(QtSelectWord) rect = docViewer.cursorRect() docViewer._linkClicked(QUrl("#tag_bod")) assert docViewer.docHandle == "4c4f28287af27" diff --git a/tests/test_gui/test_gui_mainmenu.py b/tests/test_gui/test_gui_mainmenu.py index 9c327cda..0d6344bc 100644 --- a/tests/test_gui/test_gui_mainmenu.py +++ b/tests/test_gui/test_gui_mainmenu.py @@ -24,14 +24,14 @@ from unittest.mock import MagicMock import pytest -from PyQt6.QtGui import QAction, QDesktopServices, QTextBlock, QTextCursor +from PyQt6.QtGui import QAction, QDesktopServices, QTextBlock from PyQt6.QtWidgets import QFileDialog, QMessageBox from novelwriter import CONFIG, SHARED from novelwriter.constants import nwKeyWords, nwShortcode, nwStats, nwUnicode from novelwriter.enum import nwDocAction, nwDocInsert from novelwriter.gui.doceditor import GuiDocEditor -from novelwriter.types import QtKeepAnchor, QtMoveRight +from novelwriter.types import QtKeepAnchor, QtMoveRight, QtSelectWord from tests.tools import C, buildTestProject, writeFile @@ -188,7 +188,7 @@ def testGuiMainMenu_EditFormat(qtbot, monkeypatch, nwGUI, prjLipsum): # Cut, Copy and Paste docEditor.setCursorPosition(x) - docEditor._makeSelection(QTextCursor.SelectionType.WordUnderCursor) + docEditor._makeSelection(QtSelectWord) mainMenu.aEditCut.activate(QAction.ActionEvent.Trigger) assert docEditor.getText()[x:x+50] == ( @@ -201,7 +201,7 @@ def testGuiMainMenu_EditFormat(qtbot, monkeypatch, nwGUI, prjLipsum): ) docEditor.setCursorPosition(x) - docEditor._makeSelection(QTextCursor.SelectionType.WordUnderCursor) + docEditor._makeSelection(QtSelectWord) mainMenu.aEditCopy.activate(QAction.ActionEvent.Trigger) assert docEditor.getText()[x:x+50] == (