Skip to content

Commit

Permalink
GUI improvements and fixes (#2089)
Browse files Browse the repository at this point in the history
  • Loading branch information
vkbo authored Nov 8, 2024
2 parents 87ae758 + 25297b8 commit 08ef352
Show file tree
Hide file tree
Showing 7 changed files with 82 additions and 29 deletions.
27 changes: 22 additions & 5 deletions novelwriter/gui/docviewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,13 @@

class GuiDocViewer(QTextBrowser):

closeDocumentRequest = pyqtSignal()
documentLoaded = pyqtSignal(str)
loadDocumentTagRequest = pyqtSignal(str, Enum)
closeDocumentRequest = pyqtSignal()
openDocumentRequest = pyqtSignal(str, Enum, str, bool)
reloadDocumentRequest = pyqtSignal()
togglePanelVisibility = pyqtSignal()
requestProjectItemSelected = pyqtSignal(str, bool)
togglePanelVisibility = pyqtSignal()

def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent)
Expand Down Expand Up @@ -640,6 +641,11 @@ def __init__(self, docViewer: GuiDocViewer) -> None:
self.forwardButton.setToolTip(self.tr("Go Forward"))
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.setVisible(False)
self.refreshButton.setToolTip(self.tr("Reload"))
Expand All @@ -658,7 +664,7 @@ def __init__(self, docViewer: GuiDocViewer) -> None:
self.outerBox.addSpacing(mPx)
self.outerBox.addWidget(self.itemTitle, 1)
self.outerBox.addSpacing(mPx)
self.outerBox.addSpacing(iPx)
self.outerBox.addWidget(self.editButton, 0)
self.outerBox.addWidget(self.refreshButton, 0)
self.outerBox.addWidget(self.closeButton, 0)
self.outerBox.setSpacing(0)
Expand Down Expand Up @@ -692,8 +698,9 @@ def clearHeader(self) -> None:
self.outlineButton.setVisible(False)
self.backButton.setVisible(False)
self.forwardButton.setVisible(False)
self.closeButton.setVisible(False)
self.editButton.setVisible(False)
self.refreshButton.setVisible(False)
self.closeButton.setVisible(False)
return

def setOutline(self, data: dict[str, tuple[str, int]]) -> None:
Expand Down Expand Up @@ -727,13 +734,15 @@ def updateTheme(self) -> None:
self.outlineButton.setThemeIcon("list")
self.backButton.setThemeIcon("backward")
self.forwardButton.setThemeIcon("forward")
self.editButton.setThemeIcon("edit")
self.refreshButton.setThemeIcon("refresh")
self.closeButton.setThemeIcon("close")

buttonStyle = SHARED.theme.getStyleSheet(STYLES_MIN_TOOLBUTTON)
self.outlineButton.setStyleSheet(buttonStyle)
self.backButton.setStyleSheet(buttonStyle)
self.forwardButton.setStyleSheet(buttonStyle)
self.editButton.setStyleSheet(buttonStyle)
self.refreshButton.setStyleSheet(buttonStyle)
self.closeButton.setStyleSheet(buttonStyle)

Expand Down Expand Up @@ -776,8 +785,9 @@ def setHandle(self, tHandle: str) -> None:
self.backButton.setVisible(True)
self.forwardButton.setVisible(True)
self.outlineButton.setVisible(True)
self.closeButton.setVisible(True)
self.editButton.setVisible(True)
self.refreshButton.setVisible(True)
self.closeButton.setVisible(True)

return

Expand All @@ -804,6 +814,13 @@ def _refreshDocument(self) -> None:
self.docViewer.reloadDocumentRequest.emit()
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
##
Expand Down
16 changes: 12 additions & 4 deletions novelwriter/gui/outline.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,10 @@ def openProjectTasks(self) -> None:

def closeProjectTasks(self) -> None:
"""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.outlineData.saveGuiSettings()
self.outlineData.updateClasses()
self.clearOutline()
return
Expand Down Expand Up @@ -434,6 +436,11 @@ def __init__(self, outlineView: GuiOutlineView) -> None:
def hiddenColumns(self) -> dict[nwOutline, bool]:
return self._colHidden

@property
def wasRendered(self) -> bool:
"""Returns True after the Outline has been rendered once."""
return not self._firstView

##
# Methods
##
Expand Down Expand Up @@ -947,11 +954,12 @@ def initSettings(self) -> None:

def loadGuiSettings(self) -> None:
"""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
self.mainSplit.setSizes([
CONFIG.pxInt(pOptions.getInt("GuiOutlineDetails", "detailsWidth", half)),
CONFIG.pxInt(pOptions.getInt("GuiOutlineDetails", "tagsWidth", half))
CONFIG.pxInt(pOptions.getInt("GuiOutlineDetails", "detailsWidth", width//3)),
CONFIG.pxInt(pOptions.getInt("GuiOutlineDetails", "tagsWidth", 2*width//3))
])
return

Expand Down
37 changes: 19 additions & 18 deletions novelwriter/guimain.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ def __init__(self) -> None:
self.docViewer.closeDocumentRequest.connect(self.closeDocViewer)
self.docViewer.documentLoaded.connect(self.docViewerPanel.updateHandle)
self.docViewer.loadDocumentTagRequest.connect(self._followTag)
self.docViewer.openDocumentRequest.connect(self._openDocument)
self.docViewer.reloadDocumentRequest.connect(self._reloadViewer)
self.docViewer.requestProjectItemSelected.connect(self.projView.setSelectedHandle)
self.docViewer.togglePanelVisibility.connect(self._toggleViewerPanelVisibility)
Expand Down Expand Up @@ -844,33 +845,33 @@ def showDictionariesDialog(self) -> None:

def closeMain(self) -> bool:
"""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("Changes are saved automatically.")
)):
logger.info("Exiting novelWriter")
return False

if not SHARED.focusMode:
CONFIG.setMainPanePos(self.splitMain.sizes())
CONFIG.setOutlinePanePos(self.outlineView.splitSizes())
if self.docViewerPanel.isVisible():
CONFIG.setViewPanePos(self.splitView.sizes())
logger.info("Exiting novelWriter")

CONFIG.showViewerPanel = self.docViewerPanel.isVisible()
wFull = Qt.WindowState.WindowFullScreen
if self.windowState() & wFull != wFull:
# Ignore window size if in full screen mode
CONFIG.setMainWinSize(self.width(), self.height())
if not SHARED.focusMode:
CONFIG.setMainPanePos(self.splitMain.sizes())
CONFIG.setOutlinePanePos(self.outlineView.splitSizes())
if self.docViewerPanel.isVisible():
CONFIG.setViewPanePos(self.splitView.sizes())

if SHARED.hasProject:
self.closeProject(True)
CONFIG.saveConfig()
CONFIG.showViewerPanel = self.docViewerPanel.isVisible()
wFull = Qt.WindowState.WindowFullScreen
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:
"""Close the document view panel."""
Expand Down
6 changes: 6 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,9 @@ exclude = ["docs/*"]
[tool.autopep8]
max_line_length = 99
ignore = ["E133", "E221", "E226", "E228", "E241", "W503"]

[tool.coverage.run]
branch = false

[tool.coverage.report]
precision = 2
8 changes: 8 additions & 0 deletions tests/test_base/test_base_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,26 +85,30 @@ def testBaseInit_Options(monkeypatch, fncPath):

# Defaults w/None Args
nwGUI = main()
assert nwGUI is not None
assert logger.getEffectiveLevel() == logging.WARNING
assert nwGUI.closeMain() == "closeMain"

# Defaults
nwGUI = main(
["--testmode", f"--config={fncPath}", f"--data={fncPath}", "--style=Fusion"]
)
assert nwGUI is not None
assert logger.getEffectiveLevel() == logging.WARNING
assert nwGUI.closeMain() == "closeMain"

# Log Levels
nwGUI = main(
["--testmode", "--info", f"--config={fncPath}", f"--data={fncPath}"]
)
assert nwGUI is not None
assert logger.getEffectiveLevel() == logging.INFO
assert nwGUI.closeMain() == "closeMain"

nwGUI = main(
["--testmode", "--debug", f"--config={fncPath}", f"--data={fncPath}"]
)
assert nwGUI is not None
assert logger.getEffectiveLevel() == logging.DEBUG
assert nwGUI.closeMain() == "closeMain"

Expand All @@ -113,13 +117,15 @@ def testBaseInit_Options(monkeypatch, fncPath):
nwGUI = main(
["--testmode", "--help", f"--config={fncPath}", f"--data={fncPath}"]
)
assert nwGUI is not None
assert nwGUI.closeMain() == "closeMain"
assert ex.value.code == 0

with pytest.raises(SystemExit) as ex:
nwGUI = main(
["--testmode", "--version", f"--config={fncPath}", f"--data={fncPath}"]
)
assert nwGUI is not None
assert nwGUI.closeMain() == "closeMain"
assert ex.value.code == 0

Expand All @@ -128,13 +134,15 @@ def testBaseInit_Options(monkeypatch, fncPath):
nwGUI = main(
["--testmode", "--invalid", f"--config={fncPath}", f"--data={fncPath}"]
)
assert nwGUI is not None
assert nwGUI.closeMain() == "closeMain"
assert ex.value.code == 2

# Project Path
nwGUI = main(
["--testmode", f"--config={fncPath}", f"--data={fncPath}", "sample/"]
)
assert nwGUI is not None
assert nwGUI.closeMain() == "closeMain"


Expand Down
10 changes: 8 additions & 2 deletions tests/test_gui/test_gui_docviewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
from novelwriter import CONFIG, SHARED
from novelwriter.enum import nwDocAction
from novelwriter.formats.toqdoc import ToQTextDocument
from novelwriter.gui.docviewer import GuiDocViewer
from novelwriter.types import QtModNone, QtMouseLeft

from tests.mocked import causeException
Expand All @@ -42,7 +41,8 @@ def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, prjLipsum):
"""Test the document viewer."""
# Open project
assert nwGUI.openProject(prjLipsum)
docViewer: GuiDocViewer = nwGUI.docViewer
docEditor = nwGUI.docEditor
docViewer = nwGUI.docViewer

# Select a document in the project tree
nwGUI.projView.setSelectedHandle("88243afbe5ed8")
Expand Down Expand Up @@ -74,6 +74,12 @@ def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, prjLipsum):
docViewer.docHeader._refreshDocument()
assert docViewer.toPlainText() == origText

# Open in editor
nwGUI.closeDocument()
assert docEditor.docHandle is None
docViewer.docHeader._editDocument()
assert docEditor.docHandle == docViewer.docHandle

# Select word
cursor = docViewer.textCursor()
cursor.setPosition(100)
Expand Down
7 changes: 7 additions & 0 deletions tests/test_gui/test_gui_guimain.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,13 @@ def testGuiMain_Launch(qtbot, monkeypatch, nwGUI, projPath):
assert nwGUI.openProject(projPath) is True
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
assert CONFIG.lastNotes != "0x0"

Expand Down

0 comments on commit 08ef352

Please sign in to comment.