diff --git a/novelwriter/core/coretools.py b/novelwriter/core/coretools.py index 2ac855eb..0eb4197b 100644 --- a/novelwriter/core/coretools.py +++ b/novelwriter/core/coretools.py @@ -460,6 +460,7 @@ class ProjectBuilder: source = data.get("template") if not (isinstance(source, Path) and source.is_file() and source.name == nwFiles.PROJ_FILE): + logger.error("Could not access source project: %s", source) return False logger.info("Copying project: %s", source) diff --git a/novelwriter/dialogs/preferences.py b/novelwriter/dialogs/preferences.py index 698d6376..690ad08e 100644 --- a/novelwriter/dialogs/preferences.py +++ b/novelwriter/dialogs/preferences.py @@ -29,15 +29,16 @@ import logging from PyQt5.QtGui import QCloseEvent, QFont, QKeyEvent, QKeySequence from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot from PyQt5.QtWidgets import ( - QAbstractButton, QComboBox, QCompleter, QDialog, QDialogButtonBox, - QDoubleSpinBox, QFileDialog, QFontDialog, QHBoxLayout, QLineEdit, - QPushButton, QSpinBox, QToolButton, QVBoxLayout, QWidget, qApp + QAbstractButton, QCompleter, QDialog, QDialogButtonBox, QFileDialog, + QFontDialog, QHBoxLayout, QLineEdit, QPushButton, QToolButton, QVBoxLayout, + QWidget, qApp ) from novelwriter import CONFIG, SHARED from novelwriter.constants import nwConst, nwUnicode from novelwriter.dialogs.quotes import GuiQuoteSelect from novelwriter.extensions.switch import NSwitch +from novelwriter.extensions.modified import NComboBox, NDoubleSpinBox, NSpinBox from novelwriter.extensions.configlayout import NColourLabel, NScrollableForm from novelwriter.extensions.pagedsidebar import NPagedSideBar @@ -146,7 +147,7 @@ class GuiPreferences(QDialog): self.mainForm.addGroupLabel(title, section) # Display Language - self.guiLocale = QComboBox(self) + self.guiLocale = NComboBox(self) self.guiLocale.setMinimumWidth(minWidth) for lang, name in CONFIG.listLanguages(CONFIG.LANG_NW): self.guiLocale.addItem(name, lang) @@ -159,7 +160,7 @@ class GuiPreferences(QDialog): ) # Colour Theme - self.guiTheme = QComboBox(self) + self.guiTheme = NComboBox(self) self.guiTheme.setMinimumWidth(minWidth) for theme, name in SHARED.theme.listThemes(): self.guiTheme.addItem(name, theme) @@ -186,7 +187,7 @@ class GuiPreferences(QDialog): ) # Application Font Size - self.guiFontSize = QSpinBox(self) + self.guiFontSize = NSpinBox(self) self.guiFontSize.setMinimum(8) self.guiFontSize.setMaximum(60) self.guiFontSize.setSingleStep(1) @@ -221,7 +222,7 @@ class GuiPreferences(QDialog): self.mainForm.addGroupLabel(title, section) # Document Colour Theme - self.guiSyntax = QComboBox(self) + self.guiSyntax = NComboBox(self) self.guiSyntax.setMinimumWidth(CONFIG.pxInt(200)) for syntax, name in SHARED.theme.listSyntax(): self.guiSyntax.addItem(name, syntax) @@ -248,7 +249,7 @@ class GuiPreferences(QDialog): ) # Document Font Size - self.textSize = QSpinBox(self) + self.textSize = NSpinBox(self) self.textSize.setMinimum(8) self.textSize.setMaximum(60) self.textSize.setSingleStep(1) @@ -290,7 +291,7 @@ class GuiPreferences(QDialog): self.mainForm.addGroupLabel(title, section) # Document Save Timer - self.autoSaveDoc = QSpinBox(self) + self.autoSaveDoc = NSpinBox(self) self.autoSaveDoc.setMinimum(5) self.autoSaveDoc.setMaximum(600) self.autoSaveDoc.setSingleStep(1) @@ -301,7 +302,7 @@ class GuiPreferences(QDialog): ) # Project Save Timer - self.autoSaveProj = QSpinBox(self) + self.autoSaveProj = NSpinBox(self) self.autoSaveProj.setMinimum(5) self.autoSaveProj.setMaximum(600) self.autoSaveProj.setSingleStep(1) @@ -364,7 +365,7 @@ class GuiPreferences(QDialog): ) # Inactive Time for Idle - self.userIdleTime = QDoubleSpinBox(self) + self.userIdleTime = NDoubleSpinBox(self) self.userIdleTime.setMinimum(0.5) self.userIdleTime.setMaximum(600.0) self.userIdleTime.setSingleStep(0.5) @@ -388,7 +389,7 @@ class GuiPreferences(QDialog): self.mainForm.addGroupLabel(title, section) # Max Text Width in Normal Mode - self.textWidth = QSpinBox(self) + self.textWidth = NSpinBox(self) self.textWidth.setMinimum(0) self.textWidth.setMaximum(10000) self.textWidth.setSingleStep(10) @@ -399,7 +400,7 @@ class GuiPreferences(QDialog): ) # Max Text Width in Focus Mode - self.focusWidth = QSpinBox(self) + self.focusWidth = NSpinBox(self) self.focusWidth.setMinimum(200) self.focusWidth.setMaximum(10000) self.focusWidth.setSingleStep(10) @@ -426,7 +427,7 @@ class GuiPreferences(QDialog): ) # Document Margins - self.textMargin = QSpinBox(self) + self.textMargin = NSpinBox(self) self.textMargin.setMinimum(0) self.textMargin.setMaximum(900) self.textMargin.setSingleStep(1) @@ -438,7 +439,7 @@ class GuiPreferences(QDialog): ) # Tab Width - self.tabWidth = QSpinBox(self) + self.tabWidth = NSpinBox(self) self.tabWidth.setMinimum(0) self.tabWidth.setMaximum(200) self.tabWidth.setSingleStep(1) @@ -458,7 +459,7 @@ class GuiPreferences(QDialog): self.mainForm.addGroupLabel(title, section) # Spell Checking - self.spellLanguage = QComboBox(self) + self.spellLanguage = NComboBox(self) self.spellLanguage.setMinimumWidth(minWidth) if CONFIG.hasEnchant: @@ -523,7 +524,7 @@ class GuiPreferences(QDialog): ) # Typewriter Position - self.autoScrollPos = QSpinBox(self) + self.autoScrollPos = NSpinBox(self) self.autoScrollPos.setMinimum(10) self.autoScrollPos.setMaximum(90) self.autoScrollPos.setSingleStep(1) diff --git a/novelwriter/dialogs/projectsettings.py b/novelwriter/dialogs/projectsettings.py index c76c9f6b..c9e327cb 100644 --- a/novelwriter/dialogs/projectsettings.py +++ b/novelwriter/dialogs/projectsettings.py @@ -29,7 +29,7 @@ import logging from PyQt5.QtGui import QCloseEvent, QColor, QIcon, QPixmap from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot from PyQt5.QtWidgets import ( - QColorDialog, QComboBox, QDialog, QDialogButtonBox, QHBoxLayout, QLineEdit, + QColorDialog, QDialog, QDialogButtonBox, QHBoxLayout, QLineEdit, QPushButton, QStackedWidget, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget, qApp ) @@ -37,6 +37,7 @@ from PyQt5.QtWidgets import ( from novelwriter import CONFIG, SHARED from novelwriter.common import simplified from novelwriter.extensions.switch import NSwitch +from novelwriter.extensions.modified import NComboBox from novelwriter.extensions.configlayout import NColourLabel, NFixedPage, NScrollableForm from novelwriter.extensions.pagedsidebar import NPagedSideBar @@ -257,7 +258,7 @@ class _SettingsPage(NScrollableForm): ) # Project Language - self.projLang = QComboBox(self) + self.projLang = NComboBox(self) self.projLang.setMinimumWidth(xW) for tag, language in CONFIG.listLanguages(CONFIG.LANG_PROJ): self.projLang.addItem(language, tag) @@ -270,7 +271,7 @@ class _SettingsPage(NScrollableForm): self.projLang.setCurrentIndex(idx) # Spell Check Language - self.spellLang = QComboBox(self) + self.spellLang = NComboBox(self) self.spellLang.setMinimumWidth(xW) self.spellLang.addItem(self.tr("Default"), "None") if CONFIG.hasEnchant: diff --git a/novelwriter/extensions/circularprogress.py b/novelwriter/extensions/circularprogress.py index f638d3e0..c7ece503 100644 --- a/novelwriter/extensions/circularprogress.py +++ b/novelwriter/extensions/circularprogress.py @@ -84,17 +84,17 @@ class NProgressCircle(QProgressBar): """Custom painter for the progress bar.""" progress = 100.0*self.value()/self.maximum() angle = ceil(16*3.6*progress) - qPaint = QPainter(self) - qPaint.setRenderHint(QPainter.Antialiasing, True) - qPaint.setPen(self._dPen) - qPaint.setBrush(self._dBrush) - qPaint.drawEllipse(self._dRect) - qPaint.setPen(self._bPen) - qPaint.drawArc(self._cRect, 0, 360*16) - qPaint.setPen(self._cPen) - qPaint.drawArc(self._cRect, 90*16, -angle) - qPaint.setPen(self._tColor) - qPaint.drawText(self._cRect, Qt.AlignCenter, self._text or f"{progress:.1f} %") + painter = QPainter(self) + painter.setRenderHint(QPainter.Antialiasing, True) + painter.setPen(self._dPen) + painter.setBrush(self._dBrush) + painter.drawEllipse(self._dRect) + painter.setPen(self._bPen) + painter.drawArc(self._cRect, 0, 360*16) + painter.setPen(self._cPen) + painter.drawArc(self._cRect, 90*16, -angle) + painter.setPen(self._tColor) + painter.drawText(self._cRect, Qt.AlignCenter, self._text or f"{progress:.1f} %") return # END Class NProgressCircle diff --git a/novelwriter/extensions/modified.py b/novelwriter/extensions/modified.py new file mode 100644 index 00000000..cebb8b74 --- /dev/null +++ b/novelwriter/extensions/modified.py @@ -0,0 +1,81 @@ +""" +novelWriter – Custom Widget: Modified Widgets +============================================= + +File History: +Created: 2024-02-01 [2.3b1] NComboBox +Created: 2024-02-01 [2.3b1] NSpinBox +Created: 2024-02-01 [2.3b1] NDoubleSpinBox + +This file is a part of novelWriter +Copyright 2018–2024, Veronica Berglyd Olsen + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +""" +from __future__ import annotations + +from PyQt5.QtCore import Qt +from PyQt5.QtGui import QWheelEvent +from PyQt5.QtWidgets import QComboBox, QDoubleSpinBox, QSpinBox, QWidget + + +class NComboBox(QComboBox): + + def __init__(self, parent: QWidget | None = None) -> None: + super().__init__(parent=parent) + self.setFocusPolicy(Qt.FocusPolicy.StrongFocus) + return + + def wheelEvent(self, event: QWheelEvent) -> None: + if self.hasFocus(): + super().wheelEvent(event) + else: + event.ignore() + return + +# END Class NComboBox + + +class NSpinBox(QSpinBox): + + def __init__(self, parent: QWidget | None = None) -> None: + super().__init__(parent=parent) + self.setFocusPolicy(Qt.FocusPolicy.StrongFocus) + return + + def wheelEvent(self, event: QWheelEvent) -> None: + if self.hasFocus(): + super().wheelEvent(event) + else: + event.ignore() + return + +# END Class NSpinBox + + +class NDoubleSpinBox(QDoubleSpinBox): + + def __init__(self, parent: QWidget | None = None) -> None: + super().__init__(parent=parent) + self.setFocusPolicy(Qt.FocusPolicy.StrongFocus) + return + + def wheelEvent(self, event: QWheelEvent) -> None: + if self.hasFocus(): + super().wheelEvent(event) + else: + event.ignore() + return + +# END Class NDoubleSpinBox diff --git a/novelwriter/extensions/pagedsidebar.py b/novelwriter/extensions/pagedsidebar.py index 86a08fe2..5cba8e41 100644 --- a/novelwriter/extensions/pagedsidebar.py +++ b/novelwriter/extensions/pagedsidebar.py @@ -160,7 +160,7 @@ class _NPagedToolButton(QToolButton): if self.isChecked(): backCol = palette.highlight() paint.setBrush(backCol) - paint.setOpacity(0.5) + paint.setOpacity(0.35) paint.drawRoundedRect(0, 0, width, height, self._cR, self._cR) textCol = palette.highlightedText().color() else: diff --git a/novelwriter/extensions/simpleprogress.py b/novelwriter/extensions/simpleprogress.py index 519d0c32..73452ad8 100644 --- a/novelwriter/extensions/simpleprogress.py +++ b/novelwriter/extensions/simpleprogress.py @@ -43,11 +43,11 @@ class NProgressSimple(QProgressBar): """Custom painter for the progress bar.""" if (value := self.value()) > 0: progress = ceil(self.width()*float(value)/self.maximum()) - qPaint = QPainter(self) - qPaint.setRenderHint(QPainter.Antialiasing, True) - qPaint.setPen(self.palette().highlight().color()) - qPaint.setBrush(self.palette().highlight()) - qPaint.drawRect(0, 0, progress, self.height()) + painter = QPainter(self) + painter.setRenderHint(QPainter.Antialiasing, True) + painter.setPen(self.palette().highlight().color()) + painter.setBrush(self.palette().highlight()) + painter.drawRect(0, 0, progress, self.height()) return # END Class NProgressSimple diff --git a/novelwriter/extensions/statusled.py b/novelwriter/extensions/statusled.py index c49bc76d..77006a02 100644 --- a/novelwriter/extensions/statusled.py +++ b/novelwriter/extensions/statusled.py @@ -24,6 +24,7 @@ along with this program. If not, see . from __future__ import annotations import logging + from typing import Literal from PyQt5.QtGui import QColor, QPaintEvent, QPainter @@ -64,14 +65,13 @@ class StatusLED(QAbstractButton): return def paintEvent(self, event: QPaintEvent) -> None: - """Drawing the LED.""" - qPalette = self.palette() - qPaint = QPainter(self) - qPaint.setRenderHint(QPainter.Antialiasing, True) - qPaint.setPen(qPalette.dark().color()) - qPaint.setBrush(self._theCol) - qPaint.setOpacity(1.0) - qPaint.drawEllipse(1, 1, self.width() - 2, self.height() - 2) + """Draw the LED.""" + painter = QPainter(self) + painter.setRenderHint(QPainter.Antialiasing, True) + painter.setPen(self.palette().dark().color()) + painter.setBrush(self._theCol) + painter.setOpacity(1.0) + painter.drawEllipse(1, 1, self.width() - 2, self.height() - 2) return # END Class StatusLED diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index 7438172b..0978d855 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -1146,6 +1146,7 @@ class GuiMain(QMainWindow): @pyqtSlot(Path) def _openProject(self, path: Path) -> None: """Handle an open project request.""" + qApp.processEvents() self.openProject(path) return diff --git a/novelwriter/tools/manussettings.py b/novelwriter/tools/manussettings.py index 76709dbf..28404c50 100644 --- a/novelwriter/tools/manussettings.py +++ b/novelwriter/tools/manussettings.py @@ -30,17 +30,17 @@ from typing import TYPE_CHECKING from PyQt5.QtGui import QFont, QIcon, QSyntaxHighlighter, QTextCharFormat, QTextDocument from PyQt5.QtCore import QEvent, QSize, Qt, pyqtSignal, pyqtSlot from PyQt5.QtWidgets import ( - QAbstractButton, QAbstractItemView, QComboBox, QDialog, QDialogButtonBox, - QDoubleSpinBox, QFontDialog, QFrame, QGridLayout, QHBoxLayout, QHeaderView, - QLabel, QLineEdit, QMenu, QPlainTextEdit, QPushButton, QSpinBox, QSplitter, - QStackedWidget, QToolButton, QTreeWidget, QTreeWidgetItem, QVBoxLayout, - QWidget + QAbstractButton, QAbstractItemView, QDialog, QDialogButtonBox, + QFontDialog, QFrame, QGridLayout, QHBoxLayout, QHeaderView, QLabel, + QLineEdit, QMenu, QPlainTextEdit, QPushButton, QSplitter, QStackedWidget, + QToolButton, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget ) from novelwriter import CONFIG, SHARED from novelwriter.constants import nwHeadFmt, nwLabels, trConst from novelwriter.core.buildsettings import BuildSettings, FilterMode from novelwriter.extensions.switch import NSwitch +from novelwriter.extensions.modified import NComboBox, NDoubleSpinBox, NSpinBox from novelwriter.extensions.switchbox import NSwitchBox from novelwriter.extensions.configlayout import ( NColourLabel, NFixedPage, NScrollableForm, NScrollablePage @@ -968,7 +968,7 @@ class _FormatTab(NScrollableForm): ) # Font Size - self.textSize = QSpinBox(self) + self.textSize = NSpinBox(self) self.textSize.setMinimum(8) self.textSize.setMaximum(60) self.textSize.setSingleStep(1) @@ -976,7 +976,7 @@ class _FormatTab(NScrollableForm): self.addRow(self._build.getLabel("format.textSize"), self.textSize, unit="pt") # Line Height - self.lineHeight = QDoubleSpinBox(self) + self.lineHeight = NDoubleSpinBox(self) self.lineHeight.setFixedWidth(spW) self.lineHeight.setMinimum(0.75) self.lineHeight.setMaximum(3.0) @@ -1002,34 +1002,34 @@ class _FormatTab(NScrollableForm): self.addGroupLabel(self._build.getLabel("format.grpPage")) - self.pageUnit = QComboBox(self) + self.pageUnit = NComboBox(self) for key, name in nwLabels.UNIT_NAME.items(): self.pageUnit.addItem(trConst(name), key) - self.pageSize = QComboBox(self) + self.pageSize = NComboBox(self) for key, name in nwLabels.PAPER_NAME.items(): self.pageSize.addItem(trConst(name), key) - self.pageWidth = QDoubleSpinBox(self) + self.pageWidth = NDoubleSpinBox(self) self.pageWidth.setFixedWidth(dbW) self.pageWidth.setMaximum(500.0) self.pageWidth.valueChanged.connect(self._pageSizeValueChanged) - self.pageHeight = QDoubleSpinBox(self) + self.pageHeight = NDoubleSpinBox(self) self.pageHeight.setFixedWidth(dbW) self.pageHeight.setMaximum(500.0) self.pageHeight.valueChanged.connect(self._pageSizeValueChanged) - self.topMargin = QDoubleSpinBox(self) + self.topMargin = NDoubleSpinBox(self) self.topMargin.setFixedWidth(dbW) - self.bottomMargin = QDoubleSpinBox(self) + self.bottomMargin = NDoubleSpinBox(self) self.bottomMargin.setFixedWidth(dbW) - self.leftMargin = QDoubleSpinBox(self) + self.leftMargin = NDoubleSpinBox(self) self.leftMargin.setFixedWidth(dbW) - self.rightMargin = QDoubleSpinBox(self) + self.rightMargin = NDoubleSpinBox(self) self.rightMargin.setFixedWidth(dbW) self.addRow(self._build.getLabel("format.pageUnit"), self.pageUnit) @@ -1232,7 +1232,7 @@ class _OutputTab(NScrollableForm): button=self.btnPageHeader, stretch=(1, 1) ) - self.odtPageCountOffset = QSpinBox(self) + self.odtPageCountOffset = NSpinBox(self) self.odtPageCountOffset.setMinimum(0) self.odtPageCountOffset.setMaximum(999) self.odtPageCountOffset.setSingleStep(1) diff --git a/novelwriter/tools/welcome.py b/novelwriter/tools/welcome.py index 385f7bd2..98c3520b 100644 --- a/novelwriter/tools/welcome.py +++ b/novelwriter/tools/welcome.py @@ -34,9 +34,9 @@ from PyQt5.QtCore import ( pyqtSignal, pyqtSlot ) from PyQt5.QtWidgets import ( - QComboBox, QDialog, QDialogButtonBox, QFileDialog, QFormLayout, - QHBoxLayout, QLabel, QLineEdit, QListView, QMenu, QPushButton, QScrollArea, QShortcut, - QSpinBox, QStackedWidget, QStyle, QStyleOptionViewItem, QStyledItemDelegate, + QDialog, QDialogButtonBox, QFileDialog, QFormLayout, QHBoxLayout, QLabel, + QLineEdit, QListView, QMenu, QPushButton, QScrollArea, QShortcut, + QStackedWidget, QStyle, QStyleOptionViewItem, QStyledItemDelegate, QToolButton, QVBoxLayout, QWidget, qApp ) @@ -46,6 +46,7 @@ from novelwriter.common import formatInt, formatVersion, makeFileNameSafe from novelwriter.constants import nwUnicode from novelwriter.core.coretools import ProjectBuilder from novelwriter.extensions.switch import NSwitch +from novelwriter.extensions.modified import NComboBox, NSpinBox logger = logging.getLogger(__name__) @@ -197,6 +198,9 @@ class GuiWelcome(QDialog): def _openProjectPath(self, path: Path) -> None: """Emit a project open signal.""" if isinstance(path, Path): + # Hide before emitting the open project signal so that any + # close/backup dialogs don't pop up over it. + self.hide() self.openProjectRequest.emit(path) self.close() return @@ -249,8 +253,7 @@ class _OpenProjectPage(QWidget): self.setLayout(self.outerBox) - self.listWidget.setCurrentIndex(self.listModel.index(0)) - self._projectClicked(self.listModel.index(0)) + self._selectFirstItem() baseCol = self.palette().base().color() self.setStyleSheet(( @@ -278,9 +281,9 @@ class _OpenProjectPage(QWidget): @pyqtSlot(QModelIndex) def _projectClicked(self, index: QModelIndex) -> None: """Process single click on project item.""" - if index.isValid(): - path = self.tr("Path") - self.selectedPath.setText(f"{path}: {index.data()[1]}") + path = self.tr("Path") + value = index.data()[1] if index.isValid() else "" + self.selectedPath.setText(f"{path}: {value}") return @pyqtSlot(QModelIndex) @@ -300,6 +303,7 @@ class _OpenProjectPage(QWidget): ).format(index.data()[0]) if SHARED.question(text): self.listModel.removeEntry(index) + self._selectFirstItem() return @pyqtSlot("QPoint") @@ -315,6 +319,17 @@ class _OpenProjectPage(QWidget): ctxMenu.deleteLater() return + ## + # Internal Functions + ## + + def _selectFirstItem(self) -> None: + """Select the first item, if any are available.""" + index = self.listModel.index(0) + self.listWidget.setCurrentIndex(index) + self._projectClicked(index) + return + # END Class _OpenProjectPage @@ -355,7 +370,7 @@ class _ProjectListItem(QStyledItemDelegate): painter.save() if opt.state & QStyle.StateFlag.State_Selected == QStyle.StateFlag.State_Selected: - painter.setOpacity(0.5) + painter.setOpacity(0.25) painter.fillRect(rect, qApp.palette().highlight()) painter.setOpacity(1.0) @@ -520,7 +535,7 @@ class _NewProjectForm(QWidget): self.projAuthor.setPlaceholderText(self.tr("Optional")) # Project Language - self.projLang = QComboBox(self) + self.projLang = NComboBox(self) for tag, language in CONFIG.listLanguages(CONFIG.LANG_PROJ): self.projLang.addItem(language, tag) @@ -580,7 +595,7 @@ class _NewProjectForm(QWidget): # Chapters and Scenes # =================== - self.numChapters = QSpinBox() + self.numChapters = NSpinBox(self) self.numChapters.setRange(0, 200) self.numChapters.setValue(5) self.numChapters.setToolTip(self.tr("Set to 0 to only add scenes")) @@ -591,7 +606,7 @@ class _NewProjectForm(QWidget): self.chapterBox.addWidget(QLabel(self.tr("chapter documents"))) self.chapterBox.addStretch(1) - self.numScenes = QSpinBox() + self.numScenes = NSpinBox(self) self.numScenes.setRange(0, 200) self.numScenes.setValue(5) diff --git a/tests/test_ext/test_ext_modified.py b/tests/test_ext/test_ext_modified.py new file mode 100644 index 00000000..c1d0e3c2 --- /dev/null +++ b/tests/test_ext/test_ext_modified.py @@ -0,0 +1,132 @@ +""" +novelWriter – Wheel Event Filter Tester +======================================= + +This file is a part of novelWriter +Copyright 2018–2024, Veronica Berglyd Olsen + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +""" +from __future__ import annotations + +import pytest + +from tools import SimpleDialog + +from PyQt5.QtGui import QWheelEvent +from PyQt5.QtCore import QPoint, QPointF, Qt + +from novelwriter.extensions.modified import NComboBox, NDoubleSpinBox, NSpinBox + + +class MockWheelEvent(QWheelEvent): + + def __init__(self): + super().__init__( + QPointF(0, 0), QPointF(0, 0), QPoint(0, 0), QPoint(0, 0), + Qt.MouseButton.NoButton, Qt.KeyboardModifier.NoModifier, + Qt.ScrollPhase.ScrollUpdate, False + ) + self.ignored = False + return + + def ignore(self): + super().ignore() + self.ignored = True + return + +# END Class MockWheelEvent + + +@pytest.mark.gui +def testExtModified_NComboBox(qtbot, monkeypatch): + """Test the NComboBox class.""" + widget = NComboBox() + widget.addItem("Item 1", 1) + widget.addItem("Item 2", 2) + + dialog = SimpleDialog(widget) + dialog.show() + + with monkeypatch.context() as mp: + mp.setattr(NComboBox, "hasFocus", lambda *a: True) + event = MockWheelEvent() + widget.wheelEvent(event) + assert event.ignored is False + + with monkeypatch.context() as mp: + mp.setattr(NComboBox, "hasFocus", lambda *a: False) + event = MockWheelEvent() + widget.wheelEvent(event) + assert event.ignored is True + + # qtbot.stop() + +# END Test testExtModified_NComboBox + + +@pytest.mark.gui +def testExtModified_NSpinBox(qtbot, monkeypatch): + """Test the NSpinBox class.""" + widget = NSpinBox() + widget.setMinimum(0) + widget.setMaximum(100) + widget.setValue(42) + + dialog = SimpleDialog(widget) + dialog.show() + + with monkeypatch.context() as mp: + mp.setattr(NSpinBox, "hasFocus", lambda *a: True) + event = MockWheelEvent() + widget.wheelEvent(event) + assert event.ignored is False + + with monkeypatch.context() as mp: + mp.setattr(NSpinBox, "hasFocus", lambda *a: False) + event = MockWheelEvent() + widget.wheelEvent(event) + assert event.ignored is True + + # qtbot.stop() + +# END Test testExtModified_NSpinBox + + +@pytest.mark.gui +def testExtModified_NDoubleSpinBox(qtbot, monkeypatch): + """Test the NDoubleSpinBox class.""" + widget = NDoubleSpinBox() + widget.setMinimum(0.0) + widget.setMaximum(100.0) + widget.setValue(42.0) + + dialog = SimpleDialog(widget) + dialog.show() + + with monkeypatch.context() as mp: + mp.setattr(NDoubleSpinBox, "hasFocus", lambda *a: True) + event = MockWheelEvent() + widget.wheelEvent(event) + assert event.ignored is False + + with monkeypatch.context() as mp: + mp.setattr(NDoubleSpinBox, "hasFocus", lambda *a: False) + event = MockWheelEvent() + widget.wheelEvent(event) + assert event.ignored is True + + # qtbot.stop() + +# END Test testExtModified_NDoubleSpinBox diff --git a/tests/test_tools/test_tools_welcome.py b/tests/test_tools/test_tools_welcome.py index 60518786..96ed814c 100644 --- a/tests/test_tools/test_tools_welcome.py +++ b/tests/test_tools/test_tools_welcome.py @@ -152,11 +152,6 @@ def testToolWelcome_Open(qtbot: QtBot, monkeypatch, nwGUI, fncPath): mp.setattr(listModel, "data", lambda *a: ("a", "b", "c")) assert listModel.removeEntry(listModel.createIndex(1, 0)) is False - # Delete last entry with keypress - qtbot.mouseClick(vPort, Qt.MouseButton.LeftButton, pos=posOne, delay=10) - qtbot.keyClick(tabOpen, Qt.Key.Key_Delete, delay=10) - assert len(CONFIG.recentProjects.listEntries()) == 0 - # qtbot.stop() welcome.close() diff --git a/tests/tools.py b/tests/tools.py index 2da23818..7a00b4b6 100644 --- a/tests/tools.py +++ b/tests/tools.py @@ -25,7 +25,7 @@ import shutil from pathlib import Path from datetime import datetime -from PyQt5.QtWidgets import qApp +from PyQt5.QtWidgets import QDialog, QVBoxLayout, QWidget, qApp XML_IGNORE = (" None: nwGUI.rebuildTrees() return + + +class SimpleDialog(QDialog): + + def __init__(self, widget: QWidget) -> None: + super().__init__() + self._widget = widget + + layout = QVBoxLayout() + layout.addWidget(widget) + layout.setContentsMargins(40, 40, 40, 40) + self.setLayout(layout) + + return + + @property + def widget(self) -> QWidget: + return self._widget + +# END Class TestDialog