GUI improvements and fixes (#2089)

This commit is contained in:
Veronica Berglyd Olsen
2024-11-08 22:02:35 +01:00
committed by GitHub
7 changed files with 82 additions and 29 deletions
+22 -5
View File
@@ -58,12 +58,13 @@ logger = logging.getLogger(__name__)
class GuiDocViewer(QTextBrowser): class GuiDocViewer(QTextBrowser):
closeDocumentRequest = pyqtSignal()
documentLoaded = pyqtSignal(str) documentLoaded = pyqtSignal(str)
loadDocumentTagRequest = pyqtSignal(str, Enum) loadDocumentTagRequest = pyqtSignal(str, Enum)
closeDocumentRequest = pyqtSignal() openDocumentRequest = pyqtSignal(str, Enum, str, bool)
reloadDocumentRequest = pyqtSignal() reloadDocumentRequest = pyqtSignal()
togglePanelVisibility = pyqtSignal()
requestProjectItemSelected = pyqtSignal(str, bool) requestProjectItemSelected = pyqtSignal(str, bool)
togglePanelVisibility = pyqtSignal()
def __init__(self, parent: QWidget) -> None: def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent) super().__init__(parent=parent)
@@ -640,6 +641,11 @@ class GuiDocViewHeader(QWidget):
self.forwardButton.setToolTip(self.tr("Go Forward")) self.forwardButton.setToolTip(self.tr("Go Forward"))
self.forwardButton.clicked.connect(self.docViewer.navForward) self.forwardButton.clicked.connect(self.docViewer.navForward)
self.editButton = NIconToolButton(self, iSz)
self.editButton.setVisible(False)
self.editButton.setToolTip(self.tr("Open in Editor"))
self.editButton.clicked.connect(self._editDocument)
self.refreshButton = NIconToolButton(self, iSz) self.refreshButton = NIconToolButton(self, iSz)
self.refreshButton.setVisible(False) self.refreshButton.setVisible(False)
self.refreshButton.setToolTip(self.tr("Reload")) self.refreshButton.setToolTip(self.tr("Reload"))
@@ -658,7 +664,7 @@ class GuiDocViewHeader(QWidget):
self.outerBox.addSpacing(mPx) self.outerBox.addSpacing(mPx)
self.outerBox.addWidget(self.itemTitle, 1) self.outerBox.addWidget(self.itemTitle, 1)
self.outerBox.addSpacing(mPx) self.outerBox.addSpacing(mPx)
self.outerBox.addSpacing(iPx) self.outerBox.addWidget(self.editButton, 0)
self.outerBox.addWidget(self.refreshButton, 0) self.outerBox.addWidget(self.refreshButton, 0)
self.outerBox.addWidget(self.closeButton, 0) self.outerBox.addWidget(self.closeButton, 0)
self.outerBox.setSpacing(0) self.outerBox.setSpacing(0)
@@ -692,8 +698,9 @@ class GuiDocViewHeader(QWidget):
self.outlineButton.setVisible(False) self.outlineButton.setVisible(False)
self.backButton.setVisible(False) self.backButton.setVisible(False)
self.forwardButton.setVisible(False) self.forwardButton.setVisible(False)
self.closeButton.setVisible(False) self.editButton.setVisible(False)
self.refreshButton.setVisible(False) self.refreshButton.setVisible(False)
self.closeButton.setVisible(False)
return return
def setOutline(self, data: dict[str, tuple[str, int]]) -> None: def setOutline(self, data: dict[str, tuple[str, int]]) -> None:
@@ -727,6 +734,7 @@ class GuiDocViewHeader(QWidget):
self.outlineButton.setThemeIcon("list") self.outlineButton.setThemeIcon("list")
self.backButton.setThemeIcon("backward") self.backButton.setThemeIcon("backward")
self.forwardButton.setThemeIcon("forward") self.forwardButton.setThemeIcon("forward")
self.editButton.setThemeIcon("edit")
self.refreshButton.setThemeIcon("refresh") self.refreshButton.setThemeIcon("refresh")
self.closeButton.setThemeIcon("close") self.closeButton.setThemeIcon("close")
@@ -734,6 +742,7 @@ class GuiDocViewHeader(QWidget):
self.outlineButton.setStyleSheet(buttonStyle) self.outlineButton.setStyleSheet(buttonStyle)
self.backButton.setStyleSheet(buttonStyle) self.backButton.setStyleSheet(buttonStyle)
self.forwardButton.setStyleSheet(buttonStyle) self.forwardButton.setStyleSheet(buttonStyle)
self.editButton.setStyleSheet(buttonStyle)
self.refreshButton.setStyleSheet(buttonStyle) self.refreshButton.setStyleSheet(buttonStyle)
self.closeButton.setStyleSheet(buttonStyle) self.closeButton.setStyleSheet(buttonStyle)
@@ -776,8 +785,9 @@ class GuiDocViewHeader(QWidget):
self.backButton.setVisible(True) self.backButton.setVisible(True)
self.forwardButton.setVisible(True) self.forwardButton.setVisible(True)
self.outlineButton.setVisible(True) self.outlineButton.setVisible(True)
self.closeButton.setVisible(True) self.editButton.setVisible(True)
self.refreshButton.setVisible(True) self.refreshButton.setVisible(True)
self.closeButton.setVisible(True)
return return
@@ -804,6 +814,13 @@ class GuiDocViewHeader(QWidget):
self.docViewer.reloadDocumentRequest.emit() self.docViewer.reloadDocumentRequest.emit()
return return
@pyqtSlot()
def _editDocument(self) -> None:
"""Open the document in the editor."""
if tHandle := self._docHandle:
self.docViewer.openDocumentRequest.emit(tHandle, nwDocMode.EDIT, "", True)
return
## ##
# Events # Events
## ##
+12 -4
View File
@@ -141,8 +141,10 @@ class GuiOutlineView(QWidget):
def closeProjectTasks(self) -> None: def closeProjectTasks(self) -> None:
"""Run closing project tasks.""" """Run closing project tasks."""
if self.outlineTree.wasRendered:
# If the panel hasn't been drawn yet, those values are incorrect
self.outlineData.saveGuiSettings()
self.outlineTree.closeProjectTasks() self.outlineTree.closeProjectTasks()
self.outlineData.saveGuiSettings()
self.outlineData.updateClasses() self.outlineData.updateClasses()
self.clearOutline() self.clearOutline()
return return
@@ -434,6 +436,11 @@ class GuiOutlineTree(QTreeWidget):
def hiddenColumns(self) -> dict[nwOutline, bool]: def hiddenColumns(self) -> dict[nwOutline, bool]:
return self._colHidden return self._colHidden
@property
def wasRendered(self) -> bool:
"""Returns True after the Outline has been rendered once."""
return not self._firstView
## ##
# Methods # Methods
## ##
@@ -947,11 +954,12 @@ class GuiOutlineDetails(QScrollArea):
def loadGuiSettings(self) -> None: def loadGuiSettings(self) -> None:
"""Run open project tasks.""" """Run open project tasks."""
half = self.width() // 2 parent = self.outlineView.parent() # This widget is rendered already
width = parent.width() if isinstance(parent, QWidget) else 1000
pOptions = SHARED.project.options pOptions = SHARED.project.options
self.mainSplit.setSizes([ self.mainSplit.setSizes([
CONFIG.pxInt(pOptions.getInt("GuiOutlineDetails", "detailsWidth", half)), CONFIG.pxInt(pOptions.getInt("GuiOutlineDetails", "detailsWidth", width//3)),
CONFIG.pxInt(pOptions.getInt("GuiOutlineDetails", "tagsWidth", half)) CONFIG.pxInt(pOptions.getInt("GuiOutlineDetails", "tagsWidth", 2*width//3))
]) ])
return return
+19 -18
View File
@@ -270,6 +270,7 @@ class GuiMain(QMainWindow):
self.docViewer.closeDocumentRequest.connect(self.closeDocViewer) self.docViewer.closeDocumentRequest.connect(self.closeDocViewer)
self.docViewer.documentLoaded.connect(self.docViewerPanel.updateHandle) self.docViewer.documentLoaded.connect(self.docViewerPanel.updateHandle)
self.docViewer.loadDocumentTagRequest.connect(self._followTag) self.docViewer.loadDocumentTagRequest.connect(self._followTag)
self.docViewer.openDocumentRequest.connect(self._openDocument)
self.docViewer.reloadDocumentRequest.connect(self._reloadViewer) self.docViewer.reloadDocumentRequest.connect(self._reloadViewer)
self.docViewer.requestProjectItemSelected.connect(self.projView.setSelectedHandle) self.docViewer.requestProjectItemSelected.connect(self.projView.setSelectedHandle)
self.docViewer.togglePanelVisibility.connect(self._toggleViewerPanelVisibility) self.docViewer.togglePanelVisibility.connect(self._toggleViewerPanelVisibility)
@@ -844,33 +845,33 @@ class GuiMain(QMainWindow):
def closeMain(self) -> bool: def closeMain(self) -> bool:
"""Save everything, and close novelWriter.""" """Save everything, and close novelWriter."""
if SHARED.hasProject and SHARED.question("%s<br>%s" % ( if SHARED.hasProject and not SHARED.question("%s<br>%s" % (
self.tr("Do you want to exit novelWriter?"), self.tr("Do you want to exit novelWriter?"),
self.tr("Changes are saved automatically.") self.tr("Changes are saved automatically.")
)): )):
logger.info("Exiting novelWriter") return False
if not SHARED.focusMode: logger.info("Exiting novelWriter")
CONFIG.setMainPanePos(self.splitMain.sizes())
CONFIG.setOutlinePanePos(self.outlineView.splitSizes())
if self.docViewerPanel.isVisible():
CONFIG.setViewPanePos(self.splitView.sizes())
CONFIG.showViewerPanel = self.docViewerPanel.isVisible() if not SHARED.focusMode:
wFull = Qt.WindowState.WindowFullScreen CONFIG.setMainPanePos(self.splitMain.sizes())
if self.windowState() & wFull != wFull: CONFIG.setOutlinePanePos(self.outlineView.splitSizes())
# Ignore window size if in full screen mode if self.docViewerPanel.isVisible():
CONFIG.setMainWinSize(self.width(), self.height()) CONFIG.setViewPanePos(self.splitView.sizes())
if SHARED.hasProject: CONFIG.showViewerPanel = self.docViewerPanel.isVisible()
self.closeProject(True) wFull = Qt.WindowState.WindowFullScreen
CONFIG.saveConfig() if self.windowState() & wFull != wFull:
# Ignore window size if in full screen mode
CONFIG.setMainWinSize(self.width(), self.height())
QApplication.quit() if SHARED.hasProject:
self.closeProject(True)
CONFIG.saveConfig()
return True QApplication.quit()
return False return True
def closeViewerPanel(self, byUser: bool = True) -> bool: def closeViewerPanel(self, byUser: bool = True) -> bool:
"""Close the document view panel.""" """Close the document view panel."""
+6
View File
@@ -65,3 +65,9 @@ exclude = ["docs/*"]
[tool.autopep8] [tool.autopep8]
max_line_length = 99 max_line_length = 99
ignore = ["E133", "E221", "E226", "E228", "E241", "W503"] ignore = ["E133", "E221", "E226", "E228", "E241", "W503"]
[tool.coverage.run]
branch = false
[tool.coverage.report]
precision = 2
+8
View File
@@ -85,6 +85,7 @@ def testBaseInit_Options(monkeypatch, fncPath):
# Defaults w/None Args # Defaults w/None Args
nwGUI = main() nwGUI = main()
assert nwGUI is not None
assert logger.getEffectiveLevel() == logging.WARNING assert logger.getEffectiveLevel() == logging.WARNING
assert nwGUI.closeMain() == "closeMain" assert nwGUI.closeMain() == "closeMain"
@@ -92,6 +93,7 @@ def testBaseInit_Options(monkeypatch, fncPath):
nwGUI = main( nwGUI = main(
["--testmode", f"--config={fncPath}", f"--data={fncPath}", "--style=Fusion"] ["--testmode", f"--config={fncPath}", f"--data={fncPath}", "--style=Fusion"]
) )
assert nwGUI is not None
assert logger.getEffectiveLevel() == logging.WARNING assert logger.getEffectiveLevel() == logging.WARNING
assert nwGUI.closeMain() == "closeMain" assert nwGUI.closeMain() == "closeMain"
@@ -99,12 +101,14 @@ def testBaseInit_Options(monkeypatch, fncPath):
nwGUI = main( nwGUI = main(
["--testmode", "--info", f"--config={fncPath}", f"--data={fncPath}"] ["--testmode", "--info", f"--config={fncPath}", f"--data={fncPath}"]
) )
assert nwGUI is not None
assert logger.getEffectiveLevel() == logging.INFO assert logger.getEffectiveLevel() == logging.INFO
assert nwGUI.closeMain() == "closeMain" assert nwGUI.closeMain() == "closeMain"
nwGUI = main( nwGUI = main(
["--testmode", "--debug", f"--config={fncPath}", f"--data={fncPath}"] ["--testmode", "--debug", f"--config={fncPath}", f"--data={fncPath}"]
) )
assert nwGUI is not None
assert logger.getEffectiveLevel() == logging.DEBUG assert logger.getEffectiveLevel() == logging.DEBUG
assert nwGUI.closeMain() == "closeMain" assert nwGUI.closeMain() == "closeMain"
@@ -113,6 +117,7 @@ def testBaseInit_Options(monkeypatch, fncPath):
nwGUI = main( nwGUI = main(
["--testmode", "--help", f"--config={fncPath}", f"--data={fncPath}"] ["--testmode", "--help", f"--config={fncPath}", f"--data={fncPath}"]
) )
assert nwGUI is not None
assert nwGUI.closeMain() == "closeMain" assert nwGUI.closeMain() == "closeMain"
assert ex.value.code == 0 assert ex.value.code == 0
@@ -120,6 +125,7 @@ def testBaseInit_Options(monkeypatch, fncPath):
nwGUI = main( nwGUI = main(
["--testmode", "--version", f"--config={fncPath}", f"--data={fncPath}"] ["--testmode", "--version", f"--config={fncPath}", f"--data={fncPath}"]
) )
assert nwGUI is not None
assert nwGUI.closeMain() == "closeMain" assert nwGUI.closeMain() == "closeMain"
assert ex.value.code == 0 assert ex.value.code == 0
@@ -128,6 +134,7 @@ def testBaseInit_Options(monkeypatch, fncPath):
nwGUI = main( nwGUI = main(
["--testmode", "--invalid", f"--config={fncPath}", f"--data={fncPath}"] ["--testmode", "--invalid", f"--config={fncPath}", f"--data={fncPath}"]
) )
assert nwGUI is not None
assert nwGUI.closeMain() == "closeMain" assert nwGUI.closeMain() == "closeMain"
assert ex.value.code == 2 assert ex.value.code == 2
@@ -135,6 +142,7 @@ def testBaseInit_Options(monkeypatch, fncPath):
nwGUI = main( nwGUI = main(
["--testmode", f"--config={fncPath}", f"--data={fncPath}", "sample/"] ["--testmode", f"--config={fncPath}", f"--data={fncPath}", "sample/"]
) )
assert nwGUI is not None
assert nwGUI.closeMain() == "closeMain" assert nwGUI.closeMain() == "closeMain"
+8 -2
View File
@@ -31,7 +31,6 @@ from PyQt5.QtWidgets import QAction, QApplication, QMenu
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
from novelwriter.enum import nwDocAction from novelwriter.enum import nwDocAction
from novelwriter.formats.toqdoc import ToQTextDocument from novelwriter.formats.toqdoc import ToQTextDocument
from novelwriter.gui.docviewer import GuiDocViewer
from novelwriter.types import QtModNone, QtMouseLeft from novelwriter.types import QtModNone, QtMouseLeft
from tests.mocked import causeException from tests.mocked import causeException
@@ -42,7 +41,8 @@ def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, prjLipsum):
"""Test the document viewer.""" """Test the document viewer."""
# Open project # Open project
assert nwGUI.openProject(prjLipsum) assert nwGUI.openProject(prjLipsum)
docViewer: GuiDocViewer = nwGUI.docViewer docEditor = nwGUI.docEditor
docViewer = nwGUI.docViewer
# Select a document in the project tree # Select a document in the project tree
nwGUI.projView.setSelectedHandle("88243afbe5ed8") nwGUI.projView.setSelectedHandle("88243afbe5ed8")
@@ -74,6 +74,12 @@ def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, prjLipsum):
docViewer.docHeader._refreshDocument() docViewer.docHeader._refreshDocument()
assert docViewer.toPlainText() == origText assert docViewer.toPlainText() == origText
# Open in editor
nwGUI.closeDocument()
assert docEditor.docHandle is None
docViewer.docHeader._editDocument()
assert docEditor.docHandle == docViewer.docHandle
# Select word # Select word
cursor = docViewer.textCursor() cursor = docViewer.textCursor()
cursor.setPosition(100) cursor.setPosition(100)
+7
View File
@@ -100,6 +100,13 @@ def testGuiMain_Launch(qtbot, monkeypatch, nwGUI, projPath):
assert nwGUI.openProject(projPath) is True assert nwGUI.openProject(projPath) is True
nwGUI.closeProject() nwGUI.closeProject()
# Check that closes can be blocked
with monkeypatch.context() as mp:
mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.StandardButton.No)
assert nwGUI.openProject(projPath) is True
assert nwGUI.closeMain() is False
nwGUI.closeProject()
# Check that latest release info updated # Check that latest release info updated
assert CONFIG.lastNotes != "0x0" assert CONFIG.lastNotes != "0x0"