Refactor Main GUI inheritance (#1849)

This commit is contained in:
Veronica Berglyd Olsen
2024-05-02 22:36:44 +02:00
committed by GitHub
17 changed files with 128 additions and 139 deletions
+3 -7
View File
@@ -25,14 +25,13 @@ from __future__ import annotations
import logging import logging
from typing import TYPE_CHECKING
from pathlib import Path from pathlib import Path
from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot
from PyQt5.QtGui import QCloseEvent from PyQt5.QtGui import QCloseEvent
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QAbstractItemView, QApplication, QDialog, QDialogButtonBox, QFileDialog, QAbstractItemView, QApplication, QDialog, QDialogButtonBox, QFileDialog,
QHBoxLayout, QLineEdit, QListWidget, QVBoxLayout QHBoxLayout, QLineEdit, QListWidget, QVBoxLayout, QWidget
) )
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
@@ -42,9 +41,6 @@ from novelwriter.extensions.configlayout import NColourLabel
from novelwriter.extensions.modified import NIconToolButton from novelwriter.extensions.modified import NIconToolButton
from novelwriter.types import QtDialogClose, QtDialogSave from novelwriter.types import QtDialogClose, QtDialogSave
if TYPE_CHECKING: # pragma: no cover
from novelwriter.guimain import GuiMain
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -52,8 +48,8 @@ class GuiWordList(QDialog):
newWordListReady = pyqtSignal() newWordListReady = pyqtSignal()
def __init__(self, mainGui: GuiMain) -> None: def __init__(self, parent: QWidget) -> None:
super().__init__(parent=mainGui) super().__init__(parent=parent)
logger.debug("Create: GuiWordList") logger.debug("Create: GuiWordList")
self.setObjectName("GuiWordList") self.setObjectName("GuiWordList")
+5 -1
View File
@@ -28,6 +28,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
from __future__ import annotations from __future__ import annotations
from enum import Enum from enum import Enum
from typing import TYPE_CHECKING
from PyQt5.QtCore import QSize, Qt from PyQt5.QtCore import QSize, Qt
from PyQt5.QtGui import QWheelEvent from PyQt5.QtGui import QWheelEvent
@@ -38,10 +39,13 @@ from PyQt5.QtWidgets import (
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
if TYPE_CHECKING: # pragma: no cover
from novelwriter.guimain import GuiMain
class NToolDialog(QDialog): class NToolDialog(QDialog):
def __init__(self, parent: QWidget | None = None) -> None: def __init__(self, parent: GuiMain) -> None:
super().__init__(parent=parent) super().__init__(parent=parent)
self.setModal(False) self.setModal(False)
if CONFIG.osDarwin: if CONFIG.osDarwin:
+7 -15
View File
@@ -36,7 +36,6 @@ import logging
from enum import Enum from enum import Enum
from time import time from time import time
from typing import TYPE_CHECKING
from PyQt5.QtCore import ( from PyQt5.QtCore import (
QObject, QPoint, QRegularExpression, QRunnable, Qt, QTimer, pyqtSignal, QObject, QPoint, QRegularExpression, QRunnable, Qt, QTimer, pyqtSignal,
@@ -69,9 +68,6 @@ from novelwriter.types import (
QtMoveAnchor, QtMoveLeft, QtMoveRight QtMoveAnchor, QtMoveLeft, QtMoveRight
) )
if TYPE_CHECKING: # pragma: no cover
from novelwriter.guimain import GuiMain
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -107,15 +103,14 @@ class GuiDocEditor(QPlainTextEdit):
requestProjectItemSelected = pyqtSignal(str, bool) requestProjectItemSelected = pyqtSignal(str, bool)
requestProjectItemRenamed = pyqtSignal(str, str) requestProjectItemRenamed = pyqtSignal(str, str)
requestNewNoteCreation = pyqtSignal(str, nwItemClass) requestNewNoteCreation = pyqtSignal(str, nwItemClass)
requestNextDocument = pyqtSignal(str, bool)
def __init__(self, mainGui: GuiMain) -> None: def __init__(self, parent: QWidget) -> None:
super().__init__(parent=mainGui) super().__init__(parent=parent)
logger.debug("Create: GuiDocEditor") logger.debug("Create: GuiDocEditor")
# Class Variables # Class Variables
self.mainGui = mainGui
self._nwDocument = None self._nwDocument = None
self._nwItem = None self._nwItem = None
@@ -477,7 +472,6 @@ class GuiDocEditor(QPlainTextEdit):
cC, wC, pC = standardCounter(docText) cC, wC, pC = standardCounter(docText)
self._updateDocCounts(cC, wC, pC) self._updateDocCounts(cC, wC, pC)
self.saveCursorPosition()
if not self._nwDocument.writeDocument(docText): if not self._nwDocument.writeDocument(docText):
saveOk = False saveOk = False
if self._nwDocument.hashError: if self._nwDocument.hashError:
@@ -1320,9 +1314,8 @@ class GuiDocEditor(QPlainTextEdit):
self.docSearch.setResultCount(0, 0) self.docSearch.setResultCount(0, 0)
self._lastFind = None self._lastFind = None
if CONFIG.searchNextFile and not goBack: if CONFIG.searchNextFile and not goBack:
self.mainGui.openNextDocument( self.requestNextDocument.emit(self._docHandle, CONFIG.searchLoop)
self._docHandle, wrapAround=CONFIG.searchLoop QApplication.processEvents()
)
self.beginSearch() self.beginSearch()
self.setFocus() self.setFocus()
return return
@@ -1341,9 +1334,8 @@ class GuiDocEditor(QPlainTextEdit):
if resIdx > maxIdx and self._docHandle: if resIdx > maxIdx and self._docHandle:
if CONFIG.searchNextFile and not goBack: if CONFIG.searchNextFile and not goBack:
self.mainGui.openNextDocument( self.requestNextDocument.emit(self._docHandle, CONFIG.searchLoop)
self._docHandle, wrapAround=CONFIG.searchLoop QApplication.processEvents()
)
self.beginSearch() self.beginSearch()
self.setFocus() self.setFocus()
return return
+5 -5
View File
@@ -25,16 +25,16 @@ from __future__ import annotations
import logging import logging
from typing import TYPE_CHECKING
from pathlib import Path from pathlib import Path
from typing import TYPE_CHECKING
from PyQt5.QtGui import QDesktopServices
from PyQt5.QtCore import QUrl, pyqtSignal, pyqtSlot from PyQt5.QtCore import QUrl, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import QMenuBar, QAction from PyQt5.QtGui import QDesktopServices
from PyQt5.QtWidgets import QAction, QMenuBar
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
from novelwriter.common import openExternalPath from novelwriter.common import openExternalPath
from novelwriter.constants import nwConst, trConst, nwKeyWords, nwLabels, nwUnicode from novelwriter.constants import nwConst, nwKeyWords, nwLabels, nwUnicode, trConst
from novelwriter.enum import nwDocAction, nwDocInsert, nwView, nwWidget from novelwriter.enum import nwDocAction, nwDocInsert, nwView, nwWidget
from novelwriter.extensions.eventfilters import StatusTipFilter from novelwriter.extensions.eventfilters import StatusTipFilter
@@ -202,7 +202,7 @@ class GuiMainMenu(QMenuBar):
# Document > Save # Document > Save
self.aSaveDoc = self.docuMenu.addAction(self.tr("Save Document")) self.aSaveDoc = self.docuMenu.addAction(self.tr("Save Document"))
self.aSaveDoc.setShortcut("Ctrl+S") self.aSaveDoc.setShortcut("Ctrl+S")
self.aSaveDoc.triggered.connect(self.mainGui.saveDocument) self.aSaveDoc.triggered.connect(self.mainGui.forceSaveDocument)
# Document > Close # Document > Close
self.aCloseDoc = self.docuMenu.addAction(self.tr("Close Document")) self.aCloseDoc = self.docuMenu.addAction(self.tr("Close Document"))
+4 -12
View File
@@ -29,9 +29,8 @@ import logging
from enum import Enum from enum import Enum
from time import time from time import time
from typing import TYPE_CHECKING
from PyQt5.QtCore import QModelIndex, QPoint, Qt, pyqtSlot, pyqtSignal from PyQt5.QtCore import QModelIndex, QPoint, Qt, pyqtSignal, pyqtSlot
from PyQt5.QtGui import QFocusEvent, QFont, QMouseEvent, QPalette, QResizeEvent from PyQt5.QtGui import QFocusEvent, QFont, QMouseEvent, QPalette, QResizeEvent
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QAbstractItemView, QActionGroup, QFrame, QHBoxLayout, QHeaderView, QAbstractItemView, QActionGroup, QFrame, QHBoxLayout, QHeaderView,
@@ -52,9 +51,6 @@ from novelwriter.types import (
QtUserRole QtUserRole
) )
if TYPE_CHECKING: # pragma: no cover
from novelwriter.guimain import GuiMain
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -74,10 +70,8 @@ class GuiNovelView(QWidget):
selectedItemChanged = pyqtSignal(str) selectedItemChanged = pyqtSignal(str)
openDocumentRequest = pyqtSignal(str, Enum, str, bool) openDocumentRequest = pyqtSignal(str, Enum, str, bool)
def __init__(self, mainGui: GuiMain) -> None: def __init__(self, parent: QWidget) -> None:
super().__init__(parent=mainGui) super().__init__(parent=parent)
self.mainGui = mainGui
# Build GUI # Build GUI
self.novelTree = GuiNovelTree(self) self.novelTree = GuiNovelTree(self)
@@ -202,7 +196,6 @@ class GuiNovelToolBar(QWidget):
logger.debug("Create: GuiNovelToolBar") logger.debug("Create: GuiNovelToolBar")
self.novelView = novelView self.novelView = novelView
self.mainGui = novelView.mainGui
iSz = SHARED.theme.baseIconSize iSz = SHARED.theme.baseIconSize
mPx = CONFIG.pxInt(2) mPx = CONFIG.pxInt(2)
@@ -378,7 +371,6 @@ class GuiNovelTree(QTreeWidget):
logger.debug("Create: GuiNovelTree") logger.debug("Create: GuiNovelTree")
self.novelView = novelView self.novelView = novelView
self.mainGui = novelView.mainGui
# Internal Variables # Internal Variables
self._treeMap = {} self._treeMap = {}
@@ -493,7 +485,7 @@ class GuiNovelTree(QTreeWidget):
if rootHandle is None: if rootHandle is None:
rootHandle = SHARED.project.tree.findRoot(nwItemClass.NOVEL) rootHandle = SHARED.project.tree.findRoot(nwItemClass.NOVEL)
treeChanged = self.mainGui.projView.changedSince(self._lastBuild) treeChanged = SHARED.mainGui.projView.changedSince(self._lastBuild)
indexChanged = SHARED.project.index.rootChangedSince(rootHandle, self._lastBuild) indexChanged = SHARED.project.index.rootChangedSince(rootHandle, self._lastBuild)
if not (treeChanged or indexChanged or overRide): if not (treeChanged or indexChanged or overRide):
logger.debug("No changes have been made to the novel index") logger.debug("No changes have been made to the novel index")
+13 -23
View File
@@ -30,12 +30,9 @@ import logging
from enum import Enum from enum import Enum
from time import time from time import time
from typing import TYPE_CHECKING
from PyQt5.QtCore import QPoint, QTimer, Qt, pyqtSignal, pyqtSlot from PyQt5.QtCore import QPoint, Qt, QTimer, pyqtSignal, pyqtSlot
from PyQt5.QtGui import ( from PyQt5.QtGui import QDragEnterEvent, QDragMoveEvent, QDropEvent, QIcon, QMouseEvent, QPalette
QDragEnterEvent, QDragMoveEvent, QDropEvent, QIcon, QMouseEvent, QPalette
)
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QAbstractItemView, QAction, QDialog, QFrame, QHBoxLayout, QHeaderView, QAbstractItemView, QAction, QDialog, QFrame, QHBoxLayout, QHeaderView,
QLabel, QMenu, QShortcut, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QLabel, QMenu, QShortcut, QTreeWidget, QTreeWidgetItem, QVBoxLayout,
@@ -44,24 +41,21 @@ from PyQt5.QtWidgets import (
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
from novelwriter.common import minmax from novelwriter.common import minmax
from novelwriter.constants import nwHeaders, nwUnicode, trConst, nwLabels from novelwriter.constants import nwHeaders, nwLabels, nwUnicode, trConst
from novelwriter.core.coretools import DocDuplicator, DocMerger, DocSplitter from novelwriter.core.coretools import DocDuplicator, DocMerger, DocSplitter
from novelwriter.core.item import NWItem from novelwriter.core.item import NWItem
from novelwriter.dialogs.docmerge import GuiDocMerge from novelwriter.dialogs.docmerge import GuiDocMerge
from novelwriter.dialogs.docsplit import GuiDocSplit from novelwriter.dialogs.docsplit import GuiDocSplit
from novelwriter.dialogs.editlabel import GuiEditLabel from novelwriter.dialogs.editlabel import GuiEditLabel
from novelwriter.dialogs.projectsettings import GuiProjectSettings from novelwriter.dialogs.projectsettings import GuiProjectSettings
from novelwriter.enum import nwDocMode, nwItemType, nwItemClass, nwItemLayout from novelwriter.enum import nwDocMode, nwItemClass, nwItemLayout, nwItemType
from novelwriter.extensions.modified import NIconToolButton from novelwriter.extensions.modified import NIconToolButton
from novelwriter.gui.theme import STYLES_MIN_TOOLBUTTON from novelwriter.gui.theme import STYLES_MIN_TOOLBUTTON
from novelwriter.types import ( from novelwriter.types import (
QtAlignLeft, QtAlignRight, QtMouseLeft, QtMouseMiddle, QtSizeExpanding, QtAlignLeft, QtAlignRight, QtMouseLeft, QtMouseMiddle, QtSizeExpanding,
QtUserRole, QtUserRole
) )
if TYPE_CHECKING: # pragma: no cover
from novelwriter.guimain import GuiMain
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -83,10 +77,8 @@ class GuiProjectView(QWidget):
# Requests for the main GUI # Requests for the main GUI
projectSettingsRequest = pyqtSignal(int) projectSettingsRequest = pyqtSignal(int)
def __init__(self, mainGui: GuiMain) -> None: def __init__(self, parent: QWidget) -> None:
super().__init__(parent=mainGui) super().__init__(parent=parent)
self.mainGui = mainGui
# Build GUI # Build GUI
self.projTree = GuiProjectTree(self) self.projTree = GuiProjectTree(self)
@@ -265,7 +257,6 @@ class GuiProjectToolBar(QWidget):
self.projView = projView self.projView = projView
self.projTree = projView.projTree self.projTree = projView.projTree
self.mainGui = projView.mainGui
iSz = SHARED.theme.baseIconSize iSz = SHARED.theme.baseIconSize
mPx = CONFIG.pxInt(2) mPx = CONFIG.pxInt(2)
@@ -501,7 +492,6 @@ class GuiProjectTree(QTreeWidget):
logger.debug("Create: GuiProjectTree") logger.debug("Create: GuiProjectTree")
self.projView = projView self.projView = projView
self.mainGui = projView.mainGui
# Internal Variables # Internal Variables
self._treeMap: dict[str, QTreeWidgetItem] = {} self._treeMap: dict[str, QTreeWidgetItem] = {}
@@ -1012,8 +1002,7 @@ class GuiProjectTree(QTreeWidget):
trItemP.takeChild(tIndex) trItemP.takeChild(tIndex)
for dHandle in reversed(self.getTreeFromHandle(tHandle)): for dHandle in reversed(self.getTreeFromHandle(tHandle)):
if self.mainGui.docEditor.docHandle == dHandle: SHARED.closeDocument(dHandle)
self.mainGui.closeDocument()
SHARED.project.removeItem(dHandle) SHARED.project.removeItem(dHandle)
self._treeMap.pop(dHandle, None) self._treeMap.pop(dHandle, None)
@@ -1412,7 +1401,7 @@ class GuiProjectTree(QTreeWidget):
if not newFile: if not newFile:
itemList.remove(tHandle) itemList.remove(tHandle)
dlgMerge = GuiDocMerge(self.mainGui, tHandle, itemList) dlgMerge = GuiDocMerge(SHARED.mainGui, tHandle, itemList)
dlgMerge.exec() dlgMerge.exec()
if dlgMerge.result() == QDialog.DialogCode.Accepted: if dlgMerge.result() == QDialog.DialogCode.Accepted:
@@ -1424,7 +1413,7 @@ class GuiProjectTree(QTreeWidget):
return False return False
# Save the open document first, in case it's part of merge # Save the open document first, in case it's part of merge
self.mainGui.saveDocument() SHARED.saveDocument()
# Create merge object, and append docs # Create merge object, and append docs
docMerger = DocMerger(SHARED.project) docMerger = DocMerger(SHARED.project)
@@ -1453,7 +1442,8 @@ class GuiProjectTree(QTreeWidget):
if newFile: if newFile:
self.revealNewTreeItem(mHandle, nHandle=tHandle, wordCount=True) self.revealNewTreeItem(mHandle, nHandle=tHandle, wordCount=True)
self.mainGui.openDocument(mHandle, doScroll=True) self.projView.openDocumentRequest.emit(mHandle, nwDocMode.EDIT, "", False)
self.projView.setSelectedHandle(mHandle, doScroll=True)
if mrgData.get("moveToTrash", False): if mrgData.get("moveToTrash", False):
for sHandle in reversed(mrgData.get("finalItems", [])): for sHandle in reversed(mrgData.get("finalItems", [])):
@@ -1482,7 +1472,7 @@ class GuiProjectTree(QTreeWidget):
logger.error("Only valid document items can be split") logger.error("Only valid document items can be split")
return False return False
dlgSplit = GuiDocSplit(self.mainGui, tHandle) dlgSplit = GuiDocSplit(SHARED.mainGui, tHandle)
dlgSplit.exec() dlgSplit.exec()
if dlgSplit.result() == QDialog.DialogCode.Accepted: if dlgSplit.result() == QDialog.DialogCode.Accepted:
+1 -1
View File
@@ -259,7 +259,7 @@ class GuiProjectSearch(QWidget):
if not self._blocked: if not self._blocked:
QApplication.setOverrideCursor(QCursor(Qt.CursorShape.WaitCursor)) QApplication.setOverrideCursor(QCursor(Qt.CursorShape.WaitCursor))
start = time() start = time()
SHARED.mainGui.saveDocument() SHARED.saveDocument()
self._blocked = True self._blocked = True
self._map = {} self._map = {}
self.searchResult.clear() self.searchResult.clear()
+2 -2
View File
@@ -27,8 +27,8 @@ import logging
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from PyQt5.QtGui import QPalette
from PyQt5.QtCore import QEvent, QPoint, QSize, pyqtSignal from PyQt5.QtCore import QEvent, QPoint, QSize, pyqtSignal
from PyQt5.QtGui import QPalette
from PyQt5.QtWidgets import QMenu, QVBoxLayout, QWidget from PyQt5.QtWidgets import QMenu, QVBoxLayout, QWidget
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
@@ -58,7 +58,7 @@ class GuiSideBar(QWidget):
iSz = QSize(iPx, iPx) iSz = QSize(iPx, iPx)
self.setContentsMargins(0, 0, 0, 0) self.setContentsMargins(0, 0, 0, 0)
self.installEventFilter(StatusTipFilter(mainGui)) self.installEventFilter(StatusTipFilter(self.mainGui))
# Buttons # Buttons
self.tbProject = NIconToolButton(self, iSz) self.tbProject = NIconToolButton(self, iSz)
+6 -8
View File
@@ -27,26 +27,23 @@ import logging
from datetime import datetime from datetime import datetime
from time import time from time import time
from typing import TYPE_CHECKING, Literal from typing import Literal
from PyQt5.QtCore import pyqtSlot, QLocale from PyQt5.QtCore import QLocale, pyqtSlot
from PyQt5.QtWidgets import QApplication, QStatusBar, QLabel from PyQt5.QtWidgets import QApplication, QLabel, QStatusBar, QWidget
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
from novelwriter.common import formatTime from novelwriter.common import formatTime
from novelwriter.constants import nwConst from novelwriter.constants import nwConst
from novelwriter.extensions.statusled import StatusLED from novelwriter.extensions.statusled import StatusLED
if TYPE_CHECKING: # pragma: no cover
from novelwriter.guimain import GuiMain
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class GuiMainStatus(QStatusBar): class GuiMainStatus(QStatusBar):
def __init__(self, mainGui: GuiMain) -> None: def __init__(self, parent: QWidget) -> None:
super().__init__(parent=mainGui) super().__init__(parent=parent)
logger.debug("Create: GuiMainStatus") logger.debug("Create: GuiMainStatus")
@@ -238,6 +235,7 @@ class GuiMainStatus(QStatusBar):
before starting novelWriter. before starting novelWriter.
""" """
import tracemalloc import tracemalloc
from collections import Counter from collections import Counter
widgets = QApplication.allWidgets() widgets = QApplication.allWidgets()
+37 -39
View File
@@ -263,6 +263,7 @@ class GuiMain(QMainWindow):
self.docEditor.requestProjectItemRenamed.connect(self.projView.renameTreeItem) self.docEditor.requestProjectItemRenamed.connect(self.projView.renameTreeItem)
self.docEditor.requestNewNoteCreation.connect(self.projView.createNewNote) self.docEditor.requestNewNoteCreation.connect(self.projView.createNewNote)
self.docEditor.docTextChanged.connect(self.projSearch.textChanged) self.docEditor.docTextChanged.connect(self.projSearch.textChanged)
self.docEditor.requestNextDocument.connect(self.openNextDocument)
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)
@@ -369,9 +370,7 @@ class GuiMain(QMainWindow):
if not msgYes: if not msgYes:
return False return False
if self.docEditor.docChanged: self.saveDocument()
self.saveDocument()
saveOK = self.saveProject() saveOK = self.saveProject()
doBackup = False doBackup = False
if SHARED.project.data.doBackup and CONFIG.backupOnClose: if SHARED.project.data.doBackup and CONFIG.backupOnClose:
@@ -514,9 +513,7 @@ class GuiMain(QMainWindow):
# Disable focus mode if it is active # Disable focus mode if it is active
if SHARED.focusMode: if SHARED.focusMode:
SHARED.setFocusMode(False) SHARED.setFocusMode(False)
self.docEditor.saveCursorPosition() self.saveDocument()
if self.docEditor.docChanged:
self.saveDocument()
self.docEditor.clearEditor() self.docEditor.clearEditor()
if not beforeOpen: if not beforeOpen:
self.novelView.setActiveHandle(None) self.novelView.setActiveHandle(None)
@@ -553,42 +550,43 @@ class GuiMain(QMainWindow):
return True return True
def openNextDocument(self, tHandle: str, wrapAround: bool = False) -> bool: @pyqtSlot(str, bool)
def openNextDocument(self, tHandle: str, wrapAround: bool) -> None:
"""Open the next document in the project tree, following the """Open the next document in the project tree, following the
document with the given handle. Stop when reaching the end. document with the given handle. Stop when reaching the end.
""" """
if not SHARED.hasProject: if SHARED.hasProject:
logger.error("No project open") nHandle = None # The next handle after tHandle
return False fHandle = None # The first file handle we encounter
foundIt = False # We've found tHandle, pick the next we see
for tItem in SHARED.project.tree:
if not tItem.isFileType():
continue
if fHandle is None:
fHandle = tItem.itemHandle
if tItem.itemHandle == tHandle:
foundIt = True
elif foundIt:
nHandle = tItem.itemHandle
break
if nHandle is not None:
self.openDocument(nHandle, tLine=1, doScroll=True)
elif wrapAround:
self.openDocument(fHandle, tLine=1, doScroll=True)
return
nHandle = None # The next handle after tHandle def saveDocument(self, force: bool = False) -> None:
fHandle = None # The first file handle we encounter
foundIt = False # We've found tHandle, pick the next we see
for tItem in SHARED.project.tree:
if not tItem.isFileType():
continue
if fHandle is None:
fHandle = tItem.itemHandle
if tItem.itemHandle == tHandle:
foundIt = True
elif foundIt:
nHandle = tItem.itemHandle
break
if nHandle is not None:
self.openDocument(nHandle, tLine=1, doScroll=True)
return True
elif wrapAround:
self.openDocument(fHandle, tLine=1, doScroll=True)
return False
return False
@pyqtSlot()
def saveDocument(self) -> None:
"""Save the current documents.""" """Save the current documents."""
if SHARED.hasProject: if SHARED.hasProject:
self.docEditor.saveText() self.docEditor.saveCursorPosition()
if force or self.docEditor.docChanged:
self.docEditor.saveText()
return
@pyqtSlot()
def forceSaveDocument(self) -> None:
"""Save document even of it has not changed."""
self.saveDocument(force=True)
return return
def viewDocument(self, tHandle: str | None = None, sTitle: str | None = None) -> bool: def viewDocument(self, tHandle: str | None = None, sTitle: str | None = None) -> bool:
@@ -1133,7 +1131,7 @@ class GuiMain(QMainWindow):
@pyqtSlot() @pyqtSlot()
def _reloadViewer(self) -> None: def _reloadViewer(self) -> None:
"""Reload the document in the viewer.""" """Reload the document in the viewer."""
if self.docEditor.docChanged and self.docEditor.docHandle == self.docViewer.docHandle: if self.docEditor.docHandle == self.docViewer.docHandle:
# If the two panels have the same document, save any changes in the editor # If the two panels have the same document, save any changes in the editor
self.saveDocument() self.saveDocument()
self.docViewer.reloadText() self.docViewer.reloadText()
@@ -1213,7 +1211,7 @@ class GuiMain(QMainWindow):
doSave &= SHARED.project.projChanged doSave &= SHARED.project.projChanged
doSave &= SHARED.project.storage.isOpen() doSave &= SHARED.project.storage.isOpen()
if doSave: if doSave:
logger.debug("Autosaving project") logger.debug("Auto-saving project")
self.saveProject(autoSave=True) self.saveProject(autoSave=True)
return return
@@ -1221,7 +1219,7 @@ class GuiMain(QMainWindow):
def _autoSaveDocument(self) -> None: def _autoSaveDocument(self) -> None:
"""Autosave of the document. This is a timer-activated slot.""" """Autosave of the document. This is a timer-activated slot."""
if SHARED.hasProject and self.docEditor.docChanged: if SHARED.hasProject and self.docEditor.docChanged:
logger.debug("Autosaving document") logger.debug("Auto-saving document")
self.saveDocument() self.saveDocument()
return return
+16 -8
View File
@@ -171,6 +171,17 @@ class SharedData(QObject):
logger.debug("Thread Pool Max Count: %d", QThreadPool.globalInstance().maxThreadCount()) logger.debug("Thread Pool Max Count: %d", QThreadPool.globalInstance().maxThreadCount())
return return
def closeDocument(self, tHandle: str | None = None) -> None:
"""Close the document editor, optionally a specific document."""
if tHandle is None or tHandle == self.mainGui.docEditor.docHandle:
self.mainGui.closeDocument()
return
def saveDocument(self) -> None:
"""Forward save document call to main GUI."""
self.mainGui.saveDocument()
return
def openProject(self, path: str | Path, clearLock: bool = False) -> bool: def openProject(self, path: str | Path, clearLock: bool = False) -> bool:
"""Open a project.""" """Open a project."""
if self.project.isValid: if self.project.isValid:
@@ -198,7 +209,7 @@ class SharedData(QObject):
def closeProject(self) -> None: def closeProject(self) -> None:
"""Close the current project.""" """Close the current project."""
self._closeDialogs() self._closeToolDialogs()
self.project.closeProject(self._idleTime) self.project.closeProject(self._idleTime)
self._resetProject() self._resetProject()
self._resetIdleTimer() self._resetIdleTimer()
@@ -357,15 +368,12 @@ class SharedData(QObject):
self._idleTime = 0.0 self._idleTime = 0.0
return return
def _closeDialogs(self) -> None: def _closeToolDialogs(self) -> None:
"""Close non-modal dialogs.""" """Close all open tool dialogs."""
from novelwriter.tools.manuscript import GuiManuscript from novelwriter.extensions.modified import NToolDialog
from novelwriter.tools.writingstats import GuiWritingStats
for widget in self.mainGui.children(): for widget in self.mainGui.children():
if isinstance(widget, (GuiManuscript, GuiWritingStats)): if isinstance(widget, NToolDialog):
widget.close() widget.close()
return return
# END Class SharedData # END Class SharedData
+1 -1
View File
@@ -327,7 +327,7 @@ class GuiManuscriptBuild(QDialog):
return False return False
# Make sure editor content is saved before we start # Make sure editor content is saved before we start
SHARED.mainGui.saveDocument() SHARED.saveDocument()
docBuild = NWBuildDocument(SHARED.project, self._build) docBuild = NWBuildDocument(SHARED.project, self._build)
docBuild.queueAll() docBuild.queueAll()
+6 -2
View File
@@ -28,6 +28,7 @@ import logging
from datetime import datetime from datetime import datetime
from time import time from time import time
from typing import TYPE_CHECKING
from PyQt5.QtCore import Qt, QTimer, QUrl, pyqtSignal, pyqtSlot from PyQt5.QtCore import Qt, QTimer, QUrl, pyqtSignal, pyqtSlot
from PyQt5.QtGui import QCloseEvent, QColor, QCursor, QFont, QPalette, QResizeEvent from PyQt5.QtGui import QCloseEvent, QColor, QCursor, QFont, QPalette, QResizeEvent
@@ -56,6 +57,9 @@ from novelwriter.types import (
QtSizeExpanding, QtSizeIgnored, QtUserRole QtSizeExpanding, QtSizeIgnored, QtUserRole
) )
if TYPE_CHECKING: # pragma: no cover
from novelwriter.guimain import GuiMain
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -69,7 +73,7 @@ class GuiManuscript(NToolDialog):
D_KEY = QtUserRole D_KEY = QtUserRole
def __init__(self, parent: QWidget) -> None: def __init__(self, parent: GuiMain) -> None:
super().__init__(parent=parent) super().__init__(parent=parent)
logger.debug("Create: GuiManuscript") logger.debug("Create: GuiManuscript")
@@ -335,7 +339,7 @@ class GuiManuscript(NToolDialog):
return return
# Make sure editor content is saved before we start # Make sure editor content is saved before we start
SHARED.mainGui.saveDocument() SHARED.saveDocument()
docBuild = NWBuildDocument(SHARED.project, build) docBuild = NWBuildDocument(SHARED.project, build)
docBuild.setPreviewMode(True) docBuild.setPreviewMode(True)
+6 -1
View File
@@ -25,6 +25,8 @@ from __future__ import annotations
import logging import logging
from typing import TYPE_CHECKING
from PyQt5.QtCore import QEvent, pyqtSignal, pyqtSlot from PyQt5.QtCore import QEvent, pyqtSignal, pyqtSlot
from PyQt5.QtGui import QFont, QIcon, QSyntaxHighlighter, QTextCharFormat, QTextDocument from PyQt5.QtGui import QFont, QIcon, QSyntaxHighlighter, QTextCharFormat, QTextDocument
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
@@ -51,6 +53,9 @@ from novelwriter.types import (
QtRoleApply, QtRoleReject, QtUserRole QtRoleApply, QtRoleReject, QtUserRole
) )
if TYPE_CHECKING: # pragma: no cover
from novelwriter.guimain import GuiMain
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -69,7 +74,7 @@ class GuiBuildSettings(NToolDialog):
newSettingsReady = pyqtSignal(BuildSettings) newSettingsReady = pyqtSignal(BuildSettings)
def __init__(self, parent: QWidget, build: BuildSettings) -> None: def __init__(self, parent: GuiMain, build: BuildSettings) -> None:
super().__init__(parent=parent) super().__init__(parent=parent)
logger.debug("Create: GuiBuildSettings") logger.debug("Create: GuiBuildSettings")
+2 -2
View File
@@ -69,8 +69,8 @@ class GuiWritingStats(NToolDialog):
FMT_JSON = 0 FMT_JSON = 0
FMT_CSV = 1 FMT_CSV = 1
def __init__(self, mainGui: GuiMain) -> None: def __init__(self, parent: GuiMain) -> None:
super().__init__(parent=mainGui) super().__init__(parent=parent)
logger.debug("Create: GuiWritingStats") logger.debug("Create: GuiWritingStats")
self.setObjectName("GuiWritingStats") self.setObjectName("GuiWritingStats")
+3 -3
View File
@@ -1,6 +1,6 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.5a3" hexVersion="0x020500a3" fileVersion="1.5" fileRevision="4" timeStamp="2024-05-01 19:43:08"> <novelWriterXML appVersion="2.5a3" hexVersion="0x020500a3" fileVersion="1.5" fileRevision="4" timeStamp="2024-05-02 21:45:22">
<project id="e2be99af-f9bf-4403-857a-c3d1ac25abea" saveCount="1917" autoCount="274" editTime="87503"> <project id="e2be99af-f9bf-4403-857a-c3d1ac25abea" saveCount="1918" autoCount="274" editTime="87508">
<name>Sample Project</name> <name>Sample Project</name>
<author>Jane Smith</author> <author>Jane Smith</author>
</project> </project>
@@ -58,7 +58,7 @@
<name status="sf24ce6" import="ia857f0" active="yes">Chapter One</name> <name status="sf24ce6" import="ia857f0" active="yes">Chapter One</name>
</item> </item>
<item handle="636b6aa9b697b" parent="6a2d6d5f4f401" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="636b6aa9b697b" parent="6a2d6d5f4f401" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="no" heading="H3" charCount="2953" wordCount="520" paraCount="15" cursorPos="2155" /> <meta expanded="no" heading="H3" charCount="2953" wordCount="520" paraCount="15" cursorPos="19" />
<name status="s90e6c9" import="ia857f0" active="yes">Making a Scene</name> <name status="s90e6c9" import="ia857f0" active="yes">Making a Scene</name>
</item> </item>
<item handle="bc0cbd2a407f3" parent="6a2d6d5f4f401" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="bc0cbd2a407f3" parent="6a2d6d5f4f401" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
+11 -9
View File
@@ -21,24 +21,25 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
from __future__ import annotations from __future__ import annotations
import sys import sys
import pytest
from shutil import copyfile from shutil import copyfile
from tools import C, NWD_IGNORE, cmpFiles, buildTestProject, XML_IGNORE import pytest
from PyQt5.QtGui import QPalette
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QMenu, QInputDialog from PyQt5.QtGui import QPalette
from PyQt5.QtWidgets import QInputDialog, QMenu
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
from novelwriter.dialogs.editlabel import GuiEditLabel
from novelwriter.enum import nwItemType, nwView, nwWidget from novelwriter.enum import nwItemType, nwView, nwWidget
from novelwriter.gui.outline import GuiOutlineView
from novelwriter.gui.projtree import GuiProjectTree
from novelwriter.gui.doceditor import GuiDocEditor from novelwriter.gui.doceditor import GuiDocEditor
from novelwriter.gui.noveltree import GuiNovelView from novelwriter.gui.noveltree import GuiNovelView
from novelwriter.gui.outline import GuiOutlineView
from novelwriter.gui.projtree import GuiProjectTree
from novelwriter.tools.welcome import GuiWelcome from novelwriter.tools.welcome import GuiWelcome
from novelwriter.dialogs.editlabel import GuiEditLabel
from tests.tools import NWD_IGNORE, XML_IGNORE, C, buildTestProject, cmpFiles
KEY_DELAY = 1 KEY_DELAY = 1
@@ -50,7 +51,6 @@ def testGuiMain_ProjectBlocker(nwGUI):
assert nwGUI.closeProject() is True assert nwGUI.closeProject() is True
assert nwGUI.saveProject() is False assert nwGUI.saveProject() is False
assert nwGUI.openDocument(None) is False assert nwGUI.openDocument(None) is False
assert nwGUI.openNextDocument(None) is False
assert nwGUI.viewDocument(None) is False assert nwGUI.viewDocument(None) is False
assert nwGUI.importDocument() is False assert nwGUI.importDocument() is False
@@ -84,7 +84,7 @@ def testGuiMain_Launch(qtbot, monkeypatch, nwGUI, projPath):
nwGUI.closeProject() nwGUI.closeProject()
# Check that latest release info updated # Check that latest release info updated
CONFIG.lastNotes != "0x0" assert CONFIG.lastNotes != "0x0"
# Check that project open dialog launches # Check that project open dialog launches
nwGUI.postLaunchTasks(None) nwGUI.postLaunchTasks(None)
@@ -522,6 +522,8 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd):
assert docEditor.docChanged assert docEditor.docChanged
nwGUI.saveDocument() nwGUI.saveDocument()
assert docEditor.docChanged is False assert docEditor.docChanged is False
nwGUI.forceSaveDocument()
assert docEditor.docChanged is False
nwGUI.rebuildIndex() nwGUI.rebuildIndex()
# Open and view the edited document # Open and view the edited document