GUI improvements and fixes (#2089)
This commit is contained in:
@@ -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
|
||||||
##
|
##
|
||||||
|
|||||||
@@ -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
@@ -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."""
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user