Remove re-usage of project class (#1504)_
This commit is contained in:
@@ -31,6 +31,7 @@ from PyQt5.QtWidgets import QApplication, QErrorMessage
|
||||
|
||||
from novelwriter.error import exceptionHandler, logException
|
||||
from novelwriter.config import Config
|
||||
from novelwriter.shared import SharedData
|
||||
|
||||
##
|
||||
# Version Scheme
|
||||
@@ -74,6 +75,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
# Global config singleton
|
||||
CONFIG = Config()
|
||||
SHARED = SharedData()
|
||||
|
||||
|
||||
def main(sysArgs: list | None = None):
|
||||
|
||||
@@ -512,8 +512,7 @@ def sha256sum(path: str | Path) -> str | None:
|
||||
# =============================================================================================== #
|
||||
|
||||
def getGuiItem(objName: str) -> QWidget | None:
|
||||
"""Returns a QtWidget based on its objectName.
|
||||
"""
|
||||
"""Returns a QtWidget based on its objectName."""
|
||||
for qWidget in qApp.topLevelWidgets():
|
||||
if qWidget.objectName() == objName:
|
||||
return qWidget
|
||||
|
||||
+3
-18
@@ -3,7 +3,8 @@ novelWriter – Config Class
|
||||
==========================
|
||||
|
||||
File History:
|
||||
Created: 2018-09-22 [0.0.1] Config
|
||||
Created: 2018-09-22 [0.0.1] Config
|
||||
Created: 2022-11-09 [2.0rc2] RecentProjects
|
||||
|
||||
This file is a part of novelWriter
|
||||
Copyright 2018–2023, Veronica Berglyd Olsen
|
||||
@@ -28,7 +29,6 @@ import json
|
||||
import logging
|
||||
|
||||
from time import time
|
||||
from typing import TYPE_CHECKING
|
||||
from pathlib import Path
|
||||
|
||||
from PyQt5.QtGui import QFontDatabase
|
||||
@@ -42,9 +42,6 @@ from novelwriter.error import formatException, logException
|
||||
from novelwriter.common import NWConfigParser, checkInt, checkPath, formatTimeStamp
|
||||
from novelwriter.constants import nwFiles, nwUnicode
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from novelwriter.gui.theme import GuiTheme
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -96,7 +93,6 @@ class Config:
|
||||
# User Settings
|
||||
# =============
|
||||
|
||||
self._themeObj = None
|
||||
self._recentObj = RecentProjects(self)
|
||||
|
||||
# General GUI Settings
|
||||
@@ -244,12 +240,6 @@ class Config:
|
||||
def recentProjects(self) -> RecentProjects:
|
||||
return self._recentObj
|
||||
|
||||
@property
|
||||
def theme(self) -> GuiTheme:
|
||||
if self._themeObj is None:
|
||||
raise Exception("Cannot access GUI theme before it is initialised")
|
||||
return self._themeObj
|
||||
|
||||
@property
|
||||
def mainWinSize(self) -> list[int]:
|
||||
return [int(x*self.guiScale) for x in self._mainWinSize]
|
||||
@@ -297,11 +287,6 @@ class Config:
|
||||
# Setters
|
||||
##
|
||||
|
||||
def setThemeInstance(self, theme: GuiTheme) -> None:
|
||||
"""Set the applications theme instance."""
|
||||
self._themeObj = theme
|
||||
return
|
||||
|
||||
def setMainWinSize(self, width: int, height: int) -> None:
|
||||
"""Set the size of the main window, but only if the change is
|
||||
larger than 5 pixels. The OS window manager will sometimes
|
||||
@@ -499,7 +484,7 @@ class Config:
|
||||
self._recentObj.loadCache()
|
||||
self._checkOptionalPackages()
|
||||
|
||||
logger.debug("Config initialisation complete")
|
||||
logger.debug("Config instance initialised")
|
||||
|
||||
return
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ from __future__ import annotations
|
||||
|
||||
from PyQt5.QtCore import QCoreApplication, QT_TRANSLATE_NOOP
|
||||
|
||||
from novelwriter.enum import nwAlert, nwBuildFmt, nwItemClass, nwItemLayout, nwOutline
|
||||
from novelwriter.enum import nwBuildFmt, nwItemClass, nwItemLayout, nwOutline
|
||||
|
||||
|
||||
def trConst(text: str) -> str:
|
||||
@@ -52,6 +52,9 @@ class nwConst:
|
||||
URL_HELP = "https://github.com/vkbo/novelWriter/discussions"
|
||||
URL_RELEASE = "https://github.com/vkbo/novelWriter/releases/latest"
|
||||
|
||||
# Gui Settings
|
||||
STATUS_MSG_TIMEOUT = 15000 # milliseconds
|
||||
|
||||
# END Class nwConst
|
||||
|
||||
|
||||
@@ -162,12 +165,6 @@ class nwLabels:
|
||||
nwItemLayout.DOCUMENT: QT_TRANSLATE_NOOP("Constant", "Novel Document"),
|
||||
nwItemLayout.NOTE: QT_TRANSLATE_NOOP("Constant", "Project Note"),
|
||||
}
|
||||
ALERT_NAME = {
|
||||
nwAlert.INFO: QT_TRANSLATE_NOOP("Constant", "Information"),
|
||||
nwAlert.WARN: QT_TRANSLATE_NOOP("Constant", "Warning"),
|
||||
nwAlert.ERROR: QT_TRANSLATE_NOOP("Constant", "Error"),
|
||||
nwAlert.ASK: QT_TRANSLATE_NOOP("Constant", "Question"),
|
||||
}
|
||||
ITEM_DESCRIPTION = {
|
||||
"none": QT_TRANSLATE_NOOP("Constant", "None"),
|
||||
"root": QT_TRANSLATE_NOOP("Constant", "Root Folder"),
|
||||
|
||||
@@ -28,21 +28,17 @@ from __future__ import annotations
|
||||
import shutil
|
||||
import logging
|
||||
|
||||
from typing import TYPE_CHECKING, Iterable
|
||||
from typing import Iterable
|
||||
from functools import partial
|
||||
|
||||
from PyQt5.QtCore import QCoreApplication
|
||||
|
||||
from novelwriter import CONFIG
|
||||
from novelwriter.enum import nwAlert
|
||||
from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.common import minmax, simplified
|
||||
from novelwriter.constants import nwItemClass
|
||||
from novelwriter.core.item import NWItem
|
||||
from novelwriter.core.project import NWProject
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from novelwriter.guimain import GuiMain
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -312,8 +308,7 @@ class ProjectBuilder:
|
||||
parameter provided by the New Project Wizard.
|
||||
"""
|
||||
|
||||
def __init__(self, mainGui: GuiMain) -> None:
|
||||
self.mainGui = mainGui
|
||||
def __init__(self) -> None:
|
||||
self.tr = partial(QCoreApplication.translate, "NWProject")
|
||||
return
|
||||
|
||||
@@ -344,7 +339,7 @@ class ProjectBuilder:
|
||||
logger.error("No project path set for the new project")
|
||||
return False
|
||||
|
||||
project = NWProject(self.mainGui)
|
||||
project = NWProject()
|
||||
if not project.storage.openProjectInPlace(projPath, newProject=True):
|
||||
return False
|
||||
|
||||
@@ -478,17 +473,17 @@ class ProjectBuilder:
|
||||
try:
|
||||
shutil.unpack_archive(pkgSample, projPath)
|
||||
except Exception as exc:
|
||||
self.mainGui.makeAlert(self.tr(
|
||||
SHARED.error(self.tr(
|
||||
"Failed to create a new example project."
|
||||
), level=nwAlert.ERROR, exception=exc)
|
||||
), exc=exc)
|
||||
return False
|
||||
|
||||
else:
|
||||
self.mainGui.makeAlert(self.tr(
|
||||
SHARED.error(self.tr(
|
||||
"Failed to create a new example project. "
|
||||
"Could not find the necessary files. "
|
||||
"They seem to be missing from this installation."
|
||||
), level=nwAlert.ERROR)
|
||||
))
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
@@ -533,7 +533,7 @@ class NWIndex:
|
||||
|
||||
def getTableOfContents(
|
||||
self, rHandle: str, maxDepth: int, skipExcl: bool = True
|
||||
) -> list[tuple[str, str, str, int]]:
|
||||
) -> list[tuple[str, int, str, int]]:
|
||||
"""Generate a table of contents up to a maximum depth."""
|
||||
tOrder = []
|
||||
tData = {}
|
||||
|
||||
+85
-119
@@ -33,8 +33,8 @@ from functools import partial
|
||||
|
||||
from PyQt5.QtCore import QCoreApplication, QObject, pyqtSignal
|
||||
|
||||
from novelwriter import CONFIG, __version__, __hexversion__
|
||||
from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout, nwAlert
|
||||
from novelwriter import CONFIG, SHARED, __version__, __hexversion__
|
||||
from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout
|
||||
from novelwriter.error import logException
|
||||
from novelwriter.constants import trConst, nwLabels
|
||||
from novelwriter.core.tree import NWTree
|
||||
@@ -49,7 +49,6 @@ from novelwriter.common import (
|
||||
)
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from novelwriter.guimain import GuiMain
|
||||
from novelwriter.core.item import NWItem
|
||||
from novelwriter.core.status import NWStatus
|
||||
|
||||
@@ -58,13 +57,11 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class NWProject(QObject):
|
||||
|
||||
projectStatusChanged = pyqtSignal(bool)
|
||||
statusChanged = pyqtSignal(bool)
|
||||
statusMessage = pyqtSignal(str)
|
||||
|
||||
def __init__(self, mainGui: GuiMain) -> None:
|
||||
super().__init__(parent=mainGui)
|
||||
|
||||
# Internal
|
||||
self.mainGui = mainGui
|
||||
def __init__(self, parent: QObject | None = None) -> None:
|
||||
super().__init__(parent=parent)
|
||||
|
||||
# Core Elements
|
||||
self._options = OptionState(self) # Project-specific GUI options
|
||||
@@ -75,13 +72,20 @@ class NWProject(QObject):
|
||||
self._session = NWSessionLog(self) # The session record
|
||||
|
||||
# Project Status
|
||||
self._langData = {} # Localisation data
|
||||
self._projChanged = False # The project has unsaved changes
|
||||
self._lockedBy = None # Data on which computer has the project open
|
||||
self._langData = {} # Localisation data
|
||||
self._lockedBy = None # Data on which computer has the project open
|
||||
self._changed = False # The project has unsaved changes
|
||||
self._valid = False # The project was successfully loaded
|
||||
|
||||
# Internal Mapping
|
||||
self.tr = partial(QCoreApplication.translate, "NWProject")
|
||||
|
||||
logger.debug("Ready: NWProject")
|
||||
|
||||
return
|
||||
|
||||
def __del__(self): # pragma: no cover
|
||||
logger.debug("Delete: NWProject")
|
||||
return
|
||||
|
||||
##
|
||||
@@ -118,7 +122,24 @@ class NWProject(QObject):
|
||||
|
||||
@property
|
||||
def projChanged(self) -> bool:
|
||||
return self._projChanged
|
||||
return self._changed
|
||||
|
||||
@property
|
||||
def isValid(self) -> bool:
|
||||
"""Return True if a project is loaded."""
|
||||
return self._valid
|
||||
|
||||
@property
|
||||
def lockStatus(self) -> list | None:
|
||||
"""Return the project lock information."""
|
||||
if isinstance(self._lockedBy, list) and len(self._lockedBy) == 4:
|
||||
return self._lockedBy
|
||||
return None
|
||||
|
||||
@property
|
||||
def currentEditTime(self) -> int:
|
||||
"""Return total edit time, including the current session."""
|
||||
return self._data.editTime + round(time() - self._session.start)
|
||||
|
||||
##
|
||||
# Item Methods
|
||||
@@ -139,7 +160,7 @@ class NWProject(QObject):
|
||||
"""Add a new file with a given label and parent item."""
|
||||
return self._tree.create(label, parent, nwItemType.FILE)
|
||||
|
||||
def writeNewFile(self, tHandle: str, hLevel: int, isDocument: bool, addText: str = "") -> bool:
|
||||
def writeNewFile(self, tHandle: str, hLevel: int, isDocument: bool, text: str = "") -> bool:
|
||||
"""Write content to a new document after it is created. This
|
||||
will not run if the file exists and is not empty.
|
||||
"""
|
||||
@@ -154,7 +175,7 @@ class NWProject(QObject):
|
||||
return False
|
||||
|
||||
hshText = "#"*minmax(hLevel, 1, 4)
|
||||
newText = f"{hshText} {tItem.itemName}\n\n{addText}"
|
||||
newText = f"{hshText} {tItem.itemName}\n\n{text}"
|
||||
if tItem.isNovelLike() and isDocument:
|
||||
tItem.setLayout(nwItemLayout.DOCUMENT)
|
||||
else:
|
||||
@@ -172,9 +193,9 @@ class NWProject(QObject):
|
||||
if self._tree.checkType(tHandle, nwItemType.FILE):
|
||||
delDoc = self._storage.getDocument(tHandle)
|
||||
if not delDoc.deleteDocument():
|
||||
self.mainGui.makeAlert(
|
||||
SHARED.error(
|
||||
self.tr("Could not delete document file."),
|
||||
info=delDoc.getError(), level=nwAlert.ERROR
|
||||
info=delDoc.getError()
|
||||
)
|
||||
return False
|
||||
|
||||
@@ -195,45 +216,21 @@ class NWProject(QObject):
|
||||
# Project Methods
|
||||
##
|
||||
|
||||
def clearProject(self) -> None:
|
||||
"""Clear the data for the current project, and set them to
|
||||
default values.
|
||||
|
||||
Note: Don't clear the lockedBy data here as it is needed after
|
||||
this function is called.
|
||||
"""
|
||||
# Core Elements
|
||||
self._options = OptionState(self)
|
||||
self._storage.clear()
|
||||
self._data = NWProjectData(self)
|
||||
self._tree.clear()
|
||||
self._index.clearIndex()
|
||||
self._session = NWSessionLog(self)
|
||||
|
||||
# Project Status
|
||||
self._langData = {}
|
||||
self._projChanged = False
|
||||
|
||||
return
|
||||
|
||||
def openProject(self, projPath: str | Path, overrideLock: bool = False) -> bool:
|
||||
def openProject(self, projPath: str | Path, clearLock: bool = False) -> bool:
|
||||
"""Open the project file provided. If it doesn't exist, assume
|
||||
it is a folder and look for the file within it. If successful,
|
||||
parse the XML of the file and populate the project variables and
|
||||
build the tree of project items.
|
||||
"""
|
||||
self.clearProject()
|
||||
logger.info("Opening project: %s", projPath)
|
||||
if not self._storage.openProjectInPlace(projPath):
|
||||
self.mainGui.makeAlert(self.tr(
|
||||
"Could not open project with path: {0}"
|
||||
).format(projPath), level=nwAlert.ERROR)
|
||||
SHARED.error(self.tr("Could not open project with path: {0}").format(projPath))
|
||||
return False
|
||||
|
||||
# Project Lock
|
||||
# ============
|
||||
|
||||
if overrideLock:
|
||||
if clearLock:
|
||||
self._storage.clearLockFile()
|
||||
|
||||
lockStatus = self._storage.readLockFile()
|
||||
@@ -243,7 +240,6 @@ class NWProject(QObject):
|
||||
else:
|
||||
logger.error("Project is locked, so not opening")
|
||||
self._lockedBy = lockStatus
|
||||
self.clearProject()
|
||||
return False
|
||||
else:
|
||||
logger.debug("Project is not locked")
|
||||
@@ -253,7 +249,6 @@ class NWProject(QObject):
|
||||
|
||||
xmlReader = self._storage.getXmlReader()
|
||||
if not isinstance(xmlReader, ProjectXMLReader):
|
||||
self.clearProject()
|
||||
return False
|
||||
|
||||
self._data = NWProjectData(self)
|
||||
@@ -264,49 +259,43 @@ class NWProject(QObject):
|
||||
|
||||
if not xmlParsed:
|
||||
if xmlReader.state == XMLReadState.NOT_NWX_FILE:
|
||||
self.mainGui.makeAlert(self.tr(
|
||||
SHARED.error(self.tr(
|
||||
"Project file does not appear to be a novelWriterXML file."
|
||||
), level=nwAlert.ERROR)
|
||||
))
|
||||
elif xmlReader.state == XMLReadState.UNKNOWN_VERSION:
|
||||
self.mainGui.makeAlert(self.tr(
|
||||
SHARED.error(self.tr(
|
||||
"Unknown or unsupported novelWriter project file format. "
|
||||
"The project cannot be opened by this version of novelWriter. "
|
||||
"The file was saved with novelWriter version {0}."
|
||||
).format(appVersion), level=nwAlert.ERROR)
|
||||
).format(appVersion))
|
||||
else:
|
||||
self.mainGui.makeAlert(self.tr(
|
||||
"Failed to parse project xml."
|
||||
), level=nwAlert.ERROR)
|
||||
|
||||
self.clearProject()
|
||||
SHARED.error(self.tr("Failed to parse project xml."))
|
||||
return False
|
||||
|
||||
# Check Legacy Upgrade
|
||||
# ====================
|
||||
|
||||
if xmlReader.state == XMLReadState.WAS_LEGACY:
|
||||
msgYes = self.mainGui.askQuestion(self.tr(
|
||||
msgYes = SHARED.question(self.tr(
|
||||
"The file format of your project is about to be updated. "
|
||||
"If you proceed, older versions of novelWriter will no "
|
||||
"longer be able to open this project. Continue?"
|
||||
))
|
||||
if not msgYes:
|
||||
self.clearProject()
|
||||
return False
|
||||
|
||||
# Check novelWriter Version
|
||||
# =========================
|
||||
|
||||
if xmlReader.hexVersion > hexToInt(__hexversion__):
|
||||
msgYes = self.mainGui.askQuestion(self.tr(
|
||||
msgYes = SHARED.question(self.tr(
|
||||
"This project was saved by a newer version of "
|
||||
"novelWriter, version {0}. This is version {1}. If you "
|
||||
"continue to open the project, some attributes and "
|
||||
"settings may not be preserved, but the overall project "
|
||||
"should be fine. Continue opening the project?"
|
||||
).format(appVersion, __version__))
|
||||
).format(appVersion, __version__), warn=True)
|
||||
if not msgYes:
|
||||
self.clearProject()
|
||||
return False
|
||||
|
||||
# Extract Data
|
||||
@@ -317,17 +306,19 @@ class NWProject(QObject):
|
||||
self._loadProjectLocalisation()
|
||||
|
||||
# Update recent projects
|
||||
CONFIG.recentProjects.update(
|
||||
self._storage.storagePath, self._data.name, sum(self._data.initCounts), time()
|
||||
)
|
||||
storePath = self._storage.storagePath
|
||||
if storePath:
|
||||
CONFIG.recentProjects.update(
|
||||
storePath, self._data.name, sum(self._data.initCounts), time()
|
||||
)
|
||||
|
||||
# Check the project tree consistency
|
||||
# This also handles any orphaned files found
|
||||
orphans, recovered = self._tree.checkConsistency(self.tr("Recovered"))
|
||||
if orphans > 0:
|
||||
self.mainGui.makeAlert(self.tr(
|
||||
SHARED.warn(self.tr(
|
||||
"Found {0} orphaned file(s) in the project. {1} file(s) were recovered."
|
||||
).format(orphans, recovered), level=nwAlert.WARN)
|
||||
).format(orphans, recovered))
|
||||
|
||||
self._index.loadIndex()
|
||||
if xmlReader.state == XMLReadState.WAS_LEGACY:
|
||||
@@ -338,7 +329,9 @@ class NWProject(QObject):
|
||||
self._session.startSession()
|
||||
self._storage.writeLockFile()
|
||||
self.setProjectChanged(False)
|
||||
self.mainGui.setStatus(self.tr("Opened Project: {0}").format(self._data.name))
|
||||
self._valid = True
|
||||
|
||||
self.statusMessage.emit(self.tr("Opened Project: {0}").format(self._data.name))
|
||||
|
||||
return True
|
||||
|
||||
@@ -349,9 +342,7 @@ class NWProject(QObject):
|
||||
file.
|
||||
"""
|
||||
if not self._storage.isOpen():
|
||||
self.mainGui.makeAlert(self.tr(
|
||||
"There is no project open."
|
||||
), level=nwAlert.ERROR)
|
||||
SHARED.error(self.tr("There is no project open."))
|
||||
return False
|
||||
|
||||
saveTime = time()
|
||||
@@ -374,9 +365,7 @@ class NWProject(QObject):
|
||||
editTime = self._data.editTime + max(round(saveTime - self._session.start), 0)
|
||||
content = self._tree.pack()
|
||||
if not xmlWriter.write(self._data, content, saveTime, editTime):
|
||||
self.mainGui.makeAlert(self.tr(
|
||||
"Failed to save project."
|
||||
), level=nwAlert.ERROR, exception=xmlWriter.error)
|
||||
SHARED.error(self.tr("Failed to save project."), exc=xmlWriter.error)
|
||||
return False
|
||||
|
||||
# Save other project data
|
||||
@@ -385,24 +374,25 @@ class NWProject(QObject):
|
||||
self._storage.runPostSaveTasks(autoSave=autoSave)
|
||||
|
||||
# Update recent projects
|
||||
CONFIG.recentProjects.update(
|
||||
self._storage.storagePath, self._data.name, sum(self._data.currCounts), saveTime
|
||||
)
|
||||
storePath = self._storage.storagePath
|
||||
if storePath:
|
||||
CONFIG.recentProjects.update(
|
||||
storePath, self._data.name, sum(self._data.currCounts), saveTime
|
||||
)
|
||||
|
||||
self._storage.writeLockFile()
|
||||
self.mainGui.setStatus(self.tr("Saved Project: {0}").format(self._data.name))
|
||||
self.statusMessage.emit(self.tr("Saved Project: {0}").format(self._data.name))
|
||||
self.setProjectChanged(False)
|
||||
|
||||
return True
|
||||
|
||||
def closeProject(self, idleTime: float = 0.0) -> None:
|
||||
"""Close the current project and clear all meta data."""
|
||||
"""Close the project."""
|
||||
logger.info("Closing project")
|
||||
self._options.saveSettings()
|
||||
self._tree.writeToCFile()
|
||||
self._session.appendSession(idleTime)
|
||||
self._storage.closeSession()
|
||||
self.clearProject()
|
||||
self._lockedBy = None
|
||||
return
|
||||
|
||||
@@ -413,13 +403,13 @@ class NWProject(QObject):
|
||||
return False
|
||||
|
||||
logger.info("Backing up project")
|
||||
self.mainGui.setStatus(self.tr("Backing up project ..."))
|
||||
self.statusMessage.emit(self.tr("Backing up project ..."))
|
||||
|
||||
if not self._data.name:
|
||||
self.mainGui.makeAlert(self.tr(
|
||||
SHARED.error(self.tr(
|
||||
"Cannot backup project because no project name is set. "
|
||||
"Please set a Project Name in Project Settings."
|
||||
), level=nwAlert.ERROR)
|
||||
))
|
||||
return False
|
||||
|
||||
cleanName = makeFileNameSafe(self._data.name)
|
||||
@@ -428,9 +418,7 @@ class NWProject(QObject):
|
||||
try:
|
||||
baseDir.mkdir(exist_ok=True, parents=True)
|
||||
except Exception as exc:
|
||||
self.mainGui.makeAlert(self.tr(
|
||||
"Could not create backup folder."
|
||||
), level=nwAlert.ERROR, exception=exc)
|
||||
SHARED.error(self.tr("Could not create backup folder."), exc=exc)
|
||||
return False
|
||||
|
||||
timeStamp = formatTimeStamp(time(), fileSafe=True)
|
||||
@@ -438,19 +426,15 @@ class NWProject(QObject):
|
||||
if self._storage.zipIt(archName, compression=2):
|
||||
size = formatInt(archName.stat().st_size)
|
||||
if doNotify:
|
||||
self.mainGui.makeAlert(
|
||||
SHARED.info(
|
||||
self.tr("Created a backup of your project of size {0}B.").format(size),
|
||||
info=self.tr("Path: {0}").format(str(backupPath))
|
||||
)
|
||||
else:
|
||||
self.mainGui.makeAlert(self.tr(
|
||||
"Could not write backup archive."
|
||||
), level=nwAlert.ERROR)
|
||||
SHARED.error(self.tr("Could not write backup archive."))
|
||||
return False
|
||||
|
||||
self.mainGui.setStatus(self.tr(
|
||||
"Project backed up to '{0}'"
|
||||
).format(str(archName)))
|
||||
self.statusMessage.emit(self.tr("Project backed up to '{0}'").format(str(archName)))
|
||||
|
||||
return True
|
||||
|
||||
@@ -503,27 +487,15 @@ class NWProject(QObject):
|
||||
information to the GUI statusbar.
|
||||
"""
|
||||
if isinstance(status, bool):
|
||||
self._projChanged = status
|
||||
self.projectStatusChanged.emit(self._projChanged)
|
||||
return self._projChanged
|
||||
self._changed = status
|
||||
self.statusChanged.emit(self._changed)
|
||||
return self._changed
|
||||
|
||||
##
|
||||
# Getters
|
||||
# Class Methods
|
||||
##
|
||||
|
||||
def getLockStatus(self) -> list | None:
|
||||
"""Return the project lock information for the project."""
|
||||
if isinstance(self._lockedBy, list) and len(self._lockedBy) == 4:
|
||||
return self._lockedBy
|
||||
return None
|
||||
|
||||
def getCurrentEditTime(self) -> int:
|
||||
"""Get the total project edit time, including the time spent in
|
||||
the current session.
|
||||
"""
|
||||
return self._data.editTime + round(time() - self._session.start)
|
||||
|
||||
def getProjectItems(self) -> Iterator[NWItem]:
|
||||
def iterProjectItems(self) -> Iterator[NWItem]:
|
||||
"""This function ensures that the item tree loaded is sent to
|
||||
the GUI tree view in such a way that the tree can be built. That
|
||||
is, the parent item must be sent before its child. In principle,
|
||||
@@ -531,7 +503,7 @@ class NWProject(QObject):
|
||||
order has been altered, or a file is orphaned, this function is
|
||||
capable of handling it.
|
||||
"""
|
||||
sentItems = []
|
||||
sentItems = set()
|
||||
iterItems = self._tree.handles()
|
||||
n = 0
|
||||
nMax = min(len(iterItems), 10000)
|
||||
@@ -540,17 +512,15 @@ class NWProject(QObject):
|
||||
tItem = self._tree[tHandle]
|
||||
n += 1
|
||||
if tItem is None:
|
||||
# Technically a bug since treeOrder is built from the
|
||||
# same data as _projTree
|
||||
# Technically a bug
|
||||
continue
|
||||
elif tItem.itemParent is None:
|
||||
# Item is a root, or already been identified as an
|
||||
# orphaned item
|
||||
sentItems.append(tHandle)
|
||||
# Item is a root, or already been identified as orphaned
|
||||
sentItems.add(tHandle)
|
||||
yield tItem
|
||||
elif tItem.itemParent in sentItems:
|
||||
# Item's parent has been sent, so all is fine
|
||||
sentItems.append(tHandle)
|
||||
sentItems.add(tHandle)
|
||||
yield tItem
|
||||
elif tItem.itemParent in iterItems:
|
||||
# Item's parent exists, but hasn't been sent yet, so add
|
||||
@@ -566,10 +536,6 @@ class NWProject(QObject):
|
||||
yield tItem
|
||||
return
|
||||
|
||||
##
|
||||
# Class Methods
|
||||
##
|
||||
|
||||
def updateWordCounts(self) -> None:
|
||||
"""Update the total word count values."""
|
||||
novel, notes = self._tree.sumWords()
|
||||
|
||||
@@ -213,15 +213,15 @@ class UserDictionary:
|
||||
def load(self) -> None:
|
||||
"""Load the user's dictionary."""
|
||||
self._path = self._project.storage.getMetaFile(nwFiles.DICT_FILE)
|
||||
if not isinstance(self._path, Path):
|
||||
return
|
||||
try:
|
||||
with open(self._path, mode="r", encoding="utf-8") as fObj:
|
||||
data = json.load(fObj)
|
||||
self._words = set(data.get("novelWriter.userDict", []))
|
||||
except Exception:
|
||||
logger.error("Failed to load user dictionary")
|
||||
logException()
|
||||
self._words = set()
|
||||
if isinstance(self._path, Path) and self._path.is_file():
|
||||
try:
|
||||
with open(self._path, mode="r", encoding="utf-8") as fObj:
|
||||
data = json.load(fObj)
|
||||
self._words = set(data.get("novelWriter.userDict", []))
|
||||
except Exception:
|
||||
logger.error("Failed to load user dictionary")
|
||||
logException()
|
||||
return
|
||||
|
||||
def save(self) -> None:
|
||||
|
||||
@@ -129,7 +129,7 @@ class NWStatus:
|
||||
|
||||
def name(self, key: str | None) -> str:
|
||||
"""Return the name associated with a given key."""
|
||||
if key in self._store:
|
||||
if key and key in self._store:
|
||||
return self._store[key]["name"]
|
||||
elif self._default is not None:
|
||||
return self._store[self._default]["name"]
|
||||
@@ -137,7 +137,7 @@ class NWStatus:
|
||||
|
||||
def cols(self, key: str | None) -> tuple[int, int, int]:
|
||||
"""Return the colours associated with a given key."""
|
||||
if key in self._store:
|
||||
if key and key in self._store:
|
||||
return self._store[key]["cols"]
|
||||
elif self._default is not None:
|
||||
return self._store[self._default]["cols"]
|
||||
@@ -145,7 +145,7 @@ class NWStatus:
|
||||
|
||||
def count(self, key: str | None) -> int:
|
||||
"""Return the count associated with a given key."""
|
||||
if key in self._store:
|
||||
if key and key in self._store:
|
||||
return self._store[key]["count"]
|
||||
elif self._default is not None:
|
||||
return self._store[self._default]["count"]
|
||||
@@ -153,7 +153,7 @@ class NWStatus:
|
||||
|
||||
def icon(self, key: str | None) -> QIcon:
|
||||
"""Return the icon associated with a given key."""
|
||||
if key in self._store:
|
||||
if key and key in self._store:
|
||||
return self._store[key]["icon"]
|
||||
elif self._default is not None:
|
||||
return self._store[self._default]["icon"]
|
||||
@@ -186,9 +186,9 @@ class NWStatus:
|
||||
self._store[key]["count"] = 0
|
||||
return
|
||||
|
||||
def increment(self, key: str) -> None:
|
||||
def increment(self, key: str | None) -> None:
|
||||
"""Increment the counter for a given entry."""
|
||||
if key in self._store:
|
||||
if key and key in self._store:
|
||||
self._store[key]["count"] += 1
|
||||
return
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ from functools import partial
|
||||
|
||||
from PyQt5.QtCore import QCoreApplication, QRegularExpression
|
||||
|
||||
from novelwriter.enum import nwItemLayout, nwItemType
|
||||
from novelwriter.enum import nwItemLayout
|
||||
from novelwriter.common import formatTimeStamp, numberToRoman, checkInt
|
||||
from novelwriter.constants import nwConst, nwHeadFmt, nwRegEx, nwUnicode
|
||||
from novelwriter.core.project import NWProject
|
||||
@@ -315,10 +315,8 @@ class Tokenizer(ABC):
|
||||
|
||||
def addRootHeading(self, tHandle: str) -> bool:
|
||||
"""Add a heading at the start of a new root folder."""
|
||||
if not self._project.tree.checkType(tHandle, nwItemType.ROOT):
|
||||
return False
|
||||
theItem = self._project.tree[tHandle]
|
||||
if not theItem:
|
||||
tItem = self._project.tree[tHandle]
|
||||
if not tItem or not tItem.isRootType():
|
||||
return False
|
||||
|
||||
if self._isFirst:
|
||||
@@ -327,14 +325,14 @@ class Tokenizer(ABC):
|
||||
else:
|
||||
textAlign = self.A_PBB | self.A_CENTRE
|
||||
|
||||
locNotes = self._localLookup("Notes")
|
||||
theTitle = f"{locNotes}: {theItem.itemName}"
|
||||
trNotes = self._localLookup("Notes")
|
||||
title = f"{trNotes}: {tItem.itemName}"
|
||||
self._tokens = []
|
||||
self._tokens.append((
|
||||
self.T_TITLE, 0, theTitle, None, textAlign
|
||||
self.T_TITLE, 0, title, None, textAlign
|
||||
))
|
||||
if self._keepMarkdown:
|
||||
self._allMarkdown.append(f"# {theTitle}\n\n")
|
||||
self._allMarkdown.append(f"# {title}\n\n")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
+58
-63
@@ -65,13 +65,12 @@ class NWTree:
|
||||
|
||||
self._project = project
|
||||
|
||||
self._projTree: dict[str, NWItem] = {} # Holds all the items of the project
|
||||
self._treeOrder: list[str] = [] # The order of the tree items in the tree view
|
||||
self._treeRoots: dict[str, NWItem] = {} # The root items of the tree
|
||||
self._tree: dict[str, NWItem] = {} # Holds all the items of the project
|
||||
self._order: list[str] = [] # The order of the tree items in the tree view
|
||||
self._roots: dict[str, NWItem] = {} # The root items of the tree
|
||||
|
||||
self._trashRoot = None # The handle of the trash root folder
|
||||
self._archRoot = None # The handle of the archive root folder
|
||||
self._treeChanged = False # True if tree structure has changed
|
||||
self._trash = None # The handle of the trash root folder
|
||||
self._changed = False # True if tree structure has changed
|
||||
|
||||
return
|
||||
|
||||
@@ -81,17 +80,16 @@ class NWTree:
|
||||
|
||||
def clear(self) -> None:
|
||||
"""Clear the item tree entirely."""
|
||||
self._projTree = {}
|
||||
self._treeOrder = []
|
||||
self._treeRoots = {}
|
||||
self._trashRoot = None
|
||||
self._archRoot = None
|
||||
self._treeChanged = False
|
||||
self._tree = {}
|
||||
self._order = []
|
||||
self._roots = {}
|
||||
self._trash = None
|
||||
self._changed = False
|
||||
return
|
||||
|
||||
def handles(self) -> list[str]:
|
||||
"""Returns a copy of the list of all the active handles."""
|
||||
return self._treeOrder.copy()
|
||||
return self._order.copy()
|
||||
|
||||
@overload # pragma: no cover
|
||||
def create(self, label: str, parent: None, itemType: Literal[nwItemType.ROOT],
|
||||
@@ -109,7 +107,7 @@ class NWTree:
|
||||
parent, None is returned. For root elements, this cannot occur.
|
||||
"""
|
||||
parent = None if itemType == nwItemType.ROOT else parent
|
||||
if parent is None or parent in self._treeOrder:
|
||||
if parent is None or parent in self._order:
|
||||
tHandle = self._makeHandle()
|
||||
newItem = NWItem(self._project, tHandle)
|
||||
newItem.setName(label)
|
||||
@@ -130,7 +128,7 @@ class NWTree:
|
||||
logger.warning("Invalid item handle '%s' detected, skipping", tHandle)
|
||||
return False
|
||||
|
||||
if tHandle in self._projTree:
|
||||
if tHandle in self._tree:
|
||||
logger.warning("Duplicate handle '%s' detected, skipping", tHandle)
|
||||
return False
|
||||
|
||||
@@ -138,20 +136,17 @@ class NWTree:
|
||||
|
||||
if nwItem.isRootType():
|
||||
logger.debug("Item '%s' is a root item", str(tHandle))
|
||||
self._treeRoots[tHandle] = nwItem
|
||||
if nwItem.itemClass == nwItemClass.ARCHIVE:
|
||||
logger.debug("Item '%s' is the archive folder", str(tHandle))
|
||||
self._archRoot = tHandle
|
||||
elif nwItem.itemClass == nwItemClass.TRASH:
|
||||
if self._trashRoot is None:
|
||||
self._roots[tHandle] = nwItem
|
||||
if nwItem.itemClass == nwItemClass.TRASH:
|
||||
if self._trash is None:
|
||||
logger.debug("Item '%s' is the trash folder", str(tHandle))
|
||||
self._trashRoot = tHandle
|
||||
self._trash = tHandle
|
||||
else:
|
||||
logger.error("Only one trash folder allowed")
|
||||
return False
|
||||
|
||||
self._projTree[tHandle] = nwItem
|
||||
self._treeOrder.append(tHandle)
|
||||
self._tree[tHandle] = nwItem
|
||||
self._order.append(tHandle)
|
||||
self._setTreeChanged(True)
|
||||
|
||||
return True
|
||||
@@ -171,7 +166,7 @@ class NWTree:
|
||||
items. In the order defined by the _treeOrder list.
|
||||
"""
|
||||
tree = []
|
||||
for tHandle in self._treeOrder:
|
||||
for tHandle in self._order:
|
||||
tItem = self.__getitem__(tHandle)
|
||||
if tItem:
|
||||
tree.append(tItem.pack())
|
||||
@@ -199,7 +194,7 @@ class NWTree:
|
||||
"""
|
||||
storage = self._project.storage
|
||||
files = set(storage.scanContent())
|
||||
for tHandle in self._treeOrder:
|
||||
for tHandle in self._order:
|
||||
if self.updateItemData(tHandle):
|
||||
logger.debug("Checking item '%s' ... OK", tHandle)
|
||||
files.discard(tHandle) # Remove it from the record
|
||||
@@ -220,7 +215,7 @@ class NWTree:
|
||||
oName, oParent, oClass, oLayout = aDoc.getMeta()
|
||||
|
||||
oName = oName or cHandle
|
||||
oParent = oParent if oParent in self._treeOrder else None
|
||||
oParent = oParent if oParent in self._order else None
|
||||
oClass = oClass or nwItemClass.NOVEL
|
||||
oLayout = oLayout or nwItemLayout.NOTE
|
||||
|
||||
@@ -229,8 +224,10 @@ class NWTree:
|
||||
oParent = self.findRoot(oClass)
|
||||
if oParent is None: # Otherwise, add to the Novel root
|
||||
oParent = self.findRoot(nwItemClass.NOVEL)
|
||||
if oParent is None: # If not, give up
|
||||
continue
|
||||
if oParent is None: # If not, create a new novel folder
|
||||
oParent = self.create(prefix, None, nwItemType.ROOT, nwItemClass.NOVEL)
|
||||
|
||||
assert oParent is not None # Otherwise there's an issue with self.create()
|
||||
|
||||
# Create a new item
|
||||
newItem = NWItem(self._project, cHandle)
|
||||
@@ -256,7 +253,7 @@ class NWTree:
|
||||
|
||||
tocList = []
|
||||
tocLen = 0
|
||||
for tHandle in self._treeOrder:
|
||||
for tHandle in self._order:
|
||||
tItem = self.__getitem__(tHandle)
|
||||
if tItem is None:
|
||||
continue
|
||||
@@ -298,7 +295,7 @@ class NWTree:
|
||||
"""Loop over all entries and add up the word counts."""
|
||||
noteWords = 0
|
||||
novelWords = 0
|
||||
for tHandle in self._treeOrder:
|
||||
for tHandle in self._order:
|
||||
tItem = self.__getitem__(tHandle)
|
||||
if tItem is None:
|
||||
continue
|
||||
@@ -374,13 +371,13 @@ class NWTree:
|
||||
def rootClasses(self) -> set[nwItemClass]:
|
||||
"""Return a set of all root classes in use by the project."""
|
||||
rootClasses = set()
|
||||
for nwItem in self._treeRoots.values():
|
||||
for nwItem in self._roots.values():
|
||||
rootClasses.add(nwItem.itemClass)
|
||||
return rootClasses
|
||||
|
||||
def iterRoots(self, itemClass: nwItemClass | None) -> Iterator[tuple[str, NWItem]]:
|
||||
"""Iterate over all root items of a given class in order."""
|
||||
for tHandle in self._treeOrder:
|
||||
for tHandle in self._order:
|
||||
nwItem = self.__getitem__(tHandle)
|
||||
if isinstance(nwItem, NWItem) and nwItem.isRootType():
|
||||
if itemClass is None or nwItem.itemClass == itemClass:
|
||||
@@ -394,12 +391,12 @@ class NWTree:
|
||||
return True
|
||||
if tItem.itemClass == nwItemClass.TRASH:
|
||||
return True
|
||||
if self._trashRoot is not None:
|
||||
if tHandle == self._trashRoot:
|
||||
if self._trash is not None:
|
||||
if tHandle == self._trash:
|
||||
return True
|
||||
elif tItem.itemParent == self._trashRoot:
|
||||
elif tItem.itemParent == self._trash:
|
||||
return True
|
||||
elif tItem.itemRoot == self._trashRoot:
|
||||
elif tItem.itemRoot == self._trash:
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -407,13 +404,13 @@ class NWTree:
|
||||
"""Returns the handle of the trash folder, or None if there
|
||||
isn't one.
|
||||
"""
|
||||
if self._trashRoot:
|
||||
return self._trashRoot
|
||||
if self._trash:
|
||||
return self._trash
|
||||
return None
|
||||
|
||||
def findRoot(self, itemClass: nwItemClass | None) -> str | None:
|
||||
"""Find the first root item for a given class."""
|
||||
for aRoot in self._treeRoots:
|
||||
for aRoot in self._roots:
|
||||
tItem = self.__getitem__(aRoot)
|
||||
if tItem is None:
|
||||
continue
|
||||
@@ -427,18 +424,18 @@ class NWTree:
|
||||
|
||||
def setOrder(self, newOrder: list[str]) -> None:
|
||||
"""Reorders the tree based on a list of items."""
|
||||
tmpOrder = [tHandle for tHandle in newOrder if tHandle in self._projTree]
|
||||
if not (len(tmpOrder) == len(newOrder) == len(self._treeOrder)):
|
||||
tmpOrder = [tHandle for tHandle in newOrder if tHandle in self._tree]
|
||||
if not (len(tmpOrder) == len(newOrder) == len(self._order)):
|
||||
# Something is wrong, so let's debug it
|
||||
for tHandle in newOrder:
|
||||
if tHandle not in self._projTree:
|
||||
if tHandle not in self._tree:
|
||||
logger.error("Handle '%s' in new tree order is not in old order", tHandle)
|
||||
for tHandle in self._treeOrder:
|
||||
for tHandle in self._order:
|
||||
if tHandle not in tmpOrder:
|
||||
logger.warning("Handle '%s' in old tree order is not in new order", tHandle)
|
||||
|
||||
# Save the temp list
|
||||
self._treeOrder = tmpOrder
|
||||
self._order = tmpOrder
|
||||
self._setTreeChanged(True)
|
||||
logger.debug("Project tree order updated")
|
||||
|
||||
@@ -450,36 +447,34 @@ class NWTree:
|
||||
|
||||
def __len__(self) -> int:
|
||||
"""The number of items in the project."""
|
||||
return len(self._treeOrder)
|
||||
return len(self._order)
|
||||
|
||||
def __bool__(self) -> bool:
|
||||
"""True if there are any items in the project."""
|
||||
return bool(self._treeOrder)
|
||||
return bool(self._order)
|
||||
|
||||
def __getitem__(self, tHandle: str | None) -> NWItem | None:
|
||||
"""Return a project item based on its handle. Returns None if
|
||||
the handle doesn't exist in the project.
|
||||
"""
|
||||
if tHandle and tHandle in self._projTree:
|
||||
return self._projTree[tHandle]
|
||||
if tHandle and tHandle in self._tree:
|
||||
return self._tree[tHandle]
|
||||
logger.error("No tree item with handle '%s'", str(tHandle))
|
||||
return None
|
||||
|
||||
def __delitem__(self, tHandle: str) -> None:
|
||||
"""Remove an item from the internal lists and dictionaries."""
|
||||
if tHandle in self._treeOrder and tHandle in self._projTree:
|
||||
self._treeOrder.remove(tHandle)
|
||||
del self._projTree[tHandle]
|
||||
if tHandle in self._order and tHandle in self._tree:
|
||||
self._order.remove(tHandle)
|
||||
del self._tree[tHandle]
|
||||
else:
|
||||
logger.warning("Failed to delete item '%s': item not found", tHandle)
|
||||
return
|
||||
|
||||
if tHandle in self._treeRoots:
|
||||
del self._treeRoots[tHandle]
|
||||
if tHandle == self._trashRoot:
|
||||
self._trashRoot = None
|
||||
if tHandle == self._archRoot:
|
||||
self._archRoot = None
|
||||
if tHandle in self._roots:
|
||||
del self._roots[tHandle]
|
||||
if tHandle == self._trash:
|
||||
self._trash = None
|
||||
|
||||
self._setTreeChanged(True)
|
||||
|
||||
@@ -487,12 +482,12 @@ class NWTree:
|
||||
|
||||
def __contains__(self, tHandle: str) -> bool:
|
||||
"""Checks if a handle exists in the tree."""
|
||||
return tHandle in self._treeOrder
|
||||
return tHandle in self._order
|
||||
|
||||
def __iter__(self) -> Iterator[NWItem]:
|
||||
"""Iterate through project items."""
|
||||
for tHandle in self._treeOrder:
|
||||
tItem = self._projTree.get(tHandle)
|
||||
for tHandle in self._order:
|
||||
tItem = self._tree.get(tHandle)
|
||||
if isinstance(tItem, NWItem):
|
||||
yield tItem
|
||||
return
|
||||
@@ -505,7 +500,7 @@ class NWTree:
|
||||
"""Set the changed flag to theState, and if being set to True,
|
||||
propagate that state change to the parent NWProject class.
|
||||
"""
|
||||
self._treeChanged = state
|
||||
self._changed = state
|
||||
if state:
|
||||
self._project.setProjectChanged(True)
|
||||
return
|
||||
@@ -516,7 +511,7 @@ class NWTree:
|
||||
"""
|
||||
logger.debug("Generating new handle")
|
||||
handle = f"{random.getrandbits(52):013x}"
|
||||
if handle in self._projTree:
|
||||
if handle in self._tree:
|
||||
logger.warning("Duplicate handle encountered! Retrying ...")
|
||||
handle = self._makeHandle()
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ from PyQt5.QtWidgets import (
|
||||
QTextBrowser, QVBoxLayout, QWidget
|
||||
)
|
||||
|
||||
from novelwriter import CONFIG
|
||||
from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.common import readTextFile
|
||||
from novelwriter.constants import nwConst
|
||||
|
||||
@@ -60,7 +60,7 @@ class GuiAbout(QDialog):
|
||||
|
||||
nPx = CONFIG.pxInt(96)
|
||||
self.nwIcon = QLabel()
|
||||
self.nwIcon.setPixmap(CONFIG.theme.getPixmap("novelwriter", (nPx, nPx)))
|
||||
self.nwIcon.setPixmap(SHARED.theme.getPixmap("novelwriter", (nPx, nPx)))
|
||||
self.lblName = QLabel("<b>novelWriter</b>")
|
||||
self.lblVers = QLabel(f"v{novelwriter.__version__}")
|
||||
self.lblDate = QLabel(datetime.strptime(novelwriter.__date__, "%Y-%m-%d").strftime("%x"))
|
||||
@@ -228,12 +228,12 @@ class GuiAbout(QDialog):
|
||||
" color: rgb({kColR},{kColG},{kColB});"
|
||||
"}}\n"
|
||||
).format(
|
||||
hColR=CONFIG.theme.colHead[0],
|
||||
hColG=CONFIG.theme.colHead[1],
|
||||
hColB=CONFIG.theme.colHead[2],
|
||||
kColR=CONFIG.theme.colKey[0],
|
||||
kColG=CONFIG.theme.colKey[1],
|
||||
kColB=CONFIG.theme.colKey[2],
|
||||
hColR=SHARED.theme.colHead[0],
|
||||
hColG=SHARED.theme.colHead[1],
|
||||
hColB=SHARED.theme.colHead[2],
|
||||
kColR=SHARED.theme.colKey[0],
|
||||
kColG=SHARED.theme.colKey[1],
|
||||
kColB=SHARED.theme.colKey[2],
|
||||
)
|
||||
self.pageAbout.document().setDefaultStyleSheet(styleSheet)
|
||||
self.pageNotes.document().setDefaultStyleSheet(styleSheet)
|
||||
|
||||
@@ -29,10 +29,10 @@ import logging
|
||||
from PyQt5.QtCore import Qt, QSize
|
||||
from PyQt5.QtWidgets import (
|
||||
QAbstractItemView, QDialog, QDialogButtonBox, QGridLayout, QLabel,
|
||||
QListWidget, QListWidgetItem, QVBoxLayout,
|
||||
QListWidget, QListWidgetItem, QVBoxLayout, QWidget
|
||||
)
|
||||
|
||||
from novelwriter import CONFIG
|
||||
from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.extensions.switch import NSwitch
|
||||
from novelwriter.extensions.configlayout import NHelpLabel
|
||||
|
||||
@@ -43,24 +43,21 @@ class GuiDocMerge(QDialog):
|
||||
|
||||
D_HANDLE = Qt.ItemDataRole.UserRole
|
||||
|
||||
def __init__(self, mainGui, sHandle, itemList):
|
||||
super().__init__(parent=mainGui)
|
||||
def __init__(self, parent: QWidget, sHandle: str, itemList: list[str]) -> None:
|
||||
super().__init__(parent=parent)
|
||||
|
||||
logger.debug("Create: GuiDocMerge")
|
||||
self.setObjectName("GuiDocMerge")
|
||||
|
||||
self.mainGui = mainGui
|
||||
self.setWindowTitle(self.tr("Merge Documents"))
|
||||
|
||||
self._data = {}
|
||||
|
||||
self.setWindowTitle(self.tr("Merge Documents"))
|
||||
|
||||
self.headLabel = QLabel("<b>{0}</b>".format(self.tr("Documents to Merge")))
|
||||
self.helpLabel = NHelpLabel(self.tr(
|
||||
"Drag and drop items to change the order, or uncheck to exclude."
|
||||
), CONFIG.theme.helpText)
|
||||
), SHARED.theme.helpText)
|
||||
|
||||
iPx = CONFIG.theme.baseIconSize
|
||||
iPx = SHARED.theme.baseIconSize
|
||||
hSp = CONFIG.pxInt(12)
|
||||
vSp = CONFIG.pxInt(8)
|
||||
bSp = CONFIG.pxInt(12)
|
||||
@@ -155,11 +152,11 @@ class GuiDocMerge(QDialog):
|
||||
|
||||
self.listBox.clear()
|
||||
for tHandle in itemList:
|
||||
nwItem = self.mainGui.project.tree[tHandle]
|
||||
nwItem = SHARED.project.tree[tHandle]
|
||||
if nwItem is None or not nwItem.isFileType():
|
||||
continue
|
||||
|
||||
itemIcon = CONFIG.theme.getItemIcon(
|
||||
itemIcon = SHARED.theme.getItemIcon(
|
||||
nwItem.itemType, nwItem.itemClass, nwItem.itemLayout, nwItem.mainHeading
|
||||
)
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ from PyQt5.QtWidgets import (
|
||||
QListWidgetItem, QDialogButtonBox, QLabel, QGridLayout
|
||||
)
|
||||
|
||||
from novelwriter import CONFIG
|
||||
from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.extensions.switch import NSwitch
|
||||
from novelwriter.extensions.configlayout import NHelpLabel
|
||||
|
||||
@@ -45,14 +45,12 @@ class GuiDocSplit(QDialog):
|
||||
LEVEL_ROLE = Qt.ItemDataRole.UserRole + 1
|
||||
LABEL_ROLE = Qt.ItemDataRole.UserRole + 2
|
||||
|
||||
def __init__(self, mainGui, sHandle):
|
||||
super().__init__(parent=mainGui)
|
||||
def __init__(self, parent, sHandle):
|
||||
super().__init__(parent=parent)
|
||||
|
||||
logger.debug("Create: GuiDocSplit")
|
||||
self.setObjectName("GuiDocSplit")
|
||||
|
||||
self.mainGui = mainGui
|
||||
|
||||
self._data = {}
|
||||
self._text = []
|
||||
|
||||
@@ -61,16 +59,16 @@ class GuiDocSplit(QDialog):
|
||||
self.headLabel = QLabel("<b>{0}</b>".format(self.tr("Document Headers")))
|
||||
self.helpLabel = NHelpLabel(
|
||||
self.tr("Select the maximum level to split into files."),
|
||||
CONFIG.theme.helpText
|
||||
SHARED.theme.helpText
|
||||
)
|
||||
|
||||
# Values
|
||||
iPx = CONFIG.theme.baseIconSize
|
||||
iPx = SHARED.theme.baseIconSize
|
||||
hSp = CONFIG.pxInt(12)
|
||||
vSp = CONFIG.pxInt(8)
|
||||
bSp = CONFIG.pxInt(12)
|
||||
|
||||
pOptions = self.mainGui.project.options
|
||||
pOptions = SHARED.project.options
|
||||
spLevel = pOptions.getInt("GuiDocSplit", "spLevel", 3)
|
||||
intoFolder = pOptions.getBool("GuiDocSplit", "intoFolder", True)
|
||||
docHierarchy = pOptions.getBool("GuiDocSplit", "docHierarchy", True)
|
||||
@@ -169,7 +167,7 @@ class GuiDocSplit(QDialog):
|
||||
self._data["docHierarchy"] = docHierarchy
|
||||
self._data["moveToTrash"] = moveToTrash
|
||||
|
||||
pOptions = self.mainGui.project.options
|
||||
pOptions = SHARED.project.options
|
||||
pOptions.setValue("GuiDocSplit", "spLevel", spLevel)
|
||||
pOptions.setValue("GuiDocSplit", "intoFolder", intoFolder)
|
||||
pOptions.setValue("GuiDocSplit", "docHierarchy", docHierarchy)
|
||||
@@ -199,13 +197,13 @@ class GuiDocSplit(QDialog):
|
||||
|
||||
self.listBox.clear()
|
||||
|
||||
nwItem = self.mainGui.project.tree[sHandle]
|
||||
nwItem = SHARED.project.tree[sHandle]
|
||||
if nwItem is None or not nwItem.isFileType():
|
||||
return
|
||||
|
||||
spLevel = self.splitLevel.currentData()
|
||||
if not self._text:
|
||||
inDoc = self.mainGui.project.storage.getDocument(sHandle)
|
||||
inDoc = SHARED.project.storage.getDocument(sHandle)
|
||||
self._text = (inDoc.readDocument() or "").splitlines()
|
||||
|
||||
for lineNo, aLine in enumerate(self._text):
|
||||
|
||||
@@ -32,7 +32,7 @@ from PyQt5.QtWidgets import (
|
||||
QLineEdit, QFileDialog, QFontDialog, QDoubleSpinBox
|
||||
)
|
||||
|
||||
from novelwriter import CONFIG
|
||||
from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.dialogs.quotes import GuiQuoteSelect
|
||||
from novelwriter.extensions.switch import NSwitch
|
||||
from novelwriter.extensions.pageddialog import NPagedDialog
|
||||
@@ -163,7 +163,7 @@ class GuiPreferencesGeneral(QWidget):
|
||||
|
||||
# The Form
|
||||
self.mainForm = NConfigLayout()
|
||||
self.mainForm.setHelpTextStyle(CONFIG.theme.helpText)
|
||||
self.mainForm.setHelpTextStyle(SHARED.theme.helpText)
|
||||
self.setLayout(self.mainForm)
|
||||
|
||||
# Look and Feel
|
||||
@@ -190,7 +190,7 @@ class GuiPreferencesGeneral(QWidget):
|
||||
# Select Theme
|
||||
self.guiTheme = QComboBox()
|
||||
self.guiTheme.setMinimumWidth(minWidth)
|
||||
self.theThemes = CONFIG.theme.listThemes()
|
||||
self.theThemes = SHARED.theme.listThemes()
|
||||
for themeDir, themeName in self.theThemes:
|
||||
self.guiTheme.addItem(themeName, themeDir)
|
||||
themeIdx = self.guiTheme.findData(CONFIG.guiTheme)
|
||||
@@ -206,7 +206,7 @@ class GuiPreferencesGeneral(QWidget):
|
||||
# Editor Theme
|
||||
self.guiSyntax = QComboBox()
|
||||
self.guiSyntax.setMinimumWidth(CONFIG.pxInt(200))
|
||||
self.theSyntaxes = CONFIG.theme.listSyntax()
|
||||
self.theSyntaxes = SHARED.theme.listSyntax()
|
||||
for syntaxFile, syntaxName in self.theSyntaxes:
|
||||
self.guiSyntax.addItem(syntaxName, syntaxFile)
|
||||
syntaxIdx = self.guiSyntax.findData(CONFIG.guiSyntax)
|
||||
@@ -225,7 +225,7 @@ class GuiPreferencesGeneral(QWidget):
|
||||
self.guiFont.setFixedWidth(CONFIG.pxInt(162))
|
||||
self.guiFont.setText(CONFIG.guiFont)
|
||||
self.fontButton = QPushButton("...")
|
||||
self.fontButton.setMaximumWidth(int(2.5*CONFIG.theme.getTextWidth("...")))
|
||||
self.fontButton.setMaximumWidth(int(2.5*SHARED.theme.getTextWidth("...")))
|
||||
self.fontButton.clicked.connect(self._selectFont)
|
||||
self.mainForm.addRow(
|
||||
self.tr("Font family"),
|
||||
@@ -341,7 +341,7 @@ class GuiPreferencesProjects(QWidget):
|
||||
|
||||
# The Form
|
||||
self.mainForm = NConfigLayout()
|
||||
self.mainForm.setHelpTextStyle(CONFIG.theme.helpText)
|
||||
self.mainForm.setHelpTextStyle(SHARED.theme.helpText)
|
||||
self.setLayout(self.mainForm)
|
||||
|
||||
# Automatic Save
|
||||
@@ -493,7 +493,7 @@ class GuiPreferencesDocuments(QWidget):
|
||||
|
||||
# The Form
|
||||
self.mainForm = NConfigLayout()
|
||||
self.mainForm.setHelpTextStyle(CONFIG.theme.helpText)
|
||||
self.mainForm.setHelpTextStyle(SHARED.theme.helpText)
|
||||
self.setLayout(self.mainForm)
|
||||
|
||||
# Text Style
|
||||
@@ -506,7 +506,7 @@ class GuiPreferencesDocuments(QWidget):
|
||||
self.textFont.setFixedWidth(CONFIG.pxInt(162))
|
||||
self.textFont.setText(CONFIG.textFont)
|
||||
self.fontButton = QPushButton("...")
|
||||
self.fontButton.setMaximumWidth(int(2.5*CONFIG.theme.getTextWidth("...")))
|
||||
self.fontButton.setMaximumWidth(int(2.5*SHARED.theme.getTextWidth("...")))
|
||||
self.fontButton.clicked.connect(self._selectFont)
|
||||
self.mainForm.addRow(
|
||||
self.tr("Font family"),
|
||||
@@ -649,7 +649,7 @@ class GuiPreferencesEditor(QWidget):
|
||||
|
||||
# The Form
|
||||
self.mainForm = NConfigLayout()
|
||||
self.mainForm.setHelpTextStyle(CONFIG.theme.helpText)
|
||||
self.mainForm.setHelpTextStyle(SHARED.theme.helpText)
|
||||
self.setLayout(self.mainForm)
|
||||
|
||||
mW = CONFIG.pxInt(250)
|
||||
@@ -663,17 +663,13 @@ class GuiPreferencesEditor(QWidget):
|
||||
self.spellLanguage.setMaximumWidth(mW)
|
||||
|
||||
langAvail = self.mainGui.docEditor.spEnchant.listDictionaries()
|
||||
if CONFIG.hasEnchant:
|
||||
if langAvail:
|
||||
for spTag, spProv in langAvail:
|
||||
qLocal = QLocale(spTag)
|
||||
spLang = qLocal.nativeLanguageName().title()
|
||||
self.spellLanguage.addItem("%s [%s]" % (spLang, spProv), spTag)
|
||||
else:
|
||||
self.spellLanguage.addItem(self.tr("None"), "")
|
||||
self.spellLanguage.setEnabled(False)
|
||||
if CONFIG.hasEnchant and langAvail:
|
||||
for spTag, spProv in langAvail:
|
||||
qLocal = QLocale(spTag)
|
||||
spLang = qLocal.nativeLanguageName().title()
|
||||
self.spellLanguage.addItem("%s [%s]" % (spLang, spProv), spTag)
|
||||
else:
|
||||
self.spellLanguage.addItem(self.tr("Not installed"), "")
|
||||
self.spellLanguage.addItem(self.tr("None"), "")
|
||||
self.spellLanguage.setEnabled(False)
|
||||
|
||||
spellIdx = self.spellLanguage.findData(CONFIG.spellLanguage)
|
||||
@@ -819,7 +815,7 @@ class GuiPreferencesSyntax(QWidget):
|
||||
|
||||
# The Form
|
||||
self.mainForm = NConfigLayout()
|
||||
self.mainForm.setHelpTextStyle(CONFIG.theme.helpText)
|
||||
self.mainForm.setHelpTextStyle(SHARED.theme.helpText)
|
||||
self.setLayout(self.mainForm)
|
||||
|
||||
# Quotes & Dialogue
|
||||
@@ -921,7 +917,7 @@ class GuiPreferencesAutomation(QWidget):
|
||||
|
||||
# The Form
|
||||
self.mainForm = NConfigLayout()
|
||||
self.mainForm.setHelpTextStyle(CONFIG.theme.helpText)
|
||||
self.mainForm.setHelpTextStyle(SHARED.theme.helpText)
|
||||
self.setLayout(self.mainForm)
|
||||
|
||||
# Automatic Features
|
||||
@@ -1072,7 +1068,7 @@ class GuiPreferencesQuotes(QWidget):
|
||||
|
||||
# The Form
|
||||
self.mainForm = NConfigLayout()
|
||||
self.mainForm.setHelpTextStyle(CONFIG.theme.helpText)
|
||||
self.mainForm.setHelpTextStyle(SHARED.theme.helpText)
|
||||
self.setLayout(self.mainForm)
|
||||
|
||||
# Quotation Style
|
||||
@@ -1080,7 +1076,7 @@ class GuiPreferencesQuotes(QWidget):
|
||||
self.mainForm.addGroupLabel(self.tr("Quotation Style"))
|
||||
|
||||
qWidth = CONFIG.pxInt(40)
|
||||
bWidth = int(2.5*CONFIG.theme.getTextWidth("..."))
|
||||
bWidth = int(2.5*SHARED.theme.getTextWidth("..."))
|
||||
self.quoteSym = {}
|
||||
|
||||
# Single Quote Style
|
||||
|
||||
@@ -33,7 +33,7 @@ from PyQt5.QtWidgets import (
|
||||
QLineEdit, QSpinBox, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget
|
||||
)
|
||||
|
||||
from novelwriter import CONFIG
|
||||
from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.common import formatTime, numberToRoman
|
||||
from novelwriter.constants import nwUnicode
|
||||
from novelwriter.extensions.switch import NSwitch
|
||||
@@ -45,19 +45,17 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class GuiProjectDetails(NPagedDialog):
|
||||
|
||||
def __init__(self, mainGui):
|
||||
super().__init__(parent=mainGui)
|
||||
def __init__(self, parent):
|
||||
super().__init__(parent=parent)
|
||||
|
||||
logger.debug("Create: GuiProjectDetails")
|
||||
self.setObjectName("GuiProjectDetails")
|
||||
|
||||
self.mainGui = mainGui
|
||||
|
||||
self.setWindowTitle(self.tr("Project Details"))
|
||||
|
||||
wW = CONFIG.pxInt(600)
|
||||
wH = CONFIG.pxInt(400)
|
||||
pOptions = self.mainGui.project.options
|
||||
pOptions = SHARED.project.options
|
||||
|
||||
self.setMinimumWidth(wW)
|
||||
self.setMinimumHeight(wH)
|
||||
@@ -66,8 +64,8 @@ class GuiProjectDetails(NPagedDialog):
|
||||
CONFIG.pxInt(pOptions.getInt("GuiProjectDetails", "winHeight", wH))
|
||||
)
|
||||
|
||||
self.tabMain = GuiProjectDetailsMain(self.mainGui)
|
||||
self.tabContents = GuiProjectDetailsContents(self.mainGui)
|
||||
self.tabMain = GuiProjectDetailsMain(self)
|
||||
self.tabContents = GuiProjectDetailsContents(self)
|
||||
|
||||
self.addTab(self.tabMain, self.tr("Overview"))
|
||||
self.addTab(self.tabContents, self.tr("Contents"))
|
||||
@@ -124,7 +122,7 @@ class GuiProjectDetails(NPagedDialog):
|
||||
countFrom = self.tabContents.poValue.value()
|
||||
clearDouble = self.tabContents.dblValue.isChecked()
|
||||
|
||||
pOptions = self.mainGui.project.options
|
||||
pOptions = SHARED.project.options
|
||||
pOptions.setValue("GuiProjectDetails", "winWidth", winWidth)
|
||||
pOptions.setValue("GuiProjectDetails", "winHeight", winHeight)
|
||||
pOptions.setValue("GuiProjectDetails", "widthCol0", widthCol0)
|
||||
@@ -143,13 +141,11 @@ class GuiProjectDetails(NPagedDialog):
|
||||
|
||||
class GuiProjectDetailsMain(QWidget):
|
||||
|
||||
def __init__(self, mainGui):
|
||||
super().__init__(parent=mainGui)
|
||||
def __init__(self, parent):
|
||||
super().__init__(parent=parent)
|
||||
|
||||
self.mainGui = mainGui
|
||||
|
||||
fPx = CONFIG.theme.fontPixelSize
|
||||
fPt = CONFIG.theme.fontPointSize
|
||||
fPx = SHARED.theme.fontPixelSize
|
||||
fPt = SHARED.theme.fontPointSize
|
||||
vPx = CONFIG.pxInt(4)
|
||||
hPx = CONFIG.pxInt(12)
|
||||
|
||||
@@ -241,14 +237,13 @@ class GuiProjectDetailsMain(QWidget):
|
||||
|
||||
return
|
||||
|
||||
def updateValues(self):
|
||||
"""Set all the values.
|
||||
"""
|
||||
project = self.mainGui.project
|
||||
def updateValues(self) -> None:
|
||||
"""Set all the values."""
|
||||
project = SHARED.project
|
||||
pIndex = project.index
|
||||
hCounts = pIndex.getNovelTitleCounts()
|
||||
nwCount = pIndex.getNovelWordCount()
|
||||
edTime = project.getCurrentEditTime()
|
||||
edTime = project.currentEditTime
|
||||
|
||||
self.bookTitle.setText(project.data.title or project.data.name)
|
||||
self.projName.setText(self.tr("Project: {0}").format(project.data.name))
|
||||
@@ -275,26 +270,24 @@ class GuiProjectDetailsContents(QWidget):
|
||||
C_PAGE = 3
|
||||
C_PROG = 4
|
||||
|
||||
def __init__(self, mainGui):
|
||||
super().__init__(parent=mainGui)
|
||||
|
||||
self.mainGui = mainGui
|
||||
def __init__(self, parent):
|
||||
super().__init__(parent=parent)
|
||||
|
||||
# Internal
|
||||
self._theToC = []
|
||||
self._currentRoot = None
|
||||
|
||||
iPx = CONFIG.theme.baseIconSize
|
||||
iPx = SHARED.theme.baseIconSize
|
||||
hPx = CONFIG.pxInt(12)
|
||||
vPx = CONFIG.pxInt(4)
|
||||
pOptions = self.mainGui.project.options
|
||||
pOptions = SHARED.project.options
|
||||
|
||||
# Header
|
||||
# ======
|
||||
|
||||
self.tocLabel = QLabel("<b>%s</b>" % self.tr("Table of Contents"))
|
||||
|
||||
self.novelValue = NovelSelector(self, self.mainGui)
|
||||
self.novelValue = NovelSelector(self)
|
||||
self.novelValue.setMinimumWidth(CONFIG.pxInt(200))
|
||||
self.novelValue.novelSelectionChanged.connect(self._novelValueChanged)
|
||||
|
||||
@@ -320,10 +313,11 @@ class GuiProjectDetailsContents(QWidget):
|
||||
])
|
||||
|
||||
treeHeadItem = self.tocTree.headerItem()
|
||||
treeHeadItem.setTextAlignment(self.C_WORDS, Qt.AlignRight)
|
||||
treeHeadItem.setTextAlignment(self.C_PAGES, Qt.AlignRight)
|
||||
treeHeadItem.setTextAlignment(self.C_PAGE, Qt.AlignRight)
|
||||
treeHeadItem.setTextAlignment(self.C_PROG, Qt.AlignRight)
|
||||
if treeHeadItem:
|
||||
treeHeadItem.setTextAlignment(self.C_WORDS, Qt.AlignRight)
|
||||
treeHeadItem.setTextAlignment(self.C_PAGES, Qt.AlignRight)
|
||||
treeHeadItem.setTextAlignment(self.C_PAGE, Qt.AlignRight)
|
||||
treeHeadItem.setTextAlignment(self.C_PROG, Qt.AlignRight)
|
||||
|
||||
treeHeader = self.tocTree.header()
|
||||
treeHeader.setStretchLastSection(True)
|
||||
@@ -347,7 +341,7 @@ class GuiProjectDetailsContents(QWidget):
|
||||
|
||||
wordsPerPage = pOptions.getInt("GuiProjectDetails", "wordsPerPage", 350)
|
||||
countFrom = pOptions.getInt("GuiProjectDetails", "countFrom", 1)
|
||||
clearDouble = pOptions.getInt("GuiProjectDetails", "clearDouble", True)
|
||||
clearDouble = pOptions.getBool("GuiProjectDetails", "clearDouble", True)
|
||||
|
||||
wordsHelp = (
|
||||
self.tr("Typical word count for a 5 by 8 inch book page with 11 pt font is 350.")
|
||||
@@ -443,7 +437,7 @@ class GuiProjectDetailsContents(QWidget):
|
||||
"""Extract the information from the project index.
|
||||
"""
|
||||
logger.debug("Populating ToC from handle '%s'", rootHandle)
|
||||
self._theToC = self.mainGui.project.index.getTableOfContents(rootHandle, 2)
|
||||
self._theToC = SHARED.project.index.getTableOfContents(rootHandle, 2)
|
||||
self._theToC.append(("", 0, self.tr("END"), 0))
|
||||
return
|
||||
|
||||
@@ -496,7 +490,7 @@ class GuiProjectDetailsContents(QWidget):
|
||||
progPage = f"{cPage:n}"
|
||||
progText = f"{pgProg:.1f}{nwUnicode.U_THSP}%"
|
||||
|
||||
hDec = CONFIG.theme.getHeaderDecoration(tLevel)
|
||||
hDec = SHARED.theme.getHeaderDecoration(tLevel)
|
||||
if tTitle.strip() == "":
|
||||
tTitle = self.tr("Untitled")
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
|
||||
@@ -36,10 +37,13 @@ from PyQt5.QtWidgets import (
|
||||
QFileDialog, QLineEdit
|
||||
)
|
||||
|
||||
from novelwriter import CONFIG
|
||||
from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.common import formatInt
|
||||
from novelwriter.constants import nwFiles
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from novelwriter.guimain import GuiMain
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -55,19 +59,18 @@ class GuiProjectLoad(QDialog):
|
||||
|
||||
D_PATH = Qt.ItemDataRole.UserRole
|
||||
|
||||
def __init__(self, mainGui):
|
||||
def __init__(self, mainGui: GuiMain) -> None:
|
||||
super().__init__(parent=mainGui)
|
||||
|
||||
logger.debug("Create: GuiProjectLoad")
|
||||
self.setObjectName("GuiProjectLoad")
|
||||
|
||||
self.mainGui = mainGui
|
||||
self.openState = self.NONE_STATE
|
||||
self.openPath = None
|
||||
|
||||
sPx = CONFIG.pxInt(16)
|
||||
nPx = CONFIG.pxInt(96)
|
||||
iPx = CONFIG.theme.baseIconSize
|
||||
iPx = SHARED.theme.baseIconSize
|
||||
|
||||
self.outerBox = QVBoxLayout()
|
||||
self.innerBox = QHBoxLayout()
|
||||
@@ -79,7 +82,7 @@ class GuiProjectLoad(QDialog):
|
||||
self.setMinimumHeight(CONFIG.pxInt(400))
|
||||
|
||||
self.nwIcon = QLabel()
|
||||
self.nwIcon.setPixmap(CONFIG.theme.getPixmap("novelwriter", (nPx, nPx)))
|
||||
self.nwIcon.setPixmap(SHARED.theme.getPixmap("novelwriter", (nPx, nPx)))
|
||||
self.innerBox.addWidget(self.nwIcon, 0, Qt.AlignTop)
|
||||
|
||||
self.projectForm = QGridLayout()
|
||||
@@ -110,7 +113,7 @@ class GuiProjectLoad(QDialog):
|
||||
self.selPath.setReadOnly(True)
|
||||
|
||||
self.browseButton = QPushButton("...")
|
||||
self.browseButton.setMaximumWidth(int(2.5*CONFIG.theme.getTextWidth("...")))
|
||||
self.browseButton.setMaximumWidth(int(2.5*SHARED.theme.getTextWidth("...")))
|
||||
self.browseButton.clicked.connect(self._doBrowse)
|
||||
|
||||
self.projectForm.addWidget(self.lblRecent, 0, 0, 1, 3)
|
||||
@@ -225,7 +228,7 @@ class GuiProjectLoad(QDialog):
|
||||
selList = self.listBox.selectedItems()
|
||||
if selList:
|
||||
projName = selList[0].text(self.C_NAME)
|
||||
msgYes = self.mainGui.askQuestion(self.tr(
|
||||
msgYes = SHARED.question(self.tr(
|
||||
"Remove '{0}' from the recent projects list? "
|
||||
"The project files will not be deleted."
|
||||
).format(projName))
|
||||
@@ -268,7 +271,7 @@ class GuiProjectLoad(QDialog):
|
||||
self.listBox.clear()
|
||||
dataList = CONFIG.recentProjects.listEntries()
|
||||
sortList = sorted(dataList, key=lambda x: x[3], reverse=True)
|
||||
nwxIcon = CONFIG.theme.getIcon("proj_nwx")
|
||||
nwxIcon = SHARED.theme.getIcon("proj_nwx")
|
||||
for path, title, words, time in sortList:
|
||||
newItem = QTreeWidgetItem([""]*4)
|
||||
newItem.setIcon(self.C_NAME, nwxIcon)
|
||||
@@ -279,7 +282,7 @@ class GuiProjectLoad(QDialog):
|
||||
newItem.setTextAlignment(self.C_NAME, Qt.AlignLeft | Qt.AlignVCenter)
|
||||
newItem.setTextAlignment(self.C_COUNT, Qt.AlignRight | Qt.AlignVCenter)
|
||||
newItem.setTextAlignment(self.C_TIME, Qt.AlignRight | Qt.AlignVCenter)
|
||||
newItem.setFont(self.C_TIME, CONFIG.theme.guiFontFixed)
|
||||
newItem.setFont(self.C_TIME, SHARED.theme.guiFontFixed)
|
||||
self.listBox.addTopLevelItem(newItem)
|
||||
|
||||
self.listBox.setCurrentItem(self.listBox.topLevelItem(0))
|
||||
|
||||
@@ -34,8 +34,7 @@ from PyQt5.QtWidgets import (
|
||||
QPushButton, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget
|
||||
)
|
||||
|
||||
from novelwriter import CONFIG
|
||||
from novelwriter.enum import nwAlert
|
||||
from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.common import simplified
|
||||
from novelwriter.extensions.switch import NSwitch
|
||||
from novelwriter.extensions.pageddialog import NPagedDialog
|
||||
@@ -61,12 +60,12 @@ class GuiProjectSettings(NPagedDialog):
|
||||
self.setObjectName("GuiProjectSettings")
|
||||
|
||||
self.mainGui = mainGui
|
||||
self.mainGui.project.countStatus()
|
||||
SHARED.project.countStatus()
|
||||
self.setWindowTitle(self.tr("Project Settings"))
|
||||
|
||||
wW = CONFIG.pxInt(570)
|
||||
wH = CONFIG.pxInt(375)
|
||||
pOptions = self.mainGui.project.options
|
||||
pOptions = SHARED.project.options
|
||||
|
||||
self.setMinimumWidth(wW)
|
||||
self.setMinimumHeight(wH)
|
||||
@@ -115,7 +114,7 @@ class GuiProjectSettings(NPagedDialog):
|
||||
def _doSave(self):
|
||||
"""Save settings and close dialog.
|
||||
"""
|
||||
project = self.mainGui.project
|
||||
project = SHARED.project
|
||||
projName = self.tabMain.editName.text()
|
||||
bookTitle = self.tabMain.editTitle.text()
|
||||
bookAuthor = self.tabMain.editAuthor.text()
|
||||
@@ -183,7 +182,7 @@ class GuiProjectSettings(NPagedDialog):
|
||||
statusColW = CONFIG.rpxInt(self.tabStatus.listBox.columnWidth(0))
|
||||
importColW = CONFIG.rpxInt(self.tabImport.listBox.columnWidth(0))
|
||||
|
||||
pOptions = self.mainGui.project.options
|
||||
pOptions = SHARED.project.options
|
||||
pOptions.setValue("GuiProjectSettings", "winWidth", winWidth)
|
||||
pOptions.setValue("GuiProjectSettings", "winHeight", winHeight)
|
||||
pOptions.setValue("GuiProjectSettings", "replaceColW", replaceColW)
|
||||
@@ -204,13 +203,13 @@ class GuiProjectEditMain(QWidget):
|
||||
|
||||
# The Form
|
||||
self.mainForm = NConfigLayout()
|
||||
self.mainForm.setHelpTextStyle(CONFIG.theme.helpText)
|
||||
self.mainForm.setHelpTextStyle(SHARED.theme.helpText)
|
||||
self.setLayout(self.mainForm)
|
||||
|
||||
self.mainForm.addGroupLabel(self.tr("Project Settings"))
|
||||
|
||||
xW = CONFIG.pxInt(250)
|
||||
pData = self.mainGui.project.data
|
||||
pData = SHARED.project.data
|
||||
|
||||
self.editName = QLineEdit()
|
||||
self.editName.setMaxLength(200)
|
||||
@@ -288,26 +287,24 @@ class GuiProjectEditStatus(QWidget):
|
||||
def __init__(self, projGui, isStatus):
|
||||
super().__init__(parent=projGui)
|
||||
|
||||
self.mainGui = projGui.mainGui
|
||||
|
||||
if isStatus:
|
||||
self.theStatus = self.mainGui.project.data.itemStatus
|
||||
self.theStatus = SHARED.project.data.itemStatus
|
||||
pageLabel = self.tr("Novel File Status Levels")
|
||||
colSetting = "statusColW"
|
||||
else:
|
||||
self.theStatus = self.mainGui.project.data.itemImport
|
||||
self.theStatus = SHARED.project.data.itemImport
|
||||
pageLabel = self.tr("Note File Importance Levels")
|
||||
colSetting = "importColW"
|
||||
|
||||
wCol0 = CONFIG.pxInt(
|
||||
self.mainGui.project.options.getInt("GuiProjectSettings", colSetting, 130)
|
||||
SHARED.project.options.getInt("GuiProjectSettings", colSetting, 130)
|
||||
)
|
||||
|
||||
self.colDeleted = []
|
||||
self.colChanged = False
|
||||
self.selColour = QColor(100, 100, 100)
|
||||
|
||||
self.iPx = CONFIG.theme.baseIconSize
|
||||
self.iPx = SHARED.theme.baseIconSize
|
||||
|
||||
# The List
|
||||
# ========
|
||||
@@ -326,16 +323,16 @@ class GuiProjectEditStatus(QWidget):
|
||||
# List Controls
|
||||
# =============
|
||||
|
||||
self.addButton = QPushButton(CONFIG.theme.getIcon("add"), "")
|
||||
self.addButton = QPushButton(SHARED.theme.getIcon("add"), "")
|
||||
self.addButton.clicked.connect(self._newItem)
|
||||
|
||||
self.delButton = QPushButton(CONFIG.theme.getIcon("remove"), "")
|
||||
self.delButton = QPushButton(SHARED.theme.getIcon("remove"), "")
|
||||
self.delButton.clicked.connect(self._delItem)
|
||||
|
||||
self.upButton = QPushButton(CONFIG.theme.getIcon("up"), "")
|
||||
self.upButton = QPushButton(SHARED.theme.getIcon("up"), "")
|
||||
self.upButton.clicked.connect(lambda: self._moveItem(-1))
|
||||
|
||||
self.dnButton = QPushButton(CONFIG.theme.getIcon("down"), "")
|
||||
self.dnButton = QPushButton(SHARED.theme.getIcon("down"), "")
|
||||
self.dnButton.clicked.connect(lambda: self._moveItem(1))
|
||||
|
||||
# Edit Form
|
||||
@@ -441,9 +438,7 @@ class GuiProjectEditStatus(QWidget):
|
||||
if isinstance(selItem, QTreeWidgetItem):
|
||||
iRow = self.listBox.indexOfTopLevelItem(selItem)
|
||||
if selItem.data(self.COL_LABEL, self.NUM_ROLE) > 0:
|
||||
self.mainGui.makeAlert(self.tr(
|
||||
"Cannot delete a status item that is in use."
|
||||
), level=nwAlert.ERROR)
|
||||
SHARED.error(self.tr("Cannot delete a status item that is in use."))
|
||||
else:
|
||||
self.listBox.takeTopLevelItem(iRow)
|
||||
self.colDeleted.append(selItem.data(self.COL_LABEL, self.KEY_ROLE))
|
||||
@@ -574,11 +569,10 @@ class GuiProjectEditReplace(QWidget):
|
||||
def __init__(self, projGui):
|
||||
super().__init__(parent=projGui)
|
||||
|
||||
self.mainGui = projGui.mainGui
|
||||
self.arChanged = False
|
||||
|
||||
wCol0 = CONFIG.pxInt(
|
||||
self.mainGui.project.options.getInt("GuiProjectSettings", "replaceColW", 130)
|
||||
SHARED.project.options.getInt("GuiProjectSettings", "replaceColW", 130)
|
||||
)
|
||||
pageLabel = self.tr("Text Replace List for Preview and Export")
|
||||
|
||||
@@ -594,7 +588,7 @@ class GuiProjectEditReplace(QWidget):
|
||||
self.listBox.setColumnWidth(self.COL_KEY, wCol0)
|
||||
self.listBox.setIndentation(0)
|
||||
|
||||
for aKey, aVal in self.mainGui.project.data.autoReplace.items():
|
||||
for aKey, aVal in SHARED.project.data.autoReplace.items():
|
||||
newItem = QTreeWidgetItem(["<%s>" % aKey, aVal])
|
||||
self.listBox.addTopLevelItem(newItem)
|
||||
|
||||
@@ -604,10 +598,10 @@ class GuiProjectEditReplace(QWidget):
|
||||
# List Controls
|
||||
# =============
|
||||
|
||||
self.addButton = QPushButton(CONFIG.theme.getIcon("add"), "")
|
||||
self.addButton = QPushButton(SHARED.theme.getIcon("add"), "")
|
||||
self.addButton.clicked.connect(self._addEntry)
|
||||
|
||||
self.delButton = QPushButton(CONFIG.theme.getIcon("remove"), "")
|
||||
self.delButton = QPushButton(SHARED.theme.getIcon("remove"), "")
|
||||
self.delButton.clicked.connect(self._delEntry)
|
||||
|
||||
# Edit Form
|
||||
|
||||
@@ -35,7 +35,7 @@ from PyQt5.QtWidgets import (
|
||||
qApp, QDialog, QHBoxLayout, QVBoxLayout, QDialogButtonBox, QLabel
|
||||
)
|
||||
|
||||
from novelwriter import CONFIG, __version__, __date__
|
||||
from novelwriter import CONFIG, SHARED, __version__, __date__
|
||||
from novelwriter.common import logException
|
||||
from novelwriter.constants import nwConst
|
||||
|
||||
@@ -58,7 +58,7 @@ class GuiUpdates(QDialog):
|
||||
|
||||
# Left Box
|
||||
self.nwIcon = QLabel()
|
||||
self.nwIcon.setPixmap(CONFIG.theme.getPixmap("novelwriter", (nPx, nPx)))
|
||||
self.nwIcon.setPixmap(SHARED.theme.getPixmap("novelwriter", (nPx, nPx)))
|
||||
|
||||
self.leftBox = QVBoxLayout()
|
||||
self.leftBox.addWidget(self.nwIcon)
|
||||
|
||||
@@ -33,8 +33,7 @@ from PyQt5.QtWidgets import (
|
||||
QLineEdit, QListWidget, QListWidgetItem, QPushButton, QVBoxLayout
|
||||
)
|
||||
|
||||
from novelwriter import CONFIG
|
||||
from novelwriter.enum import nwAlert
|
||||
from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.core.spellcheck import UserDictionary
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
@@ -52,12 +51,10 @@ class GuiWordList(QDialog):
|
||||
self.setObjectName("GuiWordList")
|
||||
self.setWindowTitle(self.tr("Project Word List"))
|
||||
|
||||
self.mainGui = mainGui
|
||||
|
||||
mS = CONFIG.pxInt(250)
|
||||
wW = CONFIG.pxInt(320)
|
||||
wH = CONFIG.pxInt(340)
|
||||
pOptions = self.mainGui.project.options
|
||||
pOptions = SHARED.project.options
|
||||
|
||||
self.setMinimumWidth(mS)
|
||||
self.setMinimumHeight(mS)
|
||||
@@ -77,10 +74,10 @@ class GuiWordList(QDialog):
|
||||
|
||||
self.newEntry = QLineEdit()
|
||||
|
||||
self.addButton = QPushButton(CONFIG.theme.getIcon("add"), "")
|
||||
self.addButton = QPushButton(SHARED.theme.getIcon("add"), "")
|
||||
self.addButton.clicked.connect(self._doAdd)
|
||||
|
||||
self.delButton = QPushButton(CONFIG.theme.getIcon("remove"), "")
|
||||
self.delButton = QPushButton(SHARED.theme.getIcon("remove"), "")
|
||||
self.delButton.clicked.connect(self._doDelete)
|
||||
|
||||
self.editBox = QHBoxLayout()
|
||||
@@ -123,15 +120,13 @@ class GuiWordList(QDialog):
|
||||
"""Add a new word to the word list."""
|
||||
word = self.newEntry.text().strip()
|
||||
if word == "":
|
||||
self.mainGui.makeAlert(self.tr(
|
||||
"Cannot add a blank word."
|
||||
), level=nwAlert.ERROR)
|
||||
SHARED.error(self.tr("Cannot add a blank word."))
|
||||
return
|
||||
|
||||
if self.listBox.findItems(word, Qt.MatchExactly):
|
||||
self.mainGui.makeAlert(self.tr(
|
||||
SHARED.error(self.tr(
|
||||
"The word '{0}' is already in the word list."
|
||||
).format(word), level=nwAlert.ERROR)
|
||||
).format(word))
|
||||
return
|
||||
|
||||
self.listBox.addItem(word)
|
||||
@@ -149,7 +144,7 @@ class GuiWordList(QDialog):
|
||||
def _doSave(self):
|
||||
"""Save the new word list and close."""
|
||||
self._saveGuiSettings()
|
||||
userDict = UserDictionary(self.mainGui.project)
|
||||
userDict = UserDictionary(SHARED.project)
|
||||
for i in range(self.listBox.count()):
|
||||
item = self.listBox.item(i)
|
||||
if isinstance(item, QListWidgetItem):
|
||||
@@ -172,7 +167,7 @@ class GuiWordList(QDialog):
|
||||
|
||||
def _loadWordList(self):
|
||||
"""Load the project's word list, if it exists."""
|
||||
userDict = UserDictionary(self.mainGui.project)
|
||||
userDict = UserDictionary(SHARED.project)
|
||||
userDict.load()
|
||||
self.listBox.clear()
|
||||
for word in userDict:
|
||||
@@ -185,7 +180,7 @@ class GuiWordList(QDialog):
|
||||
winWidth = CONFIG.rpxInt(self.width())
|
||||
winHeight = CONFIG.rpxInt(self.height())
|
||||
|
||||
pOptions = self.mainGui.project.options
|
||||
pOptions = SHARED.project.options
|
||||
pOptions.setValue("GuiWordList", "winWidth", winWidth)
|
||||
pOptions.setValue("GuiWordList", "winHeight", winHeight)
|
||||
|
||||
|
||||
@@ -119,16 +119,6 @@ class nwDocInsert(Enum):
|
||||
# END Enum nwDocInsert
|
||||
|
||||
|
||||
class nwAlert(Enum):
|
||||
|
||||
INFO = 0
|
||||
WARN = 1
|
||||
ERROR = 2
|
||||
ASK = 3
|
||||
|
||||
# END Enum nwAlert
|
||||
|
||||
|
||||
class nwView(Enum):
|
||||
|
||||
EDITOR = 0
|
||||
|
||||
@@ -25,18 +25,13 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot
|
||||
from PyQt5.QtWidgets import QComboBox, QWidget
|
||||
|
||||
from novelwriter import CONFIG
|
||||
from novelwriter import SHARED
|
||||
from novelwriter.enum import nwItemClass
|
||||
from novelwriter.constants import nwLabels
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from novelwriter.guimain import GuiMain
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -44,9 +39,8 @@ class NovelSelector(QComboBox):
|
||||
|
||||
novelSelectionChanged = pyqtSignal(str)
|
||||
|
||||
def __init__(self, parent: QWidget, mainGui: GuiMain) -> None:
|
||||
def __init__(self, parent: QWidget) -> None:
|
||||
super().__init__(parent=parent)
|
||||
self._mainGui = mainGui
|
||||
self._blockSignal = False
|
||||
self._firstHandle = None
|
||||
self.currentIndexChanged.connect(self._indexChanged)
|
||||
@@ -86,9 +80,9 @@ class NovelSelector(QComboBox):
|
||||
self._firstHandle = None
|
||||
self.clear()
|
||||
|
||||
icon = CONFIG.theme.getIcon(nwLabels.CLASS_ICON[nwItemClass.NOVEL])
|
||||
icon = SHARED.theme.getIcon(nwLabels.CLASS_ICON[nwItemClass.NOVEL])
|
||||
handle = self.currentData()
|
||||
for tHandle, nwItem in self._mainGui.project.tree.iterRoots(nwItemClass.NOVEL):
|
||||
for tHandle, nwItem in SHARED.project.tree.iterRoots(nwItemClass.NOVEL):
|
||||
if prefix:
|
||||
name = prefix.format(nwItem.itemName)
|
||||
self.addItem(name, tHandle)
|
||||
|
||||
+104
-108
@@ -49,8 +49,8 @@ from PyQt5.QtWidgets import (
|
||||
QPushButton, QShortcut, QTextEdit, QToolBar, QToolButton, QWidget
|
||||
)
|
||||
|
||||
from novelwriter import CONFIG
|
||||
from novelwriter.enum import nwAlert, nwDocAction, nwDocInsert, nwDocMode, nwItemClass
|
||||
from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.enum import nwDocAction, nwDocInsert, nwDocMode, nwItemClass
|
||||
from novelwriter.common import minmax, transferCase
|
||||
from novelwriter.constants import nwConst, nwKeyWords, nwUnicode
|
||||
from novelwriter.core.index import countWords
|
||||
@@ -71,9 +71,10 @@ class GuiDocEditor(QTextEdit):
|
||||
)
|
||||
|
||||
# Custom Signals
|
||||
spellDictionaryChanged = pyqtSignal(str, str)
|
||||
docEditedStatusChanged = pyqtSignal(bool)
|
||||
statusMessage = pyqtSignal(str)
|
||||
docCountsChanged = pyqtSignal(str, int, int, int)
|
||||
editedStatusChanged = pyqtSignal(bool)
|
||||
spellDictionaryChanged = pyqtSignal(str, str)
|
||||
loadDocumentTagRequest = pyqtSignal(str, Enum)
|
||||
novelStructureChanged = pyqtSignal()
|
||||
novelItemMetaChanged = pyqtSignal(str)
|
||||
@@ -101,7 +102,7 @@ class GuiDocEditor(QTextEdit):
|
||||
self._wordCount = 0 # Word count
|
||||
self._paraCount = 0 # Paragraph count
|
||||
self._lastEdit = 0 # Time stamp of last edit
|
||||
self._lastActive = 0 # Time stamp of last activity
|
||||
self._lastActive = 0.0 # Time stamp of last activity
|
||||
self._lastFind = None # Position of the last found search word
|
||||
self._bigDoc = False # Flag for very large document size
|
||||
self._doReplace = False # Switch to temporarily disable auto-replace
|
||||
@@ -132,8 +133,8 @@ class GuiDocEditor(QTextEdit):
|
||||
self.docSearch = GuiDocEditSearch(self)
|
||||
|
||||
# Syntax
|
||||
self.spEnchant = NWSpellEnchant(self.mainGui.project)
|
||||
self.highLight = GuiDocHighlighter(qDoc, self.mainGui, self.spEnchant)
|
||||
self.spEnchant = NWSpellEnchant(SHARED.project)
|
||||
self.highLight = GuiDocHighlighter(qDoc, self.spEnchant)
|
||||
|
||||
# Context Menu
|
||||
self.setContextMenuPolicy(Qt.CustomContextMenu)
|
||||
@@ -188,6 +189,34 @@ class GuiDocEditor(QTextEdit):
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Properties
|
||||
##
|
||||
|
||||
@property
|
||||
def docChanged(self) -> bool:
|
||||
"""Return the changed status of the document."""
|
||||
return self._docChanged
|
||||
|
||||
@property
|
||||
def docHandle(self) -> str | None:
|
||||
"""Return the handle of the currently open document."""
|
||||
return self._docHandle
|
||||
|
||||
@property
|
||||
def lastActive(self) -> float:
|
||||
"""Return the last active timestamp for the user."""
|
||||
return self._lastActive
|
||||
|
||||
@property
|
||||
def isEmpty(self) -> bool:
|
||||
"""Check if the current document is empty."""
|
||||
return self.document().isEmpty()
|
||||
|
||||
##
|
||||
# Methods
|
||||
##
|
||||
|
||||
def clearEditor(self):
|
||||
"""Clear the current document and reset all document-related
|
||||
flags and counters.
|
||||
@@ -203,7 +232,7 @@ class GuiDocEditor(QTextEdit):
|
||||
self._wordCount = 0
|
||||
self._paraCount = 0
|
||||
self._lastEdit = 0
|
||||
self._lastActive = 0
|
||||
self._lastActive = 0.0
|
||||
self._lastFind = None
|
||||
self._bigDoc = False
|
||||
self._doReplace = False
|
||||
@@ -227,14 +256,14 @@ class GuiDocEditor(QTextEdit):
|
||||
"""Update the syntax highlighting theme.
|
||||
"""
|
||||
mainPalette = self.palette()
|
||||
mainPalette.setColor(QPalette.Window, QColor(*CONFIG.theme.colBack))
|
||||
mainPalette.setColor(QPalette.Base, QColor(*CONFIG.theme.colBack))
|
||||
mainPalette.setColor(QPalette.Text, QColor(*CONFIG.theme.colText))
|
||||
mainPalette.setColor(QPalette.Window, QColor(*SHARED.theme.colBack))
|
||||
mainPalette.setColor(QPalette.Base, QColor(*SHARED.theme.colBack))
|
||||
mainPalette.setColor(QPalette.Text, QColor(*SHARED.theme.colText))
|
||||
self.setPalette(mainPalette)
|
||||
|
||||
docPalette = self.viewport().palette()
|
||||
docPalette.setColor(QPalette.Base, QColor(*CONFIG.theme.colBack))
|
||||
docPalette.setColor(QPalette.Text, QColor(*CONFIG.theme.colText))
|
||||
docPalette.setColor(QPalette.Base, QColor(*SHARED.theme.colBack))
|
||||
docPalette.setColor(QPalette.Text, QColor(*SHARED.theme.colText))
|
||||
self.viewport().setPalette(docPalette)
|
||||
|
||||
self.docHeader.matchColours()
|
||||
@@ -340,7 +369,7 @@ class GuiDocEditor(QTextEdit):
|
||||
document is new (empty string), we set up the editor for editing
|
||||
the file.
|
||||
"""
|
||||
self._nwDocument = self.mainGui.project.storage.getDocument(tHandle)
|
||||
self._nwDocument = SHARED.project.storage.getDocument(tHandle)
|
||||
self._nwItem = self._nwDocument.getCurrentItem()
|
||||
|
||||
theDoc = self._nwDocument.readDocument()
|
||||
@@ -351,14 +380,14 @@ class GuiDocEditor(QTextEdit):
|
||||
|
||||
docSize = len(theDoc)
|
||||
if docSize > nwConst.MAX_DOCSIZE:
|
||||
self.mainGui.makeAlert(self.tr(
|
||||
SHARED.error(self.tr(
|
||||
"The document you are trying to open is too big. "
|
||||
"The document size is {0} MB. "
|
||||
"The maximum size allowed is {1} MB."
|
||||
).format(
|
||||
f"{docSize/1.0e6:.2f}",
|
||||
f"{nwConst.MAX_DOCSIZE/1.0e6:.2f}"
|
||||
), level=nwAlert.ERROR)
|
||||
))
|
||||
self.clearEditor()
|
||||
return False
|
||||
|
||||
@@ -426,9 +455,7 @@ class GuiDocEditor(QTextEdit):
|
||||
|
||||
# Update the status bar
|
||||
if self._nwItem is not None:
|
||||
self.mainGui.setStatus(
|
||||
self.tr("Opened Document: {0}").format(self._nwItem.itemName)
|
||||
)
|
||||
self.statusMessage.emit(self.tr("Opened Document: {0}").format(self._nwItem.itemName))
|
||||
|
||||
return True
|
||||
|
||||
@@ -451,14 +478,14 @@ class GuiDocEditor(QTextEdit):
|
||||
"""
|
||||
docSize = len(theText)
|
||||
if docSize > nwConst.MAX_DOCSIZE:
|
||||
self.mainGui.makeAlert(self.tr(
|
||||
SHARED.error(self.tr(
|
||||
"The text you are trying to add is too big. "
|
||||
"The text size is {0} MB. "
|
||||
"The maximum size allowed is {1} MB."
|
||||
).format(
|
||||
f"{docSize/1.0e6:.2f}",
|
||||
f"{nwConst.MAX_DOCSIZE/1.0e6:.2f}"
|
||||
), level=nwAlert.ERROR)
|
||||
))
|
||||
return False
|
||||
|
||||
qApp.setOverrideCursor(QCursor(Qt.WaitCursor))
|
||||
@@ -497,7 +524,7 @@ class GuiDocEditor(QTextEdit):
|
||||
if not self._nwDocument.writeDocument(docText):
|
||||
saveOk = False
|
||||
if self._nwDocument._currHash != self._nwDocument._prevHash:
|
||||
msgYes = self.mainGui.askQuestion(self.tr(
|
||||
msgYes = SHARED.question(self.tr(
|
||||
"This document has been changed outside of novelWriter "
|
||||
"while it was open. Overwrite the file on disk?"
|
||||
))
|
||||
@@ -505,10 +532,9 @@ class GuiDocEditor(QTextEdit):
|
||||
saveOk = self._nwDocument.writeDocument(docText, forceWrite=True)
|
||||
|
||||
if not saveOk:
|
||||
self.mainGui.makeAlert(
|
||||
SHARED.error(
|
||||
self.tr("Could not save document."),
|
||||
info=self._nwDocument.getError(),
|
||||
level=nwAlert.ERROR
|
||||
info=self._nwDocument.getError()
|
||||
)
|
||||
|
||||
return False
|
||||
@@ -516,10 +542,10 @@ class GuiDocEditor(QTextEdit):
|
||||
self.setDocumentChanged(False)
|
||||
|
||||
oldHeader = self._nwItem.mainHeading
|
||||
oldCount = self.mainGui.project.index.getHandleHeaderCount(tHandle)
|
||||
self.mainGui.project.index.scanText(tHandle, docText)
|
||||
oldCount = SHARED.project.index.getHandleHeaderCount(tHandle)
|
||||
SHARED.project.index.scanText(tHandle, docText)
|
||||
newHeader = self._nwItem.mainHeading
|
||||
newCount = self.mainGui.project.index.getHandleHeaderCount(tHandle)
|
||||
newCount = SHARED.project.index.getHandleHeaderCount(tHandle)
|
||||
|
||||
if self._nwItem.itemClass == nwItemClass.NOVEL:
|
||||
if oldCount == newCount:
|
||||
@@ -534,9 +560,7 @@ class GuiDocEditor(QTextEdit):
|
||||
self.docFooter.updateInfo()
|
||||
|
||||
# Update the status bar
|
||||
self.mainGui.setStatus(
|
||||
self.tr("Saved Document: {0}").format(self._nwItem.itemName)
|
||||
)
|
||||
self.statusMessage.emit(self.tr("Saved Document: {0}").format(self._nwItem.itemName))
|
||||
|
||||
return True
|
||||
|
||||
@@ -580,31 +604,6 @@ class GuiDocEditor(QTextEdit):
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Properties
|
||||
##
|
||||
|
||||
def docChanged(self):
|
||||
"""Return the changed status of the document in the editor.
|
||||
"""
|
||||
return self._docChanged
|
||||
|
||||
def docHandle(self):
|
||||
"""Return the handle of the currently open document. Return
|
||||
None if no document is open.
|
||||
"""
|
||||
return self._docHandle
|
||||
|
||||
def lastActive(self):
|
||||
"""Return the last active timestamp for the user.
|
||||
"""
|
||||
return self._lastActive
|
||||
|
||||
def isEmpty(self):
|
||||
"""Wrapper function to check if the current document is empty.
|
||||
"""
|
||||
return self.document().isEmpty()
|
||||
|
||||
##
|
||||
# Getters
|
||||
##
|
||||
@@ -636,7 +635,7 @@ class GuiDocEditor(QTextEdit):
|
||||
document change signal.
|
||||
"""
|
||||
self._docChanged = bValue
|
||||
self.docEditedStatusChanged.emit(self._docChanged)
|
||||
self.editedStatusChanged.emit(self._docChanged)
|
||||
return self._docChanged
|
||||
|
||||
def setCursorPosition(self, position):
|
||||
@@ -698,10 +697,10 @@ class GuiDocEditor(QTextEdit):
|
||||
"""Set the spell checker dictionary language, and emit the
|
||||
dictionary changed signal.
|
||||
"""
|
||||
if self.mainGui.project.data.spellLang is None:
|
||||
if SHARED.project.data.spellLang is None:
|
||||
theLang = CONFIG.spellLanguage
|
||||
else:
|
||||
theLang = self.mainGui.project.data.spellLang
|
||||
theLang = SHARED.project.data.spellLang
|
||||
|
||||
self.spEnchant.setLanguage(theLang)
|
||||
_, theProvider = self.spEnchant.describeDict()
|
||||
@@ -723,7 +722,7 @@ class GuiDocEditor(QTextEdit):
|
||||
|
||||
if not CONFIG.hasEnchant:
|
||||
if theMode:
|
||||
self.mainGui.makeAlert(self.tr(
|
||||
SHARED.info(self.tr(
|
||||
"Spell checking requires the package PyEnchant. "
|
||||
"It does not appear to be installed."
|
||||
))
|
||||
@@ -734,7 +733,7 @@ class GuiDocEditor(QTextEdit):
|
||||
|
||||
self._spellCheck = theMode
|
||||
self.mainGui.mainMenu.setSpellCheck(theMode)
|
||||
self.mainGui.project.data.setSpellCheck(theMode)
|
||||
SHARED.project.data.setSpellCheck(theMode)
|
||||
self.highLight.setSpellCheck(theMode)
|
||||
if not self._bigDoc or theMode is False:
|
||||
# We don't run the spell checker automatically on big docs
|
||||
@@ -744,7 +743,7 @@ class GuiDocEditor(QTextEdit):
|
||||
|
||||
return True
|
||||
|
||||
def spellCheckDocument(self):
|
||||
def spellCheckDocument(self) -> None:
|
||||
"""Rerun the highlighter to update spell checking status of the
|
||||
currently loaded text. The fastest way to do this, at least as
|
||||
of Qt 5.13, is to clear the text and put it back. This clears
|
||||
@@ -760,9 +759,8 @@ class GuiDocEditor(QTextEdit):
|
||||
self.highLight.rehighlight()
|
||||
qApp.restoreOverrideCursor()
|
||||
logger.debug("Document highlighted in %.3f ms", 1000*(time() - start))
|
||||
self.mainGui.mainStatus.setStatus(self.tr("Spell check complete"))
|
||||
|
||||
return True
|
||||
self.statusMessage.emit(self.tr("Spell check complete"))
|
||||
return
|
||||
|
||||
##
|
||||
# General Class Methods
|
||||
@@ -868,7 +866,7 @@ class GuiDocEditor(QTextEdit):
|
||||
if self._nwDocument is None:
|
||||
logger.error("No document open")
|
||||
return False
|
||||
self.mainGui.makeAlert(
|
||||
SHARED.info(
|
||||
self.tr("The currently open file is saved in:"),
|
||||
info=self._nwDocument.getFileLocation()
|
||||
)
|
||||
@@ -1104,12 +1102,12 @@ class GuiDocEditor(QTextEdit):
|
||||
self._lastFind = None
|
||||
|
||||
if self.document().characterCount() > nwConst.MAX_DOCSIZE:
|
||||
self.mainGui.makeAlert(self.tr(
|
||||
SHARED.error(self.tr(
|
||||
"The document has grown too big and you cannot add more text to it. "
|
||||
"The maximum size of a single novelWriter document is {0} MB."
|
||||
).format(
|
||||
f"{nwConst.MAX_DOCSIZE/1.0e6:.2f}"
|
||||
), level=nwAlert.ERROR)
|
||||
))
|
||||
self.undo()
|
||||
return
|
||||
|
||||
@@ -1666,9 +1664,7 @@ class GuiDocEditor(QTextEdit):
|
||||
"""
|
||||
theCursor = self.textCursor()
|
||||
if not theCursor.hasSelection():
|
||||
self.mainGui.makeAlert(self.tr(
|
||||
"Please select some text before calling replace quotes."
|
||||
), level=nwAlert.ERROR)
|
||||
SHARED.error(self.tr("Please select some text before calling replace quotes."))
|
||||
return False
|
||||
|
||||
posS = theCursor.selectionStart()
|
||||
@@ -1916,7 +1912,7 @@ class GuiDocEditor(QTextEdit):
|
||||
|
||||
if theText.startswith("@"):
|
||||
|
||||
isGood, tBits, tPos = self.mainGui.project.index.scanThis(theText)
|
||||
isGood, tBits, tPos = SHARED.project.index.scanThis(theText)
|
||||
if not isGood:
|
||||
return False
|
||||
|
||||
@@ -2233,9 +2229,9 @@ class GuiDocEditSearch(QFrame):
|
||||
self.doMatchCap = CONFIG.searchMatchCap
|
||||
|
||||
mPx = CONFIG.pxInt(6)
|
||||
tPx = int(0.8*CONFIG.theme.fontPixelSize)
|
||||
self.boxFont = CONFIG.theme.guiFont
|
||||
self.boxFont.setPointSizeF(0.9*CONFIG.theme.fontPointSize)
|
||||
tPx = int(0.8*SHARED.theme.fontPixelSize)
|
||||
self.boxFont = SHARED.theme.guiFont
|
||||
self.boxFont.setPointSizeF(0.9*SHARED.theme.fontPointSize)
|
||||
|
||||
self.setContentsMargins(0, 0, 0, 0)
|
||||
self.setAutoFillBackground(True)
|
||||
@@ -2268,7 +2264,7 @@ class GuiDocEditSearch(QFrame):
|
||||
|
||||
self.resultLabel = QLabel("?/?")
|
||||
self.resultLabel.setFont(self.boxFont)
|
||||
self.resultLabel.setMinimumWidth(CONFIG.theme.getTextWidth("?/?", self.boxFont))
|
||||
self.resultLabel.setMinimumWidth(SHARED.theme.getTextWidth("?/?", self.boxFont))
|
||||
|
||||
self.toggleCase = QAction(self.tr("Case Sensitive"), self)
|
||||
self.toggleCase.setCheckable(True)
|
||||
@@ -2374,15 +2370,15 @@ class GuiDocEditSearch(QFrame):
|
||||
self.replaceBox.setPalette(qPalette)
|
||||
|
||||
# Set icons
|
||||
self.toggleCase.setIcon(CONFIG.theme.getIcon("search_case"))
|
||||
self.toggleWord.setIcon(CONFIG.theme.getIcon("search_word"))
|
||||
self.toggleRegEx.setIcon(CONFIG.theme.getIcon("search_regex"))
|
||||
self.toggleLoop.setIcon(CONFIG.theme.getIcon("search_loop"))
|
||||
self.toggleProject.setIcon(CONFIG.theme.getIcon("search_project"))
|
||||
self.toggleMatchCap.setIcon(CONFIG.theme.getIcon("search_preserve"))
|
||||
self.cancelSearch.setIcon(CONFIG.theme.getIcon("search_cancel"))
|
||||
self.searchButton.setIcon(CONFIG.theme.getIcon("search"))
|
||||
self.replaceButton.setIcon(CONFIG.theme.getIcon("search_replace"))
|
||||
self.toggleCase.setIcon(SHARED.theme.getIcon("search_case"))
|
||||
self.toggleWord.setIcon(SHARED.theme.getIcon("search_word"))
|
||||
self.toggleRegEx.setIcon(SHARED.theme.getIcon("search_regex"))
|
||||
self.toggleLoop.setIcon(SHARED.theme.getIcon("search_loop"))
|
||||
self.toggleProject.setIcon(SHARED.theme.getIcon("search_project"))
|
||||
self.toggleMatchCap.setIcon(SHARED.theme.getIcon("search_preserve"))
|
||||
self.cancelSearch.setIcon(SHARED.theme.getIcon("search_cancel"))
|
||||
self.searchButton.setIcon(SHARED.theme.getIcon("search"))
|
||||
self.replaceButton.setIcon(SHARED.theme.getIcon("search_replace"))
|
||||
|
||||
# Set stylesheets
|
||||
self.searchOpt.setStyleSheet("QToolBar {padding: 0;}")
|
||||
@@ -2474,7 +2470,7 @@ class GuiDocEditSearch(QFrame):
|
||||
"""
|
||||
currRes = "?" if currRes is None else currRes
|
||||
resCount = "?" if resCount is None else "1000+" if resCount > 1000 else resCount
|
||||
minWidth = CONFIG.theme.getTextWidth(f"{resCount}//{resCount}", self.boxFont)
|
||||
minWidth = SHARED.theme.getTextWidth(f"{resCount}//{resCount}", self.boxFont)
|
||||
self.resultLabel.setText(f"{currRes}/{resCount}")
|
||||
self.resultLabel.setMinimumWidth(minWidth)
|
||||
self.adjustSize()
|
||||
@@ -2639,7 +2635,7 @@ class GuiDocEditHeader(QWidget):
|
||||
|
||||
self._docHandle = None
|
||||
|
||||
fPx = int(0.9*CONFIG.theme.fontPixelSize)
|
||||
fPx = int(0.9*SHARED.theme.fontPixelSize)
|
||||
hSp = CONFIG.pxInt(6)
|
||||
|
||||
# Main Widget Settings
|
||||
@@ -2656,7 +2652,7 @@ class GuiDocEditHeader(QWidget):
|
||||
self.theTitle.setFixedHeight(fPx)
|
||||
|
||||
lblFont = self.theTitle.font()
|
||||
lblFont.setPointSizeF(0.9*CONFIG.theme.fontPointSize)
|
||||
lblFont.setPointSizeF(0.9*SHARED.theme.fontPointSize)
|
||||
self.theTitle.setFont(lblFont)
|
||||
|
||||
# Buttons
|
||||
@@ -2726,15 +2722,15 @@ class GuiDocEditHeader(QWidget):
|
||||
def updateTheme(self):
|
||||
"""Update theme elements.
|
||||
"""
|
||||
self.editButton.setIcon(CONFIG.theme.getIcon("edit"))
|
||||
self.searchButton.setIcon(CONFIG.theme.getIcon("search"))
|
||||
self.minmaxButton.setIcon(CONFIG.theme.getIcon("maximise"))
|
||||
self.closeButton.setIcon(CONFIG.theme.getIcon("close"))
|
||||
self.editButton.setIcon(SHARED.theme.getIcon("edit"))
|
||||
self.searchButton.setIcon(SHARED.theme.getIcon("search"))
|
||||
self.minmaxButton.setIcon(SHARED.theme.getIcon("maximise"))
|
||||
self.closeButton.setIcon(SHARED.theme.getIcon("close"))
|
||||
|
||||
buttonStyle = (
|
||||
"QToolButton {{border: none; background: transparent;}} "
|
||||
"QToolButton:hover {{border: none; background: rgba({0},{1},{2},0.2);}}"
|
||||
).format(*CONFIG.theme.colText)
|
||||
).format(*SHARED.theme.colText)
|
||||
|
||||
self.editButton.setStyleSheet(buttonStyle)
|
||||
self.searchButton.setStyleSheet(buttonStyle)
|
||||
@@ -2750,9 +2746,9 @@ class GuiDocEditHeader(QWidget):
|
||||
theme rather than the main GUI.
|
||||
"""
|
||||
thePalette = QPalette()
|
||||
thePalette.setColor(QPalette.Window, QColor(*CONFIG.theme.colBack))
|
||||
thePalette.setColor(QPalette.WindowText, QColor(*CONFIG.theme.colText))
|
||||
thePalette.setColor(QPalette.Text, QColor(*CONFIG.theme.colText))
|
||||
thePalette.setColor(QPalette.Window, QColor(*SHARED.theme.colBack))
|
||||
thePalette.setColor(QPalette.WindowText, QColor(*SHARED.theme.colText))
|
||||
thePalette.setColor(QPalette.Text, QColor(*SHARED.theme.colText))
|
||||
|
||||
self.setPalette(thePalette)
|
||||
self.theTitle.setPalette(thePalette)
|
||||
@@ -2772,7 +2768,7 @@ class GuiDocEditHeader(QWidget):
|
||||
self.minmaxButton.setVisible(False)
|
||||
return True
|
||||
|
||||
pTree = self.mainGui.project.tree
|
||||
pTree = SHARED.project.tree
|
||||
if CONFIG.showFullPath:
|
||||
tTitle = []
|
||||
tTree = pTree.getItemPath(tHandle)
|
||||
@@ -2801,9 +2797,9 @@ class GuiDocEditHeader(QWidget):
|
||||
toggleFocusMode function and should not be activated directly.
|
||||
"""
|
||||
if self.mainGui.isFocusMode:
|
||||
self.minmaxButton.setIcon(CONFIG.theme.getIcon("minimise"))
|
||||
self.minmaxButton.setIcon(SHARED.theme.getIcon("minimise"))
|
||||
else:
|
||||
self.minmaxButton.setIcon(CONFIG.theme.getIcon("maximise"))
|
||||
self.minmaxButton.setIcon(SHARED.theme.getIcon("maximise"))
|
||||
return
|
||||
|
||||
##
|
||||
@@ -2876,13 +2872,13 @@ class GuiDocEditFooter(QWidget):
|
||||
|
||||
self._docSelection = False
|
||||
|
||||
self.sPx = int(round(0.9*CONFIG.theme.baseIconSize))
|
||||
fPx = int(0.9*CONFIG.theme.fontPixelSize)
|
||||
self.sPx = int(round(0.9*SHARED.theme.baseIconSize))
|
||||
fPx = int(0.9*SHARED.theme.fontPixelSize)
|
||||
bSp = CONFIG.pxInt(4)
|
||||
hSp = CONFIG.pxInt(6)
|
||||
|
||||
lblFont = self.font()
|
||||
lblFont.setPointSizeF(0.9*CONFIG.theme.fontPointSize)
|
||||
lblFont.setPointSizeF(0.9*SHARED.theme.fontPointSize)
|
||||
|
||||
# Main Widget Settings
|
||||
self.setContentsMargins(0, 0, 0, 0)
|
||||
@@ -2969,8 +2965,8 @@ class GuiDocEditFooter(QWidget):
|
||||
def updateTheme(self):
|
||||
"""Update theme elements.
|
||||
"""
|
||||
self.linesIcon.setPixmap(CONFIG.theme.getPixmap("status_lines", (self.sPx, self.sPx)))
|
||||
self.wordsIcon.setPixmap(CONFIG.theme.getPixmap("status_stats", (self.sPx, self.sPx)))
|
||||
self.linesIcon.setPixmap(SHARED.theme.getPixmap("status_lines", (self.sPx, self.sPx)))
|
||||
self.wordsIcon.setPixmap(SHARED.theme.getPixmap("status_stats", (self.sPx, self.sPx)))
|
||||
|
||||
self.matchColours()
|
||||
|
||||
@@ -2981,9 +2977,9 @@ class GuiDocEditFooter(QWidget):
|
||||
theme rather than the main GUI.
|
||||
"""
|
||||
thePalette = QPalette()
|
||||
thePalette.setColor(QPalette.Window, QColor(*CONFIG.theme.colBack))
|
||||
thePalette.setColor(QPalette.WindowText, QColor(*CONFIG.theme.colText))
|
||||
thePalette.setColor(QPalette.Text, QColor(*CONFIG.theme.colText))
|
||||
thePalette.setColor(QPalette.Window, QColor(*SHARED.theme.colBack))
|
||||
thePalette.setColor(QPalette.WindowText, QColor(*SHARED.theme.colText))
|
||||
thePalette.setColor(QPalette.Text, QColor(*SHARED.theme.colText))
|
||||
|
||||
self.setPalette(thePalette)
|
||||
self.statusText.setPalette(thePalette)
|
||||
@@ -3000,7 +2996,7 @@ class GuiDocEditFooter(QWidget):
|
||||
logger.debug("No handle set, so clearing the editor footer")
|
||||
self._theItem = None
|
||||
else:
|
||||
self._theItem = self.mainGui.project.tree[self._docHandle]
|
||||
self._theItem = SHARED.project.tree[self._docHandle]
|
||||
|
||||
self.setHasSelection(False)
|
||||
self.updateInfo()
|
||||
|
||||
@@ -32,7 +32,7 @@ from PyQt5.QtGui import (
|
||||
QColor, QTextCharFormat, QFont, QSyntaxHighlighter, QBrush
|
||||
)
|
||||
|
||||
from novelwriter import CONFIG
|
||||
from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.common import checkInt
|
||||
from novelwriter.constants import nwRegEx, nwUnicode
|
||||
|
||||
@@ -46,14 +46,13 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
||||
BLOCK_META = 2
|
||||
BLOCK_TITLE = 4
|
||||
|
||||
def __init__(self, theDoc, mainGui, spEnchant):
|
||||
def __init__(self, theDoc, spEnchant):
|
||||
super().__init__(theDoc)
|
||||
|
||||
logger.debug("Create: GuiDocHighlighter")
|
||||
|
||||
self.theDoc = theDoc
|
||||
self.spEnchant = spEnchant
|
||||
self.mainGui = mainGui
|
||||
self.theHandle = None
|
||||
self.spellCheck = False
|
||||
self.spellRx = None
|
||||
@@ -85,24 +84,24 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
||||
"""
|
||||
logger.debug("Setting up highlighting rules")
|
||||
|
||||
self.colHead = QColor(*CONFIG.theme.colHead)
|
||||
self.colHeadH = QColor(*CONFIG.theme.colHeadH)
|
||||
self.colDialN = QColor(*CONFIG.theme.colDialN)
|
||||
self.colDialD = QColor(*CONFIG.theme.colDialD)
|
||||
self.colDialS = QColor(*CONFIG.theme.colDialS)
|
||||
self.colHidden = QColor(*CONFIG.theme.colHidden)
|
||||
self.colKey = QColor(*CONFIG.theme.colKey)
|
||||
self.colVal = QColor(*CONFIG.theme.colVal)
|
||||
self.colSpell = QColor(*CONFIG.theme.colSpell)
|
||||
self.colError = QColor(*CONFIG.theme.colError)
|
||||
self.colRepTag = QColor(*CONFIG.theme.colRepTag)
|
||||
self.colMod = QColor(*CONFIG.theme.colMod)
|
||||
self.colBreak = QColor(*CONFIG.theme.colEmph)
|
||||
self.colHead = QColor(*SHARED.theme.colHead)
|
||||
self.colHeadH = QColor(*SHARED.theme.colHeadH)
|
||||
self.colDialN = QColor(*SHARED.theme.colDialN)
|
||||
self.colDialD = QColor(*SHARED.theme.colDialD)
|
||||
self.colDialS = QColor(*SHARED.theme.colDialS)
|
||||
self.colHidden = QColor(*SHARED.theme.colHidden)
|
||||
self.colKey = QColor(*SHARED.theme.colKey)
|
||||
self.colVal = QColor(*SHARED.theme.colVal)
|
||||
self.colSpell = QColor(*SHARED.theme.colSpell)
|
||||
self.colError = QColor(*SHARED.theme.colError)
|
||||
self.colRepTag = QColor(*SHARED.theme.colRepTag)
|
||||
self.colMod = QColor(*SHARED.theme.colMod)
|
||||
self.colBreak = QColor(*SHARED.theme.colEmph)
|
||||
self.colBreak.setAlpha(64)
|
||||
|
||||
self.colEmph = None
|
||||
if CONFIG.highlightEmph:
|
||||
self.colEmph = QColor(*CONFIG.theme.colEmph)
|
||||
self.colEmph = QColor(*SHARED.theme.colEmph)
|
||||
|
||||
self.hStyles = {
|
||||
"header1": self._makeFormat(self.colHead, "bold", 1.8),
|
||||
@@ -285,8 +284,8 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
||||
|
||||
if theText.startswith("@"): # Keywords and commands
|
||||
self.setCurrentBlockState(self.BLOCK_META)
|
||||
pIndex = self.mainGui.project.index
|
||||
tItem = self.mainGui.project.tree[self.theHandle]
|
||||
pIndex = SHARED.project.index
|
||||
tItem = SHARED.project.tree[self.theHandle]
|
||||
isValid, theBits, thePos = pIndex.scanThis(theText)
|
||||
isGood = pIndex.checkThese(theBits, tItem)
|
||||
if isValid:
|
||||
|
||||
+159
-171
@@ -30,22 +30,27 @@ from __future__ import annotations
|
||||
import logging
|
||||
|
||||
from enum import Enum
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from PyQt5.QtCore import Qt, QUrl, QSize, pyqtSlot, pyqtSignal
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QPoint, QSize, Qt, QUrl
|
||||
from PyQt5.QtGui import (
|
||||
QTextOption, QFont, QPalette, QColor, QTextCursor, QIcon, QCursor
|
||||
QColor, QCursor, QFont, QIcon, QMouseEvent, QPalette, QResizeEvent,
|
||||
QTextCursor, QTextOption
|
||||
)
|
||||
from PyQt5.QtWidgets import (
|
||||
qApp, QTextBrowser, QWidget, QScrollArea, QLabel, QHBoxLayout, QToolButton,
|
||||
QAction, QMenu, QFrame
|
||||
QAction, qApp, QFrame, QHBoxLayout, QLabel, QMenu, QScrollArea,
|
||||
QTextBrowser, QToolButton, QWidget
|
||||
)
|
||||
|
||||
from novelwriter import CONFIG
|
||||
from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.enum import nwItemType, nwDocAction, nwDocMode
|
||||
from novelwriter.error import logException
|
||||
from novelwriter.constants import nwUnicode
|
||||
from novelwriter.core.tohtml import ToHtml
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from novelwriter.guimain import GuiMain
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -53,7 +58,7 @@ class GuiDocViewer(QTextBrowser):
|
||||
|
||||
loadDocumentTagRequest = pyqtSignal(str, Enum)
|
||||
|
||||
def __init__(self, mainGui):
|
||||
def __init__(self, mainGui: GuiMain) -> None:
|
||||
super().__init__(parent=mainGui)
|
||||
|
||||
logger.debug("Create: GuiDocViewer")
|
||||
@@ -90,25 +95,43 @@ class GuiDocViewer(QTextBrowser):
|
||||
|
||||
return
|
||||
|
||||
def clearViewer(self):
|
||||
"""Clear the content of the document and reset key variables.
|
||||
"""
|
||||
##
|
||||
# Properties
|
||||
##
|
||||
|
||||
@property
|
||||
def docHandle(self) -> str | None:
|
||||
"""Return the handle of the currently open document."""
|
||||
return self._docHandle
|
||||
|
||||
@property
|
||||
def scrollPosition(self) -> int:
|
||||
"""Return the scrollbar position."""
|
||||
vBar = self.verticalScrollBar()
|
||||
if vBar.isVisible():
|
||||
return vBar.value()
|
||||
return 0
|
||||
|
||||
##
|
||||
# Methods
|
||||
##
|
||||
|
||||
def clearViewer(self) -> None:
|
||||
"""Clear the content of the document and reset key variables."""
|
||||
self.clear()
|
||||
self.setSearchPaths([""])
|
||||
self._docHandle = None
|
||||
self.docHeader.setTitleFromHandle(self._docHandle)
|
||||
return True
|
||||
return
|
||||
|
||||
def updateTheme(self):
|
||||
"""Update theme elements.
|
||||
"""
|
||||
def updateTheme(self) -> None:
|
||||
"""Update theme elements."""
|
||||
self.docHeader.updateTheme()
|
||||
self.docFooter.updateTheme()
|
||||
return
|
||||
|
||||
def initViewer(self):
|
||||
"""Set editor settings from main config.
|
||||
"""
|
||||
def initViewer(self) -> None:
|
||||
"""Set editor settings from main config."""
|
||||
self._makeStyleSheet()
|
||||
|
||||
# Set Font
|
||||
@@ -119,14 +142,14 @@ class GuiDocViewer(QTextBrowser):
|
||||
|
||||
# Set the widget colours to match syntax theme
|
||||
mainPalette = self.palette()
|
||||
mainPalette.setColor(QPalette.Window, QColor(*CONFIG.theme.colBack))
|
||||
mainPalette.setColor(QPalette.Base, QColor(*CONFIG.theme.colBack))
|
||||
mainPalette.setColor(QPalette.Text, QColor(*CONFIG.theme.colText))
|
||||
mainPalette.setColor(QPalette.Window, QColor(*SHARED.theme.colBack))
|
||||
mainPalette.setColor(QPalette.Base, QColor(*SHARED.theme.colBack))
|
||||
mainPalette.setColor(QPalette.Text, QColor(*SHARED.theme.colText))
|
||||
self.setPalette(mainPalette)
|
||||
|
||||
docPalette = self.viewport().palette()
|
||||
docPalette.setColor(QPalette.Base, QColor(*CONFIG.theme.colBack))
|
||||
docPalette.setColor(QPalette.Text, QColor(*CONFIG.theme.colText))
|
||||
docPalette.setColor(QPalette.Base, QColor(*SHARED.theme.colBack))
|
||||
docPalette.setColor(QPalette.Text, QColor(*SHARED.theme.colText))
|
||||
self.viewport().setPalette(docPalette)
|
||||
|
||||
self.docHeader.matchColours()
|
||||
@@ -157,12 +180,11 @@ class GuiDocViewer(QTextBrowser):
|
||||
if self._docHandle is not None:
|
||||
self.reloadText()
|
||||
|
||||
return True
|
||||
return
|
||||
|
||||
def loadText(self, tHandle, updateHistory=True):
|
||||
"""Load text into the viewer from an item handle.
|
||||
"""
|
||||
if not self.mainGui.project.tree.checkType(tHandle, nwItemType.FILE):
|
||||
def loadText(self, tHandle: str, updateHistory: bool = True) -> bool:
|
||||
"""Load text into the viewer from an item handle."""
|
||||
if not SHARED.project.tree.checkType(tHandle, nwItemType.FILE):
|
||||
logger.warning("Item not found")
|
||||
return False
|
||||
|
||||
@@ -170,7 +192,7 @@ class GuiDocViewer(QTextBrowser):
|
||||
qApp.setOverrideCursor(QCursor(Qt.WaitCursor))
|
||||
|
||||
sPos = self.verticalScrollBar().value()
|
||||
aDoc = ToHtml(self.mainGui.project)
|
||||
aDoc = ToHtml(SHARED.project)
|
||||
aDoc.setPreview(CONFIG.viewComments, CONFIG.viewSynopsis)
|
||||
aDoc.setLinkHeaders(True)
|
||||
|
||||
@@ -210,7 +232,7 @@ class GuiDocViewer(QTextBrowser):
|
||||
self.verticalScrollBar().setValue(sPos)
|
||||
|
||||
self._docHandle = tHandle
|
||||
self.mainGui.project.data.setLastHandle(tHandle, "viewer")
|
||||
SHARED.project.data.setLastHandle(tHandle, "viewer")
|
||||
self.docHeader.setTitleFromHandle(self._docHandle)
|
||||
self.updateDocMargins()
|
||||
|
||||
@@ -224,23 +246,20 @@ class GuiDocViewer(QTextBrowser):
|
||||
|
||||
return True
|
||||
|
||||
def reloadText(self):
|
||||
"""Reload the text in the current document.
|
||||
"""
|
||||
self.loadText(self._docHandle, updateHistory=False)
|
||||
def reloadText(self) -> None:
|
||||
"""Reload the text in the current document."""
|
||||
if self._docHandle:
|
||||
self.loadText(self._docHandle, updateHistory=False)
|
||||
return
|
||||
|
||||
def redrawText(self):
|
||||
"""Redraw the text by marking the document content as "dirty".
|
||||
"""
|
||||
def redrawText(self) -> None:
|
||||
"""Redraw the text by marking the content as "dirty"."""
|
||||
self.document().markContentsDirty(0, self.document().characterCount())
|
||||
self.updateDocMargins()
|
||||
return
|
||||
|
||||
def docAction(self, theAction):
|
||||
"""Wrapper function for various document actions on the current
|
||||
document.
|
||||
"""
|
||||
def docAction(self, theAction: nwDocAction) -> bool:
|
||||
"""Process document actions on the current document."""
|
||||
logger.debug("Requesting action: '%s'", theAction.name)
|
||||
if self._docHandle is None:
|
||||
logger.error("No document open")
|
||||
@@ -258,9 +277,8 @@ class GuiDocViewer(QTextBrowser):
|
||||
return False
|
||||
return True
|
||||
|
||||
def navigateTo(self, tAnchor):
|
||||
"""Go to a specific #link in the document.
|
||||
"""
|
||||
def navigateTo(self, tAnchor: str) -> bool:
|
||||
"""Go to a specific #link in the document."""
|
||||
if not isinstance(tAnchor, str):
|
||||
return False
|
||||
if tAnchor.startswith("#"):
|
||||
@@ -268,27 +286,13 @@ class GuiDocViewer(QTextBrowser):
|
||||
self.setSource(QUrl(tAnchor))
|
||||
return True
|
||||
|
||||
def navBackward(self):
|
||||
"""Navigate backwards in the document view history.
|
||||
"""
|
||||
self.docHistory.backward()
|
||||
return
|
||||
|
||||
def navForward(self):
|
||||
"""Navigate forwards in the document view history.
|
||||
"""
|
||||
self.docHistory.forward()
|
||||
return
|
||||
|
||||
def clearNavHistory(self):
|
||||
"""Clear the navigation history.
|
||||
"""
|
||||
def clearNavHistory(self) -> None:
|
||||
"""Clear the navigation history."""
|
||||
self.docHistory.clear()
|
||||
return
|
||||
|
||||
def updateDocMargins(self):
|
||||
"""Automatically adjust the margins so the text is centred.
|
||||
"""
|
||||
def updateDocMargins(self) -> None:
|
||||
"""Automatically adjust the margins so the text is centred."""
|
||||
wW = self.width()
|
||||
wH = self.height()
|
||||
cM = CONFIG.getTextMargin()
|
||||
@@ -320,41 +324,20 @@ class GuiDocViewer(QTextBrowser):
|
||||
# Setters
|
||||
##
|
||||
|
||||
def setScrollPosition(self, thePos):
|
||||
"""Set the scrollbar position.
|
||||
"""
|
||||
def setScrollPosition(self, pos: int) -> None:
|
||||
"""Set the scrollbar position."""
|
||||
vBar = self.verticalScrollBar()
|
||||
if vBar.isVisible():
|
||||
vBar.setValue(thePos)
|
||||
vBar.setValue(pos)
|
||||
return
|
||||
|
||||
##
|
||||
# Getters
|
||||
##
|
||||
|
||||
def docHandle(self):
|
||||
"""Return the handle of the currently open document. Returns
|
||||
None if no document is open.
|
||||
"""
|
||||
return self._docHandle
|
||||
|
||||
def getScrollPosition(self):
|
||||
"""Get the scrollbar position. Returns 0 if no scrollbar.
|
||||
"""
|
||||
vBar = self.verticalScrollBar()
|
||||
if vBar.isVisible():
|
||||
return vBar.value()
|
||||
return 0
|
||||
|
||||
##
|
||||
# Public Slots
|
||||
##
|
||||
|
||||
@pyqtSlot(str)
|
||||
def updateDocInfo(self, tHandle):
|
||||
"""Called when an item label is changed to check if the document
|
||||
title bar needs updating,
|
||||
"""
|
||||
def updateDocInfo(self, tHandle: str) -> None:
|
||||
"""Update the header titlebar if needed."""
|
||||
if tHandle == self._docHandle:
|
||||
self.docHeader.setTitleFromHandle(self._docHandle)
|
||||
self.updateDocMargins()
|
||||
@@ -364,11 +347,22 @@ class GuiDocViewer(QTextBrowser):
|
||||
# Private Slots
|
||||
##
|
||||
|
||||
@pyqtSlot()
|
||||
def navBackward(self) -> None:
|
||||
"""Navigate backwards in the document view history."""
|
||||
self.docHistory.backward()
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def navForward(self) -> None:
|
||||
"""Navigate forwards in the document view history."""
|
||||
self.docHistory.forward()
|
||||
return
|
||||
|
||||
@pyqtSlot("QUrl")
|
||||
def _linkClicked(self, theURL):
|
||||
"""Process a clicked link internally in the document.
|
||||
"""
|
||||
theLink = theURL.url()
|
||||
def _linkClicked(self, url: QUrl) -> None:
|
||||
"""Process a clicked link internally in the document."""
|
||||
theLink = url.url()
|
||||
logger.debug("Clicked link: '%s'", theLink)
|
||||
if len(theLink) > 0:
|
||||
theBits = theLink.split("=")
|
||||
@@ -377,9 +371,8 @@ class GuiDocViewer(QTextBrowser):
|
||||
return
|
||||
|
||||
@pyqtSlot("QPoint")
|
||||
def _openContextMenu(self, thePos):
|
||||
"""Triggered by right click to open the context menu.
|
||||
"""
|
||||
def _openContextMenu(self, point: QPoint) -> None:
|
||||
"""Open context menu at location."""
|
||||
userCursor = self.textCursor()
|
||||
userSelection = userCursor.hasSelection()
|
||||
|
||||
@@ -404,18 +397,18 @@ class GuiDocViewer(QTextBrowser):
|
||||
|
||||
mnuSelWord = QAction(self.tr("Select Word"), mnuContext)
|
||||
mnuSelWord.triggered.connect(
|
||||
lambda: self._makePosSelection(QTextCursor.WordUnderCursor, thePos)
|
||||
lambda: self._makePosSelection(QTextCursor.WordUnderCursor, point)
|
||||
)
|
||||
mnuContext.addAction(mnuSelWord)
|
||||
|
||||
mnuSelPara = QAction(self.tr("Select Paragraph"), mnuContext)
|
||||
mnuSelPara.triggered.connect(
|
||||
lambda: self._makePosSelection(QTextCursor.BlockUnderCursor, thePos)
|
||||
lambda: self._makePosSelection(QTextCursor.BlockUnderCursor, point)
|
||||
)
|
||||
mnuContext.addAction(mnuSelPara)
|
||||
|
||||
# Open the context menu
|
||||
mnuContext.exec_(self.viewport().mapToGlobal(thePos))
|
||||
mnuContext.exec_(self.viewport().mapToGlobal(point))
|
||||
|
||||
return
|
||||
|
||||
@@ -423,37 +416,33 @@ class GuiDocViewer(QTextBrowser):
|
||||
# Events
|
||||
##
|
||||
|
||||
def resizeEvent(self, theEvent):
|
||||
"""If the text editor is resized, we must make sure the document
|
||||
has its margins adjusted according to user preferences.
|
||||
"""
|
||||
def resizeEvent(self, event: QResizeEvent) -> None:
|
||||
"""Update document margins when widget is resized."""
|
||||
self.updateDocMargins()
|
||||
super().resizeEvent(theEvent)
|
||||
super().resizeEvent(event)
|
||||
return
|
||||
|
||||
def mouseReleaseEvent(self, theEvent):
|
||||
"""Capture mouse click events on the document.
|
||||
"""
|
||||
if theEvent.button() == Qt.BackButton:
|
||||
def mouseReleaseEvent(self, event: QMouseEvent) -> None:
|
||||
"""Capture mouse click events on the document."""
|
||||
if event.button() == Qt.BackButton:
|
||||
self.navBackward()
|
||||
elif theEvent.button() == Qt.ForwardButton:
|
||||
elif event.button() == Qt.ForwardButton:
|
||||
self.navForward()
|
||||
else:
|
||||
super().mouseReleaseEvent(theEvent)
|
||||
super().mouseReleaseEvent(event)
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
##
|
||||
|
||||
def _makeSelection(self, selMode):
|
||||
"""Wrapper function to select text based on a selection mode.
|
||||
"""
|
||||
def _makeSelection(self, selType: QTextCursor.SelectionType) -> None:
|
||||
"""Handle select of text based on a selection mode."""
|
||||
theCursor = self.textCursor()
|
||||
theCursor.clearSelection()
|
||||
theCursor.select(selMode)
|
||||
theCursor.select(selType)
|
||||
|
||||
if selMode == QTextCursor.BlockUnderCursor:
|
||||
if selType == QTextCursor.BlockUnderCursor:
|
||||
# This selection mode also selects the preceding paragraph
|
||||
# separator, which we want to avoid.
|
||||
posS = theCursor.selectionStart()
|
||||
@@ -467,19 +456,18 @@ class GuiDocViewer(QTextBrowser):
|
||||
|
||||
return
|
||||
|
||||
def _makePosSelection(self, selMode, thePos):
|
||||
"""Wrapper function to select text based on selection mode, but
|
||||
first move cursor to given position.
|
||||
"""
|
||||
theCursor = self.cursorForPosition(thePos)
|
||||
def _makePosSelection(self, selType: QTextCursor.SelectionType, pos: QPoint) -> None:
|
||||
"""Handle text selection at a given location."""
|
||||
theCursor = self.cursorForPosition(pos)
|
||||
self.setTextCursor(theCursor)
|
||||
self._makeSelection(selMode)
|
||||
self._makeSelection(selType)
|
||||
return
|
||||
|
||||
def _makeStyleSheet(self):
|
||||
def _makeStyleSheet(self) -> None:
|
||||
"""Generate an appropriate style sheet for the document viewer,
|
||||
based on the current syntax highlighter theme,
|
||||
"""
|
||||
pTheme = SHARED.theme
|
||||
styleSheet = (
|
||||
"body {{"
|
||||
" color: rgb({tColR}, {tColG}, {tColB});"
|
||||
@@ -506,31 +494,31 @@ class GuiDocViewer(QTextBrowser):
|
||||
" text-align: center;"
|
||||
"}}\n"
|
||||
).format(
|
||||
tColR=CONFIG.theme.colText[0],
|
||||
tColG=CONFIG.theme.colText[1],
|
||||
tColB=CONFIG.theme.colText[2],
|
||||
hColR=CONFIG.theme.colHead[0],
|
||||
hColG=CONFIG.theme.colHead[1],
|
||||
hColB=CONFIG.theme.colHead[2],
|
||||
aColR=CONFIG.theme.colVal[0],
|
||||
aColG=CONFIG.theme.colVal[1],
|
||||
aColB=CONFIG.theme.colVal[2],
|
||||
eColR=CONFIG.theme.colEmph[0],
|
||||
eColG=CONFIG.theme.colEmph[1],
|
||||
eColB=CONFIG.theme.colEmph[2],
|
||||
kColR=CONFIG.theme.colKey[0],
|
||||
kColG=CONFIG.theme.colKey[1],
|
||||
kColB=CONFIG.theme.colKey[2],
|
||||
cColR=CONFIG.theme.colHidden[0],
|
||||
cColG=CONFIG.theme.colHidden[1],
|
||||
cColB=CONFIG.theme.colHidden[2],
|
||||
mColR=CONFIG.theme.colMod[0],
|
||||
mColG=CONFIG.theme.colMod[1],
|
||||
mColB=CONFIG.theme.colMod[2],
|
||||
tColR=pTheme.colText[0],
|
||||
tColG=pTheme.colText[1],
|
||||
tColB=pTheme.colText[2],
|
||||
hColR=pTheme.colHead[0],
|
||||
hColG=pTheme.colHead[1],
|
||||
hColB=pTheme.colHead[2],
|
||||
aColR=pTheme.colVal[0],
|
||||
aColG=pTheme.colVal[1],
|
||||
aColB=pTheme.colVal[2],
|
||||
eColR=pTheme.colEmph[0],
|
||||
eColG=pTheme.colEmph[1],
|
||||
eColB=pTheme.colEmph[2],
|
||||
kColR=pTheme.colKey[0],
|
||||
kColG=pTheme.colKey[1],
|
||||
kColB=pTheme.colKey[2],
|
||||
cColR=pTheme.colHidden[0],
|
||||
cColG=pTheme.colHidden[1],
|
||||
cColB=pTheme.colHidden[2],
|
||||
mColR=pTheme.colMod[0],
|
||||
mColG=pTheme.colMod[1],
|
||||
mColB=pTheme.colMod[2],
|
||||
)
|
||||
self.document().setDefaultStyleSheet(styleSheet)
|
||||
|
||||
return True
|
||||
return
|
||||
|
||||
# END Class GuiDocViewer
|
||||
|
||||
@@ -628,7 +616,7 @@ class GuiDocViewHistory:
|
||||
"""Update the scrollbar position of the previous entry.
|
||||
"""
|
||||
if self._prevPos >= 0 and self._prevPos < len(self._posHistory):
|
||||
self._posHistory[self._prevPos] = self.docViewer.getScrollPosition()
|
||||
self._posHistory[self._prevPos] = self.docViewer.scrollPosition
|
||||
return
|
||||
|
||||
def _updateNavButtons(self):
|
||||
@@ -685,7 +673,7 @@ class GuiDocViewHeader(QWidget):
|
||||
# Internal Variables
|
||||
self._docHandle = None
|
||||
|
||||
fPx = int(0.9*CONFIG.theme.fontPixelSize)
|
||||
fPx = int(0.9*SHARED.theme.fontPixelSize)
|
||||
hSp = CONFIG.pxInt(6)
|
||||
|
||||
# Main Widget Settings
|
||||
@@ -702,7 +690,7 @@ class GuiDocViewHeader(QWidget):
|
||||
self.theTitle.setFixedHeight(fPx)
|
||||
|
||||
lblFont = self.theTitle.font()
|
||||
lblFont.setPointSizeF(0.9*CONFIG.theme.fontPointSize)
|
||||
lblFont.setPointSizeF(0.9*SHARED.theme.fontPointSize)
|
||||
self.theTitle.setFont(lblFont)
|
||||
|
||||
# Buttons
|
||||
@@ -773,15 +761,15 @@ class GuiDocViewHeader(QWidget):
|
||||
def updateTheme(self):
|
||||
"""Update theme elements.
|
||||
"""
|
||||
self.backButton.setIcon(CONFIG.theme.getIcon("backward"))
|
||||
self.forwardButton.setIcon(CONFIG.theme.getIcon("forward"))
|
||||
self.refreshButton.setIcon(CONFIG.theme.getIcon("refresh"))
|
||||
self.closeButton.setIcon(CONFIG.theme.getIcon("close"))
|
||||
self.backButton.setIcon(SHARED.theme.getIcon("backward"))
|
||||
self.forwardButton.setIcon(SHARED.theme.getIcon("forward"))
|
||||
self.refreshButton.setIcon(SHARED.theme.getIcon("refresh"))
|
||||
self.closeButton.setIcon(SHARED.theme.getIcon("close"))
|
||||
|
||||
buttonStyle = (
|
||||
"QToolButton {{border: none; background: transparent;}} "
|
||||
"QToolButton:hover {{border: none; background: rgba({0},{1},{2},0.2);}}"
|
||||
).format(*CONFIG.theme.colText)
|
||||
).format(*SHARED.theme.colText)
|
||||
|
||||
self.backButton.setStyleSheet(buttonStyle)
|
||||
self.forwardButton.setStyleSheet(buttonStyle)
|
||||
@@ -797,9 +785,9 @@ class GuiDocViewHeader(QWidget):
|
||||
theme rather than the main GUI.
|
||||
"""
|
||||
thePalette = QPalette()
|
||||
thePalette.setColor(QPalette.Window, QColor(*CONFIG.theme.colBack))
|
||||
thePalette.setColor(QPalette.WindowText, QColor(*CONFIG.theme.colText))
|
||||
thePalette.setColor(QPalette.Text, QColor(*CONFIG.theme.colText))
|
||||
thePalette.setColor(QPalette.Window, QColor(*SHARED.theme.colBack))
|
||||
thePalette.setColor(QPalette.WindowText, QColor(*SHARED.theme.colText))
|
||||
thePalette.setColor(QPalette.Text, QColor(*SHARED.theme.colText))
|
||||
|
||||
self.setPalette(thePalette)
|
||||
self.theTitle.setPalette(thePalette)
|
||||
@@ -819,7 +807,7 @@ class GuiDocViewHeader(QWidget):
|
||||
self.refreshButton.setVisible(False)
|
||||
return True
|
||||
|
||||
pTree = self.mainGui.project.tree
|
||||
pTree = SHARED.project.tree
|
||||
if CONFIG.showFullPath:
|
||||
tTitle = []
|
||||
tTree = pTree.getItemPath(tHandle)
|
||||
@@ -864,7 +852,7 @@ class GuiDocViewHeader(QWidget):
|
||||
def _refreshDocument(self):
|
||||
"""Reload the content of the document.
|
||||
"""
|
||||
if self.docViewer.docHandle() == self.mainGui.docEditor.docHandle():
|
||||
if self.docViewer.docHandle == self.mainGui.docEditor.docHandle:
|
||||
self.mainGui.saveDocument()
|
||||
self.docViewer.reloadText()
|
||||
return
|
||||
@@ -902,7 +890,7 @@ class GuiDocViewFooter(QWidget):
|
||||
# Internal Variables
|
||||
self._docHandle = None
|
||||
|
||||
fPx = int(0.9*CONFIG.theme.fontPixelSize)
|
||||
fPx = int(0.9*SHARED.theme.fontPixelSize)
|
||||
bSp = CONFIG.pxInt(2)
|
||||
hSp = CONFIG.pxInt(8)
|
||||
|
||||
@@ -987,7 +975,7 @@ class GuiDocViewFooter(QWidget):
|
||||
self.lblSynopsis.setAlignment(Qt.AlignLeft | Qt.AlignTop)
|
||||
|
||||
lblFont = self.font()
|
||||
lblFont.setPointSizeF(0.9*CONFIG.theme.fontPointSize)
|
||||
lblFont.setPointSizeF(0.9*SHARED.theme.fontPointSize)
|
||||
self.lblRefs.setFont(lblFont)
|
||||
self.lblSticky.setFont(lblFont)
|
||||
self.lblComments.setFont(lblFont)
|
||||
@@ -1032,21 +1020,21 @@ class GuiDocViewFooter(QWidget):
|
||||
"""
|
||||
# Icons
|
||||
|
||||
fPx = int(0.9*CONFIG.theme.fontPixelSize)
|
||||
fPx = int(0.9*SHARED.theme.fontPixelSize)
|
||||
|
||||
stickyOn = CONFIG.theme.getPixmap("sticky-on", (fPx, fPx))
|
||||
stickyOff = CONFIG.theme.getPixmap("sticky-off", (fPx, fPx))
|
||||
stickyOn = SHARED.theme.getPixmap("sticky-on", (fPx, fPx))
|
||||
stickyOff = SHARED.theme.getPixmap("sticky-off", (fPx, fPx))
|
||||
stickyIcon = QIcon()
|
||||
stickyIcon.addPixmap(stickyOn, QIcon.Normal, QIcon.On)
|
||||
stickyIcon.addPixmap(stickyOff, QIcon.Normal, QIcon.Off)
|
||||
|
||||
bulletOn = CONFIG.theme.getPixmap("bullet-on", (fPx, fPx))
|
||||
bulletOff = CONFIG.theme.getPixmap("bullet-off", (fPx, fPx))
|
||||
bulletOn = SHARED.theme.getPixmap("bullet-on", (fPx, fPx))
|
||||
bulletOff = SHARED.theme.getPixmap("bullet-off", (fPx, fPx))
|
||||
bulletIcon = QIcon()
|
||||
bulletIcon.addPixmap(bulletOn, QIcon.Normal, QIcon.On)
|
||||
bulletIcon.addPixmap(bulletOff, QIcon.Normal, QIcon.Off)
|
||||
|
||||
self.showHide.setIcon(CONFIG.theme.getIcon("reference"))
|
||||
self.showHide.setIcon(SHARED.theme.getIcon("reference"))
|
||||
self.stickyRefs.setIcon(stickyIcon)
|
||||
self.showComments.setIcon(bulletIcon)
|
||||
self.showSynopsis.setIcon(bulletIcon)
|
||||
@@ -1056,7 +1044,7 @@ class GuiDocViewFooter(QWidget):
|
||||
buttonStyle = (
|
||||
"QToolButton {{border: none; background: transparent;}} "
|
||||
"QToolButton:hover {{border: none; background: rgba({0},{1},{2},0.2);}}"
|
||||
).format(*CONFIG.theme.colText)
|
||||
).format(*SHARED.theme.colText)
|
||||
|
||||
self.showHide.setStyleSheet(buttonStyle)
|
||||
self.stickyRefs.setStyleSheet(buttonStyle)
|
||||
@@ -1072,9 +1060,9 @@ class GuiDocViewFooter(QWidget):
|
||||
theme rather than the main GUI.
|
||||
"""
|
||||
thePalette = QPalette()
|
||||
thePalette.setColor(QPalette.Window, QColor(*CONFIG.theme.colBack))
|
||||
thePalette.setColor(QPalette.WindowText, QColor(*CONFIG.theme.colText))
|
||||
thePalette.setColor(QPalette.Text, QColor(*CONFIG.theme.colText))
|
||||
thePalette.setColor(QPalette.Window, QColor(*SHARED.theme.colBack))
|
||||
thePalette.setColor(QPalette.WindowText, QColor(*SHARED.theme.colText))
|
||||
thePalette.setColor(QPalette.Text, QColor(*SHARED.theme.colText))
|
||||
|
||||
self.setPalette(thePalette)
|
||||
self.lblRefs.setPalette(thePalette)
|
||||
@@ -1102,8 +1090,8 @@ class GuiDocViewFooter(QWidget):
|
||||
"""
|
||||
logger.debug("Reference sticky is %s", str(theState))
|
||||
self.docViewer.stickyRef = theState
|
||||
if not theState and self.docViewer.docHandle() is not None:
|
||||
self.viewMeta.refreshReferences(self.docViewer.docHandle())
|
||||
if not theState and self.docViewer.docHandle is not None:
|
||||
self.viewMeta.refreshReferences(self.docViewer.docHandle)
|
||||
return
|
||||
|
||||
@pyqtSlot(bool)
|
||||
@@ -1145,7 +1133,7 @@ class GuiDocViewDetails(QScrollArea):
|
||||
self.refList.setScaledContents(True)
|
||||
self.refList.linkActivated.connect(self._linkClicked)
|
||||
|
||||
self.linkStyle = "style='color: rgb({0},{1},{2})'".format(*CONFIG.theme.colLink)
|
||||
self.linkStyle = "style='color: rgb({0},{1},{2})'".format(*SHARED.theme.colLink)
|
||||
|
||||
# Assemble
|
||||
self.outerWidget = QWidget()
|
||||
@@ -1172,10 +1160,10 @@ class GuiDocViewDetails(QScrollArea):
|
||||
if self.mainGui.docViewer.stickyRef:
|
||||
return
|
||||
|
||||
theRefs = self.mainGui.project.index.getBackReferenceList(tHandle)
|
||||
theRefs = SHARED.project.index.getBackReferenceList(tHandle)
|
||||
theList = []
|
||||
for tHandle in theRefs:
|
||||
tItem = self.mainGui.project.tree[tHandle]
|
||||
tItem = SHARED.project.tree[tHandle]
|
||||
if tItem is not None:
|
||||
theList.append("<a href='%s#%s' %s>%s</a>" % (
|
||||
tHandle, theRefs[tHandle], self.linkStyle, tItem.itemName
|
||||
|
||||
@@ -25,25 +25,28 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from PyQt5.QtGui import QFont
|
||||
from PyQt5.QtCore import Qt, pyqtSlot
|
||||
from PyQt5.QtGui import QFont, QPixmap
|
||||
from PyQt5.QtWidgets import QWidget, QGridLayout, QLabel
|
||||
|
||||
from novelwriter import CONFIG
|
||||
from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.constants import trConst, nwLabels
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from novelwriter.guimain import GuiMain
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GuiItemDetails(QWidget):
|
||||
|
||||
def __init__(self, mainGui):
|
||||
def __init__(self, mainGui: GuiMain) -> None:
|
||||
super().__init__(parent=mainGui)
|
||||
|
||||
logger.debug("Create: GuiItemDetails")
|
||||
|
||||
self.mainGui = mainGui
|
||||
|
||||
# Internal Variables
|
||||
self._itemHandle = None
|
||||
|
||||
@@ -51,7 +54,7 @@ class GuiItemDetails(QWidget):
|
||||
hSp = CONFIG.pxInt(6)
|
||||
vSp = CONFIG.pxInt(1)
|
||||
mPx = CONFIG.pxInt(6)
|
||||
fPt = CONFIG.theme.fontPointSize
|
||||
fPt = SHARED.theme.fontPointSize
|
||||
|
||||
fntLabel = QFont()
|
||||
fntLabel.setBold(True)
|
||||
@@ -176,8 +179,8 @@ class GuiItemDetails(QWidget):
|
||||
self.updateTheme()
|
||||
|
||||
# Make sure the columns for flags and counts don't resize too often
|
||||
flagWidth = CONFIG.theme.getTextWidth("Mm", fntValue)
|
||||
countWidth = CONFIG.theme.getTextWidth("99,999", fntValue)
|
||||
flagWidth = SHARED.theme.getTextWidth("Mm", fntValue)
|
||||
countWidth = SHARED.theme.getTextWidth("99,999", fntValue)
|
||||
self.mainBox.setColumnMinimumWidth(1, flagWidth)
|
||||
self.mainBox.setColumnMinimumWidth(4, countWidth)
|
||||
|
||||
@@ -189,35 +192,28 @@ class GuiItemDetails(QWidget):
|
||||
# Class Methods
|
||||
##
|
||||
|
||||
def clearDetails(self):
|
||||
"""Clear all the data values.
|
||||
"""
|
||||
def clearDetails(self) -> None:
|
||||
"""Clear all the data values."""
|
||||
self._itemHandle = None
|
||||
|
||||
self.labelIcon.setPixmap(QPixmap(1, 1))
|
||||
self.statusIcon.setPixmap(QPixmap(1, 1))
|
||||
self.classIcon.setText("")
|
||||
self.usageIcon.setText("")
|
||||
|
||||
self.labelData.setText("–")
|
||||
self.statusData.setText("–")
|
||||
self.classData.setText("–")
|
||||
self.usageData.setText("–")
|
||||
|
||||
self.cCountData.setText("–")
|
||||
self.wCountData.setText("–")
|
||||
self.pCountData.setText("–")
|
||||
|
||||
self.labelIcon.clear()
|
||||
self.labelData.clear()
|
||||
self.statusIcon.clear()
|
||||
self.statusData.clear()
|
||||
self.classIcon.clear()
|
||||
self.classData.clear()
|
||||
self.usageIcon.clear()
|
||||
self.usageData.clear()
|
||||
self.cCountData.clear()
|
||||
self.wCountData.clear()
|
||||
self.pCountData.clear()
|
||||
return
|
||||
|
||||
def refreshDetails(self):
|
||||
"""Reload the content of the details panel.
|
||||
"""
|
||||
def refreshDetails(self) -> None:
|
||||
"""Reload the content of the details panel."""
|
||||
self.updateViewBox(self._itemHandle)
|
||||
|
||||
def updateTheme(self):
|
||||
"""Update theme elements.
|
||||
"""
|
||||
def updateTheme(self) -> None:
|
||||
"""Update theme elements."""
|
||||
self.updateViewBox(self._itemHandle)
|
||||
return
|
||||
|
||||
@@ -226,20 +222,19 @@ class GuiItemDetails(QWidget):
|
||||
##
|
||||
|
||||
@pyqtSlot(str)
|
||||
def updateViewBox(self, tHandle):
|
||||
"""Populate the details box from a given handle.
|
||||
"""
|
||||
def updateViewBox(self, tHandle: str) -> None:
|
||||
"""Populate the details box from a given handle."""
|
||||
if tHandle is None:
|
||||
self.clearDetails()
|
||||
return
|
||||
|
||||
nwItem = self.mainGui.project.tree[tHandle]
|
||||
nwItem = SHARED.project.tree[tHandle]
|
||||
if nwItem is None:
|
||||
self.clearDetails()
|
||||
return
|
||||
|
||||
self._itemHandle = tHandle
|
||||
iPx = int(round(0.8*CONFIG.theme.baseIconSize))
|
||||
iPx = int(round(0.8*SHARED.theme.baseIconSize))
|
||||
|
||||
# Label
|
||||
# =====
|
||||
@@ -250,11 +245,11 @@ class GuiItemDetails(QWidget):
|
||||
|
||||
if nwItem.isFileType():
|
||||
if nwItem.isActive:
|
||||
self.labelIcon.setPixmap(CONFIG.theme.getPixmap("checked", (iPx, iPx)))
|
||||
self.labelIcon.setPixmap(SHARED.theme.getPixmap("checked", (iPx, iPx)))
|
||||
else:
|
||||
self.labelIcon.setPixmap(CONFIG.theme.getPixmap("unchecked", (iPx, iPx)))
|
||||
self.labelIcon.setPixmap(SHARED.theme.getPixmap("unchecked", (iPx, iPx)))
|
||||
else:
|
||||
self.labelIcon.setPixmap(CONFIG.theme.getPixmap("noncheckable", (iPx, iPx)))
|
||||
self.labelIcon.setPixmap(SHARED.theme.getPixmap("noncheckable", (iPx, iPx)))
|
||||
|
||||
self.labelData.setText(theLabel)
|
||||
|
||||
@@ -268,14 +263,14 @@ class GuiItemDetails(QWidget):
|
||||
# Class
|
||||
# =====
|
||||
|
||||
classIcon = CONFIG.theme.getIcon(nwLabels.CLASS_ICON[nwItem.itemClass])
|
||||
classIcon = SHARED.theme.getIcon(nwLabels.CLASS_ICON[nwItem.itemClass])
|
||||
self.classIcon.setPixmap(classIcon.pixmap(iPx, iPx))
|
||||
self.classData.setText(trConst(nwLabels.CLASS_NAME[nwItem.itemClass]))
|
||||
|
||||
# Layout
|
||||
# ======
|
||||
|
||||
usageIcon = CONFIG.theme.getItemIcon(
|
||||
usageIcon = SHARED.theme.getItemIcon(
|
||||
nwItem.itemType, nwItem.itemClass, nwItem.itemLayout, nwItem.mainHeading
|
||||
)
|
||||
self.usageIcon.setPixmap(usageIcon.pixmap(iPx, iPx))
|
||||
@@ -296,7 +291,7 @@ class GuiItemDetails(QWidget):
|
||||
return
|
||||
|
||||
@pyqtSlot(str, int, int, int)
|
||||
def updateCounts(self, tHandle, cC, wC, pC):
|
||||
def updateCounts(self, tHandle: str, cC: int, wC: int, pC: int) -> None:
|
||||
"""Update the counts if the handle is the same as the one we're
|
||||
already showing. Otherwise, do nothing.
|
||||
"""
|
||||
@@ -304,7 +299,6 @@ class GuiItemDetails(QWidget):
|
||||
self.cCountData.setText(f"{cC:n}")
|
||||
self.wCountData.setText(f"{wC:n}")
|
||||
self.pCountData.setText(f"{pC:n}")
|
||||
|
||||
return
|
||||
|
||||
# END Class GuiItemDetails
|
||||
|
||||
@@ -33,7 +33,7 @@ from PyQt5.QtCore import QUrl
|
||||
from PyQt5.QtGui import QDesktopServices
|
||||
from PyQt5.QtWidgets import QMenuBar, QAction
|
||||
|
||||
from novelwriter import CONFIG
|
||||
from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.enum import nwDocAction, nwDocInsert, nwWidget
|
||||
from novelwriter.constants import nwConst, trConst, nwKeyWords, nwLabels, nwUnicode
|
||||
|
||||
@@ -349,13 +349,13 @@ class GuiMainMenu(QMenuBar):
|
||||
# View > Go Backward
|
||||
self.aViewPrev = QAction(self.tr("Navigate Backward"), self)
|
||||
self.aViewPrev.setShortcut("Alt+Left")
|
||||
self.aViewPrev.triggered.connect(lambda: self.mainGui.docViewer.navBackward())
|
||||
self.aViewPrev.triggered.connect(self.mainGui.docViewer.navBackward)
|
||||
self.viewMenu.addAction(self.aViewPrev)
|
||||
|
||||
# View > Go Forward
|
||||
self.aViewNext = QAction(self.tr("Navigate Forward"), self)
|
||||
self.aViewNext.setShortcut("Alt+Right")
|
||||
self.aViewNext.triggered.connect(lambda: self.mainGui.docViewer.navForward())
|
||||
self.aViewNext.triggered.connect(self.mainGui.docViewer.navForward)
|
||||
self.viewMenu.addAction(self.aViewNext)
|
||||
|
||||
# View > Separator
|
||||
@@ -795,7 +795,7 @@ class GuiMainMenu(QMenuBar):
|
||||
# Tools > Check Spelling
|
||||
self.aSpellCheck = QAction(self.tr("Check Spelling"), self)
|
||||
self.aSpellCheck.setCheckable(True)
|
||||
self.aSpellCheck.setChecked(self.mainGui.project.data.spellCheck)
|
||||
self.aSpellCheck.setChecked(SHARED.project.data.spellCheck)
|
||||
self.aSpellCheck.triggered.connect(self._toggleSpellCheck) # triggered, not toggled!
|
||||
self.aSpellCheck.setShortcut("Ctrl+F7")
|
||||
self.toolsMenu.addAction(self.aSpellCheck)
|
||||
@@ -825,7 +825,7 @@ class GuiMainMenu(QMenuBar):
|
||||
|
||||
# Tools > Backup Project
|
||||
self.aBackupProject = QAction(self.tr("Backup Project"), self)
|
||||
self.aBackupProject.triggered.connect(lambda: self.mainGui.project.backupProject(True))
|
||||
self.aBackupProject.triggered.connect(lambda: SHARED.project.backupProject(True))
|
||||
self.toolsMenu.addAction(self.aBackupProject)
|
||||
|
||||
# Tools > Build Manuscript
|
||||
|
||||
@@ -38,7 +38,7 @@ from PyQt5.QtWidgets import (
|
||||
QTreeWidgetItem, QVBoxLayout, QWidget
|
||||
)
|
||||
|
||||
from novelwriter import CONFIG
|
||||
from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.enum import nwDocMode, nwItemClass, nwOutline
|
||||
from novelwriter.common import minmax
|
||||
from novelwriter.constants import nwHeaders, nwKeyWords, nwLabels, trConst
|
||||
@@ -106,7 +106,7 @@ class GuiNovelView(QWidget):
|
||||
self.novelTree.initSettings()
|
||||
return
|
||||
|
||||
def clearProject(self):
|
||||
def clearNovelView(self):
|
||||
"""Clear project-related GUI content.
|
||||
"""
|
||||
self.novelTree.clearContent()
|
||||
@@ -117,21 +117,20 @@ class GuiNovelView(QWidget):
|
||||
def openProjectTasks(self):
|
||||
"""Run open project tasks.
|
||||
"""
|
||||
lastNovel = self.mainGui.project.data.getLastHandle("novelTree")
|
||||
if lastNovel not in self.mainGui.project.tree:
|
||||
lastNovel = self.mainGui.project.tree.findRoot(nwItemClass.NOVEL)
|
||||
lastNovel = SHARED.project.data.getLastHandle("novelTree")
|
||||
if lastNovel not in SHARED.project.tree:
|
||||
lastNovel = SHARED.project.tree.findRoot(nwItemClass.NOVEL)
|
||||
|
||||
logger.debug("Setting novel tree to root item '%s'", lastNovel)
|
||||
|
||||
lastCol = self.mainGui.project.options.getEnum(
|
||||
lastCol = SHARED.project.options.getEnum(
|
||||
"GuiNovelView", "lastCol", NovelTreeColumn, NovelTreeColumn.HIDDEN
|
||||
)
|
||||
lastColSize = self.mainGui.project.options.getInt(
|
||||
lastColSize = SHARED.project.options.getInt(
|
||||
"GuiNovelView", "lastColSize", 25
|
||||
)
|
||||
|
||||
self.clearProject()
|
||||
|
||||
self.clearNovelView()
|
||||
self.novelBar.buildNovelRootMenu()
|
||||
self.novelBar.setLastColType(lastCol, doRefresh=False)
|
||||
self.novelBar.setCurrentRoot(lastNovel)
|
||||
@@ -142,13 +141,13 @@ class GuiNovelView(QWidget):
|
||||
return
|
||||
|
||||
def closeProjectTasks(self):
|
||||
"""Run closing project tasks.
|
||||
"""
|
||||
"""Run closing project tasks."""
|
||||
lastColType = self.novelTree.lastColType
|
||||
lastColSize = self.novelTree.lastColSize
|
||||
pOptions = self.mainGui.project.options
|
||||
pOptions = SHARED.project.options
|
||||
pOptions.setValue("GuiNovelView", "lastCol", lastColType)
|
||||
pOptions.setValue("GuiNovelView", "lastColSize", lastColSize)
|
||||
self.clearNovelView()
|
||||
return
|
||||
|
||||
def setTreeFocus(self):
|
||||
@@ -170,7 +169,7 @@ class GuiNovelView(QWidget):
|
||||
def refreshTree(self):
|
||||
"""Refresh the current tree.
|
||||
"""
|
||||
self.novelTree.refreshTree(rootHandle=self.mainGui.project.data.getLastHandle("novelTree"))
|
||||
self.novelTree.refreshTree(rootHandle=SHARED.project.data.getLastHandle("novelTree"))
|
||||
return
|
||||
|
||||
@pyqtSlot(str)
|
||||
@@ -201,7 +200,7 @@ class GuiNovelToolBar(QWidget):
|
||||
self.novelView = novelView
|
||||
self.mainGui = novelView.mainGui
|
||||
|
||||
iPx = CONFIG.theme.baseIconSize
|
||||
iPx = SHARED.theme.baseIconSize
|
||||
mPx = CONFIG.pxInt(2)
|
||||
|
||||
self.setContentsMargins(0, 0, 0, 0)
|
||||
@@ -211,7 +210,7 @@ class GuiNovelToolBar(QWidget):
|
||||
selFont = self.font()
|
||||
selFont.setWeight(QFont.Bold)
|
||||
self.novelPrefix = self.tr("Outline of {0}")
|
||||
self.novelValue = NovelSelector(self, self.mainGui)
|
||||
self.novelValue = NovelSelector(self)
|
||||
self.novelValue.setFont(selFont)
|
||||
self.novelValue.setMinimumWidth(CONFIG.pxInt(150))
|
||||
self.novelValue.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||
@@ -274,9 +273,9 @@ class GuiNovelToolBar(QWidget):
|
||||
"""Update theme elements.
|
||||
"""
|
||||
# Icons
|
||||
self.tbNovel.setIcon(CONFIG.theme.getIcon("cls_novel"))
|
||||
self.tbRefresh.setIcon(CONFIG.theme.getIcon("refresh"))
|
||||
self.tbMore.setIcon(CONFIG.theme.getIcon("menu"))
|
||||
self.tbNovel.setIcon(SHARED.theme.getIcon("cls_novel"))
|
||||
self.tbRefresh.setIcon(SHARED.theme.getIcon("refresh"))
|
||||
self.tbMore.setIcon(SHARED.theme.getIcon("menu"))
|
||||
|
||||
qPalette = self.palette()
|
||||
qPalette.setBrush(QPalette.Window, qPalette.base())
|
||||
@@ -345,7 +344,7 @@ class GuiNovelToolBar(QWidget):
|
||||
def _refreshNovelTree(self):
|
||||
"""Rebuild the current tree.
|
||||
"""
|
||||
rootHandle = self.mainGui.project.data.getLastHandle("novelTree")
|
||||
rootHandle = SHARED.project.data.getLastHandle("novelTree")
|
||||
self.novelView.novelTree.refreshTree(rootHandle=rootHandle, overRide=True)
|
||||
return
|
||||
|
||||
@@ -415,7 +414,7 @@ class GuiNovelTree(QTreeWidget):
|
||||
# Build GUI
|
||||
# =========
|
||||
|
||||
iPx = CONFIG.theme.baseIconSize
|
||||
iPx = SHARED.theme.baseIconSize
|
||||
cMg = CONFIG.pxInt(6)
|
||||
|
||||
self.setIconSize(QSize(iPx, iPx))
|
||||
@@ -481,8 +480,8 @@ class GuiNovelTree(QTreeWidget):
|
||||
def updateTheme(self):
|
||||
"""Update theme elements.
|
||||
"""
|
||||
iPx = CONFIG.theme.baseIconSize
|
||||
self._pMore = CONFIG.theme.loadDecoration("deco_doc_more", pxH=iPx)
|
||||
iPx = SHARED.theme.baseIconSize
|
||||
self._pMore = SHARED.theme.loadDecoration("deco_doc_more", pxH=iPx)
|
||||
return
|
||||
|
||||
##
|
||||
@@ -514,10 +513,10 @@ class GuiNovelTree(QTreeWidget):
|
||||
"""
|
||||
logger.debug("Requesting refresh of the novel tree")
|
||||
if rootHandle is None:
|
||||
rootHandle = self.mainGui.project.tree.findRoot(nwItemClass.NOVEL)
|
||||
rootHandle = SHARED.project.tree.findRoot(nwItemClass.NOVEL)
|
||||
|
||||
treeChanged = self.mainGui.projView.changedSince(self._lastBuild)
|
||||
indexChanged = self.mainGui.project.index.rootChangedSince(rootHandle, self._lastBuild)
|
||||
indexChanged = SHARED.project.index.rootChangedSince(rootHandle, self._lastBuild)
|
||||
if not (treeChanged or indexChanged or overRide):
|
||||
logger.debug("No changes have been made to the novel index")
|
||||
return
|
||||
@@ -528,7 +527,7 @@ class GuiNovelTree(QTreeWidget):
|
||||
titleKey = selItem[0].data(self.C_DATA, self.D_KEY)
|
||||
|
||||
self._populateTree(rootHandle)
|
||||
self.mainGui.project.data.setLastHandle(rootHandle, "novelTree")
|
||||
SHARED.project.data.setLastHandle(rootHandle, "novelTree")
|
||||
|
||||
if titleKey is not None and titleKey in self._treeMap:
|
||||
self._treeMap[titleKey].setSelected(True)
|
||||
@@ -538,7 +537,7 @@ class GuiNovelTree(QTreeWidget):
|
||||
def refreshHandle(self, tHandle):
|
||||
"""Refresh the data for a given handle.
|
||||
"""
|
||||
idxData = self.mainGui.project.index.getItemData(tHandle)
|
||||
idxData = SHARED.project.index.getItemData(tHandle)
|
||||
if idxData is None:
|
||||
return
|
||||
|
||||
@@ -575,7 +574,7 @@ class GuiNovelTree(QTreeWidget):
|
||||
self._lastCol = colType
|
||||
self.setColumnHidden(self.C_EXTRA, colType == NovelTreeColumn.HIDDEN)
|
||||
if doRefresh:
|
||||
lastNovel = self.mainGui.project.data.getLastHandle("novelTree")
|
||||
lastNovel = SHARED.project.data.getLastHandle("novelTree")
|
||||
self.refreshTree(rootHandle=lastNovel, overRide=True)
|
||||
return
|
||||
|
||||
@@ -707,7 +706,7 @@ class GuiNovelTree(QTreeWidget):
|
||||
tStart = time()
|
||||
logger.debug("Building novel tree for root item '%s'", rootHandle)
|
||||
|
||||
novStruct = self.mainGui.project.index.novelStructure(rootHandle=rootHandle, skipExcl=True)
|
||||
novStruct = SHARED.project.index.novelStructure(rootHandle=rootHandle, skipExcl=True)
|
||||
for tKey, tHandle, sTitle, novIdx in novStruct:
|
||||
if novIdx.level == "H0":
|
||||
continue
|
||||
@@ -733,7 +732,7 @@ class GuiNovelTree(QTreeWidget):
|
||||
"""Set the tree item values from the index entry.
|
||||
"""
|
||||
iLevel = nwHeaders.H_LEVEL.get(idxItem.level, 0)
|
||||
hDec = CONFIG.theme.getHeaderDecoration(iLevel)
|
||||
hDec = SHARED.theme.getHeaderDecoration(iLevel)
|
||||
|
||||
trItem.setData(self.C_TITLE, Qt.DecorationRole, hDec)
|
||||
trItem.setText(self.C_TITLE, idxItem.title)
|
||||
@@ -759,7 +758,7 @@ class GuiNovelTree(QTreeWidget):
|
||||
|
||||
refData = []
|
||||
refName = ""
|
||||
theRefs = self.mainGui.project.index.getReferences(tHandle, sTitle)
|
||||
theRefs = SHARED.project.index.getReferences(tHandle, sTitle)
|
||||
if self._lastCol == NovelTreeColumn.POV:
|
||||
refData = theRefs[nwKeyWords.POV_KEY]
|
||||
refName = self._povLabel
|
||||
@@ -783,7 +782,7 @@ class GuiNovelTree(QTreeWidget):
|
||||
"""
|
||||
logger.debug("Generating meta data tooltip for '%s:%s'", tHandle, sTitle)
|
||||
|
||||
pIndex = self.mainGui.project.index
|
||||
pIndex = SHARED.project.index
|
||||
novIdx = pIndex.getItemHeader(tHandle, sTitle)
|
||||
refTags = pIndex.getReferences(tHandle, sTitle)
|
||||
|
||||
|
||||
+31
-36
@@ -41,7 +41,7 @@ from PyQt5.QtWidgets import (
|
||||
QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget
|
||||
)
|
||||
|
||||
from novelwriter import CONFIG
|
||||
from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.enum import (
|
||||
nwDocMode, nwItemClass, nwItemLayout, nwItemType, nwOutline
|
||||
)
|
||||
@@ -62,8 +62,6 @@ class GuiOutlineView(QWidget):
|
||||
def __init__(self, mainGui):
|
||||
super().__init__(parent=mainGui)
|
||||
|
||||
self.mainGui = mainGui
|
||||
|
||||
# Build GUI
|
||||
self.outlineTree = GuiOutlineTree(self)
|
||||
self.outlineData = GuiOutlineDetails(self)
|
||||
@@ -117,10 +115,10 @@ class GuiOutlineView(QWidget):
|
||||
def refreshTree(self):
|
||||
"""Refresh the current tree.
|
||||
"""
|
||||
self.outlineTree.refreshTree(rootHandle=self.mainGui.project.data.getLastHandle("outline"))
|
||||
self.outlineTree.refreshTree(rootHandle=SHARED.project.data.getLastHandle("outline"))
|
||||
return
|
||||
|
||||
def clearProject(self):
|
||||
def clearOutline(self):
|
||||
"""Clear project-related GUI content.
|
||||
"""
|
||||
self.outlineData.clearDetails()
|
||||
@@ -130,13 +128,13 @@ class GuiOutlineView(QWidget):
|
||||
def openProjectTasks(self):
|
||||
"""Run open project tasks.
|
||||
"""
|
||||
lastOutline = self.mainGui.project.data.getLastHandle("outline")
|
||||
if not (lastOutline in self.mainGui.project.tree or lastOutline is None):
|
||||
lastOutline = self.mainGui.project.tree.findRoot(nwItemClass.NOVEL)
|
||||
lastOutline = SHARED.project.data.getLastHandle("outline")
|
||||
if not (lastOutline in SHARED.project.tree or lastOutline is None):
|
||||
lastOutline = SHARED.project.tree.findRoot(nwItemClass.NOVEL)
|
||||
|
||||
logger.debug("Setting outline tree to root item '%s'", lastOutline)
|
||||
|
||||
self.clearProject()
|
||||
self.clearOutline()
|
||||
self.outlineBar.populateNovelList()
|
||||
self.outlineBar.setCurrentRoot(lastOutline)
|
||||
self.outlineBar.setEnabled(True)
|
||||
@@ -146,6 +144,7 @@ class GuiOutlineView(QWidget):
|
||||
def closeProjectTasks(self):
|
||||
self.outlineTree.closeProjectTasks()
|
||||
self.outlineData.updateClasses()
|
||||
self.clearOutline()
|
||||
return
|
||||
|
||||
def splitSizes(self):
|
||||
@@ -214,8 +213,6 @@ class GuiOutlineToolBar(QToolBar):
|
||||
|
||||
logger.debug("Create: GuiOutlineToolBar")
|
||||
|
||||
self.mainGui = theOutline.mainGui
|
||||
|
||||
iPx = CONFIG.pxInt(22)
|
||||
mPx = CONFIG.pxInt(12)
|
||||
|
||||
@@ -230,7 +227,7 @@ class GuiOutlineToolBar(QToolBar):
|
||||
self.novelLabel = QLabel(self.tr("Outline of"))
|
||||
self.novelLabel.setContentsMargins(0, 0, mPx, 0)
|
||||
|
||||
self.novelValue = NovelSelector(self, self.mainGui)
|
||||
self.novelValue = NovelSelector(self)
|
||||
self.novelValue.setMinimumWidth(CONFIG.pxInt(200))
|
||||
self.novelValue.novelSelectionChanged.connect(self._novelValueChanged)
|
||||
|
||||
@@ -272,8 +269,8 @@ class GuiOutlineToolBar(QToolBar):
|
||||
self.setStyleSheet("QToolBar {border: 0px;}")
|
||||
|
||||
self.novelValue.updateList(includeAll=True)
|
||||
self.aRefresh.setIcon(CONFIG.theme.getIcon("refresh"))
|
||||
self.tbColumns.setIcon(CONFIG.theme.getIcon("menu"))
|
||||
self.aRefresh.setIcon(SHARED.theme.getIcon("refresh"))
|
||||
self.tbColumns.setIcon(SHARED.theme.getIcon("menu"))
|
||||
|
||||
return
|
||||
|
||||
@@ -370,7 +367,6 @@ class GuiOutlineTree(QTreeWidget):
|
||||
logger.debug("Create: GuiOutlineTree")
|
||||
|
||||
self.outlineView = outlineView
|
||||
self.mainGui = outlineView.mainGui
|
||||
|
||||
self.setUniformRowHeights(True)
|
||||
self.setFrameStyle(QFrame.NoFrame)
|
||||
@@ -381,7 +377,7 @@ class GuiOutlineTree(QTreeWidget):
|
||||
self.itemDoubleClicked.connect(self._treeDoubleClick)
|
||||
self.itemSelectionChanged.connect(self._itemSelected)
|
||||
|
||||
iPx = CONFIG.theme.baseIconSize
|
||||
iPx = SHARED.theme.baseIconSize
|
||||
self.setIconSize(QSize(iPx, iPx))
|
||||
self.setIndentation(0)
|
||||
|
||||
@@ -398,11 +394,11 @@ class GuiOutlineTree(QTreeWidget):
|
||||
|
||||
self._hFonts = [self.font(), fH1, fH2, self.font(), self.font()]
|
||||
self._dIcon = {
|
||||
"H0": CONFIG.theme.getItemIcon(nwItemType.FILE, None, nwItemLayout.DOCUMENT, "H0"),
|
||||
"H1": CONFIG.theme.getItemIcon(nwItemType.FILE, None, nwItemLayout.DOCUMENT, "H1"),
|
||||
"H2": CONFIG.theme.getItemIcon(nwItemType.FILE, None, nwItemLayout.DOCUMENT, "H2"),
|
||||
"H3": CONFIG.theme.getItemIcon(nwItemType.FILE, None, nwItemLayout.DOCUMENT, "H3"),
|
||||
"H4": CONFIG.theme.getItemIcon(nwItemType.FILE, None, nwItemLayout.DOCUMENT, "H4"),
|
||||
"H0": SHARED.theme.getItemIcon(nwItemType.FILE, None, nwItemLayout.DOCUMENT, "H0"),
|
||||
"H1": SHARED.theme.getItemIcon(nwItemType.FILE, None, nwItemLayout.DOCUMENT, "H1"),
|
||||
"H2": SHARED.theme.getItemIcon(nwItemType.FILE, None, nwItemLayout.DOCUMENT, "H2"),
|
||||
"H3": SHARED.theme.getItemIcon(nwItemType.FILE, None, nwItemLayout.DOCUMENT, "H3"),
|
||||
"H4": SHARED.theme.getItemIcon(nwItemType.FILE, None, nwItemLayout.DOCUMENT, "H4"),
|
||||
}
|
||||
|
||||
# Internals
|
||||
@@ -488,13 +484,13 @@ class GuiOutlineTree(QTreeWidget):
|
||||
|
||||
# If the novel index or novel tree has changed since the tree
|
||||
# was last built, we rebuild the tree from the updated index.
|
||||
indexChanged = self.mainGui.project.index.rootChangedSince(rootHandle, self._lastBuild)
|
||||
indexChanged = SHARED.project.index.rootChangedSince(rootHandle, self._lastBuild)
|
||||
if not (novelChanged or indexChanged or overRide):
|
||||
logger.debug("No changes have been made to the novel index")
|
||||
return
|
||||
|
||||
self._populateTree(rootHandle)
|
||||
self.mainGui.project.data.setLastHandle(rootHandle or None, "outline")
|
||||
SHARED.project.data.setLastHandle(rootHandle or None, "outline")
|
||||
|
||||
return
|
||||
|
||||
@@ -574,7 +570,7 @@ class GuiOutlineTree(QTreeWidget):
|
||||
"""
|
||||
# Load whatever we saved last time, regardless of wether it
|
||||
# contains the correct names or number of columns.
|
||||
colState = self.mainGui.project.options.getValue("GuiOutline", "columnState", {})
|
||||
colState = SHARED.project.options.getValue("GuiOutline", "columnState", {})
|
||||
|
||||
tmpOrder = []
|
||||
tmpHidden = {}
|
||||
@@ -625,7 +621,7 @@ class GuiOutlineTree(QTreeWidget):
|
||||
logHidden, orgWidth if logHidden and logWidth == 0 else logWidth
|
||||
]
|
||||
|
||||
pOptions = self.mainGui.project.options
|
||||
pOptions = SHARED.project.options
|
||||
pOptions.setValue("GuiOutline", "columnState", colState)
|
||||
pOptions.saveSettings()
|
||||
|
||||
@@ -661,7 +657,7 @@ class GuiOutlineTree(QTreeWidget):
|
||||
headItem.setTextAlignment(self._colIdx[nwOutline.WCOUNT], Qt.AlignRight)
|
||||
headItem.setTextAlignment(self._colIdx[nwOutline.PCOUNT], Qt.AlignRight)
|
||||
|
||||
novStruct = self.mainGui.project.index.novelStructure(rootHandle=rootHandle, skipExcl=True)
|
||||
novStruct = SHARED.project.index.novelStructure(rootHandle=rootHandle, skipExcl=True)
|
||||
for _, tHandle, sTitle, novIdx in novStruct:
|
||||
|
||||
iLevel = nwHeaders.H_LEVEL.get(novIdx.level, 0)
|
||||
@@ -669,8 +665,8 @@ class GuiOutlineTree(QTreeWidget):
|
||||
continue
|
||||
|
||||
trItem = QTreeWidgetItem()
|
||||
nwItem = self.mainGui.project.tree[tHandle]
|
||||
hDec = CONFIG.theme.getHeaderDecoration(iLevel)
|
||||
nwItem = SHARED.project.tree[tHandle]
|
||||
hDec = SHARED.theme.getHeaderDecoration(iLevel)
|
||||
|
||||
trItem.setData(self._colIdx[nwOutline.TITLE], Qt.DecorationRole, hDec)
|
||||
trItem.setText(self._colIdx[nwOutline.TITLE], novIdx.title)
|
||||
@@ -689,7 +685,7 @@ class GuiOutlineTree(QTreeWidget):
|
||||
trItem.setTextAlignment(self._colIdx[nwOutline.WCOUNT], Qt.AlignRight)
|
||||
trItem.setTextAlignment(self._colIdx[nwOutline.PCOUNT], Qt.AlignRight)
|
||||
|
||||
refs = self.mainGui.project.index.getReferences(tHandle, sTitle)
|
||||
refs = SHARED.project.index.getReferences(tHandle, sTitle)
|
||||
trItem.setText(self._colIdx[nwOutline.POV], ", ".join(refs[nwKeyWords.POV_KEY]))
|
||||
trItem.setText(self._colIdx[nwOutline.FOCUS], ", ".join(refs[nwKeyWords.FOCUS_KEY]))
|
||||
trItem.setText(self._colIdx[nwOutline.CHAR], ", ".join(refs[nwKeyWords.CHAR_KEY]))
|
||||
@@ -770,12 +766,11 @@ class GuiOutlineDetails(QScrollArea):
|
||||
logger.debug("Create: GuiOutlineDetails")
|
||||
|
||||
self.theOutline = theOutline
|
||||
self.mainGui = theOutline.mainGui
|
||||
|
||||
# Sizes
|
||||
minTitle = 30*CONFIG.theme.textNWidth
|
||||
maxTitle = 40*CONFIG.theme.textNWidth
|
||||
wCount = CONFIG.theme.getTextWidth("999,999")
|
||||
minTitle = 30*SHARED.theme.textNWidth
|
||||
maxTitle = 40*SHARED.theme.textNWidth
|
||||
wCount = SHARED.theme.getTextWidth("999,999")
|
||||
hSpace = int(CONFIG.pxInt(10))
|
||||
vSpace = int(CONFIG.pxInt(4))
|
||||
|
||||
@@ -1005,8 +1000,8 @@ class GuiOutlineDetails(QScrollArea):
|
||||
"""Update the content of the tree with the given handle and line
|
||||
number pointing to a header.
|
||||
"""
|
||||
pIndex = self.mainGui.project.index
|
||||
nwItem = self.mainGui.project.tree[tHandle]
|
||||
pIndex = SHARED.project.index
|
||||
nwItem = SHARED.project.tree[tHandle]
|
||||
novIdx = pIndex.getItemHeader(tHandle, sTitle)
|
||||
theRefs = pIndex.getReferences(tHandle, sTitle)
|
||||
if nwItem is None or novIdx is None:
|
||||
@@ -1049,7 +1044,7 @@ class GuiOutlineDetails(QScrollArea):
|
||||
def updateClasses(self):
|
||||
"""Update the visibility status of class details.
|
||||
"""
|
||||
usedClasses = self.mainGui.project.tree.rootClasses()
|
||||
usedClasses = SHARED.project.tree.rootClasses()
|
||||
|
||||
pltVisible = nwItemClass.PLOT in usedClasses
|
||||
timVisible = nwItemClass.TIMELINE in usedClasses
|
||||
|
||||
+97
-107
@@ -39,7 +39,7 @@ from PyQt5.QtWidgets import (
|
||||
QVBoxLayout, QWidget
|
||||
)
|
||||
|
||||
from novelwriter import CONFIG
|
||||
from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.common import minmax
|
||||
from novelwriter.constants import nwHeaders, nwUnicode, trConst, nwLabels
|
||||
from novelwriter.core.item import NWItem
|
||||
@@ -49,7 +49,7 @@ from novelwriter.dialogs.docsplit import GuiDocSplit
|
||||
from novelwriter.dialogs.editlabel import GuiEditLabel
|
||||
from novelwriter.dialogs.projsettings import GuiProjectSettings
|
||||
from novelwriter.enum import (
|
||||
nwDocMode, nwItemType, nwItemClass, nwItemLayout, nwAlert, nwWidget
|
||||
nwDocMode, nwItemType, nwItemClass, nwItemLayout, nwWidget
|
||||
)
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
@@ -165,7 +165,7 @@ class GuiProjectView(QWidget):
|
||||
self.projTree.initSettings()
|
||||
return
|
||||
|
||||
def clearProject(self) -> None:
|
||||
def clearProjectView(self) -> None:
|
||||
"""Clear project-related GUI content."""
|
||||
self.projBar.clearContent()
|
||||
self.projBar.setEnabled(False)
|
||||
@@ -236,7 +236,7 @@ class GuiProjectToolBar(QWidget):
|
||||
self.projTree = projView.projTree
|
||||
self.mainGui = projView.mainGui
|
||||
|
||||
iPx = CONFIG.theme.baseIconSize
|
||||
iPx = SHARED.theme.baseIconSize
|
||||
mPx = CONFIG.pxInt(2)
|
||||
|
||||
self.setContentsMargins(0, 0, 0, 0)
|
||||
@@ -367,16 +367,16 @@ class GuiProjectToolBar(QWidget):
|
||||
self.tbAdd.setStyleSheet(buttonStyle)
|
||||
self.tbMore.setStyleSheet(buttonStyle)
|
||||
|
||||
self.tbQuick.setIcon(CONFIG.theme.getIcon("bookmark"))
|
||||
self.tbMoveU.setIcon(CONFIG.theme.getIcon("up"))
|
||||
self.tbMoveD.setIcon(CONFIG.theme.getIcon("down"))
|
||||
self.aAddEmpty.setIcon(CONFIG.theme.getIcon("proj_document"))
|
||||
self.aAddChap.setIcon(CONFIG.theme.getIcon("proj_chapter"))
|
||||
self.aAddScene.setIcon(CONFIG.theme.getIcon("proj_scene"))
|
||||
self.aAddNote.setIcon(CONFIG.theme.getIcon("proj_note"))
|
||||
self.aAddFolder.setIcon(CONFIG.theme.getIcon("proj_folder"))
|
||||
self.tbAdd.setIcon(CONFIG.theme.getIcon("add"))
|
||||
self.tbMore.setIcon(CONFIG.theme.getIcon("menu"))
|
||||
self.tbQuick.setIcon(SHARED.theme.getIcon("bookmark"))
|
||||
self.tbMoveU.setIcon(SHARED.theme.getIcon("up"))
|
||||
self.tbMoveD.setIcon(SHARED.theme.getIcon("down"))
|
||||
self.aAddEmpty.setIcon(SHARED.theme.getIcon("proj_document"))
|
||||
self.aAddChap.setIcon(SHARED.theme.getIcon("proj_chapter"))
|
||||
self.aAddScene.setIcon(SHARED.theme.getIcon("proj_scene"))
|
||||
self.aAddNote.setIcon(SHARED.theme.getIcon("proj_note"))
|
||||
self.aAddFolder.setIcon(SHARED.theme.getIcon("proj_folder"))
|
||||
self.tbAdd.setIcon(SHARED.theme.getIcon("add"))
|
||||
self.tbMore.setIcon(SHARED.theme.getIcon("menu"))
|
||||
|
||||
self.buildQuickLinkMenu()
|
||||
self._buildRootMenu()
|
||||
@@ -392,10 +392,10 @@ class GuiProjectToolBar(QWidget):
|
||||
"""Build the quick link menu."""
|
||||
logger.debug("Rebuilding quick links menu")
|
||||
self.mQuick.clear()
|
||||
for n, (tHandle, nwItem) in enumerate(self.mainGui.project.tree.iterRoots(None)):
|
||||
for n, (tHandle, nwItem) in enumerate(SHARED.project.tree.iterRoots(None)):
|
||||
aRoot = self.mQuick.addAction(nwItem.itemName)
|
||||
aRoot.setData(tHandle)
|
||||
aRoot.setIcon(CONFIG.theme.getIcon(nwLabels.CLASS_ICON[nwItem.itemClass]))
|
||||
aRoot.setIcon(SHARED.theme.getIcon(nwLabels.CLASS_ICON[nwItem.itemClass]))
|
||||
aRoot.triggered.connect(
|
||||
lambda n, tHandle=tHandle: self.projView.setSelectedHandle(tHandle, doScroll=True)
|
||||
)
|
||||
@@ -409,7 +409,7 @@ class GuiProjectToolBar(QWidget):
|
||||
"""Build the rood folder menu."""
|
||||
def addClass(itemClass):
|
||||
aNew = self.mAddRoot.addAction(trConst(nwLabels.CLASS_NAME[itemClass]))
|
||||
aNew.setIcon(CONFIG.theme.getIcon(nwLabels.CLASS_ICON[itemClass]))
|
||||
aNew.setIcon(SHARED.theme.getIcon(nwLabels.CLASS_ICON[itemClass]))
|
||||
aNew.triggered.connect(lambda: self.projTree.newTreeItem(nwItemType.ROOT, itemClass))
|
||||
self.mAddRoot.addAction(aNew)
|
||||
return
|
||||
@@ -438,7 +438,7 @@ class GuiProjectToolBar(QWidget):
|
||||
documents. They should only be visible if novel documents can
|
||||
actually be added.
|
||||
"""
|
||||
nwItem = self.mainGui.project.tree[tHandle]
|
||||
nwItem = SHARED.project.tree[tHandle]
|
||||
allowDoc = isinstance(nwItem, NWItem) and nwItem.documentAllowed()
|
||||
self.aAddEmpty.setVisible(allowDoc)
|
||||
self.aAddChap.setVisible(allowDoc)
|
||||
@@ -480,7 +480,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
self.customContextMenuRequested.connect(self._openContextMenu)
|
||||
|
||||
# Tree Settings
|
||||
iPx = CONFIG.theme.baseIconSize
|
||||
iPx = SHARED.theme.baseIconSize
|
||||
cMg = CONFIG.pxInt(6)
|
||||
|
||||
self.setIconSize(QSize(iPx, iPx))
|
||||
@@ -563,7 +563,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
make sure the item is added in a place it can be added, and that
|
||||
other meta data is set correctly to ensure a valid project tree.
|
||||
"""
|
||||
if not self.mainGui.hasProject:
|
||||
if not SHARED.hasProject:
|
||||
logger.error("No project open")
|
||||
return False
|
||||
|
||||
@@ -572,19 +572,17 @@ class GuiProjectTree(QTreeWidget):
|
||||
|
||||
if itemType == nwItemType.ROOT and isinstance(itemClass, nwItemClass):
|
||||
|
||||
tHandle = self.mainGui.project.newRoot(itemClass)
|
||||
tHandle = SHARED.project.newRoot(itemClass)
|
||||
sHandle = self.getSelectedHandle()
|
||||
pItem = self.mainGui.project.tree[sHandle] if sHandle else None
|
||||
pItem = SHARED.project.tree[sHandle] if sHandle else None
|
||||
nHandle = pItem.itemRoot if pItem else None
|
||||
|
||||
elif itemType in (nwItemType.FILE, nwItemType.FOLDER):
|
||||
|
||||
sHandle = self.getSelectedHandle()
|
||||
pItem = self.mainGui.project.tree[sHandle] if sHandle else None
|
||||
pItem = SHARED.project.tree[sHandle] if sHandle else None
|
||||
if sHandle is None or pItem is None:
|
||||
self.mainGui.makeAlert(self.tr(
|
||||
"Did not find anywhere to add the file or folder!"
|
||||
), level=nwAlert.ERROR)
|
||||
SHARED.error(self.tr("Did not find anywhere to add the file or folder!"))
|
||||
return False
|
||||
|
||||
# Collect some information about the selected item
|
||||
@@ -592,10 +590,8 @@ class GuiProjectTree(QTreeWidget):
|
||||
sLevel = nwHeaders.H_LEVEL.get(pItem.mainHeading, 0)
|
||||
sIsParent = False if qItem is None else qItem.childCount() > 0
|
||||
|
||||
if self.mainGui.project.tree.isTrash(sHandle):
|
||||
self.mainGui.makeAlert(self.tr(
|
||||
"Cannot add new files or folders to the Trash folder."
|
||||
), level=nwAlert.ERROR)
|
||||
if SHARED.project.tree.isTrash(sHandle):
|
||||
SHARED.error(self.tr("Cannot add new files or folders to the Trash folder."))
|
||||
return False
|
||||
|
||||
# Set default label and determine if new item is to be added
|
||||
@@ -635,9 +631,9 @@ class GuiProjectTree(QTreeWidget):
|
||||
|
||||
# Add the file or folder
|
||||
if itemType == nwItemType.FILE:
|
||||
tHandle = self.mainGui.project.newFile(newLabel, sHandle)
|
||||
tHandle = SHARED.project.newFile(newLabel, sHandle)
|
||||
else:
|
||||
tHandle = self.mainGui.project.newFolder(newLabel, sHandle)
|
||||
tHandle = SHARED.project.newFolder(newLabel, sHandle)
|
||||
|
||||
else:
|
||||
logger.error("Failed to add new item")
|
||||
@@ -650,7 +646,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
|
||||
# Handle new file creation
|
||||
if itemType == nwItemType.FILE and hLevel > 0:
|
||||
self.mainGui.project.writeNewFile(tHandle, hLevel, not isNote)
|
||||
SHARED.project.writeNewFile(tHandle, hLevel, not isNote)
|
||||
|
||||
# Add the new item to the project tree
|
||||
self.revealNewTreeItem(tHandle, nHandle=nHandle, wordCount=True)
|
||||
@@ -661,7 +657,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
def revealNewTreeItem(self, tHandle: str | None, nHandle: str | None = None,
|
||||
wordCount: bool = False) -> bool:
|
||||
"""Reveal a newly added project item in the project tree."""
|
||||
nwItem = self.mainGui.project.tree[tHandle] if tHandle else None
|
||||
nwItem = SHARED.project.tree[tHandle] if tHandle else None
|
||||
if tHandle is None or nwItem is None:
|
||||
return False
|
||||
|
||||
@@ -670,7 +666,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
return False
|
||||
|
||||
if nwItem.isFileType() and wordCount:
|
||||
wC = self.mainGui.project.index.getCounts(tHandle)[1]
|
||||
wC = SHARED.project.index.getCounts(tHandle)[1]
|
||||
self.propagateCount(tHandle, wC)
|
||||
self.projView.wordCountsChanged.emit()
|
||||
|
||||
@@ -745,7 +741,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
|
||||
def renameTreeItem(self, tHandle: str) -> bool:
|
||||
"""Open a dialog to edit the label of an item."""
|
||||
tItem = self.mainGui.project.tree[tHandle]
|
||||
tItem = SHARED.project.tree[tHandle]
|
||||
if tItem is None:
|
||||
return False
|
||||
|
||||
@@ -769,7 +765,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
if isinstance(item, QTreeWidgetItem):
|
||||
theList = self._scanChildren(theList, item, i)
|
||||
logger.debug("Saving project tree item order")
|
||||
self.mainGui.project.setTreeOrder(theList)
|
||||
SHARED.project.setTreeOrder(theList)
|
||||
return
|
||||
|
||||
def getTreeFromHandle(self, tHandle: str) -> list[str]:
|
||||
@@ -787,7 +783,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
can be called on any item, and will check whether to attempt a
|
||||
permanent deletion or moving the item to Trash.
|
||||
"""
|
||||
if not self.mainGui.hasProject:
|
||||
if not SHARED.hasProject:
|
||||
logger.error("No project open")
|
||||
return False
|
||||
|
||||
@@ -802,16 +798,16 @@ class GuiProjectTree(QTreeWidget):
|
||||
logger.error("There is no item to delete")
|
||||
return False
|
||||
|
||||
trashHandle = self.mainGui.project.tree.trashRoot()
|
||||
trashHandle = SHARED.project.tree.trashRoot()
|
||||
if tHandle == trashHandle:
|
||||
logger.error("Cannot delete the Trash folder")
|
||||
return False
|
||||
|
||||
nwItem = self.mainGui.project.tree[tHandle]
|
||||
nwItem = SHARED.project.tree[tHandle]
|
||||
if nwItem is None:
|
||||
return False
|
||||
|
||||
if self.mainGui.project.tree.isTrash(tHandle) or nwItem.isRootType():
|
||||
if SHARED.project.tree.isTrash(tHandle) or nwItem.isRootType():
|
||||
status = self.permDeleteItem(tHandle)
|
||||
else:
|
||||
status = self.moveItemToTrash(tHandle)
|
||||
@@ -823,17 +819,15 @@ class GuiProjectTree(QTreeWidget):
|
||||
function only asks for confirmation once, and calls the regular
|
||||
deleteItem function for each document in the Trash folder.
|
||||
"""
|
||||
if not self.mainGui.hasProject:
|
||||
if not SHARED.hasProject:
|
||||
logger.error("No project open")
|
||||
return False
|
||||
|
||||
trashHandle = self.mainGui.project.tree.trashRoot()
|
||||
trashHandle = SHARED.project.tree.trashRoot()
|
||||
|
||||
logger.debug("Emptying Trash folder")
|
||||
if trashHandle is None:
|
||||
self.mainGui.makeAlert(self.tr(
|
||||
"There is currently no Trash folder in this project."
|
||||
))
|
||||
SHARED.info(self.tr("There is currently no Trash folder in this project."))
|
||||
return False
|
||||
|
||||
theTrash = self.getTreeFromHandle(trashHandle)
|
||||
@@ -842,12 +836,10 @@ class GuiProjectTree(QTreeWidget):
|
||||
|
||||
nTrash = len(theTrash)
|
||||
if nTrash == 0:
|
||||
self.mainGui.makeAlert(self.tr(
|
||||
"The Trash folder is already empty."
|
||||
))
|
||||
SHARED.info(self.tr("The Trash folder is already empty."))
|
||||
return False
|
||||
|
||||
msgYes = self.mainGui.askQuestion(
|
||||
msgYes = SHARED.question(
|
||||
self.tr("Permanently delete {0} file(s) from Trash?").format(nTrash)
|
||||
)
|
||||
if not msgYes:
|
||||
@@ -870,13 +862,13 @@ class GuiProjectTree(QTreeWidget):
|
||||
so such a request is cancelled.
|
||||
"""
|
||||
trItemS = self._getTreeItem(tHandle)
|
||||
nwItemS = self.mainGui.project.tree[tHandle]
|
||||
nwItemS = SHARED.project.tree[tHandle]
|
||||
|
||||
if trItemS is None or nwItemS is None:
|
||||
logger.error("Could not find tree item for deletion")
|
||||
return False
|
||||
|
||||
if self.mainGui.project.tree.isTrash(tHandle):
|
||||
if SHARED.project.tree.isTrash(tHandle):
|
||||
logger.error("Item is already in the Trash folder")
|
||||
return False
|
||||
|
||||
@@ -893,8 +885,8 @@ class GuiProjectTree(QTreeWidget):
|
||||
return False
|
||||
|
||||
if askFirst:
|
||||
msgYes = self.mainGui.askQuestion(
|
||||
self.tr("Move '{0}' to Trash?").format(nwItemS.itemName),
|
||||
msgYes = SHARED.question(
|
||||
self.tr("Move '{0}' to Trash?").format(nwItemS.itemName)
|
||||
)
|
||||
if not msgYes:
|
||||
logger.info("Action cancelled by user")
|
||||
@@ -920,7 +912,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
Root items are handled a little different than other items.
|
||||
"""
|
||||
trItemS = self._getTreeItem(tHandle)
|
||||
nwItemS = self.mainGui.project.tree[tHandle]
|
||||
nwItemS = SHARED.project.tree[tHandle]
|
||||
if trItemS is None or nwItemS is None:
|
||||
logger.error("Could not find tree item for deletion")
|
||||
return False
|
||||
@@ -928,16 +920,14 @@ class GuiProjectTree(QTreeWidget):
|
||||
if nwItemS.isRootType():
|
||||
# Only an empty ROOT folder can be deleted
|
||||
if trItemS.childCount() > 0:
|
||||
self.mainGui.makeAlert(self.tr(
|
||||
"Root folders can only be deleted when they are empty."
|
||||
), level=nwAlert.ERROR)
|
||||
SHARED.error(self.tr("Root folders can only be deleted when they are empty."))
|
||||
return False
|
||||
|
||||
logger.debug("Permanently deleting root folder '%s'", tHandle)
|
||||
|
||||
tIndex = self.indexOfTopLevelItem(trItemS)
|
||||
self.takeTopLevelItem(tIndex)
|
||||
self.mainGui.project.removeItem(tHandle)
|
||||
SHARED.project.removeItem(tHandle)
|
||||
self._treeMap.pop(tHandle, None)
|
||||
self._alertTreeChange(tHandle, flush=True)
|
||||
|
||||
@@ -948,7 +938,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
|
||||
else:
|
||||
if askFirst:
|
||||
msgYes = self.mainGui.askQuestion(
|
||||
msgYes = SHARED.question(
|
||||
self.tr("Permanently delete '{0}'?").format(nwItemS.itemName)
|
||||
)
|
||||
if not msgYes:
|
||||
@@ -964,9 +954,9 @@ class GuiProjectTree(QTreeWidget):
|
||||
trItemP.takeChild(tIndex)
|
||||
|
||||
for dHandle in reversed(self.getTreeFromHandle(tHandle)):
|
||||
if self.mainGui.docEditor.docHandle() == dHandle:
|
||||
if self.mainGui.docEditor.docHandle == dHandle:
|
||||
self.mainGui.closeDocument()
|
||||
self.mainGui.project.removeItem(dHandle)
|
||||
SHARED.project.removeItem(dHandle)
|
||||
self._treeMap.pop(dHandle, None)
|
||||
|
||||
self._alertTreeChange(tHandle, flush=flush)
|
||||
@@ -984,13 +974,13 @@ class GuiProjectTree(QTreeWidget):
|
||||
already coming from the project tree.
|
||||
"""
|
||||
trItem = self._getTreeItem(tHandle)
|
||||
nwItem = self.mainGui.project.tree[tHandle]
|
||||
nwItem = SHARED.project.tree[tHandle]
|
||||
if trItem is None or nwItem is None:
|
||||
return
|
||||
|
||||
itemStatus, statusIcon = nwItem.getImportStatus(incIcon=True)
|
||||
hLevel = nwItem.mainHeading
|
||||
itemIcon = CONFIG.theme.getItemIcon(
|
||||
itemIcon = SHARED.theme.getItemIcon(
|
||||
nwItem.itemType, nwItem.itemClass, nwItem.itemLayout, hLevel
|
||||
)
|
||||
|
||||
@@ -1006,7 +996,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
else:
|
||||
iconName = "noncheckable"
|
||||
|
||||
trItem.setIcon(self.C_ACTIVE, CONFIG.theme.getIcon(iconName))
|
||||
trItem.setIcon(self.C_ACTIVE, SHARED.theme.getIcon(iconName))
|
||||
|
||||
if CONFIG.emphLabels and nwItem.isDocumentLayout():
|
||||
trFont = trItem.font(self.C_NAME)
|
||||
@@ -1046,10 +1036,10 @@ class GuiProjectTree(QTreeWidget):
|
||||
pHandle = pItem.data(self.C_DATA, self.D_HANDLE)
|
||||
|
||||
if pHandle:
|
||||
if self.mainGui.project.tree.checkType(pHandle, nwItemType.FILE):
|
||||
if SHARED.project.tree.checkType(pHandle, nwItemType.FILE):
|
||||
# A file has an internal word count we need to account
|
||||
# for, but a folder always has 0 words on its own.
|
||||
pCount += self.mainGui.project.index.getCounts(pHandle)[1]
|
||||
pCount += SHARED.project.index.getCounts(pHandle)[1]
|
||||
|
||||
self.propagateCount(pHandle, pCount, countChildren=False)
|
||||
|
||||
@@ -1064,7 +1054,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
logger.debug("Building the project tree ...")
|
||||
self.clearTree()
|
||||
count = 0
|
||||
for nwItem in self.mainGui.project.getProjectItems():
|
||||
for nwItem in SHARED.project.iterProjectItems():
|
||||
count += 1
|
||||
self._addTreeItem(nwItem)
|
||||
if count > 0:
|
||||
@@ -1177,7 +1167,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
if tHandle is None:
|
||||
return
|
||||
|
||||
tItem = self.mainGui.project.tree[tHandle]
|
||||
tItem = SHARED.project.tree[tHandle]
|
||||
if tItem is None:
|
||||
return
|
||||
|
||||
@@ -1199,7 +1189,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
selItem = self.itemAt(clickPos)
|
||||
if isinstance(selItem, QTreeWidgetItem):
|
||||
tHandle = selItem.data(self.C_DATA, self.D_HANDLE)
|
||||
tItem = self.mainGui.project.tree[tHandle]
|
||||
tItem = SHARED.project.tree[tHandle]
|
||||
hasChild = selItem.childCount() > 0
|
||||
|
||||
if tItem is None or tHandle is None:
|
||||
@@ -1211,7 +1201,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
# Trash Folder
|
||||
# ============
|
||||
|
||||
trashHandle = self.mainGui.project.tree.trashRoot()
|
||||
trashHandle = SHARED.project.tree.trashRoot()
|
||||
if tItem.itemHandle == trashHandle and trashHandle is not None:
|
||||
# The trash folder only has one option
|
||||
aEmptyTrash = ctxMenu.addAction(self.tr("Empty Trash"))
|
||||
@@ -1250,7 +1240,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
checkMark = f" ({nwUnicode.U_CHECK})"
|
||||
if tItem.isNovelLike():
|
||||
mStatus = ctxMenu.addMenu(self.tr("Set Status to ..."))
|
||||
for n, (key, entry) in enumerate(self.mainGui.project.data.itemStatus.items()):
|
||||
for n, (key, entry) in enumerate(SHARED.project.data.itemStatus.items()):
|
||||
entryName = entry["name"] + (checkMark if tItem.itemStatus == key else "")
|
||||
aStatus = mStatus.addAction(entry["icon"], entryName)
|
||||
aStatus.triggered.connect(
|
||||
@@ -1263,7 +1253,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
)
|
||||
else:
|
||||
mImport = ctxMenu.addMenu(self.tr("Set Importance to ..."))
|
||||
for n, (key, entry) in enumerate(self.mainGui.project.data.itemImport.items()):
|
||||
for n, (key, entry) in enumerate(SHARED.project.data.itemImport.items()):
|
||||
entryName = entry["name"] + (checkMark if tItem.itemImport == key else "")
|
||||
aImport = mImport.addAction(entry["icon"], entryName)
|
||||
aImport.triggered.connect(
|
||||
@@ -1375,7 +1365,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
return
|
||||
|
||||
tHandle = selItem.data(self.C_DATA, self.D_HANDLE)
|
||||
tItem = self.mainGui.project.tree[tHandle]
|
||||
tItem = SHARED.project.tree[tHandle]
|
||||
if tItem is None:
|
||||
return
|
||||
|
||||
@@ -1419,7 +1409,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
def _postItemMove(self, tHandle: str, wCount: int) -> bool:
|
||||
"""Run various maintenance tasks for a moved item."""
|
||||
trItemS = self._getTreeItem(tHandle)
|
||||
nwItemS = self.mainGui.project.tree[tHandle]
|
||||
nwItemS = SHARED.project.tree[tHandle]
|
||||
trItemP = trItemS.parent() if trItemS else None
|
||||
if trItemP is None or nwItemS is None:
|
||||
logger.error("Failed to find new parent item of '%s'", tHandle)
|
||||
@@ -1436,13 +1426,13 @@ class GuiProjectTree(QTreeWidget):
|
||||
logger.debug("A total of %d item(s) were moved", len(mHandles))
|
||||
for mHandle in mHandles:
|
||||
logger.debug("Updating item '%s'", mHandle)
|
||||
self.mainGui.project.tree.updateItemData(mHandle)
|
||||
SHARED.project.tree.updateItemData(mHandle)
|
||||
|
||||
# Update the index
|
||||
if nwItemS.isInactiveClass():
|
||||
self.mainGui.project.index.deleteHandle(mHandle)
|
||||
SHARED.project.index.deleteHandle(mHandle)
|
||||
else:
|
||||
self.mainGui.project.index.reIndexHandle(mHandle)
|
||||
SHARED.project.index.reIndexHandle(mHandle)
|
||||
|
||||
self.setTreeItemValues(mHandle)
|
||||
|
||||
@@ -1462,7 +1452,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
|
||||
def _toggleItemActive(self, tHandle: str) -> None:
|
||||
"""Toggle the active status of an item."""
|
||||
tItem = self.mainGui.project.tree[tHandle]
|
||||
tItem = SHARED.project.tree[tHandle]
|
||||
if tItem is not None:
|
||||
tItem.setActive(not tItem.isActive)
|
||||
self.setTreeItemValues(tItem.itemHandle)
|
||||
@@ -1483,7 +1473,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
|
||||
def _changeItemStatus(self, tHandle: str, tStatus: str) -> None:
|
||||
"""Set a new status value of an item."""
|
||||
tItem = self.mainGui.project.tree[tHandle]
|
||||
tItem = SHARED.project.tree[tHandle]
|
||||
if tItem is not None:
|
||||
tItem.setStatus(tStatus)
|
||||
self.setTreeItemValues(tItem.itemHandle)
|
||||
@@ -1492,7 +1482,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
|
||||
def _changeItemImport(self, tHandle: str, tImport: str) -> None:
|
||||
"""Set a new importance value of an item."""
|
||||
tItem = self.mainGui.project.tree[tHandle]
|
||||
tItem = SHARED.project.tree[tHandle]
|
||||
if tItem is not None:
|
||||
tItem.setImport(tImport)
|
||||
self.setTreeItemValues(tItem.itemHandle)
|
||||
@@ -1501,7 +1491,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
|
||||
def _changeItemLayout(self, tHandle: str, itemLayout: nwItemLayout) -> None:
|
||||
"""Set a new item layout value of an item."""
|
||||
tItem = self.mainGui.project.tree[tHandle]
|
||||
tItem = SHARED.project.tree[tHandle]
|
||||
if tItem is not None:
|
||||
if itemLayout == nwItemLayout.DOCUMENT and tItem.documentAllowed():
|
||||
tItem.setLayout(nwItemLayout.DOCUMENT)
|
||||
@@ -1515,9 +1505,9 @@ class GuiProjectTree(QTreeWidget):
|
||||
|
||||
def _covertFolderToFile(self, tHandle: str, itemLayout: nwItemLayout) -> None:
|
||||
"""Convert a folder to a note or document."""
|
||||
tItem = self.mainGui.project.tree[tHandle]
|
||||
tItem = SHARED.project.tree[tHandle]
|
||||
if tItem is not None and tItem.isFolderType():
|
||||
msgYes = self.mainGui.askQuestion(self.tr(
|
||||
msgYes = SHARED.question(self.tr(
|
||||
"Do you want to convert the folder to a {0}? "
|
||||
"This action cannot be reversed."
|
||||
).format(trConst(nwLabels.LAYOUT_NAME[itemLayout])))
|
||||
@@ -1540,7 +1530,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
logger.info("Request to merge items under handle '%s'", tHandle)
|
||||
itemList = self.getTreeFromHandle(tHandle)
|
||||
|
||||
tItem = self.mainGui.project.tree[tHandle]
|
||||
tItem = SHARED.project.tree[tHandle]
|
||||
if tItem is None:
|
||||
return False
|
||||
|
||||
@@ -1559,14 +1549,14 @@ class GuiProjectTree(QTreeWidget):
|
||||
mrgData = dlgMerge.getData()
|
||||
mrgList = mrgData.get("finalItems", [])
|
||||
if not mrgList:
|
||||
self.mainGui.makeAlert(self.tr("No documents selected for merging."))
|
||||
SHARED.info(self.tr("No documents selected for merging."))
|
||||
return False
|
||||
|
||||
# Save the open document first, in case it's part of merge
|
||||
self.mainGui.saveDocument()
|
||||
|
||||
# Create merge object, and append docs
|
||||
docMerger = DocMerger(self.mainGui.project)
|
||||
docMerger = DocMerger(SHARED.project)
|
||||
mLabel = self.tr("Merged")
|
||||
|
||||
if newFile:
|
||||
@@ -1582,13 +1572,13 @@ class GuiProjectTree(QTreeWidget):
|
||||
docMerger.appendText(sHandle, True, mLabel)
|
||||
|
||||
if not docMerger.writeTargetDoc():
|
||||
self.mainGui.makeAlert(
|
||||
SHARED.error(
|
||||
self.tr("Could not write document content."),
|
||||
info=docMerger.getError(), level=nwAlert.ERROR
|
||||
info=docMerger.getError()
|
||||
)
|
||||
return False
|
||||
|
||||
self.mainGui.project.index.reIndexHandle(mHandle)
|
||||
SHARED.project.index.reIndexHandle(mHandle)
|
||||
if newFile:
|
||||
self.revealNewTreeItem(mHandle, nHandle=tHandle, wordCount=True)
|
||||
|
||||
@@ -1613,7 +1603,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
"""Split a document into multiple documents."""
|
||||
logger.info("Request to split items with handle '%s'", tHandle)
|
||||
|
||||
tItem = self.mainGui.project.tree[tHandle]
|
||||
tItem = SHARED.project.tree[tHandle]
|
||||
if tItem is None:
|
||||
return False
|
||||
|
||||
@@ -1632,7 +1622,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
intoFolder = splitData.get("intoFolder", False)
|
||||
docHierarchy = splitData.get("docHierarchy", False)
|
||||
|
||||
docSplit = DocSplitter(self.mainGui.project, tHandle)
|
||||
docSplit = DocSplitter(SHARED.project, tHandle)
|
||||
if intoFolder:
|
||||
fHandle = docSplit.newParentFolder(tItem.itemParent, tItem.itemName)
|
||||
self.revealNewTreeItem(fHandle, nHandle=tHandle)
|
||||
@@ -1642,13 +1632,13 @@ class GuiProjectTree(QTreeWidget):
|
||||
|
||||
docSplit.splitDocument(headerList, splitText)
|
||||
for writeOk, dHandle, nHandle in docSplit.writeDocuments(docHierarchy):
|
||||
self.mainGui.project.index.reIndexHandle(dHandle)
|
||||
SHARED.project.index.reIndexHandle(dHandle)
|
||||
self.revealNewTreeItem(dHandle, nHandle=nHandle, wordCount=True)
|
||||
self._alertTreeChange(dHandle, flush=False)
|
||||
if not writeOk:
|
||||
self.mainGui.makeAlert(
|
||||
SHARED.error(
|
||||
self.tr("Could not write document content."),
|
||||
info=docSplit.getError(), level=nwAlert.ERROR
|
||||
info=docSplit.getError()
|
||||
)
|
||||
|
||||
if splitData.get("moveToTrash", False):
|
||||
@@ -1673,19 +1663,19 @@ class GuiProjectTree(QTreeWidget):
|
||||
else:
|
||||
question = self.tr("Do you want to duplicate this item and all child items?")
|
||||
|
||||
if not self.mainGui.askQuestion(question):
|
||||
if not SHARED.question(question):
|
||||
return False
|
||||
|
||||
docDup = DocDuplicator(self.mainGui.project)
|
||||
docDup = DocDuplicator(SHARED.project)
|
||||
dupCount = 0
|
||||
for dHandle, nHandle in docDup.duplicate(itemTree):
|
||||
self.mainGui.project.index.reIndexHandle(dHandle)
|
||||
SHARED.project.index.reIndexHandle(dHandle)
|
||||
self.revealNewTreeItem(dHandle, nHandle=nHandle, wordCount=True)
|
||||
self._alertTreeChange(dHandle, flush=False)
|
||||
dupCount += 1
|
||||
|
||||
if dupCount != nItems:
|
||||
self.mainGui.makeAlert(self.tr("Could not duplicate all items."), level=nwAlert.WARN)
|
||||
SHARED.warn(self.tr("Could not duplicate all items."))
|
||||
|
||||
self.saveTreeOrder()
|
||||
|
||||
@@ -1699,7 +1689,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
cCount = tItem.childCount()
|
||||
|
||||
# Update tree-related meta data
|
||||
nwItem = self.mainGui.project.tree[tHandle]
|
||||
nwItem = SHARED.project.tree[tHandle]
|
||||
if nwItem is not None:
|
||||
nwItem.setExpanded(tItem.isExpanded() and cCount > 0)
|
||||
nwItem.setOrder(tIndex)
|
||||
@@ -1742,9 +1732,9 @@ class GuiProjectTree(QTreeWidget):
|
||||
elif pHandle and pHandle in self._treeMap:
|
||||
pItem = self._treeMap[pHandle]
|
||||
else:
|
||||
self.mainGui.makeAlert(self.tr(
|
||||
SHARED.error(self.tr(
|
||||
"There is nowhere to add item with name '{0}'."
|
||||
).format(nwItem.itemName), level=nwAlert.ERROR)
|
||||
).format(nwItem.itemName))
|
||||
return None
|
||||
|
||||
byIndex = -1
|
||||
@@ -1766,13 +1756,13 @@ class GuiProjectTree(QTreeWidget):
|
||||
"""Adds the trash root folder if it doesn't already exist in the
|
||||
project tree.
|
||||
"""
|
||||
trashHandle = self.mainGui.project.trashFolder()
|
||||
trashHandle = SHARED.project.trashFolder()
|
||||
if trashHandle is None:
|
||||
return None
|
||||
|
||||
trItem = self._getTreeItem(trashHandle)
|
||||
if trItem is None:
|
||||
trItem = self._addTreeItem(self.mainGui.project.tree[trashHandle])
|
||||
trItem = self._addTreeItem(SHARED.project.tree[trashHandle])
|
||||
if trItem is not None:
|
||||
trItem.setExpanded(True)
|
||||
self._alertTreeChange(trashHandle, flush=True)
|
||||
@@ -1785,14 +1775,14 @@ class GuiProjectTree(QTreeWidget):
|
||||
deleted.
|
||||
"""
|
||||
self._timeChanged = time()
|
||||
self.mainGui.project.setProjectChanged(True)
|
||||
SHARED.project.setProjectChanged(True)
|
||||
if flush:
|
||||
self.saveTreeOrder()
|
||||
|
||||
if tHandle is None or tHandle not in self.mainGui.project.tree:
|
||||
if tHandle is None or tHandle not in SHARED.project.tree:
|
||||
return
|
||||
|
||||
tItem = self.mainGui.project.tree[tHandle]
|
||||
tItem = SHARED.project.tree[tHandle]
|
||||
if tItem and tItem.isRootType():
|
||||
self.projView.rootFolderChanged.emit(tHandle)
|
||||
|
||||
|
||||
+10
-10
@@ -30,7 +30,7 @@ from PyQt5.QtWidgets import (
|
||||
QToolBar, QWidget, QSizePolicy, QAction, QMenu, QToolButton
|
||||
)
|
||||
|
||||
from novelwriter import CONFIG
|
||||
from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.enum import nwView
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -51,8 +51,8 @@ class GuiSideBar(QToolBar):
|
||||
iPx = CONFIG.pxInt(22)
|
||||
mPx = CONFIG.pxInt(60)
|
||||
|
||||
lblFont = CONFIG.theme.guiFont
|
||||
lblFont.setPointSizeF(0.65*CONFIG.theme.fontPointSize)
|
||||
lblFont = SHARED.theme.guiFont
|
||||
lblFont.setPointSizeF(0.65*SHARED.theme.fontPointSize)
|
||||
|
||||
self.setMovable(False)
|
||||
self.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
|
||||
@@ -130,13 +130,13 @@ class GuiSideBar(QToolBar):
|
||||
"""
|
||||
self.setStyleSheet("QToolBar {border: 0px;}")
|
||||
|
||||
self.aProject.setIcon(CONFIG.theme.getIcon("view_editor"))
|
||||
self.aNovel.setIcon(CONFIG.theme.getIcon("view_novel"))
|
||||
self.aOutline.setIcon(CONFIG.theme.getIcon("view_outline"))
|
||||
self.aBuild.setIcon(CONFIG.theme.getIcon("view_build"))
|
||||
self.aDetails.setIcon(CONFIG.theme.getIcon("proj_details"))
|
||||
self.aStats.setIcon(CONFIG.theme.getIcon("proj_stats"))
|
||||
self.tbSettings.setIcon(CONFIG.theme.getIcon("settings"))
|
||||
self.aProject.setIcon(SHARED.theme.getIcon("view_editor"))
|
||||
self.aNovel.setIcon(SHARED.theme.getIcon("view_novel"))
|
||||
self.aOutline.setIcon(SHARED.theme.getIcon("view_outline"))
|
||||
self.aBuild.setIcon(SHARED.theme.getIcon("view_build"))
|
||||
self.aDetails.setIcon(SHARED.theme.getIcon("proj_details"))
|
||||
self.aStats.setIcon(SHARED.theme.getIcon("proj_stats"))
|
||||
self.tbSettings.setIcon(SHARED.theme.getIcon("settings"))
|
||||
|
||||
return
|
||||
|
||||
|
||||
@@ -27,34 +27,38 @@ from __future__ import annotations
|
||||
import logging
|
||||
|
||||
from time import time
|
||||
from typing import TYPE_CHECKING, Literal
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, QLocale
|
||||
from PyQt5.QtGui import QColor
|
||||
from PyQt5.QtWidgets import qApp, QStatusBar, QLabel
|
||||
|
||||
from novelwriter import CONFIG
|
||||
from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.common import formatTime
|
||||
from novelwriter.constants import nwConst
|
||||
from novelwriter.extensions.statusled import StatusLED
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from novelwriter.guimain import GuiMain
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GuiMainStatus(QStatusBar):
|
||||
|
||||
def __init__(self, mainGui):
|
||||
def __init__(self, mainGui: GuiMain) -> None:
|
||||
super().__init__(parent=mainGui)
|
||||
|
||||
logger.debug("Create: GuiMainStatus")
|
||||
|
||||
self.mainGui = mainGui
|
||||
self.refTime = None
|
||||
self.userIdle = False
|
||||
self._refTime = -1.0
|
||||
self._userIdle = False
|
||||
|
||||
colNone = QColor(*CONFIG.theme.statNone)
|
||||
colSaved = QColor(*CONFIG.theme.statSaved)
|
||||
colUnsaved = QColor(*CONFIG.theme.statUnsaved)
|
||||
colNone = QColor(*SHARED.theme.statNone)
|
||||
colSaved = QColor(*SHARED.theme.statSaved)
|
||||
colUnsaved = QColor(*SHARED.theme.statUnsaved)
|
||||
|
||||
iPx = CONFIG.theme.baseIconSize
|
||||
iPx = SHARED.theme.baseIconSize
|
||||
|
||||
# Permanent Widgets
|
||||
# =================
|
||||
@@ -98,7 +102,7 @@ class GuiMainStatus(QStatusBar):
|
||||
self.timeIcon = QLabel()
|
||||
self.timeText = QLabel("")
|
||||
self.timeText.setToolTip(self.tr("Session Time"))
|
||||
self.timeText.setMinimumWidth(CONFIG.theme.getTextWidth("00:00:00:"))
|
||||
self.timeText.setMinimumWidth(SHARED.theme.getTextWidth("00:00:00:"))
|
||||
self.timeIcon.setContentsMargins(0, 0, 0, 0)
|
||||
self.timeText.setContentsMargins(0, 0, 0, 0)
|
||||
self.addPermanentWidget(self.timeIcon)
|
||||
@@ -114,80 +118,59 @@ class GuiMainStatus(QStatusBar):
|
||||
|
||||
return
|
||||
|
||||
def clearStatus(self):
|
||||
"""Reset all widgets on the status bar to default values.
|
||||
"""
|
||||
self.setRefTime(None)
|
||||
def clearStatus(self) -> None:
|
||||
"""Reset all widgets on the status bar to default values."""
|
||||
self.setRefTime(-1.0)
|
||||
self.setLanguage(None, "")
|
||||
self.setProjectStats(0, 0)
|
||||
self.setProjectStatus(StatusLED.S_NONE)
|
||||
self.setDocumentStatus(StatusLED.S_NONE)
|
||||
self.updateTime()
|
||||
return True
|
||||
|
||||
def updateTheme(self):
|
||||
"""Update theme elements.
|
||||
"""
|
||||
iPx = CONFIG.theme.baseIconSize
|
||||
|
||||
self.langIcon.setPixmap(CONFIG.theme.getPixmap("status_lang", (iPx, iPx)))
|
||||
self.statsIcon.setPixmap(CONFIG.theme.getPixmap("status_stats", (iPx, iPx)))
|
||||
|
||||
self.timePixmap = CONFIG.theme.getPixmap("status_time", (iPx, iPx))
|
||||
self.idlePixmap = CONFIG.theme.getPixmap("status_idle", (iPx, iPx))
|
||||
return
|
||||
|
||||
def updateTheme(self) -> None:
|
||||
"""Update theme elements."""
|
||||
iPx = SHARED.theme.baseIconSize
|
||||
self.langIcon.setPixmap(SHARED.theme.getPixmap("status_lang", (iPx, iPx)))
|
||||
self.statsIcon.setPixmap(SHARED.theme.getPixmap("status_stats", (iPx, iPx)))
|
||||
self.timePixmap = SHARED.theme.getPixmap("status_time", (iPx, iPx))
|
||||
self.idlePixmap = SHARED.theme.getPixmap("status_idle", (iPx, iPx))
|
||||
self.timeIcon.setPixmap(self.timePixmap)
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Setters
|
||||
##
|
||||
|
||||
def setRefTime(self, theTime):
|
||||
"""Set the reference time for the status bar clock.
|
||||
"""
|
||||
self.refTime = theTime
|
||||
def setRefTime(self, refTime: float) -> None:
|
||||
"""Set the reference time for the status bar clock."""
|
||||
self._refTime = refTime
|
||||
return
|
||||
|
||||
def setStatus(self, theMessage, timeOut=20.0):
|
||||
"""Set the status bar message to display for 'timeOut' seconds.
|
||||
"""
|
||||
self.showMessage(theMessage, int(timeOut*1000))
|
||||
qApp.processEvents()
|
||||
def setProjectStatus(self, state: Literal[0, 1, 2]) -> None:
|
||||
"""Set the project status colour icon."""
|
||||
self.projIcon.setState(state)
|
||||
return
|
||||
|
||||
def setProjectStatus(self, theState):
|
||||
"""Set the project status colour icon.
|
||||
"""
|
||||
self.projIcon.setState(theState)
|
||||
def setDocumentStatus(self, state: Literal[0, 1, 2]) -> None:
|
||||
"""Set the document status colour icon."""
|
||||
self.docIcon.setState(state)
|
||||
return
|
||||
|
||||
def setDocumentStatus(self, theState):
|
||||
"""Set the document status colour icon.
|
||||
"""
|
||||
self.docIcon.setState(theState)
|
||||
return
|
||||
|
||||
def setUserIdle(self, userIdle):
|
||||
"""Change the idle status icon.
|
||||
"""
|
||||
def setUserIdle(self, idle: bool) -> None:
|
||||
"""Change the idle status icon."""
|
||||
if not CONFIG.stopWhenIdle:
|
||||
userIdle = False
|
||||
|
||||
if self.userIdle != userIdle:
|
||||
if userIdle:
|
||||
idle = False
|
||||
if self._userIdle != idle:
|
||||
if idle:
|
||||
self.timeIcon.setPixmap(self.idlePixmap)
|
||||
else:
|
||||
self.timeIcon.setPixmap(self.timePixmap)
|
||||
|
||||
self.userIdle = userIdle
|
||||
|
||||
self._userIdle = idle
|
||||
return
|
||||
|
||||
def setProjectStats(self, pWC, sWC):
|
||||
"""Update the current project statistics.
|
||||
"""
|
||||
def setProjectStats(self, pWC: int, sWC: int) -> None:
|
||||
"""Update the current project statistics."""
|
||||
self.statsText.setText(self.tr("Words: {0} ({1})").format(f"{pWC:n}", f"{sWC:+n}"))
|
||||
if CONFIG.incNotesWCount:
|
||||
self.statsText.setToolTip(self.tr("Project word count (session change)"))
|
||||
@@ -195,53 +178,55 @@ class GuiMainStatus(QStatusBar):
|
||||
self.statsText.setToolTip(self.tr("Novel word count (session change)"))
|
||||
return
|
||||
|
||||
def updateTime(self, idleTime=0.0):
|
||||
"""Update the session clock.
|
||||
"""
|
||||
if self.refTime is None:
|
||||
def updateTime(self, idleTime: float = 0.0) -> None:
|
||||
"""Update the session clock."""
|
||||
if self._refTime < 0.0:
|
||||
self.timeText.setText("00:00:00")
|
||||
else:
|
||||
if CONFIG.stopWhenIdle:
|
||||
sessTime = round(time() - self.refTime - idleTime)
|
||||
sessTime = round(time() - self._refTime - idleTime)
|
||||
else:
|
||||
sessTime = round(time() - self.refTime)
|
||||
sessTime = round(time() - self._refTime)
|
||||
self.timeText.setText(formatTime(sessTime))
|
||||
return
|
||||
|
||||
##
|
||||
# Slots
|
||||
# Public Slots
|
||||
##
|
||||
|
||||
@pyqtSlot(str)
|
||||
def setStatusMessage(self, message: str) -> None:
|
||||
"""Set the status bar message to display."""
|
||||
self.showMessage(message, nwConst.STATUS_MSG_TIMEOUT)
|
||||
qApp.processEvents()
|
||||
return
|
||||
|
||||
@pyqtSlot(str, str)
|
||||
def setLanguage(self, theLanguage, theProvider):
|
||||
"""Set the language code for the spell checker.
|
||||
"""
|
||||
if theLanguage == "None":
|
||||
def setLanguage(self, language: str, provider: str) -> None:
|
||||
"""Set the language code for the spell checker."""
|
||||
if language == "None":
|
||||
self.langText.setText(self.tr("None"))
|
||||
self.langText.setToolTip("")
|
||||
else:
|
||||
qLocal = QLocale(theLanguage)
|
||||
qLocal = QLocale(language)
|
||||
spLang = qLocal.nativeLanguageName().title()
|
||||
self.langText.setText(spLang)
|
||||
if theProvider:
|
||||
self.langText.setToolTip("%s (%s)" % (theLanguage, theProvider))
|
||||
if provider:
|
||||
self.langText.setToolTip("%s (%s)" % (language, provider))
|
||||
else:
|
||||
self.langText.setToolTip(theLanguage)
|
||||
|
||||
self.langText.setToolTip(language)
|
||||
return
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def doUpdateProjectStatus(self, isChanged):
|
||||
"""Slot for updating the project status.
|
||||
"""
|
||||
self.setProjectStatus(StatusLED.S_BAD if isChanged else StatusLED.S_GOOD)
|
||||
def updateProjectStatus(self, status: bool) -> None:
|
||||
"""Update the project status."""
|
||||
self.setProjectStatus(StatusLED.S_BAD if status else StatusLED.S_GOOD)
|
||||
return
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def doUpdateDocumentStatus(self, isChanged):
|
||||
"""Slot for updating the document status.
|
||||
"""
|
||||
self.setDocumentStatus(StatusLED.S_BAD if isChanged else StatusLED.S_GOOD)
|
||||
def updateDocumentStatus(self, status: bool) -> None:
|
||||
"""Update the document status."""
|
||||
self.setDocumentStatus(StatusLED.S_BAD if status else StatusLED.S_GOOD)
|
||||
return
|
||||
|
||||
# END Class GuiMainStatus
|
||||
|
||||
+118
-219
@@ -31,13 +31,13 @@ from pathlib import Path
|
||||
from datetime import datetime
|
||||
|
||||
from PyQt5.QtCore import Qt, QTimer, QThreadPool, pyqtSlot
|
||||
from PyQt5.QtGui import QCloseEvent, QCursor, QIcon, QKeySequence, QPixmap
|
||||
from PyQt5.QtGui import QCloseEvent, QCursor, QIcon, QKeySequence
|
||||
from PyQt5.QtWidgets import (
|
||||
qApp, QDialog, QFileDialog, QMainWindow, QMessageBox, QShortcut, QSplitter,
|
||||
QStackedWidget, QVBoxLayout, QWidget
|
||||
)
|
||||
|
||||
from novelwriter import CONFIG, __hexversion__
|
||||
from novelwriter import CONFIG, SHARED, __hexversion__
|
||||
from novelwriter.gui.theme import GuiTheme
|
||||
from novelwriter.gui.sidebar import GuiSideBar
|
||||
from novelwriter.gui.outline import GuiOutlineView
|
||||
@@ -59,14 +59,13 @@ from novelwriter.tools.lipsum import GuiLipsum
|
||||
from novelwriter.tools.manuscript import GuiManuscript
|
||||
from novelwriter.tools.projwizard import GuiProjectWizard
|
||||
from novelwriter.tools.writingstats import GuiWritingStats
|
||||
from novelwriter.core.project import NWProject
|
||||
from novelwriter.core.coretools import ProjectBuilder
|
||||
|
||||
from novelwriter.enum import (
|
||||
nwDocAction, nwDocMode, nwItemType, nwItemClass, nwAlert, nwWidget, nwView
|
||||
nwDocAction, nwDocMode, nwItemType, nwItemClass, nwWidget, nwView
|
||||
)
|
||||
from novelwriter.common import getGuiItem, hexToInt
|
||||
from novelwriter.constants import nwFiles, nwLabels, trConst
|
||||
from novelwriter.constants import nwFiles
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -112,15 +111,11 @@ class GuiMain(QMainWindow):
|
||||
# Core Classes
|
||||
# ============
|
||||
|
||||
# Core Classes
|
||||
CONFIG.setThemeInstance(GuiTheme())
|
||||
self._project = NWProject(self)
|
||||
# Initialise UserData Instance
|
||||
SHARED.initSharedData(self, GuiTheme())
|
||||
|
||||
# Core Settings
|
||||
self.hasProject = False
|
||||
self.isFocusMode = False
|
||||
self.idleRefTime = time()
|
||||
self.idleTime = 0.0
|
||||
|
||||
# Prepare Main Window
|
||||
self.resize(*CONFIG.mainWinSize)
|
||||
@@ -135,7 +130,6 @@ class GuiMain(QMainWindow):
|
||||
# =============
|
||||
|
||||
# Sizes
|
||||
iPx = CONFIG.theme.fontPixelSize
|
||||
mPx = CONFIG.pxInt(4)
|
||||
hWd = CONFIG.pxInt(4)
|
||||
|
||||
@@ -238,7 +232,8 @@ class GuiMain(QMainWindow):
|
||||
# Connect Signals
|
||||
# ===============
|
||||
|
||||
self._project.projectStatusChanged.connect(self.mainStatus.doUpdateProjectStatus)
|
||||
SHARED.projectStatusChanged.connect(self.mainStatus.updateProjectStatus)
|
||||
SHARED.projectStatusMessage.connect(self.mainStatus.setStatusMessage)
|
||||
|
||||
self.viewsBar.viewChangeRequested.connect(self._changeView)
|
||||
|
||||
@@ -257,12 +252,13 @@ class GuiMain(QMainWindow):
|
||||
self.novelView.openDocumentRequest.connect(self._openDocument)
|
||||
|
||||
self.docEditor.spellDictionaryChanged.connect(self.mainStatus.setLanguage)
|
||||
self.docEditor.docEditedStatusChanged.connect(self.mainStatus.doUpdateDocumentStatus)
|
||||
self.docEditor.editedStatusChanged.connect(self.mainStatus.updateDocumentStatus)
|
||||
self.docEditor.docCountsChanged.connect(self.itemDetails.updateCounts)
|
||||
self.docEditor.docCountsChanged.connect(self.projView.updateCounts)
|
||||
self.docEditor.loadDocumentTagRequest.connect(self._followTag)
|
||||
self.docEditor.novelStructureChanged.connect(self.novelView.refreshTree)
|
||||
self.docEditor.novelItemMetaChanged.connect(self.novelView.updateNovelItemMeta)
|
||||
self.docEditor.statusMessage.connect(self.mainStatus.setStatusMessage)
|
||||
|
||||
self.docViewer.loadDocumentTagRequest.connect(self._followTag)
|
||||
|
||||
@@ -301,18 +297,6 @@ class GuiMain(QMainWindow):
|
||||
keyEscape.setKey(QKeySequence(Qt.Key_Escape))
|
||||
keyEscape.activated.connect(self._keyPressEscape)
|
||||
|
||||
# Forward Functions
|
||||
self.setStatus = self.mainStatus.setStatus
|
||||
|
||||
# Cache Alert Pixmaps
|
||||
pxSize = (2*iPx, 2*iPx)
|
||||
self.alertPix: dict[nwAlert, QPixmap] = {
|
||||
nwAlert.INFO: CONFIG.theme.getPixmap("alert_info", pxSize),
|
||||
nwAlert.WARN: CONFIG.theme.getPixmap("alert_warn", pxSize),
|
||||
nwAlert.ERROR: CONFIG.theme.getPixmap("alert_error", pxSize),
|
||||
nwAlert.ASK: CONFIG.theme.getPixmap("alert_question", pxSize),
|
||||
}
|
||||
|
||||
# Check that config loaded fine
|
||||
self.reportConfErr()
|
||||
|
||||
@@ -328,33 +312,14 @@ class GuiMain(QMainWindow):
|
||||
logger.debug("Ready: GUI")
|
||||
|
||||
if __hexversion__[-2] == "a" and logger.getEffectiveLevel() > logging.DEBUG:
|
||||
self.makeAlert(self.tr(
|
||||
SHARED.warn(self.tr(
|
||||
"You are running an untested development version of novelWriter. "
|
||||
"Please be careful when working on a live project "
|
||||
"and make sure you take regular backups."
|
||||
), level=nwAlert.WARN)
|
||||
))
|
||||
|
||||
logger.info("novelWriter is ready ...")
|
||||
self.setStatus(self.tr("novelWriter is ready ..."))
|
||||
|
||||
return
|
||||
|
||||
def clearGUI(self) -> None:
|
||||
"""Clear all sub-elements of the main GUI."""
|
||||
# Project Area
|
||||
self.projView.clearProject()
|
||||
self.novelView.clearProject()
|
||||
self.itemDetails.clearDetails()
|
||||
|
||||
# Work Area
|
||||
self.docEditor.clearEditor()
|
||||
self.docEditor.setDictionaries()
|
||||
self.closeDocViewer(byUser=False)
|
||||
self.outlineView.clearProject()
|
||||
|
||||
# General
|
||||
self.mainStatus.clearStatus()
|
||||
self._updateWindowTitle()
|
||||
self.mainStatus.setStatusMessage(self.tr("novelWriter is ready ..."))
|
||||
|
||||
return
|
||||
|
||||
@@ -365,14 +330,12 @@ class GuiMain(QMainWindow):
|
||||
return
|
||||
|
||||
def postLaunchTasks(self, cmdOpen: str | None) -> None:
|
||||
"""This function is called after the main window is created to
|
||||
determine what to open or show after initialisation.
|
||||
"""
|
||||
"""Process tasks after the main window has been created."""
|
||||
if cmdOpen:
|
||||
logger.info("Command line path: %s", cmdOpen)
|
||||
self.openProject(cmdOpen)
|
||||
|
||||
if not self.hasProject:
|
||||
if not SHARED.hasProject:
|
||||
self.showProjectLoadDialog()
|
||||
|
||||
# Determine whether release notes need to be shown or not
|
||||
@@ -382,26 +345,17 @@ class GuiMain(QMainWindow):
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Properties
|
||||
##
|
||||
|
||||
@property
|
||||
def project(self) -> NWProject:
|
||||
"""The project instance."""
|
||||
return self._project
|
||||
|
||||
##
|
||||
# Project Actions
|
||||
##
|
||||
|
||||
def newProject(self, projData: dict | None = None) -> bool:
|
||||
"""Create a new project via the new project wizard."""
|
||||
if self.hasProject:
|
||||
if SHARED.hasProject:
|
||||
if not self.closeProject():
|
||||
self.makeAlert(self.tr(
|
||||
SHARED.error(self.tr(
|
||||
"Cannot create a new project when another project is open."
|
||||
), level=nwAlert.ERROR)
|
||||
))
|
||||
return False
|
||||
|
||||
if projData is None:
|
||||
@@ -416,14 +370,14 @@ class GuiMain(QMainWindow):
|
||||
return False
|
||||
|
||||
if (Path(projPath) / nwFiles.PROJ_FILE).is_file():
|
||||
self.makeAlert(self.tr(
|
||||
SHARED.error(self.tr(
|
||||
"A project already exists in that location. "
|
||||
"Please choose another folder."
|
||||
), level=nwAlert.ERROR)
|
||||
))
|
||||
return False
|
||||
|
||||
logger.info("Creating new project")
|
||||
nwProject = ProjectBuilder(self)
|
||||
nwProject = ProjectBuilder()
|
||||
if nwProject.buildProject(projData):
|
||||
self.openProject(projPath)
|
||||
else:
|
||||
@@ -436,45 +390,46 @@ class GuiMain(QMainWindow):
|
||||
close application event so the user doesn't get prompted twice
|
||||
to confirm.
|
||||
"""
|
||||
if not self.hasProject:
|
||||
if not SHARED.hasProject:
|
||||
# There is no project loaded, everything OK
|
||||
return True
|
||||
|
||||
if not isYes:
|
||||
msgYes = self.askQuestion("%s<br>%s" % (
|
||||
msgYes = SHARED.question("%s<br>%s" % (
|
||||
self.tr("Close the current project?"),
|
||||
self.tr("Changes are saved automatically.")
|
||||
))
|
||||
if not msgYes:
|
||||
return False
|
||||
|
||||
if self.docEditor.docChanged():
|
||||
if self.docEditor.docChanged:
|
||||
self.saveDocument()
|
||||
|
||||
saveOK = self.saveProject()
|
||||
doBackup = False
|
||||
if self._project.data.doBackup and CONFIG.backupOnClose:
|
||||
if SHARED.project.data.doBackup and CONFIG.backupOnClose:
|
||||
doBackup = True
|
||||
if CONFIG.askBeforeBackup:
|
||||
msgYes = self.askQuestion(self.tr("Backup the current project?"))
|
||||
if not msgYes:
|
||||
doBackup = False
|
||||
doBackup = SHARED.question(self.tr("Backup the current project?"))
|
||||
|
||||
if doBackup:
|
||||
self._project.backupProject(False)
|
||||
SHARED.project.backupProject(False)
|
||||
|
||||
if saveOK:
|
||||
self.closeDocument()
|
||||
self.docViewer.clearNavHistory()
|
||||
self.closeDocViewer(byUser=False)
|
||||
|
||||
self.outlineView.closeProjectTasks()
|
||||
self.novelView.closeProjectTasks()
|
||||
self.projView.clearProjectView()
|
||||
self.itemDetails.clearDetails()
|
||||
self.mainStatus.clearStatus()
|
||||
|
||||
self._project.closeProject(self.idleTime)
|
||||
self.idleRefTime = time()
|
||||
self.idleTime = 0.0
|
||||
SHARED.closeProject()
|
||||
|
||||
self.clearGUI()
|
||||
self.hasProject = False
|
||||
self.docEditor.setDictionaries()
|
||||
self._updateWindowTitle()
|
||||
self._changeView(nwView.PROJECT)
|
||||
|
||||
return saveOK
|
||||
@@ -493,9 +448,10 @@ class GuiMain(QMainWindow):
|
||||
self._changeView(nwView.PROJECT)
|
||||
|
||||
# Try to open the project
|
||||
if not self._project.openProject(projFile):
|
||||
tStart = time()
|
||||
if not SHARED.openProject(projFile):
|
||||
# The project open failed.
|
||||
lockStatus = self._project.getLockStatus()
|
||||
lockStatus = SHARED.projectLock
|
||||
if lockStatus is None:
|
||||
# The project is not locked, so failed for some other
|
||||
# reason handled by the project class.
|
||||
@@ -524,23 +480,18 @@ class GuiMain(QMainWindow):
|
||||
except Exception:
|
||||
lockDetails = ""
|
||||
|
||||
if self.askQuestion(lockText, info=lockInfo, details=lockDetails, level=nwAlert.WARN):
|
||||
if not self._project.openProject(projFile, overrideLock=True):
|
||||
if SHARED.question(lockText, info=lockInfo, details=lockDetails, warn=True):
|
||||
if not SHARED.openProject(projFile, clearLock=True):
|
||||
return False
|
||||
else:
|
||||
return False
|
||||
|
||||
# Project is loaded
|
||||
self.hasProject = True
|
||||
self.idleRefTime = time()
|
||||
self.idleTime = 0.0
|
||||
|
||||
# Update GUI
|
||||
self._updateWindowTitle(self._project.data.name)
|
||||
self._updateWindowTitle(SHARED.project.data.name)
|
||||
self.rebuildTrees()
|
||||
self.docEditor.setDictionaries()
|
||||
self.docEditor.toggleSpellCheck(self._project.data.spellCheck)
|
||||
self.mainStatus.setRefTime(self._project.projOpened)
|
||||
self.docEditor.toggleSpellCheck(SHARED.project.data.spellCheck)
|
||||
self.mainStatus.setRefTime(SHARED.project.projOpened)
|
||||
self.projView.openProjectTasks()
|
||||
self.novelView.openProjectTasks()
|
||||
self.outlineView.openProjectTasks()
|
||||
@@ -548,9 +499,9 @@ class GuiMain(QMainWindow):
|
||||
|
||||
# Restore previously open documents, if any
|
||||
# If none was recorded, open the first document found
|
||||
lastEdited = self._project.data.getLastHandle("editor")
|
||||
lastEdited = SHARED.project.data.getLastHandle("editor")
|
||||
if lastEdited is None:
|
||||
for nwItem in self._project.tree:
|
||||
for nwItem in SHARED.project.tree:
|
||||
if nwItem and nwItem.isFileType():
|
||||
lastEdited = nwItem.itemHandle
|
||||
break
|
||||
@@ -558,32 +509,31 @@ class GuiMain(QMainWindow):
|
||||
if lastEdited is not None:
|
||||
self.openDocument(lastEdited, doScroll=True)
|
||||
|
||||
lastViewed = self._project.data.getLastHandle("viewer")
|
||||
lastViewed = SHARED.project.data.getLastHandle("viewer")
|
||||
if lastViewed is not None:
|
||||
self.viewDocument(lastViewed)
|
||||
|
||||
# Check if we need to rebuild the index
|
||||
if self._project.index.indexBroken:
|
||||
self.makeAlert(self.tr("The project index is outdated or broken. Rebuilding index."))
|
||||
if SHARED.project.index.indexBroken:
|
||||
SHARED.info(self.tr("The project index is outdated or broken. Rebuilding index."))
|
||||
self.rebuildIndex()
|
||||
|
||||
# Make sure the changed status is set to false on things opened
|
||||
qApp.processEvents()
|
||||
self.docEditor.setDocumentChanged(False)
|
||||
self._project.setProjectChanged(False)
|
||||
SHARED.project.setProjectChanged(False)
|
||||
|
||||
logger.debug("Project load complete")
|
||||
logger.debug("Project loaded in %.3f ms", (time() - tStart)*1000)
|
||||
|
||||
return True
|
||||
|
||||
def saveProject(self, autoSave: bool = False) -> bool:
|
||||
"""Save the current project."""
|
||||
if not self.hasProject:
|
||||
if not SHARED.hasProject:
|
||||
logger.error("No project open")
|
||||
return False
|
||||
self.projView.saveProjectTasks()
|
||||
self._project.saveProject(autoSave=autoSave)
|
||||
return True
|
||||
return SHARED.saveProject(autoSave=autoSave)
|
||||
|
||||
##
|
||||
# Document Actions
|
||||
@@ -591,7 +541,7 @@ class GuiMain(QMainWindow):
|
||||
|
||||
def closeDocument(self, beforeOpen: bool = False) -> bool:
|
||||
"""Close the document and clear the editor and title field."""
|
||||
if not self.hasProject:
|
||||
if not SHARED.hasProject:
|
||||
logger.error("No project open")
|
||||
return False
|
||||
|
||||
@@ -600,7 +550,7 @@ class GuiMain(QMainWindow):
|
||||
self.toggleFocusMode()
|
||||
|
||||
self.docEditor.saveCursorPosition()
|
||||
if self.docEditor.docChanged():
|
||||
if self.docEditor.docChanged:
|
||||
self.saveDocument()
|
||||
self.docEditor.clearEditor()
|
||||
if not beforeOpen:
|
||||
@@ -611,16 +561,16 @@ class GuiMain(QMainWindow):
|
||||
def openDocument(self, tHandle: str | None, tLine: int | None = None,
|
||||
changeFocus: bool = True, doScroll: bool = False) -> bool:
|
||||
"""Open a specific document, optionally at a given line."""
|
||||
if not self.hasProject:
|
||||
if not SHARED.hasProject:
|
||||
logger.error("No project open")
|
||||
return False
|
||||
|
||||
if not tHandle or not self._project.tree.checkType(tHandle, nwItemType.FILE):
|
||||
if not tHandle or not SHARED.project.tree.checkType(tHandle, nwItemType.FILE):
|
||||
logger.debug("Requested item '%s' is not a document", tHandle)
|
||||
return False
|
||||
|
||||
self._changeView(nwView.EDITOR)
|
||||
cHandle = self.docEditor.docHandle()
|
||||
cHandle = self.docEditor.docHandle
|
||||
if cHandle == tHandle:
|
||||
self.docEditor.setCursorLine(tLine)
|
||||
if changeFocus:
|
||||
@@ -629,7 +579,7 @@ class GuiMain(QMainWindow):
|
||||
|
||||
self.closeDocument(beforeOpen=True)
|
||||
if self.docEditor.loadText(tHandle, tLine):
|
||||
self._project.data.setLastHandle(tHandle, "editor")
|
||||
SHARED.project.data.setLastHandle(tHandle, "editor")
|
||||
self.projView.setSelectedHandle(tHandle, doScroll=doScroll)
|
||||
self.novelView.setActiveHandle(tHandle)
|
||||
if changeFocus:
|
||||
@@ -643,14 +593,14 @@ class GuiMain(QMainWindow):
|
||||
"""Opens the next document in the project tree, following the
|
||||
document with the given handle. Stops when reaching the end.
|
||||
"""
|
||||
if not self.hasProject:
|
||||
if not SHARED.hasProject:
|
||||
logger.error("No project open")
|
||||
return False
|
||||
|
||||
nHandle = None # The next handle after tHandle
|
||||
fHandle = None # The first file handle we encounter
|
||||
foundIt = False # We've found tHandle, pick the next we see
|
||||
for tItem in self._project.tree:
|
||||
for tItem in SHARED.project.tree:
|
||||
if not tItem.isFileType():
|
||||
continue
|
||||
if fHandle is None:
|
||||
@@ -672,7 +622,7 @@ class GuiMain(QMainWindow):
|
||||
|
||||
def saveDocument(self) -> bool:
|
||||
"""Save the current documents."""
|
||||
if not self.hasProject:
|
||||
if not SHARED.hasProject:
|
||||
logger.error("No project open")
|
||||
return False
|
||||
self.docEditor.saveText()
|
||||
@@ -680,7 +630,7 @@ class GuiMain(QMainWindow):
|
||||
|
||||
def viewDocument(self, tHandle: str | None = None, sTitle: str | None = None) -> bool:
|
||||
"""Load a document for viewing in the view panel."""
|
||||
if not self.hasProject:
|
||||
if not SHARED.hasProject:
|
||||
logger.error("No project open")
|
||||
return False
|
||||
|
||||
@@ -688,7 +638,7 @@ class GuiMain(QMainWindow):
|
||||
logger.debug("Viewing document, but no handle provided")
|
||||
|
||||
if self.docEditor.hasFocus():
|
||||
tHandle = self.docEditor.docHandle()
|
||||
tHandle = self.docEditor.docHandle
|
||||
|
||||
if tHandle is not None:
|
||||
self.saveDocument()
|
||||
@@ -696,7 +646,7 @@ class GuiMain(QMainWindow):
|
||||
tHandle = self.projView.getSelectedHandle()
|
||||
|
||||
if tHandle is None:
|
||||
tHandle = self._project.data.getLastHandle("viewer")
|
||||
tHandle = SHARED.project.data.getLastHandle("viewer")
|
||||
|
||||
if tHandle is None:
|
||||
logger.debug("No document to view, giving up")
|
||||
@@ -725,7 +675,7 @@ class GuiMain(QMainWindow):
|
||||
"""Import the text contained in an out-of-project text file, and
|
||||
insert the text into the currently open document.
|
||||
"""
|
||||
if not self.hasProject:
|
||||
if not SHARED.hasProject:
|
||||
logger.error("No project open")
|
||||
return False
|
||||
|
||||
@@ -751,19 +701,19 @@ class GuiMain(QMainWindow):
|
||||
theText = inFile.read()
|
||||
CONFIG.setLastPath(loadFile)
|
||||
except Exception as exc:
|
||||
self.makeAlert(self.tr(
|
||||
SHARED.error(self.tr(
|
||||
"Could not read file. The file must be an existing text file."
|
||||
), level=nwAlert.ERROR, exception=exc)
|
||||
), exc=exc)
|
||||
return False
|
||||
|
||||
if self.docEditor.docHandle() is None:
|
||||
self.makeAlert(self.tr(
|
||||
if self.docEditor.docHandle is None:
|
||||
SHARED.error(self.tr(
|
||||
"Please open a document to import the text file into."
|
||||
), level=nwAlert.ERROR)
|
||||
))
|
||||
return False
|
||||
|
||||
if not self.docEditor.isEmpty():
|
||||
msgYes = self.askQuestion(self.tr(
|
||||
if not self.docEditor.isEmpty:
|
||||
msgYes = SHARED.question(self.tr(
|
||||
"Importing the file will overwrite the current content of "
|
||||
"the document. Do you want to proceed?"
|
||||
))
|
||||
@@ -797,7 +747,7 @@ class GuiMain(QMainWindow):
|
||||
active. It is not checked that the item is actually a document.
|
||||
That should be handled by the openDocument function.
|
||||
"""
|
||||
if not self.hasProject:
|
||||
if not SHARED.hasProject:
|
||||
logger.error("No project open")
|
||||
return False
|
||||
|
||||
@@ -815,7 +765,7 @@ class GuiMain(QMainWindow):
|
||||
return False
|
||||
|
||||
if tHandle is not None and sTitle is not None:
|
||||
hItem = self._project.index.getItemHeader(tHandle, sTitle)
|
||||
hItem = SHARED.project.index.getItemHeader(tHandle, sTitle)
|
||||
if hItem is not None:
|
||||
tLine = hItem.line
|
||||
|
||||
@@ -826,12 +776,12 @@ class GuiMain(QMainWindow):
|
||||
|
||||
def editItemLabel(self, tHandle: str | None = None) -> bool:
|
||||
"""Open the edit item dialog."""
|
||||
if not self.hasProject:
|
||||
if not SHARED.hasProject:
|
||||
logger.error("No project open")
|
||||
return False
|
||||
|
||||
if tHandle is None and (self.docEditor.anyFocus() or self.isFocusMode):
|
||||
tHandle = self.docEditor.docHandle()
|
||||
tHandle = self.docEditor.docHandle
|
||||
self.projView.renameTreeItem(tHandle)
|
||||
|
||||
return True
|
||||
@@ -843,7 +793,7 @@ class GuiMain(QMainWindow):
|
||||
|
||||
def rebuildIndex(self, beQuiet: bool = False) -> bool:
|
||||
"""Rebuild the entire index."""
|
||||
if not self.hasProject:
|
||||
if not SHARED.hasProject:
|
||||
logger.error("No project open")
|
||||
return False
|
||||
|
||||
@@ -852,12 +802,12 @@ class GuiMain(QMainWindow):
|
||||
tStart = time()
|
||||
|
||||
self.projView.saveProjectTasks()
|
||||
self._project.index.rebuildIndex()
|
||||
SHARED.project.index.rebuildIndex()
|
||||
self.projView.populateTree()
|
||||
self.novelView.refreshTree()
|
||||
|
||||
tEnd = time()
|
||||
self.setStatus(
|
||||
self.mainStatus.setStatusMessage(
|
||||
self.tr("Indexing completed in {0} ms").format(f"{(tEnd - tStart)*1000.0:.1f}")
|
||||
)
|
||||
self.docEditor.updateTagHighLighting()
|
||||
@@ -865,7 +815,7 @@ class GuiMain(QMainWindow):
|
||||
qApp.restoreOverrideCursor()
|
||||
|
||||
if not beQuiet:
|
||||
self.makeAlert(self.tr("The project index has been successfully rebuilt."))
|
||||
SHARED.info(self.tr("The project index has been successfully rebuilt."))
|
||||
|
||||
return True
|
||||
|
||||
@@ -911,7 +861,7 @@ class GuiMain(QMainWindow):
|
||||
self.saveDocument()
|
||||
|
||||
if dlgConf.needsRestart:
|
||||
self.makeAlert(self.tr(
|
||||
SHARED.info(self.tr(
|
||||
"Some changes will not be applied until novelWriter has been restarted."
|
||||
))
|
||||
|
||||
@@ -921,7 +871,7 @@ class GuiMain(QMainWindow):
|
||||
if dlgConf.updateTheme:
|
||||
# We are doing this manually instead of connecting to
|
||||
# qApp.paletteChanged since the processing order matters
|
||||
CONFIG.theme.loadTheme()
|
||||
SHARED.theme.loadTheme()
|
||||
self.docEditor.updateTheme()
|
||||
self.docViewer.updateTheme()
|
||||
self.viewsBar.updateTheme()
|
||||
@@ -932,7 +882,7 @@ class GuiMain(QMainWindow):
|
||||
self.mainStatus.updateTheme()
|
||||
|
||||
if dlgConf.updateSyntax:
|
||||
CONFIG.theme.loadSyntax()
|
||||
SHARED.theme.loadSyntax()
|
||||
self.docEditor.updateSyntaxColours()
|
||||
|
||||
self.docEditor.initEditor()
|
||||
@@ -948,7 +898,7 @@ class GuiMain(QMainWindow):
|
||||
@pyqtSlot(int)
|
||||
def showProjectSettingsDialog(self, focusTab: int = GuiProjectSettings.TAB_MAIN) -> bool:
|
||||
"""Open the project settings dialog."""
|
||||
if not self.hasProject:
|
||||
if not SHARED.hasProject:
|
||||
logger.error("No project open")
|
||||
return False
|
||||
|
||||
@@ -960,13 +910,13 @@ class GuiMain(QMainWindow):
|
||||
if dlgProj.spellChanged:
|
||||
self.docEditor.setDictionaries()
|
||||
self.itemDetails.refreshDetails()
|
||||
self._updateWindowTitle(self._project.data.name)
|
||||
self._updateWindowTitle(SHARED.project.data.name)
|
||||
|
||||
return True
|
||||
|
||||
def showProjectDetailsDialog(self) -> bool:
|
||||
"""Open the project details dialog."""
|
||||
if not self.hasProject:
|
||||
if not SHARED.hasProject:
|
||||
logger.error("No project open")
|
||||
return False
|
||||
|
||||
@@ -985,7 +935,7 @@ class GuiMain(QMainWindow):
|
||||
@pyqtSlot()
|
||||
def showBuildManuscriptDialog(self) -> bool:
|
||||
"""Open the build manuscript dialog."""
|
||||
if not self.hasProject:
|
||||
if not SHARED.hasProject:
|
||||
logger.error("No project open")
|
||||
return False
|
||||
|
||||
@@ -1005,7 +955,7 @@ class GuiMain(QMainWindow):
|
||||
|
||||
def showLoremIpsumDialog(self) -> bool:
|
||||
"""Open the insert lorem ipsum text dialog."""
|
||||
if not self.hasProject:
|
||||
if not SHARED.hasProject:
|
||||
logger.error("No project open")
|
||||
return False
|
||||
|
||||
@@ -1023,7 +973,7 @@ class GuiMain(QMainWindow):
|
||||
|
||||
def showProjectWordListDialog(self) -> bool:
|
||||
"""Open the project word list dialog."""
|
||||
if not self.hasProject:
|
||||
if not SHARED.hasProject:
|
||||
logger.error("No project open")
|
||||
return False
|
||||
|
||||
@@ -1038,7 +988,7 @@ class GuiMain(QMainWindow):
|
||||
|
||||
def showWritingStatsDialog(self) -> bool:
|
||||
"""Open the session stats dialog."""
|
||||
if not self.hasProject:
|
||||
if not SHARED.hasProject:
|
||||
logger.error("No project open")
|
||||
return False
|
||||
|
||||
@@ -1094,56 +1044,13 @@ class GuiMain(QMainWindow):
|
||||
|
||||
return
|
||||
|
||||
def makeAlert(self, text: str, info: str = "", details: str = "",
|
||||
level: nwAlert = nwAlert.INFO, exception: Exception | None = None) -> None:
|
||||
"""Alert both the user and the logger at the same time. The
|
||||
message can be either a string or a list of strings.
|
||||
"""
|
||||
logText = " ".join(filter(None, [text, info, details]))
|
||||
if level == nwAlert.INFO:
|
||||
logger.info(logText, stacklevel=2)
|
||||
elif level == nwAlert.WARN:
|
||||
logger.warning(logText, stacklevel=2)
|
||||
elif level == nwAlert.ERROR:
|
||||
logger.error(logText, stacklevel=2, exc_info=exception)
|
||||
|
||||
if exception is not None:
|
||||
excText = f"{type(exception).__name__}: {str(exception)}"
|
||||
info = f"{info}<br>{excText}" if info else excText
|
||||
|
||||
msgBox = QMessageBox(self)
|
||||
msgBox.setWindowTitle(trConst(nwLabels.ALERT_NAME[level]))
|
||||
msgBox.setText(text)
|
||||
msgBox.setInformativeText(info)
|
||||
msgBox.setDetailedText(details)
|
||||
msgBox.setStandardButtons(QMessageBox.Ok)
|
||||
msgBox.setIconPixmap(self.alertPix[level])
|
||||
msgBox.adjustSize()
|
||||
msgBox.exec_()
|
||||
|
||||
return
|
||||
|
||||
def askQuestion(self, text: str, info: str = "", details: str = "",
|
||||
level: nwAlert = nwAlert.ASK) -> bool:
|
||||
"""Ask the user a Yes/No question, and return the answer."""
|
||||
msgBox = QMessageBox(self)
|
||||
msgBox.setWindowTitle(trConst(nwLabels.ALERT_NAME[level]))
|
||||
msgBox.setText(text)
|
||||
msgBox.setInformativeText(info)
|
||||
msgBox.setDetailedText(details)
|
||||
msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
|
||||
msgBox.setIconPixmap(self.alertPix[level])
|
||||
msgBox.adjustSize()
|
||||
msgBox.exec_()
|
||||
return msgBox.result() == QMessageBox.Yes
|
||||
|
||||
def reportConfErr(self) -> bool:
|
||||
"""Checks if the Config module has any errors to report, and let
|
||||
the user know if this is the case. The Config module caches
|
||||
errors since it is initialised before the GUI itself.
|
||||
"""
|
||||
if CONFIG.hasError:
|
||||
self.makeAlert(CONFIG.errorText(), level=nwAlert.ERROR)
|
||||
SHARED.error(CONFIG.errorText())
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -1153,8 +1060,8 @@ class GuiMain(QMainWindow):
|
||||
|
||||
def closeMain(self) -> bool:
|
||||
"""Save everything, and close novelWriter."""
|
||||
if self.hasProject:
|
||||
msgYes = self.askQuestion("%s<br>%s" % (
|
||||
if SHARED.hasProject:
|
||||
msgYes = SHARED.question("%s<br>%s" % (
|
||||
self.tr("Do you want to exit novelWriter?"),
|
||||
self.tr("Changes are saved automatically.")
|
||||
))
|
||||
@@ -1174,7 +1081,7 @@ class GuiMain(QMainWindow):
|
||||
# Ignore window size if in full screen mode
|
||||
CONFIG.setMainWinSize(self.width(), self.height())
|
||||
|
||||
if self.hasProject:
|
||||
if SHARED.hasProject:
|
||||
self.closeProject(True)
|
||||
|
||||
CONFIG.saveConfig()
|
||||
@@ -1206,7 +1113,7 @@ class GuiMain(QMainWindow):
|
||||
def closeDocEditor(self) -> None:
|
||||
"""Close the document editor. This does not hide the editor."""
|
||||
self.closeDocument()
|
||||
self._project.data.setLastHandle(None, "editor")
|
||||
SHARED.project.data.setLastHandle(None, "editor")
|
||||
return
|
||||
|
||||
def closeDocViewer(self, byUser: bool = True) -> bool:
|
||||
@@ -1214,7 +1121,7 @@ class GuiMain(QMainWindow):
|
||||
self.docViewer.clearViewer()
|
||||
if byUser:
|
||||
# Only reset the last handle if the user called this
|
||||
self._project.data.setLastHandle(None, "viewer")
|
||||
SHARED.project.data.setLastHandle(None, "viewer")
|
||||
|
||||
# Hide the panel
|
||||
bPos = self.splitMain.sizes()
|
||||
@@ -1227,7 +1134,7 @@ class GuiMain(QMainWindow):
|
||||
"""Handle toggle focus mode. The Main GUI Focus Mode hides tree,
|
||||
view, statusbar and menu.
|
||||
"""
|
||||
if self.docEditor.docHandle() is None:
|
||||
if self.docEditor.docHandle is None:
|
||||
logger.error("No document open, so not activating Focus Mode")
|
||||
return False
|
||||
|
||||
@@ -1250,7 +1157,7 @@ class GuiMain(QMainWindow):
|
||||
|
||||
if self.splitView.isVisible():
|
||||
self.splitView.setVisible(False)
|
||||
elif self.docViewer.docHandle() is not None:
|
||||
elif self.docViewer.docHandle is not None:
|
||||
self.splitView.setVisible(True)
|
||||
|
||||
return True
|
||||
@@ -1400,15 +1307,15 @@ class GuiMain(QMainWindow):
|
||||
"""Handle the index lookup of a tag and display an alert if the
|
||||
tag cannot be found.
|
||||
"""
|
||||
tHandle, sTitle = self._project.index.getTagSource(tag)
|
||||
tHandle, sTitle = SHARED.project.index.getTagSource(tag)
|
||||
if tHandle is None:
|
||||
self.makeAlert(self.tr(
|
||||
SHARED.error(self.tr(
|
||||
"Could not find the reference for tag '{0}'. It either doesn't "
|
||||
"exist, or the index is out of date. The index can be updated "
|
||||
"from the Tools menu, or by pressing {1}."
|
||||
).format(
|
||||
tag, "F9"
|
||||
), level=nwAlert.ERROR)
|
||||
))
|
||||
return None, None
|
||||
return tHandle, sTitle
|
||||
|
||||
@@ -1447,7 +1354,7 @@ class GuiMain(QMainWindow):
|
||||
if tHandle is not None:
|
||||
if mode == nwDocMode.EDIT:
|
||||
tLine = None
|
||||
hItem = self._project.index.getItemHeader(tHandle, sTitle)
|
||||
hItem = SHARED.project.index.getItemHeader(tHandle, sTitle)
|
||||
if hItem is not None:
|
||||
tLine = hItem.line
|
||||
self.openDocument(tHandle, tLine=tLine, changeFocus=setFocus)
|
||||
@@ -1478,30 +1385,22 @@ class GuiMain(QMainWindow):
|
||||
@pyqtSlot()
|
||||
def _timeTick(self) -> None:
|
||||
"""Process time tick of the main timer."""
|
||||
if not self.hasProject:
|
||||
if not SHARED.hasProject:
|
||||
return
|
||||
|
||||
currTime = time()
|
||||
editIdle = currTime - self.docEditor.lastActive() > CONFIG.userIdleTime
|
||||
editIdle = currTime - self.docEditor.lastActive > CONFIG.userIdleTime
|
||||
userIdle = qApp.applicationState() != Qt.ApplicationActive
|
||||
|
||||
if editIdle or userIdle:
|
||||
self.idleTime += currTime - self.idleRefTime
|
||||
self.mainStatus.setUserIdle(True)
|
||||
else:
|
||||
self.mainStatus.setUserIdle(False)
|
||||
|
||||
self.idleRefTime = currTime
|
||||
self.mainStatus.updateTime(idleTime=self.idleTime)
|
||||
|
||||
self.mainStatus.setUserIdle(editIdle or userIdle)
|
||||
SHARED.updateIdleTime(currTime, editIdle or userIdle)
|
||||
self.mainStatus.updateTime(idleTime=SHARED.projectIdleTime)
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _autoSaveProject(self) -> None:
|
||||
"""Autosave of the project. This is a timer-activated slot."""
|
||||
doSave = self.hasProject
|
||||
doSave &= self._project.projChanged
|
||||
doSave &= self._project.storage.isOpen()
|
||||
doSave = SHARED.hasProject
|
||||
doSave &= SHARED.project.projChanged
|
||||
doSave &= SHARED.project.storage.isOpen()
|
||||
if doSave:
|
||||
logger.debug("Autosaving project")
|
||||
self.saveProject(autoSave=True)
|
||||
@@ -1510,7 +1409,7 @@ class GuiMain(QMainWindow):
|
||||
@pyqtSlot()
|
||||
def _autoSaveDocument(self) -> None:
|
||||
"""Autosave of the document. This is a timer-activated slot."""
|
||||
if self.hasProject and self.docEditor.docChanged():
|
||||
if SHARED.hasProject and self.docEditor.docChanged:
|
||||
logger.debug("Autosaving document")
|
||||
self.saveDocument()
|
||||
return
|
||||
@@ -1518,17 +1417,17 @@ class GuiMain(QMainWindow):
|
||||
@pyqtSlot()
|
||||
def _updateStatusWordCount(self) -> None:
|
||||
"""Update the word count on the status bar."""
|
||||
if not self.hasProject:
|
||||
if not SHARED.hasProject:
|
||||
self.mainStatus.setProjectStats(0, 0)
|
||||
|
||||
self._project.updateWordCounts()
|
||||
SHARED.project.updateWordCounts()
|
||||
if CONFIG.incNotesWCount:
|
||||
iTotal = sum(self._project.data.initCounts)
|
||||
cTotal = sum(self._project.data.currCounts)
|
||||
iTotal = sum(SHARED.project.data.initCounts)
|
||||
cTotal = sum(SHARED.project.data.currCounts)
|
||||
self.mainStatus.setProjectStats(cTotal, cTotal - iTotal)
|
||||
else:
|
||||
iNovel, _ = self._project.data.initCounts
|
||||
cNovel, _ = self._project.data.currCounts
|
||||
iNovel, _ = SHARED.project.data.initCounts
|
||||
cNovel, _ = SHARED.project.data.currCounts
|
||||
self.mainStatus.setProjectStats(cNovel, cNovel - iNovel)
|
||||
|
||||
return
|
||||
@@ -1554,7 +1453,7 @@ class GuiMain(QMainWindow):
|
||||
def _mainStackChanged(self, index: int) -> None:
|
||||
"""Process main window tab change."""
|
||||
if index == self.idxOutlineView:
|
||||
if self.hasProject:
|
||||
if SHARED.hasProject:
|
||||
self.outlineView.refreshTree()
|
||||
return
|
||||
|
||||
|
||||
@@ -0,0 +1,300 @@
|
||||
"""
|
||||
novelWriter – Shared Data Class
|
||||
===============================
|
||||
|
||||
File History:
|
||||
Created: 2023-08-10 [2.1b2]
|
||||
|
||||
This file is a part of novelWriter
|
||||
Copyright 2018–2023, 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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from time import time
|
||||
from typing import TYPE_CHECKING
|
||||
from pathlib import Path
|
||||
|
||||
from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot
|
||||
from PyQt5.QtWidgets import QMessageBox, QWidget
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from novelwriter.guimain import GuiMain
|
||||
from novelwriter.gui.theme import GuiTheme
|
||||
from novelwriter.core.project import NWProject
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SharedData(QObject):
|
||||
|
||||
__slots__ = (
|
||||
"_gui", "_theme", "_project", "_lockedBy", "_alert",
|
||||
"_idleTime", "_idleRefTime",
|
||||
)
|
||||
|
||||
projectStatusChanged = pyqtSignal(bool)
|
||||
projectStatusMessage = pyqtSignal(str)
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self._gui = None
|
||||
self._theme = None
|
||||
self._project = None
|
||||
self._lockedBy = None
|
||||
self._alert = None
|
||||
self._idleTime = 0.0
|
||||
self._idleRefTime = time()
|
||||
return
|
||||
|
||||
@property
|
||||
def mainGui(self) -> GuiMain:
|
||||
"""Return the Main GUI instance."""
|
||||
if self._gui is None:
|
||||
raise Exception("SharedData class not fully initialised")
|
||||
return self._gui
|
||||
|
||||
@property
|
||||
def theme(self) -> GuiTheme:
|
||||
"""Return the GUI Theme instance."""
|
||||
if self._theme is None:
|
||||
raise Exception("SharedData class not fully initialised")
|
||||
return self._theme
|
||||
|
||||
@property
|
||||
def project(self) -> NWProject:
|
||||
"""Return the active NWProject instance."""
|
||||
if self._project is None:
|
||||
raise Exception("SharedData class not fully initialised")
|
||||
return self._project
|
||||
|
||||
@property
|
||||
def hasProject(self) -> bool:
|
||||
"""Return True of the project instance is populated."""
|
||||
return self.project.isValid
|
||||
|
||||
@property
|
||||
def projectLock(self) -> list | None:
|
||||
"""Return cached lock information for the last project."""
|
||||
return self._lockedBy
|
||||
|
||||
@property
|
||||
def projectIdleTime(self) -> float:
|
||||
"""Return the session idle time."""
|
||||
return self._idleTime
|
||||
|
||||
@property
|
||||
def alert(self) -> _GuiAlert | None:
|
||||
"""Return a pointer to the last alert box."""
|
||||
return self._alert
|
||||
|
||||
##
|
||||
# Methods
|
||||
##
|
||||
|
||||
def initSharedData(self, gui: GuiMain, theme: GuiTheme) -> None:
|
||||
"""Initialise the UserData instance. This must be called as soon
|
||||
as the Main GUI is created to ensure the SHARED singleton has the
|
||||
properties needed for operation.
|
||||
"""
|
||||
self._gui = gui
|
||||
self._theme = theme
|
||||
self._resetProject()
|
||||
logger.debug("SharedData instance initialised")
|
||||
return
|
||||
|
||||
def openProject(self, path: str | Path, clearLock: bool = False) -> bool:
|
||||
"""Open a project."""
|
||||
if self.project.isValid:
|
||||
logger.error("A project is already open")
|
||||
return False
|
||||
|
||||
self._lockedBy = None
|
||||
status = self.project.openProject(path, clearLock=clearLock)
|
||||
if status is False:
|
||||
# We must cache the lock status before resetting the project
|
||||
self._lockedBy = self.project.lockStatus
|
||||
self._resetProject()
|
||||
|
||||
self._resetIdleTimer()
|
||||
|
||||
return status
|
||||
|
||||
def saveProject(self, autoSave: bool = False) -> bool:
|
||||
"""Save the current project."""
|
||||
if not self.project.isValid:
|
||||
logger.error("There is no project open")
|
||||
return False
|
||||
return self.project.saveProject(autoSave=autoSave)
|
||||
|
||||
def closeProject(self) -> None:
|
||||
"""Close the current project."""
|
||||
self.project.closeProject(self._idleTime)
|
||||
self._resetProject()
|
||||
self._resetIdleTimer()
|
||||
return
|
||||
|
||||
def updateIdleTime(self, currTime: float, userIdle: bool) -> None:
|
||||
"""Update the idle time record. If the userIdle flag is True,
|
||||
the user idle counter is updated with the time difference since
|
||||
the last time this function was called. Otherwise, only the
|
||||
reference time is updated.
|
||||
"""
|
||||
if userIdle:
|
||||
self._idleTime += currTime - self._idleRefTime
|
||||
self._idleRefTime = currTime
|
||||
return
|
||||
|
||||
##
|
||||
# Alert Boxes
|
||||
##
|
||||
|
||||
def info(self, text: str, info: str = "", details: str = "") -> None:
|
||||
"""Open an information alert box."""
|
||||
self._alert = _GuiAlert(self.mainGui, self.theme)
|
||||
self._alert.setMessage(text, info, details)
|
||||
self._alert.setAlertType(_GuiAlert.INFO, False)
|
||||
logger.info(self._alert.logMessage, stacklevel=2)
|
||||
self._alert.exec_()
|
||||
return
|
||||
|
||||
def warn(self, text: str, info: str = "", details: str = "") -> None:
|
||||
"""Open a warning alert box."""
|
||||
self._alert = _GuiAlert(self.mainGui, self.theme)
|
||||
self._alert.setMessage(text, info, details)
|
||||
self._alert.setAlertType(_GuiAlert.WARN, False)
|
||||
logger.warning(self._alert.logMessage, stacklevel=2)
|
||||
self._alert.exec_()
|
||||
return
|
||||
|
||||
def error(self, text: str, info: str = "", details: str = "",
|
||||
exc: Exception | None = None) -> None:
|
||||
"""Open an error alert box."""
|
||||
self._alert = _GuiAlert(self.mainGui, self.theme)
|
||||
self._alert.setMessage(text, info, details)
|
||||
self._alert.setAlertType(_GuiAlert.ERROR, False)
|
||||
if exc:
|
||||
self._alert.setException(exc)
|
||||
logger.error(self._alert.logMessage, stacklevel=2)
|
||||
self._alert.exec_()
|
||||
return
|
||||
|
||||
def question(self, text: str, info: str = "", details: str = "", warn: bool = False) -> bool:
|
||||
"""Open a question box."""
|
||||
self._alert = _GuiAlert(self.mainGui, self.theme)
|
||||
self._alert.setMessage(text, info, details)
|
||||
self._alert.setAlertType(_GuiAlert.WARN if warn else _GuiAlert.ASK, True)
|
||||
self._alert.exec_()
|
||||
return self._alert.result() == QMessageBox.Yes
|
||||
|
||||
##
|
||||
# Internal Slots
|
||||
##
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def _emitProjectStatusChange(self, state: bool) -> None:
|
||||
"""Forward the project status slot."""
|
||||
self.projectStatusChanged.emit(state)
|
||||
return
|
||||
|
||||
@pyqtSlot(str)
|
||||
def _emitProjectStatusMeesage(self, message: str) -> None:
|
||||
"""Forward the project message slot."""
|
||||
self.projectStatusMessage.emit(message)
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
##
|
||||
|
||||
def _resetProject(self) -> None:
|
||||
"""Create a new project instance."""
|
||||
from novelwriter.core.project import NWProject
|
||||
if isinstance(self._project, NWProject):
|
||||
self._project.statusChanged.disconnect()
|
||||
self._project.statusMessage.disconnect()
|
||||
self._project.deleteLater()
|
||||
self._project = NWProject(self)
|
||||
self._project.statusChanged.connect(self._emitProjectStatusChange)
|
||||
self._project.statusMessage.connect(self._emitProjectStatusMeesage)
|
||||
return
|
||||
|
||||
def _resetIdleTimer(self) -> None:
|
||||
"""Reset the timer data for the idle timer."""
|
||||
self._idleRefTime = time()
|
||||
self._idleTime = 0.0
|
||||
return
|
||||
|
||||
# END Class SharedData
|
||||
|
||||
|
||||
class _GuiAlert(QMessageBox):
|
||||
|
||||
INFO = 0
|
||||
WARN = 1
|
||||
ERROR = 2
|
||||
ASK = 3
|
||||
|
||||
def __init__(self, parent: QWidget, theme: GuiTheme) -> None:
|
||||
super().__init__(parent=parent)
|
||||
self._theme = theme
|
||||
self._message = ""
|
||||
return
|
||||
|
||||
@property
|
||||
def logMessage(self) -> str:
|
||||
return self._message
|
||||
|
||||
def setMessage(self, text: str, info: str, details: str) -> None:
|
||||
"""Set the alert box message."""
|
||||
self._message = " ".join(filter(None, [text, info, details]))
|
||||
self.setText(text)
|
||||
self.setInformativeText(info)
|
||||
self.setDetailedText(details)
|
||||
return
|
||||
|
||||
def setException(self, exception: Exception) -> None:
|
||||
"""Add exception details."""
|
||||
info = self.informativeText()
|
||||
text = f"<b>{type(exception).__name__}</b>: {str(exception)}"
|
||||
self.setInformativeText(f"{info}<br>{text}" if info else text)
|
||||
return
|
||||
|
||||
def setAlertType(self, level: int, isYesNo: bool) -> None:
|
||||
"""Set the type of alert and whether the dialog should have
|
||||
Yes/No buttons or just an Ok button.
|
||||
"""
|
||||
if isYesNo:
|
||||
self.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
|
||||
else:
|
||||
self.setStandardButtons(QMessageBox.Ok)
|
||||
pSz = 2*self._theme.baseIconSize
|
||||
if level == self.INFO:
|
||||
self.setIconPixmap(self._theme.getPixmap("alert_info", (pSz, pSz)))
|
||||
self.setWindowTitle(self.tr("Information"))
|
||||
elif level == self.WARN:
|
||||
self.setIconPixmap(self._theme.getPixmap("alert_warn", (pSz, pSz)))
|
||||
self.setWindowTitle(self.tr("Warning"))
|
||||
elif level == self.ERROR:
|
||||
self.setIconPixmap(self._theme.getPixmap("alert_error", (pSz, pSz)))
|
||||
self.setWindowTitle(self.tr("Error"))
|
||||
elif level == self.ASK:
|
||||
self.setIconPixmap(self._theme.getPixmap("alert_question", (pSz, pSz)))
|
||||
self.setWindowTitle(self.tr("Question"))
|
||||
return
|
||||
|
||||
# END Class _GuiAlert
|
||||
@@ -32,7 +32,7 @@ from PyQt5.QtWidgets import (
|
||||
QSpinBox
|
||||
)
|
||||
|
||||
from novelwriter import CONFIG
|
||||
from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.common import readTextFile
|
||||
from novelwriter.extensions.switch import NSwitch
|
||||
|
||||
@@ -60,7 +60,7 @@ class GuiLipsum(QDialog):
|
||||
nPx = CONFIG.pxInt(64)
|
||||
vSp = CONFIG.pxInt(4)
|
||||
self.docIcon = QLabel()
|
||||
self.docIcon.setPixmap(CONFIG.theme.getPixmap("proj_document", (nPx, nPx)))
|
||||
self.docIcon.setPixmap(SHARED.theme.getPixmap("proj_document", (nPx, nPx)))
|
||||
|
||||
self.leftBox = QVBoxLayout()
|
||||
self.leftBox.setSpacing(vSp)
|
||||
|
||||
@@ -25,7 +25,6 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
from pathlib import Path
|
||||
|
||||
from PyQt5.QtCore import QSize, QTimer, Qt, pyqtSlot
|
||||
@@ -35,8 +34,8 @@ from PyQt5.QtWidgets import (
|
||||
QPushButton, QSplitter, QVBoxLayout, QWidget
|
||||
)
|
||||
|
||||
from novelwriter import CONFIG
|
||||
from novelwriter.enum import nwAlert, nwBuildFmt
|
||||
from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.enum import nwBuildFmt
|
||||
from novelwriter.common import makeFileNameSafe
|
||||
from novelwriter.constants import nwLabels
|
||||
from novelwriter.core.item import NWItem
|
||||
@@ -44,9 +43,6 @@ from novelwriter.core.docbuild import NWBuildDocument
|
||||
from novelwriter.core.buildsettings import BuildSettings
|
||||
from novelwriter.extensions.simpleprogress import NProgressSimple
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from novelwriter.guimain import GuiMain
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -59,14 +55,12 @@ class GuiManuscriptBuild(QDialog):
|
||||
|
||||
D_KEY = Qt.ItemDataRole.UserRole
|
||||
|
||||
def __init__(self, parent: QWidget, mainGui: GuiMain, build: BuildSettings):
|
||||
def __init__(self, parent: QWidget, build: BuildSettings):
|
||||
super().__init__(parent=parent)
|
||||
|
||||
logger.debug("Create: GuiManuscriptBuild")
|
||||
self.setObjectName("GuiManuscriptBuild")
|
||||
|
||||
self.mainGui = mainGui
|
||||
|
||||
self._parent = parent
|
||||
self._build = build
|
||||
|
||||
@@ -74,14 +68,14 @@ class GuiManuscriptBuild(QDialog):
|
||||
self.setMinimumWidth(CONFIG.pxInt(500))
|
||||
self.setMinimumHeight(CONFIG.pxInt(300))
|
||||
|
||||
iPx = CONFIG.theme.baseIconSize
|
||||
iPx = SHARED.theme.baseIconSize
|
||||
sp4 = CONFIG.pxInt(4)
|
||||
sp8 = CONFIG.pxInt(8)
|
||||
sp16 = CONFIG.pxInt(16)
|
||||
wWin = CONFIG.pxInt(620)
|
||||
hWin = CONFIG.pxInt(360)
|
||||
|
||||
pOptions = self.mainGui.project.options
|
||||
pOptions = SHARED.project.options
|
||||
self.resize(
|
||||
CONFIG.pxInt(pOptions.getInt("GuiManuscriptBuild", "winWidth", wWin)),
|
||||
CONFIG.pxInt(pOptions.getInt("GuiManuscriptBuild", "winHeight", hWin))
|
||||
@@ -146,7 +140,7 @@ class GuiManuscriptBuild(QDialog):
|
||||
# Build Path
|
||||
self.lblPath = QLabel(self.tr("Path"))
|
||||
self.buildPath = QLineEdit(self)
|
||||
self.btnBrowse = QPushButton(CONFIG.theme.getIcon("browse"), "")
|
||||
self.btnBrowse = QPushButton(SHARED.theme.getIcon("browse"), "")
|
||||
|
||||
self.pathBox = QHBoxLayout()
|
||||
self.pathBox.addWidget(self.buildPath)
|
||||
@@ -156,7 +150,7 @@ class GuiManuscriptBuild(QDialog):
|
||||
# Build Name
|
||||
self.lblName = QLabel(self.tr("File Name"))
|
||||
self.buildName = QLineEdit(self)
|
||||
self.btnReset = QPushButton(CONFIG.theme.getIcon("revert"), "")
|
||||
self.btnReset = QPushButton(SHARED.theme.getIcon("revert"), "")
|
||||
self.btnReset.setToolTip(self.tr("Reset file name to default"))
|
||||
|
||||
self.nameBox = QHBoxLayout()
|
||||
@@ -181,7 +175,7 @@ class GuiManuscriptBuild(QDialog):
|
||||
self.buildBox.setVerticalSpacing(sp4)
|
||||
|
||||
# Dialog Buttons
|
||||
self.btnBuild = QPushButton(CONFIG.theme.getIcon("export"), self.tr("&Build"))
|
||||
self.btnBuild = QPushButton(SHARED.theme.getIcon("export"), self.tr("&Build"))
|
||||
self.dlgButtons = QDialogButtonBox(QDialogButtonBox.Close)
|
||||
self.dlgButtons.addButton(self.btnBuild, QDialogButtonBox.ActionRole)
|
||||
|
||||
@@ -279,7 +273,7 @@ class GuiManuscriptBuild(QDialog):
|
||||
@pyqtSlot()
|
||||
def _doResetBuildName(self):
|
||||
"""Generate a default build name."""
|
||||
bName = f"{self.mainGui.project.data.name} - {self._build.name}"
|
||||
bName = f"{SHARED.project.data.name} - {self._build.name}"
|
||||
self.buildName.setText(bName)
|
||||
self._build.setLastBuildName(bName)
|
||||
return
|
||||
@@ -308,19 +302,19 @@ class GuiManuscriptBuild(QDialog):
|
||||
self.buildProgress.setValue(0)
|
||||
bPath = Path(self.buildPath.text())
|
||||
if not bPath.is_dir():
|
||||
self.mainGui.makeAlert(self.tr("Output folder does not exist."), level=nwAlert.ERROR)
|
||||
SHARED.error(self.tr("Output folder does not exist."))
|
||||
return False
|
||||
|
||||
bExt = nwLabels.BUILD_EXT[bFormat]
|
||||
buildPath = (bPath / makeFileNameSafe(bName)).with_suffix(bExt)
|
||||
|
||||
if buildPath.exists():
|
||||
if not self.mainGui.askQuestion(
|
||||
if not SHARED.question(
|
||||
self.tr("The file already exists. Do you want to overwrite it?")
|
||||
):
|
||||
return False
|
||||
|
||||
docBuild = NWBuildDocument(self.mainGui.project, self._build)
|
||||
docBuild = NWBuildDocument(SHARED.project, self._build)
|
||||
docBuild.queueAll()
|
||||
|
||||
self.buildProgress.setMaximum(len(docBuild))
|
||||
@@ -353,7 +347,7 @@ class GuiManuscriptBuild(QDialog):
|
||||
fmtWidth = CONFIG.rpxInt(mainSplit[0])
|
||||
sumWidth = CONFIG.rpxInt(mainSplit[1])
|
||||
|
||||
pOptions = self.mainGui.project.options
|
||||
pOptions = SHARED.project.options
|
||||
pOptions.setValue("GuiManuscriptBuild", "winWidth", winWidth)
|
||||
pOptions.setValue("GuiManuscriptBuild", "winHeight", winHeight)
|
||||
pOptions.setValue("GuiManuscriptBuild", "fmtWidth", fmtWidth)
|
||||
@@ -365,9 +359,9 @@ class GuiManuscriptBuild(QDialog):
|
||||
def _populateContentList(self):
|
||||
"""Build the content list."""
|
||||
rootMap = {}
|
||||
filtered = self._build.buildItemFilter(self.mainGui.project)
|
||||
filtered = self._build.buildItemFilter(SHARED.project)
|
||||
self.listContent.clear()
|
||||
for nwItem in self.mainGui.project.tree:
|
||||
for nwItem in SHARED.project.tree:
|
||||
tHandle = nwItem.itemHandle
|
||||
rHandle = nwItem.itemRoot
|
||||
|
||||
@@ -376,11 +370,11 @@ class GuiManuscriptBuild(QDialog):
|
||||
|
||||
if filtered.get(tHandle, (False, 0))[0]:
|
||||
if rHandle not in rootMap:
|
||||
rItem = self.mainGui.project.tree[rHandle]
|
||||
rItem = SHARED.project.tree[rHandle]
|
||||
if isinstance(rItem, NWItem):
|
||||
rootMap[rHandle] = rItem.itemName
|
||||
|
||||
itemIcon = CONFIG.theme.getItemIcon(
|
||||
itemIcon = SHARED.theme.getItemIcon(
|
||||
nwItem.itemType, nwItem.itemClass,
|
||||
nwItem.itemLayout, nwItem.mainHeading
|
||||
)
|
||||
|
||||
@@ -38,7 +38,7 @@ from PyQt5.QtWidgets import (
|
||||
)
|
||||
from PyQt5.QtPrintSupport import QPrintPreviewDialog, QPrinter
|
||||
|
||||
from novelwriter import CONFIG
|
||||
from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.error import logException
|
||||
from novelwriter.common import checkInt, fuzzyTime
|
||||
from novelwriter.core.tohtml import ToHtml
|
||||
@@ -74,18 +74,18 @@ class GuiManuscript(QDialog):
|
||||
|
||||
self.mainGui = mainGui
|
||||
|
||||
self._builds = BuildCollection(self.mainGui.project)
|
||||
self._builds = BuildCollection(SHARED.project)
|
||||
self._buildMap: dict[str, QListWidgetItem] = {}
|
||||
|
||||
self.setWindowTitle(self.tr("Build Manuscript"))
|
||||
self.setMinimumWidth(CONFIG.pxInt(600))
|
||||
self.setMinimumHeight(CONFIG.pxInt(500))
|
||||
|
||||
iPx = CONFIG.theme.baseIconSize
|
||||
iPx = SHARED.theme.baseIconSize
|
||||
wWin = CONFIG.pxInt(900)
|
||||
hWin = CONFIG.pxInt(600)
|
||||
|
||||
pOptions = self.mainGui.project.options
|
||||
pOptions = SHARED.project.options
|
||||
self.resize(
|
||||
CONFIG.pxInt(pOptions.getInt("GuiManuscript", "winWidth", wWin)),
|
||||
CONFIG.pxInt(pOptions.getInt("GuiManuscript", "winHeight", hWin))
|
||||
@@ -105,21 +105,21 @@ class GuiManuscript(QDialog):
|
||||
).format(CONFIG.pxInt(2), fadeCol.red(), fadeCol.green(), fadeCol.blue())
|
||||
|
||||
self.tbAdd = QToolButton(self)
|
||||
self.tbAdd.setIcon(CONFIG.theme.getIcon("add"))
|
||||
self.tbAdd.setIcon(SHARED.theme.getIcon("add"))
|
||||
self.tbAdd.setIconSize(QSize(iPx, iPx))
|
||||
self.tbAdd.setToolTip(self.tr("Add New Build"))
|
||||
self.tbAdd.setStyleSheet(buttonStyle)
|
||||
self.tbAdd.clicked.connect(self._createNewBuild)
|
||||
|
||||
self.tbDel = QToolButton(self)
|
||||
self.tbDel.setIcon(CONFIG.theme.getIcon("remove"))
|
||||
self.tbDel.setIcon(SHARED.theme.getIcon("remove"))
|
||||
self.tbDel.setIconSize(QSize(iPx, iPx))
|
||||
self.tbDel.setToolTip(self.tr("Delete Selected Build"))
|
||||
self.tbDel.setStyleSheet(buttonStyle)
|
||||
self.tbDel.clicked.connect(self._deleteSelectedBuild)
|
||||
|
||||
self.tbEdit = QToolButton(self)
|
||||
self.tbEdit.setIcon(CONFIG.theme.getIcon("edit"))
|
||||
self.tbEdit.setIcon(SHARED.theme.getIcon("edit"))
|
||||
self.tbEdit.setIconSize(QSize(iPx, iPx))
|
||||
self.tbEdit.setToolTip(self.tr("Edit Selected Build"))
|
||||
self.tbEdit.setStyleSheet(buttonStyle)
|
||||
@@ -163,7 +163,7 @@ class GuiManuscript(QDialog):
|
||||
# Assemble GUI
|
||||
# ============
|
||||
|
||||
self.docPreview = _PreviewWidget(self.mainGui)
|
||||
self.docPreview = _PreviewWidget(self)
|
||||
|
||||
self.controlBox = QVBoxLayout()
|
||||
self.controlBox.addLayout(self.listToolBox, 0)
|
||||
@@ -210,7 +210,7 @@ class GuiManuscript(QDialog):
|
||||
self._updateBuildsList()
|
||||
|
||||
logger.debug("Loading build cache")
|
||||
cache = CONFIG.dataPath("cache") / f"build_{self.mainGui.project.data.uuid}.json"
|
||||
cache = CONFIG.dataPath("cache") / f"build_{SHARED.project.data.uuid}.json"
|
||||
if cache.is_file():
|
||||
try:
|
||||
with open(cache, mode="r", encoding="utf-8") as fObj:
|
||||
@@ -268,7 +268,7 @@ class GuiManuscript(QDialog):
|
||||
"""Delete the currently selected build settings entry."""
|
||||
build = self._getSelectedBuild()
|
||||
if build is not None:
|
||||
if self.mainGui.askQuestion(self.tr("Delete build '{0}'?".format(build.name))):
|
||||
if SHARED.question(self.tr("Delete build '{0}'?".format(build.name))):
|
||||
self._builds.removeBuild(build.buildID)
|
||||
self._updateBuildsList()
|
||||
return
|
||||
@@ -289,7 +289,7 @@ class GuiManuscript(QDialog):
|
||||
if build is None:
|
||||
return
|
||||
|
||||
docBuild = NWBuildDocument(self.mainGui.project, build)
|
||||
docBuild = NWBuildDocument(SHARED.project, build)
|
||||
docBuild.queueAll()
|
||||
|
||||
self.docPreview.beginNewBuild(len(docBuild))
|
||||
@@ -309,7 +309,7 @@ class GuiManuscript(QDialog):
|
||||
self._updatePreview(result, build)
|
||||
|
||||
logger.debug("Saving build cache")
|
||||
cache = CONFIG.dataPath("cache") / f"build_{self.mainGui.project.data.uuid}.json"
|
||||
cache = CONFIG.dataPath("cache") / f"build_{SHARED.project.data.uuid}.json"
|
||||
try:
|
||||
with open(cache, mode="w+", encoding="utf-8") as outFile:
|
||||
outFile.write(json.dumps(result, indent=2))
|
||||
@@ -325,7 +325,7 @@ class GuiManuscript(QDialog):
|
||||
"""Open the build dialog and build the manuscript."""
|
||||
build = self._getSelectedBuild()
|
||||
if isinstance(build, BuildSettings):
|
||||
dlgBuild = GuiManuscriptBuild(self, self.mainGui, build)
|
||||
dlgBuild = GuiManuscriptBuild(self, build)
|
||||
dlgBuild.exec_()
|
||||
|
||||
# After the build is done, save build settings changes
|
||||
@@ -390,7 +390,7 @@ class GuiManuscript(QDialog):
|
||||
optsWidth = CONFIG.rpxInt(mainSplit[0])
|
||||
viewWidth = CONFIG.rpxInt(mainSplit[1])
|
||||
|
||||
pOptions = self.mainGui.project.options
|
||||
pOptions = SHARED.project.options
|
||||
pOptions.setValue("GuiManuscript", "winWidth", winWidth)
|
||||
pOptions.setValue("GuiManuscript", "winHeight", winHeight)
|
||||
pOptions.setValue("GuiManuscript", "optsWidth", optsWidth)
|
||||
@@ -426,7 +426,7 @@ class GuiManuscript(QDialog):
|
||||
for key, name in self._builds.builds():
|
||||
bItem = QListWidgetItem()
|
||||
bItem.setText(name)
|
||||
bItem.setIcon(CONFIG.theme.getIcon("export"))
|
||||
bItem.setIcon(SHARED.theme.getIcon("export"))
|
||||
bItem.setData(self.D_KEY, key)
|
||||
self.buildList.addItem(bItem)
|
||||
self._buildMap[key] = bItem
|
||||
@@ -446,10 +446,8 @@ class GuiManuscript(QDialog):
|
||||
|
||||
class _PreviewWidget(QTextBrowser):
|
||||
|
||||
def __init__(self, mainGui: GuiMain):
|
||||
super().__init__(parent=mainGui)
|
||||
|
||||
self.mainGui = mainGui
|
||||
def __init__(self, parent: QWidget):
|
||||
super().__init__(parent=parent)
|
||||
|
||||
self._docTime = 0
|
||||
self._buildName = ""
|
||||
@@ -460,7 +458,7 @@ class _PreviewWidget(QTextBrowser):
|
||||
dPalette.setColor(QPalette.Text, QColor(0, 0, 0))
|
||||
self.setPalette(dPalette)
|
||||
|
||||
self.setMinimumWidth(40*CONFIG.theme.textNWidth)
|
||||
self.setMinimumWidth(40*SHARED.theme.textNWidth)
|
||||
self.setTextFont(CONFIG.textFont, CONFIG.textSize)
|
||||
self.setTabStopDistance(CONFIG.getTabWidth())
|
||||
self.setOpenExternalLinks(False)
|
||||
@@ -478,7 +476,7 @@ class _PreviewWidget(QTextBrowser):
|
||||
aPalette.setColor(QPalette.Foreground, aPalette.toolTipText().color())
|
||||
|
||||
aFont = self.font()
|
||||
aFont.setPointSizeF(0.9*CONFIG.theme.fontPointSize)
|
||||
aFont.setPointSizeF(0.9*SHARED.theme.fontPointSize)
|
||||
|
||||
self.ageLabel = QLabel("", self)
|
||||
self.ageLabel.setIndent(0)
|
||||
@@ -486,7 +484,7 @@ class _PreviewWidget(QTextBrowser):
|
||||
self.ageLabel.setPalette(aPalette)
|
||||
self.ageLabel.setAutoFillBackground(True)
|
||||
self.ageLabel.setAlignment(Qt.AlignCenter)
|
||||
self.ageLabel.setFixedHeight(int(2.1*CONFIG.theme.fontPixelSize))
|
||||
self.ageLabel.setFixedHeight(int(2.1*SHARED.theme.fontPixelSize))
|
||||
|
||||
# Progress
|
||||
self.buildProgress = NProgressCircle(self, CONFIG.pxInt(160), CONFIG.pxInt(16))
|
||||
|
||||
@@ -39,7 +39,7 @@ from PyQt5.QtWidgets import (
|
||||
QWidget
|
||||
)
|
||||
|
||||
from novelwriter import CONFIG
|
||||
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
|
||||
@@ -76,8 +76,6 @@ class GuiBuildSettings(QDialog):
|
||||
if CONFIG.osDarwin:
|
||||
self.setWindowFlag(Qt.WindowType.Tool)
|
||||
|
||||
self.mainGui = mainGui
|
||||
|
||||
self._build = build
|
||||
|
||||
self.setWindowTitle(self.tr("Manuscript Build Settings"))
|
||||
@@ -88,7 +86,7 @@ class GuiBuildSettings(QDialog):
|
||||
wWin = CONFIG.pxInt(750)
|
||||
hWin = CONFIG.pxInt(550)
|
||||
|
||||
pOptions = self.mainGui.project.options
|
||||
pOptions = SHARED.project.options
|
||||
self.resize(
|
||||
CONFIG.pxInt(pOptions.getInt("GuiBuildSettings", "winWidth", wWin)),
|
||||
CONFIG.pxInt(pOptions.getInt("GuiBuildSettings", "winHeight", hWin))
|
||||
@@ -100,7 +98,7 @@ class GuiBuildSettings(QDialog):
|
||||
self.optSideBar = NPagedSideBar(self)
|
||||
self.optSideBar.setMinimumWidth(mPx)
|
||||
self.optSideBar.setMaximumWidth(mPx)
|
||||
self.optSideBar.setLabelColor(CONFIG.theme.helpText)
|
||||
self.optSideBar.setLabelColor(SHARED.theme.helpText)
|
||||
|
||||
self.optSideBar.addLabel(self.tr("Options"))
|
||||
self.optSideBar.addButton(self.tr("Selection"), self.OPT_FILTERS)
|
||||
@@ -245,7 +243,7 @@ class GuiBuildSettings(QDialog):
|
||||
whether the user wants to save them.
|
||||
"""
|
||||
if self._build.changed:
|
||||
response = self.mainGui.askQuestion(self.tr(
|
||||
response = SHARED.question(self.tr(
|
||||
"Do you want to save your changes to '{0}'?".format(self._build.name)
|
||||
))
|
||||
if response:
|
||||
@@ -262,7 +260,7 @@ class GuiBuildSettings(QDialog):
|
||||
|
||||
treeWidth, filterWidth = self.optTabSelect.mainSplitSizes()
|
||||
|
||||
pOptions = self.mainGui.project.options
|
||||
pOptions = SHARED.project.options
|
||||
pOptions.setValue("GuiBuildSettings", "winWidth", winWidth)
|
||||
pOptions.setValue("GuiBuildSettings", "winHeight", winHeight)
|
||||
pOptions.setValue("GuiBuildSettings", "treeWidth", treeWidth)
|
||||
@@ -303,16 +301,14 @@ class _FilterTab(QWidget):
|
||||
def __init__(self, buildMain: GuiBuildSettings, build: BuildSettings) -> None:
|
||||
super().__init__(parent=buildMain)
|
||||
|
||||
self.mainGui = buildMain.mainGui
|
||||
|
||||
self._treeMap: dict[str, QTreeWidgetItem] = {}
|
||||
self._build = build
|
||||
|
||||
self._statusFlags: dict[int, QIcon] = {
|
||||
self.F_NONE: QIcon(),
|
||||
self.F_FILTERED: CONFIG.theme.getIcon("build_filtered"),
|
||||
self.F_INCLUDED: CONFIG.theme.getIcon("build_included"),
|
||||
self.F_EXCLUDED: CONFIG.theme.getIcon("build_excluded"),
|
||||
self.F_FILTERED: SHARED.theme.getIcon("build_filtered"),
|
||||
self.F_INCLUDED: SHARED.theme.getIcon("build_included"),
|
||||
self.F_EXCLUDED: SHARED.theme.getIcon("build_excluded"),
|
||||
}
|
||||
|
||||
self._trIncluded = self.tr("Included in manuscript")
|
||||
@@ -322,7 +318,7 @@ class _FilterTab(QWidget):
|
||||
# ============
|
||||
|
||||
# Tree Settings
|
||||
iPx = CONFIG.theme.baseIconSize
|
||||
iPx = SHARED.theme.baseIconSize
|
||||
cMg = CONFIG.pxInt(6)
|
||||
|
||||
# Tree Widget
|
||||
@@ -360,7 +356,7 @@ class _FilterTab(QWidget):
|
||||
|
||||
self.resetButton = QToolButton(self)
|
||||
self.resetButton.setToolTip(self.tr("Reset to default"))
|
||||
self.resetButton.setIcon(CONFIG.theme.getIcon("revert"))
|
||||
self.resetButton.setIcon(SHARED.theme.getIcon("revert"))
|
||||
self.resetButton.clicked.connect(lambda: self._setSelectedMode(self.F_FILTERED))
|
||||
|
||||
self.modeBox = QHBoxLayout()
|
||||
@@ -379,7 +375,7 @@ class _FilterTab(QWidget):
|
||||
# Assemble GUI
|
||||
# ============
|
||||
|
||||
pOptions = self.mainGui.project.options
|
||||
pOptions = SHARED.project.options
|
||||
|
||||
self.selectionBox = QVBoxLayout()
|
||||
self.selectionBox.addWidget(self.optTree)
|
||||
@@ -445,7 +441,7 @@ class _FilterTab(QWidget):
|
||||
logger.debug("Building project tree")
|
||||
self._treeMap = {}
|
||||
self.optTree.clear()
|
||||
for nwItem in self.mainGui.project.getProjectItems():
|
||||
for nwItem in SHARED.project.iterProjectItems():
|
||||
|
||||
tHandle = nwItem.itemHandle
|
||||
pHandle = nwItem.itemParent
|
||||
@@ -461,7 +457,7 @@ class _FilterTab(QWidget):
|
||||
continue
|
||||
|
||||
hLevel = nwItem.mainHeading
|
||||
itemIcon = CONFIG.theme.getItemIcon(
|
||||
itemIcon = SHARED.theme.getItemIcon(
|
||||
nwItem.itemType, nwItem.itemClass, nwItem.itemLayout, hLevel
|
||||
)
|
||||
|
||||
@@ -475,7 +471,7 @@ class _FilterTab(QWidget):
|
||||
trItem.setText(self.C_NAME, nwItem.itemName)
|
||||
trItem.setData(self.C_DATA, self.D_HANDLE, tHandle)
|
||||
trItem.setData(self.C_DATA, self.D_FILE, isFile)
|
||||
trItem.setIcon(self.C_ACTIVE, CONFIG.theme.getIcon(iconName))
|
||||
trItem.setIcon(self.C_ACTIVE, SHARED.theme.getIcon(iconName))
|
||||
|
||||
trItem.setTextAlignment(self.C_NAME, Qt.AlignLeft)
|
||||
|
||||
@@ -499,19 +495,19 @@ class _FilterTab(QWidget):
|
||||
self.filterOpt.clear()
|
||||
self.filterOpt.addLabel(self._build.getLabel("filter"))
|
||||
self.filterOpt.addItem(
|
||||
CONFIG.theme.getIcon("proj_scene"),
|
||||
SHARED.theme.getIcon("proj_scene"),
|
||||
self._build.getLabel("filter.includeNovel"),
|
||||
"doc:filter.includeNovel",
|
||||
default=self._build.getBool("filter.includeNovel")
|
||||
)
|
||||
self.filterOpt.addItem(
|
||||
CONFIG.theme.getIcon("proj_note"),
|
||||
SHARED.theme.getIcon("proj_note"),
|
||||
self._build.getLabel("filter.includeNotes"),
|
||||
"doc:filter.includeNotes",
|
||||
default=self._build.getBool("filter.includeNotes")
|
||||
)
|
||||
self.filterOpt.addItem(
|
||||
CONFIG.theme.getIcon("unchecked"),
|
||||
SHARED.theme.getIcon("unchecked"),
|
||||
self._build.getLabel("filter.includeInactive"),
|
||||
"doc:filter.includeInactive",
|
||||
default=self._build.getBool("filter.includeInactive")
|
||||
@@ -521,9 +517,9 @@ class _FilterTab(QWidget):
|
||||
|
||||
# Root Classes
|
||||
self.filterOpt.addLabel(self.tr("Select Root Folders"))
|
||||
for tHandle, nwItem in self.mainGui.project.tree.iterRoots(None):
|
||||
for tHandle, nwItem in SHARED.project.tree.iterRoots(None):
|
||||
if not nwItem.isInactiveClass():
|
||||
itemIcon = CONFIG.theme.getItemIcon(
|
||||
itemIcon = SHARED.theme.getItemIcon(
|
||||
nwItem.itemType, nwItem.itemClass, nwItem.itemLayout
|
||||
)
|
||||
self.filterOpt.addItem(
|
||||
@@ -557,7 +553,7 @@ class _FilterTab(QWidget):
|
||||
|
||||
def _setTreeItemMode(self) -> None:
|
||||
"""Update the filtered mode icon on all items."""
|
||||
filtered = self._build.buildItemFilter(self.mainGui.project)
|
||||
filtered = self._build.buildItemFilter(SHARED.project)
|
||||
for tHandle, item in self._treeMap.items():
|
||||
allow, mode = filtered.get(tHandle, (False, FilterMode.UNKNOWN))
|
||||
if mode == FilterMode.INCLUDED:
|
||||
@@ -597,12 +593,10 @@ class _HeadingsTab(QWidget):
|
||||
def __init__(self, buildMain: GuiBuildSettings, build: BuildSettings) -> None:
|
||||
super().__init__(parent=buildMain)
|
||||
|
||||
self.mainGui = buildMain.mainGui
|
||||
|
||||
self._build = build
|
||||
self._editing = 0
|
||||
|
||||
iPx = CONFIG.theme.baseIconSize
|
||||
iPx = SHARED.theme.baseIconSize
|
||||
vSp = CONFIG.pxInt(12)
|
||||
bSp = CONFIG.pxInt(6)
|
||||
|
||||
@@ -616,7 +610,7 @@ class _HeadingsTab(QWidget):
|
||||
self.fmtTitle = QLineEdit("")
|
||||
self.fmtTitle.setReadOnly(True)
|
||||
self.btnTitle = QToolButton()
|
||||
self.btnTitle.setIcon(CONFIG.theme.getIcon("edit"))
|
||||
self.btnTitle.setIcon(SHARED.theme.getIcon("edit"))
|
||||
self.btnTitle.clicked.connect(lambda: self._editHeading(self.EDIT_TITLE))
|
||||
|
||||
wrapTitle = QHBoxLayout()
|
||||
@@ -632,7 +626,7 @@ class _HeadingsTab(QWidget):
|
||||
self.fmtChapter = QLineEdit("")
|
||||
self.fmtChapter.setReadOnly(True)
|
||||
self.btnChapter = QToolButton()
|
||||
self.btnChapter.setIcon(CONFIG.theme.getIcon("edit"))
|
||||
self.btnChapter.setIcon(SHARED.theme.getIcon("edit"))
|
||||
self.btnChapter.clicked.connect(lambda: self._editHeading(self.EDIT_CHAPTER))
|
||||
|
||||
wrapChapter = QHBoxLayout()
|
||||
@@ -648,7 +642,7 @@ class _HeadingsTab(QWidget):
|
||||
self.fmtUnnumbered = QLineEdit("")
|
||||
self.fmtUnnumbered.setReadOnly(True)
|
||||
self.btnUnnumbered = QToolButton()
|
||||
self.btnUnnumbered.setIcon(CONFIG.theme.getIcon("edit"))
|
||||
self.btnUnnumbered.setIcon(SHARED.theme.getIcon("edit"))
|
||||
self.btnUnnumbered.clicked.connect(lambda: self._editHeading(self.EDIT_UNNUM))
|
||||
|
||||
wrapUnnumbered = QHBoxLayout()
|
||||
@@ -665,7 +659,7 @@ class _HeadingsTab(QWidget):
|
||||
self.fmtScene = QLineEdit("")
|
||||
self.fmtScene.setReadOnly(True)
|
||||
self.btnScene = QToolButton()
|
||||
self.btnScene.setIcon(CONFIG.theme.getIcon("edit"))
|
||||
self.btnScene.setIcon(SHARED.theme.getIcon("edit"))
|
||||
self.btnScene.clicked.connect(lambda: self._editHeading(self.EDIT_SCENE))
|
||||
self.hdeScene = QLabel(self.tr("Hide"))
|
||||
self.hdeScene.setToolTip(sceneHideTip)
|
||||
@@ -692,7 +686,7 @@ class _HeadingsTab(QWidget):
|
||||
self.fmtSection = QLineEdit("")
|
||||
self.fmtSection.setReadOnly(True)
|
||||
self.btnSection = QToolButton()
|
||||
self.btnSection.setIcon(CONFIG.theme.getIcon("edit"))
|
||||
self.btnSection.setIcon(SHARED.theme.getIcon("edit"))
|
||||
self.btnSection.clicked.connect(lambda: self._editHeading(self.EDIT_SECTION))
|
||||
self.hdeSection = QLabel(self.tr("Hide"))
|
||||
self.hdeSection.setToolTip(sectionHideTip)
|
||||
@@ -868,9 +862,9 @@ class _HeadingSyntaxHighlighter(QSyntaxHighlighter):
|
||||
def __init__(self, document: QTextDocument) -> None:
|
||||
super().__init__(document)
|
||||
self._fmtSymbol = QTextCharFormat()
|
||||
self._fmtSymbol.setForeground(QColor(*CONFIG.theme.colHead))
|
||||
self._fmtSymbol.setForeground(QColor(*SHARED.theme.colHead))
|
||||
self._fmtFormat = QTextCharFormat()
|
||||
self._fmtFormat.setForeground(QColor(*CONFIG.theme.colEmph))
|
||||
self._fmtFormat.setForeground(QColor(*SHARED.theme.colEmph))
|
||||
return
|
||||
|
||||
def highlightBlock(self, text: str) -> None:
|
||||
@@ -896,7 +890,7 @@ class _ContentTab(QWidget):
|
||||
|
||||
self._build = build
|
||||
|
||||
iPx = CONFIG.theme.baseIconSize
|
||||
iPx = SHARED.theme.baseIconSize
|
||||
|
||||
# Left Form
|
||||
# =========
|
||||
@@ -964,14 +958,13 @@ class _FormatTab(QWidget):
|
||||
super().__init__(parent=buildMain)
|
||||
|
||||
self.buildMain = buildMain
|
||||
self.mainGui = buildMain.mainGui
|
||||
|
||||
self._build = build
|
||||
self._unitScale = 1.0
|
||||
|
||||
iPx = CONFIG.theme.baseIconSize
|
||||
spW = 6*CONFIG.theme.textNWidth
|
||||
dbW = 8*CONFIG.theme.textNWidth
|
||||
iPx = SHARED.theme.baseIconSize
|
||||
spW = 6*SHARED.theme.textNWidth
|
||||
dbW = 8*SHARED.theme.textNWidth
|
||||
|
||||
# Text Format Form
|
||||
# ================
|
||||
@@ -992,7 +985,7 @@ class _FormatTab(QWidget):
|
||||
self.textFont = QLineEdit()
|
||||
self.textFont.setReadOnly(True)
|
||||
self.btnTextFont = QPushButton("...")
|
||||
self.btnTextFont.setMaximumWidth(int(2.5*CONFIG.theme.getTextWidth("...")))
|
||||
self.btnTextFont.setMaximumWidth(int(2.5*SHARED.theme.getTextWidth("...")))
|
||||
self.btnTextFont.clicked.connect(self._selectFont)
|
||||
self.formFormat.addRow(
|
||||
self._build.getLabel("format.textFont"), self.textFont, button=self.btnTextFont
|
||||
@@ -1278,7 +1271,7 @@ class _OutputTab(QWidget):
|
||||
|
||||
self._build = build
|
||||
|
||||
iPx = CONFIG.theme.baseIconSize
|
||||
iPx = SHARED.theme.baseIconSize
|
||||
|
||||
# Left Form
|
||||
# =========
|
||||
|
||||
@@ -32,7 +32,7 @@ from PyQt5.QtWidgets import (
|
||||
QPushButton, QRadioButton, QSpinBox, QVBoxLayout, QWizard, QWizardPage
|
||||
)
|
||||
|
||||
from novelwriter import CONFIG
|
||||
from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.common import makeFileNameSafe
|
||||
from novelwriter.extensions.switch import NSwitch
|
||||
|
||||
@@ -55,7 +55,7 @@ class GuiProjectWizard(QWizard):
|
||||
|
||||
self.mainGui = mainGui
|
||||
|
||||
self.sideImage = CONFIG.theme.loadDecoration(
|
||||
self.sideImage = SHARED.theme.loadDecoration(
|
||||
"wiz-back", None, CONFIG.pxInt(370)
|
||||
)
|
||||
self.setWizardStyle(QWizard.ModernStyle)
|
||||
@@ -104,7 +104,7 @@ class ProjWizardIntroPage(QWizardPage):
|
||||
"Peter Mitterhofer", "CC BY-SA 4.0"
|
||||
))
|
||||
lblFont = self.imgCredit.font()
|
||||
lblFont.setPointSizeF(0.6*CONFIG.theme.fontPointSize)
|
||||
lblFont.setPointSizeF(0.6*SHARED.theme.fontPointSize)
|
||||
self.imgCredit.setFont(lblFont)
|
||||
|
||||
xW = CONFIG.pxInt(300)
|
||||
@@ -172,7 +172,7 @@ class ProjWizardFolderPage(QWizardPage):
|
||||
self.projPath.setPlaceholderText(self.tr("Required"))
|
||||
|
||||
self.browseButton = QPushButton("...")
|
||||
self.browseButton.setMaximumWidth(int(2.5*CONFIG.theme.getTextWidth("...")))
|
||||
self.browseButton.setMaximumWidth(int(2.5*SHARED.theme.getTextWidth("...")))
|
||||
self.browseButton.clicked.connect(self._doBrowse)
|
||||
|
||||
self.errLabel = QLabel("")
|
||||
|
||||
@@ -36,8 +36,7 @@ from PyQt5.QtWidgets import (
|
||||
QLabel, QGroupBox, QMenu, QAction, QFileDialog, QSpinBox, QHBoxLayout
|
||||
)
|
||||
|
||||
from novelwriter import CONFIG
|
||||
from novelwriter.enum import nwAlert
|
||||
from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.error import formatException
|
||||
from novelwriter.common import formatTime, checkInt, checkIntTuple, minmax
|
||||
from novelwriter.constants import nwConst
|
||||
@@ -72,14 +71,12 @@ class GuiWritingStats(QDialog):
|
||||
if CONFIG.osDarwin:
|
||||
self.setWindowFlag(Qt.WindowType.Tool)
|
||||
|
||||
self.mainGui = mainGui
|
||||
|
||||
self.logData = []
|
||||
self.filterData = []
|
||||
self.timeFilter = 0.0
|
||||
self.wordOffset = 0
|
||||
|
||||
pOptions = self.mainGui.project.options
|
||||
pOptions = SHARED.project.options
|
||||
|
||||
self.setWindowTitle(self.tr("Writing Statistics"))
|
||||
self.setMinimumWidth(CONFIG.pxInt(420))
|
||||
@@ -132,7 +129,7 @@ class GuiWritingStats(QDialog):
|
||||
self.listBox.setSortingEnabled(True)
|
||||
|
||||
# Word Bar
|
||||
self.barHeight = int(round(0.5*CONFIG.theme.fontPixelSize))
|
||||
self.barHeight = int(round(0.5*SHARED.theme.fontPixelSize))
|
||||
self.barWidth = CONFIG.pxInt(200)
|
||||
self.barImage = QPixmap(self.barHeight, self.barHeight)
|
||||
self.barImage.fill(self.palette().highlight().color())
|
||||
@@ -143,27 +140,27 @@ class GuiWritingStats(QDialog):
|
||||
self.infoBox.setLayout(self.infoForm)
|
||||
|
||||
self.labelTotal = QLabel(formatTime(0))
|
||||
self.labelTotal.setFont(CONFIG.theme.guiFontFixed)
|
||||
self.labelTotal.setFont(SHARED.theme.guiFontFixed)
|
||||
self.labelTotal.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
|
||||
|
||||
self.labelIdleT = QLabel(formatTime(0))
|
||||
self.labelIdleT.setFont(CONFIG.theme.guiFontFixed)
|
||||
self.labelIdleT.setFont(SHARED.theme.guiFontFixed)
|
||||
self.labelIdleT.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
|
||||
|
||||
self.labelFilter = QLabel(formatTime(0))
|
||||
self.labelFilter.setFont(CONFIG.theme.guiFontFixed)
|
||||
self.labelFilter.setFont(SHARED.theme.guiFontFixed)
|
||||
self.labelFilter.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
|
||||
|
||||
self.novelWords = QLabel("0")
|
||||
self.novelWords.setFont(CONFIG.theme.guiFontFixed)
|
||||
self.novelWords.setFont(SHARED.theme.guiFontFixed)
|
||||
self.novelWords.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
|
||||
|
||||
self.notesWords = QLabel("0")
|
||||
self.notesWords.setFont(CONFIG.theme.guiFontFixed)
|
||||
self.notesWords.setFont(SHARED.theme.guiFontFixed)
|
||||
self.notesWords.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
|
||||
|
||||
self.totalWords = QLabel("0")
|
||||
self.totalWords.setFont(CONFIG.theme.guiFontFixed)
|
||||
self.totalWords.setFont(SHARED.theme.guiFontFixed)
|
||||
self.totalWords.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
|
||||
|
||||
lblTTime = QLabel(self.tr("Total Time:"))
|
||||
@@ -190,7 +187,7 @@ class GuiWritingStats(QDialog):
|
||||
self.infoForm.setRowStretch(6, 1)
|
||||
|
||||
# Filter Options
|
||||
sPx = CONFIG.theme.baseIconSize
|
||||
sPx = SHARED.theme.baseIconSize
|
||||
|
||||
self.filterBox = QGroupBox(self.tr("Filters"), self)
|
||||
self.filterForm = QGridLayout(self)
|
||||
@@ -333,7 +330,7 @@ class GuiWritingStats(QDialog):
|
||||
showIdleTime = self.showIdleTime.isChecked()
|
||||
histMax = self.histMax.value()
|
||||
|
||||
pOptions = self.mainGui.project.options
|
||||
pOptions = SHARED.project.options
|
||||
pOptions.setValue("GuiWritingStats", "winWidth", winWidth)
|
||||
pOptions.setValue("GuiWritingStats", "winHeight", winHeight)
|
||||
pOptions.setValue("GuiWritingStats", "widthCol0", widthCol0)
|
||||
@@ -413,14 +410,14 @@ class GuiWritingStats(QDialog):
|
||||
|
||||
# Report to user
|
||||
if wSuccess:
|
||||
self.mainGui.makeAlert(
|
||||
SHARED.info(
|
||||
self.tr("{0} file successfully written to:").format(textFmt),
|
||||
info=savePath
|
||||
)
|
||||
else:
|
||||
self.mainGui.makeAlert(
|
||||
SHARED.error(
|
||||
self.tr("Failed to write {0} file.").format(textFmt),
|
||||
info=errMsg, level=nwAlert.ERROR
|
||||
info=errMsg
|
||||
)
|
||||
|
||||
return wSuccess
|
||||
@@ -441,7 +438,7 @@ class GuiWritingStats(QDialog):
|
||||
ttTime = 0
|
||||
ttIdle = 0
|
||||
|
||||
for record in self.mainGui.project.session.iterRecords():
|
||||
for record in SHARED.project.session.iterRecords():
|
||||
rType = record.get("type")
|
||||
if rType == "initial":
|
||||
self.wordOffset = checkInt(record.get("offset"), 0)
|
||||
@@ -587,13 +584,13 @@ class GuiWritingStats(QDialog):
|
||||
newItem.setTextAlignment(self.C_COUNT, Qt.AlignRight)
|
||||
newItem.setTextAlignment(self.C_BAR, Qt.AlignLeft | Qt.AlignVCenter)
|
||||
|
||||
newItem.setFont(self.C_TIME, CONFIG.theme.guiFontFixed)
|
||||
newItem.setFont(self.C_LENGTH, CONFIG.theme.guiFontFixed)
|
||||
newItem.setFont(self.C_COUNT, CONFIG.theme.guiFontFixed)
|
||||
newItem.setFont(self.C_TIME, SHARED.theme.guiFontFixed)
|
||||
newItem.setFont(self.C_LENGTH, SHARED.theme.guiFontFixed)
|
||||
newItem.setFont(self.C_COUNT, SHARED.theme.guiFontFixed)
|
||||
if showIdleTime:
|
||||
newItem.setFont(self.C_IDLE, CONFIG.theme.guiFontFixed)
|
||||
newItem.setFont(self.C_IDLE, SHARED.theme.guiFontFixed)
|
||||
else:
|
||||
newItem.setFont(self.C_IDLE, CONFIG.theme.guiFont)
|
||||
newItem.setFont(self.C_IDLE, SHARED.theme.guiFont)
|
||||
|
||||
self.listBox.addTopLevelItem(newItem)
|
||||
self.timeFilter += sDiff
|
||||
|
||||
+13
-5
@@ -22,17 +22,18 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
import sys
|
||||
import pytest
|
||||
import shutil
|
||||
import logging
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from mocked import MockGuiMain
|
||||
from tools import cleanProject
|
||||
from mocked import MockGuiMain, MockTheme
|
||||
|
||||
from PyQt5.QtWidgets import QMessageBox
|
||||
|
||||
sys.path.insert(1, str(Path(__file__).parent.parent.absolute()))
|
||||
|
||||
from novelwriter import CONFIG, main # noqa: E402
|
||||
from novelwriter import CONFIG, SHARED, main # noqa: E402
|
||||
|
||||
_TST_ROOT = Path(__file__).parent
|
||||
_TMP_ROOT = _TST_ROOT / "temp"
|
||||
@@ -62,6 +63,7 @@ def resetConfigVars():
|
||||
@pytest.fixture(scope="session", autouse=True)
|
||||
def sessionFixture():
|
||||
"""A session wide fixture to set up the test environment."""
|
||||
logging.root.setLevel(logging.INFO)
|
||||
if _TMP_ROOT.exists():
|
||||
shutil.rmtree(_TMP_ROOT)
|
||||
_TMP_ROOT.mkdir()
|
||||
@@ -81,6 +83,7 @@ def functionFixture(qtbot):
|
||||
CONFIG.__init__()
|
||||
CONFIG.initConfig(confPath=_TMP_CONF, dataPath=_TMP_CONF)
|
||||
resetConfigVars()
|
||||
logging.getLogger("novelwriter").setLevel(logging.INFO)
|
||||
|
||||
return
|
||||
|
||||
@@ -136,10 +139,15 @@ def projPath(fncPath):
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def mockGUI():
|
||||
def mockGUI(qtbot, monkeypatch):
|
||||
"""Create a mock instance of novelWriter's main GUI class."""
|
||||
theGui = MockGuiMain()
|
||||
return theGui
|
||||
monkeypatch.setattr(QMessageBox, "exec_", lambda *a: None)
|
||||
monkeypatch.setattr(QMessageBox, "result", lambda *a: QMessageBox.Yes)
|
||||
gui = MockGuiMain()
|
||||
theme = MockTheme()
|
||||
monkeypatch.setattr(SHARED, "_gui", gui)
|
||||
monkeypatch.setattr(SHARED, "_theme", theme)
|
||||
return gui
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
|
||||
+16
-38
@@ -19,49 +19,25 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from PyQt5.QtCore import QObject
|
||||
from PyQt5.QtGui import QPixmap
|
||||
from PyQt5.QtWidgets import QWidget
|
||||
|
||||
|
||||
# =========================================================================== #
|
||||
# Mock GUI
|
||||
# =========================================================================== #
|
||||
|
||||
class MockGuiMain(QObject):
|
||||
class MockGuiMain(QWidget):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self._project = None
|
||||
|
||||
self.hasProject = True
|
||||
self.mainStatus = MockStatusBar()
|
||||
self.projPath = ""
|
||||
|
||||
# Test Variables
|
||||
self.askResponse = True
|
||||
self.lastAlert = ""
|
||||
self.lastQuestion = ""
|
||||
|
||||
return
|
||||
|
||||
@property
|
||||
def project(self):
|
||||
return self._project
|
||||
|
||||
def postLaunchTasks(self, cmdOpen):
|
||||
return
|
||||
|
||||
def makeAlert(self, text, info="", detals="", level=0, exception=None):
|
||||
assert isinstance(text, str)
|
||||
print("%s: %s" % (str(level), text))
|
||||
self.lastAlert = str(text)
|
||||
return
|
||||
|
||||
def askQuestion(self, text, info="", details="", level=3):
|
||||
print("Question: %s" % text)
|
||||
self.lastQuestion = text
|
||||
return self.askResponse
|
||||
|
||||
def setStatus(self, theMessage):
|
||||
return
|
||||
|
||||
@@ -78,16 +54,6 @@ class MockGuiMain(QObject):
|
||||
def close(self):
|
||||
return "close"
|
||||
|
||||
# Test Functions
|
||||
|
||||
def undo(self):
|
||||
self.askResponse = True
|
||||
return
|
||||
|
||||
def clear(self):
|
||||
self.lastAlert = ""
|
||||
return
|
||||
|
||||
# END Class MockGuiMain
|
||||
|
||||
|
||||
@@ -99,12 +65,24 @@ class MockStatusBar:
|
||||
def setStatus(self, theText):
|
||||
return
|
||||
|
||||
def doUpdateProjectStatus(self, theStatus):
|
||||
def updateProjectStatus(self, theStatus):
|
||||
return
|
||||
|
||||
# END Class MockStatusBar
|
||||
|
||||
|
||||
class MockTheme:
|
||||
|
||||
def __init__(self):
|
||||
self.baseIconSize = 10
|
||||
return
|
||||
|
||||
def getPixmap(self, *a):
|
||||
return QPixmap()
|
||||
|
||||
# END Class MockTheme
|
||||
|
||||
|
||||
class MockApp:
|
||||
|
||||
def __init__(self):
|
||||
|
||||
@@ -24,6 +24,7 @@ import pytest
|
||||
import hashlib
|
||||
|
||||
from pathlib import Path
|
||||
from xml.etree import ElementTree as ET
|
||||
|
||||
from tools import writeFile
|
||||
from mocked import causeOSError
|
||||
@@ -35,12 +36,12 @@ from novelwriter.common import (
|
||||
formatTimeStamp, fuzzyTime, getGuiItem, hexToInt, isHandle, isItemClass,
|
||||
isItemLayout, isItemType, isTitleTag, jsonEncode, makeFileNameSafe, minmax,
|
||||
numberToRoman, NWConfigParser, readTextFile, sha256sum, simplified,
|
||||
transferCase, yesNo
|
||||
transferCase, xmlIndent, yesNo
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_CheckStringNone():
|
||||
def testBaseCommon_checkStringNone():
|
||||
"""Test the checkStringNone function."""
|
||||
assert checkStringNone("Stuff", "NotNone") == "Stuff"
|
||||
assert checkStringNone("None", "NotNone") is None
|
||||
@@ -49,11 +50,11 @@ def testBaseCommon_CheckStringNone():
|
||||
assert checkStringNone(1.0, "NotNone") == "NotNone"
|
||||
assert checkStringNone(True, "NotNone") == "NotNone"
|
||||
|
||||
# END Test testBaseCommon_CheckStringNone
|
||||
# END Test testBaseCommon_checkStringNone
|
||||
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_CheckString():
|
||||
def testBaseCommon_checkString():
|
||||
"""Test the checkString function. Anything that is a string should
|
||||
be returned, otherwise it returns the default.
|
||||
"""
|
||||
@@ -64,11 +65,11 @@ def testBaseCommon_CheckString():
|
||||
assert checkString(1.0, "default") == "default"
|
||||
assert checkString(True, "default") == "default"
|
||||
|
||||
# END Test testBaseCommon_CheckString
|
||||
# END Test testBaseCommon_checkString
|
||||
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_CheckInt():
|
||||
def testBaseCommon_checkInt():
|
||||
"""Test the checkInt function. Anything that can be converted to an
|
||||
integer should be returned, otherwise it returns the default.
|
||||
"""
|
||||
@@ -80,11 +81,11 @@ def testBaseCommon_CheckInt():
|
||||
assert checkInt("1", 3) == 1
|
||||
assert checkInt("1.0", 3) == 3
|
||||
|
||||
# END Test testBaseCommon_CheckInt
|
||||
# END Test testBaseCommon_checkInt
|
||||
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_CheckFloat():
|
||||
def testBaseCommon_checkFloat():
|
||||
"""Test the checkFloat function. Anything that can be converted to an
|
||||
integer should be returned, otherwise it returns the default.
|
||||
"""
|
||||
@@ -96,11 +97,11 @@ def testBaseCommon_CheckFloat():
|
||||
assert checkFloat("1", 3.0) == 1.0
|
||||
assert checkFloat("1.0", 3.0) == 1.0
|
||||
|
||||
# END Test testBaseCommon_CheckInt
|
||||
# END Test testBaseCommon_checkFloat
|
||||
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_CheckBool():
|
||||
def testBaseCommon_checkBool():
|
||||
"""Test the checkBool function. Any bool, string version of Python
|
||||
bool, or integer 1 or 0, are returned as bool. Otherwise, the
|
||||
default is returned.
|
||||
@@ -145,11 +146,11 @@ def testBaseCommon_CheckBool():
|
||||
assert checkBool(2.0, True) is True
|
||||
assert checkBool(2.0, False) is False
|
||||
|
||||
# END Test testBaseCommon_CheckBool
|
||||
# END Test testBaseCommon_checkBool
|
||||
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_CheckHandle():
|
||||
def testBaseCommon_checkHandle():
|
||||
"""Test the checkHandle function."""
|
||||
assert checkHandle("None", 1, True) is None
|
||||
assert checkHandle("None", 1, False) == 1
|
||||
@@ -158,36 +159,36 @@ def testBaseCommon_CheckHandle():
|
||||
assert checkHandle("47666c91c7ccf", None, False) == "47666c91c7ccf"
|
||||
assert checkHandle("h7666c91c7ccf", None, False) is None
|
||||
|
||||
# END Test testBaseCommon_CheckHandle
|
||||
# END Test testBaseCommon_checkHandle
|
||||
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_CheckUuid():
|
||||
def testBaseCommon_checkUuid():
|
||||
"""Test the checkUuid function."""
|
||||
testUuid = "e2be99af-f9bf-4403-857a-c3d1ac25abea"
|
||||
assert checkUuid("", None) is None
|
||||
assert checkUuid("e2be99af-f9bf-4403-857a-c3d1ac25abe", None) is None
|
||||
assert checkUuid("e2be99af-f9bf-qq03-857a-c3d1ac25abea", None) is None
|
||||
assert checkUuid("e2be99af-f9bf-4403-857a-c3d1ac25abeaa", None) is None
|
||||
assert checkUuid(testUuid, None) == testUuid
|
||||
assert checkUuid("", None) is None # type: ignore
|
||||
assert checkUuid("e2be99af-f9bf-4403-857a-c3d1ac25abe", None) is None # type: ignore
|
||||
assert checkUuid("e2be99af-f9bf-qq03-857a-c3d1ac25abea", None) is None # type: ignore
|
||||
assert checkUuid("e2be99af-f9bf-4403-857a-c3d1ac25abeaa", None) is None # type: ignore
|
||||
assert checkUuid(testUuid, None) == testUuid # type: ignore
|
||||
|
||||
# END Test testBaseCommon_CheckUuid
|
||||
# END Test testBaseCommon_checkUuid
|
||||
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_CheckPath():
|
||||
def testBaseCommon_checkPath():
|
||||
"""Test the checkPath function."""
|
||||
assert checkPath(Path("test"), None) == Path("test")
|
||||
assert checkPath("test", None) == Path("test")
|
||||
assert checkPath(None, None) is None
|
||||
assert checkPath("", None) is None
|
||||
assert checkPath(" ", None) is None
|
||||
assert checkPath(Path("test"), None) == Path("test") # type: ignore
|
||||
assert checkPath("test", None) == Path("test") # type: ignore
|
||||
assert checkPath(None, None) is None # type: ignore
|
||||
assert checkPath("", None) is None # type: ignore
|
||||
assert checkPath(" ", None) is None # type: ignore
|
||||
|
||||
# END Test testBaseCommon_CheckPath
|
||||
# END Test testBaseCommon_checkPath
|
||||
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_IsHandle():
|
||||
def testBaseCommon_isHandle():
|
||||
"""Test the isHandle function."""
|
||||
assert isHandle("47666c91c7ccf") is True
|
||||
assert isHandle("47666C91C7CCF") is False
|
||||
@@ -196,12 +197,12 @@ def testBaseCommon_IsHandle():
|
||||
assert isHandle(None) is False
|
||||
assert isHandle("STUFF") is False
|
||||
|
||||
# END Test testBaseCommon_IsHandle
|
||||
# END Test testBaseCommon_isHandle
|
||||
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_IsTitleTag():
|
||||
"""Test the isItemClass function."""
|
||||
def testBaseCommon_isTitleTag():
|
||||
"""Test the isTitleTag function."""
|
||||
assert isTitleTag("T1234") is True
|
||||
|
||||
assert isTitleTag("t1234") is False
|
||||
@@ -213,11 +214,11 @@ def testBaseCommon_IsTitleTag():
|
||||
assert isTitleTag(None) is False
|
||||
assert isTitleTag("STUFF") is False
|
||||
|
||||
# END Test testBaseCommon_IsTitleTag
|
||||
# END Test testBaseCommon_isTitleTag
|
||||
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_IsItemClass():
|
||||
def testBaseCommon_isItemClass():
|
||||
"""Test the isItemClass function."""
|
||||
assert isItemClass("NO_CLASS") is True
|
||||
assert isItemClass("NOVEL") is True
|
||||
@@ -233,14 +234,14 @@ def testBaseCommon_IsItemClass():
|
||||
|
||||
# Invalid
|
||||
assert isItemClass("None") is False
|
||||
assert isItemClass(None) is False
|
||||
assert isItemClass(None) is False # type: ignore
|
||||
assert isItemClass("STUFF") is False
|
||||
|
||||
# END Test testBaseCommon_IsItemClass
|
||||
# END Test testBaseCommon_isItemClass
|
||||
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_IsItemType():
|
||||
def testBaseCommon_isItemType():
|
||||
"""Test the isItemType function."""
|
||||
assert isItemType("NO_TYPE") is True
|
||||
assert isItemType("ROOT") is True
|
||||
@@ -252,14 +253,14 @@ def testBaseCommon_IsItemType():
|
||||
|
||||
# Invalid
|
||||
assert isItemType("None") is False
|
||||
assert isItemType(None) is False
|
||||
assert isItemType(None) is False # type: ignore
|
||||
assert isItemType("STUFF") is False
|
||||
|
||||
# END Test testBaseCommon_IsItemType
|
||||
# END Test testBaseCommon_isItemType
|
||||
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_IsItemLayout():
|
||||
def testBaseCommon_isItemLayout():
|
||||
"""Test the isItemLayout function."""
|
||||
assert isItemLayout("NO_LAYOUT") is True
|
||||
assert isItemLayout("DOCUMENT") is True
|
||||
@@ -276,14 +277,14 @@ def testBaseCommon_IsItemLayout():
|
||||
|
||||
# Invalid
|
||||
assert isItemLayout("None") is False
|
||||
assert isItemLayout(None) is False
|
||||
assert isItemLayout(None) is False # type: ignore
|
||||
assert isItemLayout("STUFF") is False
|
||||
|
||||
# END Test testBaseCommon_IsItemLayout
|
||||
# END Test testBaseCommon_isItemLayout
|
||||
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_HexToInt():
|
||||
def testBaseCommon_hexToInt():
|
||||
"""Test the hexToInt function."""
|
||||
assert hexToInt(1) == 0
|
||||
assert hexToInt("1") == 1
|
||||
@@ -292,42 +293,42 @@ def testBaseCommon_HexToInt():
|
||||
assert hexToInt("0xffffq") == 0
|
||||
assert hexToInt("0xffffq", 12) == 12
|
||||
|
||||
# END Test testBaseCommon_HexToInt
|
||||
# END Test testBaseCommon_hexToInt
|
||||
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_MinMax():
|
||||
def testBaseCommon_minmax():
|
||||
"""Test the minmax function."""
|
||||
for i in range(-5, 15):
|
||||
assert 0 <= minmax(i, 0, 10) <= 10
|
||||
|
||||
# END Test testBaseCommon_MinMax
|
||||
# END Test testBaseCommon_minmax
|
||||
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_CheckIntTuple():
|
||||
def testBaseCommon_checkIntTuple():
|
||||
"""Test the checkIntTuple function."""
|
||||
assert checkIntTuple(0, (0, 1, 2), 3) == 0
|
||||
assert checkIntTuple(5, (0, 1, 2), 3) == 3
|
||||
|
||||
# END Test testBaseCommon_CheckIntTuple
|
||||
# END Test testBaseCommon_checkIntTuple
|
||||
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_FormatTimeStamp():
|
||||
def testBaseCommon_formatTimeStamp():
|
||||
"""Test the formatTimeStamp function."""
|
||||
tTime = time.mktime(time.gmtime(0))
|
||||
assert formatTimeStamp(tTime, False) == "1970-01-01 00:00:00"
|
||||
assert formatTimeStamp(tTime, True) == "1970-01-01 00.00.00"
|
||||
|
||||
# END Test testBaseCommon_FormatTimeStamp
|
||||
# END Test testBaseCommon_formatTimeStamp
|
||||
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_FormatTime():
|
||||
def testBaseCommon_formatTime():
|
||||
"""Test the formatTime function."""
|
||||
assert formatTime("1") == "ERROR"
|
||||
assert formatTime(1.0) == "ERROR"
|
||||
assert formatTime("1") == "ERROR" # type: ignore
|
||||
assert formatTime(1.0) == "ERROR" # type: ignore
|
||||
assert formatTime(1) == "00:00:01"
|
||||
assert formatTime(59) == "00:00:59"
|
||||
assert formatTime(60) == "00:01:00"
|
||||
@@ -342,21 +343,21 @@ def testBaseCommon_FormatTime():
|
||||
assert formatTime(86400) == "1-00:00:00"
|
||||
assert formatTime(360000) == "4-04:00:00"
|
||||
|
||||
# END Test testBaseCommon_FormatTime
|
||||
# END Test testBaseCommon_formatTime
|
||||
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_Simplified():
|
||||
def testBaseCommon_simplified():
|
||||
"""Test the simplified function."""
|
||||
assert simplified("Hello World") == "Hello World"
|
||||
assert simplified(" Hello World ") == "Hello World"
|
||||
assert simplified("\tHello\n\r\tWorld") == "Hello World"
|
||||
|
||||
# END Test testBaseCommon_Simplified
|
||||
# END Test testBaseCommon_simplified
|
||||
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_YesNo():
|
||||
def testBaseCommon_yesNo():
|
||||
"""Test the yesNo function."""
|
||||
# Bool
|
||||
assert yesNo(True) == "yes"
|
||||
@@ -366,8 +367,8 @@ def testBaseCommon_YesNo():
|
||||
assert yesNo(None) == "no"
|
||||
|
||||
# String
|
||||
assert yesNo("foo") == "yes"
|
||||
assert yesNo("") == "no"
|
||||
assert yesNo("foo") == "yes" # type: ignore
|
||||
assert yesNo("") == "no" # type: ignore
|
||||
|
||||
# Integer
|
||||
assert yesNo(0) == "no"
|
||||
@@ -375,15 +376,15 @@ def testBaseCommon_YesNo():
|
||||
assert yesNo(2) == "yes"
|
||||
|
||||
# Float
|
||||
assert yesNo(0.0) == "no"
|
||||
assert yesNo(1.0) == "yes"
|
||||
assert yesNo(2.0) == "yes"
|
||||
assert yesNo(0.0) == "no" # type: ignore
|
||||
assert yesNo(1.0) == "yes" # type: ignore
|
||||
assert yesNo(2.0) == "yes" # type: ignore
|
||||
|
||||
# END Test testBaseCommon_YesNo
|
||||
# END Test testBaseCommon_yesNo
|
||||
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_FormatInt():
|
||||
def testBaseCommon_formatInt():
|
||||
"""Test the formatInt function."""
|
||||
# Normal Cases
|
||||
assert formatInt(1) == "1"
|
||||
@@ -398,29 +399,29 @@ def testBaseCommon_FormatInt():
|
||||
assert formatInt(1234567890) == "1.23\u2009G"
|
||||
|
||||
# Exceptions
|
||||
assert formatInt(12.3) == "ERR"
|
||||
assert formatInt(None) == "ERR"
|
||||
assert formatInt("42") == "ERR"
|
||||
assert formatInt(12.3) == "ERR" # type: ignore
|
||||
assert formatInt(None) == "ERR" # type: ignore
|
||||
assert formatInt("42") == "ERR" # type: ignore
|
||||
|
||||
# END Test testBaseCommon_FormatInt
|
||||
# END Test testBaseCommon_formatInt
|
||||
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_TransferCase():
|
||||
def testBaseCommon_transferCase():
|
||||
"""Test the transferCase function."""
|
||||
assert transferCase(1, "TaRgEt") == "TaRgEt"
|
||||
assert transferCase("source", 1) == 1
|
||||
assert transferCase(1, "TaRgEt") == "TaRgEt" # type: ignore
|
||||
assert transferCase("source", 1) == 1 # type: ignore
|
||||
assert transferCase("", "TaRgEt") == "TaRgEt"
|
||||
assert transferCase("source", "") == ""
|
||||
assert transferCase("Source", "target") == "Target"
|
||||
assert transferCase("SOURCE", "target") == "TARGET"
|
||||
assert transferCase("source", "TARGET") == "target"
|
||||
|
||||
# END Test testBaseCommon_TransferCase
|
||||
# END Test testBaseCommon_transferCase
|
||||
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_FuzzyTime():
|
||||
def testBaseCommon_fuzzyTime():
|
||||
"""Test the fuzzyTime function."""
|
||||
assert fuzzyTime(-1) == "in the future"
|
||||
assert fuzzyTime(0) == "just now"
|
||||
@@ -451,13 +452,13 @@ def testBaseCommon_FuzzyTime():
|
||||
assert fuzzyTime(47336399) == "a year ago"
|
||||
assert fuzzyTime(47336400) == "2 years ago"
|
||||
|
||||
# END Test testBaseCommon_FuzzyTime
|
||||
# END Test testBaseCommon_fuzzyTime
|
||||
|
||||
|
||||
@pytest.mark.core
|
||||
def testBaseCommon_RomanNumbers():
|
||||
def testBaseCommon_numberToRoman():
|
||||
"""Test conversion of integers to Roman numbers."""
|
||||
assert numberToRoman(None, False) == "NAN"
|
||||
assert numberToRoman(None, False) == "NAN" # type: ignore
|
||||
assert numberToRoman(0, False) == "OOR"
|
||||
assert numberToRoman(1, False) == "I"
|
||||
assert numberToRoman(2, False) == "II"
|
||||
@@ -478,14 +479,14 @@ def testBaseCommon_RomanNumbers():
|
||||
assert numberToRoman(2010, False) == "MMX"
|
||||
assert numberToRoman(999, True) == "cmxcix"
|
||||
|
||||
# END Test testBaseCommon_RomanNumbers
|
||||
# END Test testBaseCommon_numberToRoman
|
||||
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_JsonEncode():
|
||||
def testBaseCommon_jsonEncode():
|
||||
"""Test the jsonEncode function."""
|
||||
# Wrong type
|
||||
assert jsonEncode(None) == "[]"
|
||||
assert jsonEncode(None) == "[]" # type: ignore
|
||||
|
||||
# Correct types
|
||||
assert jsonEncode([1, 2]) == "[\n 1,\n 2\n]"
|
||||
@@ -561,11 +562,38 @@ def testBaseCommon_JsonEncode():
|
||||
'}'
|
||||
)
|
||||
|
||||
# END Test testBaseCommon_JsonEncode
|
||||
# END Test testBaseCommon_jsonEncode
|
||||
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_ReadTextFile(monkeypatch, fncPath, ipsumText):
|
||||
def testBaseCommon_xmlIndent():
|
||||
"""Test the xmlIndent function."""
|
||||
xRoot = ET.fromstring(
|
||||
"<xml>"
|
||||
"<group>"
|
||||
"<item>foo</item>"
|
||||
"</group>"
|
||||
"</xml>"
|
||||
)
|
||||
xmlIndent(ET.ElementTree(xRoot))
|
||||
assert ET.tostring(xRoot) == (
|
||||
b"<xml>\n"
|
||||
b" <group>\n"
|
||||
b" <item>foo</item>\n"
|
||||
b" </group>\n"
|
||||
b"</xml>\n"
|
||||
)
|
||||
|
||||
# If we send nonsense, nothing is done
|
||||
data = "foobar"
|
||||
xmlIndent(data) # type: ignore
|
||||
assert data == "foobar"
|
||||
|
||||
# END Test testBaseCommon_xmlIndent
|
||||
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_readTextFile(monkeypatch, fncPath, ipsumText):
|
||||
"""Test the readTextFile function."""
|
||||
testText = "\n\n".join(ipsumText) + "\n"
|
||||
testFile = fncPath / "ipsum.txt"
|
||||
@@ -578,11 +606,11 @@ def testBaseCommon_ReadTextFile(monkeypatch, fncPath, ipsumText):
|
||||
mp.setattr("pathlib.Path.read_text", causeOSError)
|
||||
assert readTextFile(testFile) == ""
|
||||
|
||||
# END Test testBaseCommon_ReadTextFile
|
||||
# END Test testBaseCommon_readTextFile
|
||||
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_MakeFileNameSafe():
|
||||
def testBaseCommon_makeFileNameSafe():
|
||||
"""Test the makeFileNameSafe function."""
|
||||
assert makeFileNameSafe(" aaaa ") == "aaaa"
|
||||
assert makeFileNameSafe("aaaa,bbbb") == "aaaabbbb"
|
||||
@@ -591,11 +619,11 @@ def testBaseCommon_MakeFileNameSafe():
|
||||
assert makeFileNameSafe("æøå") == "æøå"
|
||||
assert makeFileNameSafe("Stuff œfi2⁵") == "Stuff œfi25"
|
||||
|
||||
# END Test testBaseCommon_MakeFileNameSafe
|
||||
# END Test testBaseCommon_makeFileNameSafe
|
||||
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_Sha256Sum(monkeypatch, fncPath, ipsumText):
|
||||
def testBaseCommon_sha256sum(monkeypatch, fncPath, ipsumText):
|
||||
"""Test the sha256sum function."""
|
||||
longText = 50*(" ".join(ipsumText) + " ")
|
||||
shortText = "This is a short file"
|
||||
@@ -630,16 +658,16 @@ def testBaseCommon_Sha256Sum(monkeypatch, fncPath, ipsumText):
|
||||
assert sha256sum(shortFile) is None
|
||||
assert sha256sum(noneFile) is None
|
||||
|
||||
# END Test testBaseCommon_Sha256Sum
|
||||
# END Test testBaseCommon_sha256sum
|
||||
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_GetGuiItem(nwGUI):
|
||||
def testBaseCommon_getGuiItem(nwGUI):
|
||||
"""Check the GUI item function."""
|
||||
assert getGuiItem("gibberish") is None
|
||||
assert isinstance(getGuiItem("GuiMain"), GuiMain)
|
||||
|
||||
# END Test testBaseCommon_GetGuiItem
|
||||
# END Test testBaseCommon_getGuiItem
|
||||
|
||||
|
||||
@pytest.mark.base
|
||||
@@ -675,14 +703,14 @@ def testBaseCommon_NWConfigParser(fncPath):
|
||||
assert cfgParser.rdStr("main", "blabla", "stuff") == "stuff"
|
||||
|
||||
# Read Boolean
|
||||
assert cfgParser.rdBool("main", "boolopt1", None) is True
|
||||
assert cfgParser.rdBool("main", "boolopt2", None) is True
|
||||
assert cfgParser.rdBool("main", "boolopt3", None) is True
|
||||
assert cfgParser.rdBool("main", "boolopt4", None) is False
|
||||
assert cfgParser.rdBool("main", "intopt1", None) is None
|
||||
assert cfgParser.rdBool("main", "boolopt1", None) is True # type: ignore
|
||||
assert cfgParser.rdBool("main", "boolopt2", None) is True # type: ignore
|
||||
assert cfgParser.rdBool("main", "boolopt3", None) is True # type: ignore
|
||||
assert cfgParser.rdBool("main", "boolopt4", None) is False # type: ignore
|
||||
assert cfgParser.rdBool("main", "intopt1", None) is None # type: ignore
|
||||
|
||||
assert cfgParser.rdBool("nope", "boolopt1", None) is None
|
||||
assert cfgParser.rdBool("main", "blabla", None) is None
|
||||
assert cfgParser.rdBool("nope", "boolopt1", None) is None # type: ignore
|
||||
assert cfgParser.rdBool("main", "blabla", None) is None # type: ignore
|
||||
|
||||
# Read Integer
|
||||
assert cfgParser.rdInt("main", "intopt1", 13) == 42
|
||||
|
||||
@@ -30,8 +30,7 @@ from novelwriter import CONFIG, main, logger
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseInit_Launch(caplog, monkeypatch, fncPath):
|
||||
"""Check launching the main GUI.
|
||||
"""
|
||||
"""Check launching the main GUI."""
|
||||
monkeypatch.setattr("novelwriter.guimain.GuiMain", MockGuiMain)
|
||||
|
||||
# TestMode Launch
|
||||
@@ -80,8 +79,7 @@ def testBaseInit_Launch(caplog, monkeypatch, fncPath):
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseInit_Options(monkeypatch, fncPath):
|
||||
"""Test command line options for logging level.
|
||||
"""
|
||||
"""Test command line options for logging level."""
|
||||
monkeypatch.setattr("novelwriter.guimain.GuiMain", MockGuiMain)
|
||||
monkeypatch.setattr(sys, "argv", [
|
||||
"novelWriter.py", "--testmode", f"--config={fncPath}", f"--data={fncPath}"
|
||||
@@ -146,8 +144,7 @@ def testBaseInit_Options(monkeypatch, fncPath):
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseInit_Imports(caplog, monkeypatch, fncPath):
|
||||
"""Check import error handling.
|
||||
"""
|
||||
"""Check import error handling."""
|
||||
monkeypatch.setattr("novelwriter.guimain.GuiMain", MockGuiMain)
|
||||
monkeypatch.setattr("PyQt5.QtWidgets.QApplication.__init__", lambda *a: None)
|
||||
monkeypatch.setattr("PyQt5.QtWidgets.QApplication.exec_", lambda *a: 0)
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
"""
|
||||
novelWriter – SharedData Class Tester
|
||||
=====================================
|
||||
|
||||
This file is a part of novelWriter
|
||||
Copyright 2018–2023, 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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from mocked import MockGuiMain, MockTheme
|
||||
|
||||
from PyQt5.QtWidgets import QMessageBox
|
||||
|
||||
from novelwriter.core.project import NWProject
|
||||
from novelwriter.shared import SharedData, _GuiAlert
|
||||
from tests.tools import buildTestProject
|
||||
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseSharedData_Init():
|
||||
"""Test SharedData class initialisation."""
|
||||
shared = SharedData()
|
||||
|
||||
# When not initialised, it should raise exceptions
|
||||
with pytest.raises(Exception):
|
||||
shared.mainGui
|
||||
with pytest.raises(Exception):
|
||||
shared.theme
|
||||
with pytest.raises(Exception):
|
||||
shared.project
|
||||
|
||||
# Create some mock objects
|
||||
mockGui = MockGuiMain()
|
||||
mockTheme = MockTheme()
|
||||
assert mockGui is not mockTheme
|
||||
|
||||
# Properly initialise the class
|
||||
shared.initSharedData(mockGui, mockTheme) # type: ignore
|
||||
|
||||
assert shared.mainGui is mockGui
|
||||
assert shared.theme is mockTheme
|
||||
|
||||
assert isinstance(shared.project, NWProject)
|
||||
assert shared.hasProject is False
|
||||
assert shared.projectIdleTime == 0.0
|
||||
assert shared.projectLock is None
|
||||
|
||||
assert shared.alert is None
|
||||
|
||||
# END Test testBaseSharedData_Init
|
||||
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseSharedData_Projects(fncPath, caplog: pytest.LogCaptureFixture):
|
||||
"""Test SharedData handling of projects."""
|
||||
project = NWProject()
|
||||
buildTestProject(project, fncPath)
|
||||
project.closeProject(0.0) # Clears the lockfile
|
||||
|
||||
shared = SharedData()
|
||||
assert shared._project is None
|
||||
|
||||
# Initialise the instance, should create an empty project
|
||||
mockGui = MockGuiMain()
|
||||
mockTheme = MockTheme()
|
||||
shared.initSharedData(mockGui, mockTheme) # type: ignore
|
||||
assert isinstance(shared.project, NWProject)
|
||||
assert shared.hasProject is False
|
||||
|
||||
# Load the test project
|
||||
assert shared.openProject(fncPath) is True
|
||||
assert shared.hasProject is True
|
||||
|
||||
# We cannot open two projects
|
||||
caplog.clear()
|
||||
assert shared.openProject(fncPath) is False
|
||||
assert caplog.messages[-1] == "A project is already open"
|
||||
assert shared._idleTime == 0.0
|
||||
|
||||
# Update idle time
|
||||
refTime = shared._idleRefTime
|
||||
shared.updateIdleTime(refTime + 1.0, False)
|
||||
shared.updateIdleTime(refTime + 2.0, True)
|
||||
shared.updateIdleTime(refTime + 3.0, False)
|
||||
shared.updateIdleTime(refTime + 4.0, True)
|
||||
assert round(shared.projectIdleTime) == 2
|
||||
|
||||
# Save project
|
||||
assert shared.saveProject() is True
|
||||
|
||||
# Close project
|
||||
shared.closeProject()
|
||||
assert shared.hasProject is False
|
||||
|
||||
# Cannot save a project after it's been closed
|
||||
assert shared.saveProject() is False
|
||||
|
||||
# Check locked project info
|
||||
project.openProject(fncPath) # First open with our independent project instance
|
||||
assert shared.hasProject is False
|
||||
assert shared.projectLock is None
|
||||
assert shared.openProject(fncPath) is False # Then with out shared instance
|
||||
assert shared.hasProject is False
|
||||
assert isinstance(shared.projectLock, list)
|
||||
|
||||
# END Test testBaseSharedData_Projects
|
||||
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseSharedData_Alerts(monkeypatch, caplog: pytest.LogCaptureFixture):
|
||||
"""Test SharedData class alert helper functions."""
|
||||
monkeypatch.setattr(QMessageBox, "exec_", lambda *a: None)
|
||||
monkeypatch.setattr(QMessageBox, "result", lambda *a: QMessageBox.Yes)
|
||||
|
||||
shared = SharedData()
|
||||
|
||||
mockGui = MockGuiMain()
|
||||
mockTheme = MockTheme()
|
||||
shared.initSharedData(mockGui, mockTheme) # type: ignore
|
||||
|
||||
assert shared.alert is None
|
||||
|
||||
# Info box
|
||||
caplog.clear()
|
||||
shared.info("Hello World", info="foo", details="bar")
|
||||
assert isinstance(shared.alert, _GuiAlert)
|
||||
assert shared.alert.text() == "Hello World"
|
||||
assert shared.alert.informativeText() == "foo"
|
||||
assert shared.alert.detailedText() == "bar"
|
||||
assert caplog.text.strip().startswith("INFO")
|
||||
assert caplog.text.strip().endswith("Hello World foo bar")
|
||||
shared._alert = None
|
||||
|
||||
# Warning box
|
||||
caplog.clear()
|
||||
shared.warn("Oops!", info="foo", details="bar")
|
||||
assert isinstance(shared.alert, _GuiAlert)
|
||||
assert shared.alert.text() == "Oops!"
|
||||
assert shared.alert.informativeText() == "foo"
|
||||
assert shared.alert.detailedText() == "bar"
|
||||
assert caplog.text.strip().startswith("WARNING")
|
||||
assert caplog.text.strip().endswith("Oops! foo bar")
|
||||
shared._alert = None
|
||||
|
||||
# Error box
|
||||
caplog.clear()
|
||||
shared.error("Oh noes!", info="foo", details="bar")
|
||||
assert isinstance(shared.alert, _GuiAlert)
|
||||
assert shared.alert.text() == "Oh noes!"
|
||||
assert shared.alert.informativeText() == "foo"
|
||||
assert shared.alert.detailedText() == "bar"
|
||||
assert caplog.text.strip().startswith("ERROR")
|
||||
assert caplog.text.strip().endswith("Oh noes! foo bar")
|
||||
shared._alert = None
|
||||
|
||||
# Error box with exception
|
||||
caplog.clear()
|
||||
shared.error("Oh noes!", info="foo", details="bar", exc=Exception("Boom!"))
|
||||
assert isinstance(shared.alert, _GuiAlert)
|
||||
assert shared.alert.text() == "Oh noes!"
|
||||
assert shared.alert.informativeText() == "foo<br><b>Exception</b>: Boom!"
|
||||
assert shared.alert.detailedText() == "bar"
|
||||
assert caplog.text.strip().startswith("ERROR")
|
||||
assert caplog.text.strip().endswith("Oh noes! foo bar")
|
||||
shared._alert = None
|
||||
|
||||
# Question box
|
||||
assert shared.question("Why?") is True
|
||||
assert isinstance(shared.alert, _GuiAlert)
|
||||
assert shared.alert.text() == "Why?"
|
||||
shared._alert = None
|
||||
|
||||
# END Test testBaseSharedData_Alerts
|
||||
@@ -220,7 +220,7 @@ def testCoreBuildSettings_BuildValues():
|
||||
@pytest.mark.core
|
||||
def testCoreBuildSettings_Filters(mockGUI, fncPath: Path, mockRnd):
|
||||
"""Test filters for project items."""
|
||||
project = NWProject(mockGUI)
|
||||
project = NWProject()
|
||||
buildTestProject(project, fncPath)
|
||||
build = BuildSettings()
|
||||
|
||||
@@ -368,7 +368,7 @@ def testCoreBuildSettings_Filters(mockGUI, fncPath: Path, mockRnd):
|
||||
@pytest.mark.core
|
||||
def testCoreBuildSettings_Collection(monkeypatch, mockGUI, fncPath: Path, mockRnd):
|
||||
"""Test the collections class for builds."""
|
||||
project = NWProject(mockGUI)
|
||||
project = NWProject()
|
||||
buildTestProject(project, fncPath)
|
||||
buildsFile = project.storage.getMetaFile(nwFiles.BUILDS_FILE)
|
||||
assert isinstance(buildsFile, Path)
|
||||
|
||||
@@ -38,7 +38,7 @@ from novelwriter.core.coretools import DocDuplicator, DocMerger, DocSplitter, Pr
|
||||
@pytest.mark.core
|
||||
def testCoreTools_DocMerger(monkeypatch, mockGUI, fncPath, tstPaths, mockRnd, ipsumText):
|
||||
"""Test the DocMerger utility."""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
mockRnd.reset()
|
||||
buildTestProject(theProject, fncPath)
|
||||
|
||||
@@ -126,7 +126,7 @@ def testCoreTools_DocMerger(monkeypatch, mockGUI, fncPath, tstPaths, mockRnd, ip
|
||||
@pytest.mark.core
|
||||
def testCoreTools_DocSplitter(monkeypatch, mockGUI, fncPath, mockRnd, ipsumText):
|
||||
"""Test the DocSplitter utility."""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
mockRnd.reset()
|
||||
buildTestProject(theProject, fncPath)
|
||||
|
||||
@@ -265,7 +265,7 @@ def testCoreTools_DocSplitter(monkeypatch, mockGUI, fncPath, mockRnd, ipsumText)
|
||||
@pytest.mark.core
|
||||
def testCoreTools_DocDuplicator(mockGUI, fncPath, tstPaths, mockRnd):
|
||||
"""Test the DocDuplicator utility."""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
mockRnd.reset()
|
||||
buildTestProject(theProject, fncPath)
|
||||
|
||||
@@ -290,7 +290,7 @@ def testCoreTools_DocDuplicator(mockGUI, fncPath, tstPaths, mockRnd):
|
||||
assert list(dup.duplicate([C.hSceneDoc])) == [
|
||||
("0000000000010", C.hSceneDoc), # The Scene
|
||||
]
|
||||
assert theProject.tree._treeOrder == [
|
||||
assert theProject.tree._order == [
|
||||
C.hNovelRoot, C.hPlotRoot, C.hCharRoot, C.hWorldRoot,
|
||||
C.hTitlePage, C.hChapterDir, C.hChapterDoc, C.hSceneDoc,
|
||||
"0000000000010",
|
||||
@@ -311,7 +311,7 @@ def testCoreTools_DocDuplicator(mockGUI, fncPath, tstPaths, mockRnd):
|
||||
("0000000000012", None), # The Chapter
|
||||
("0000000000013", None), # The Scene
|
||||
]
|
||||
assert theProject.tree._treeOrder == [
|
||||
assert theProject.tree._order == [
|
||||
C.hNovelRoot, C.hPlotRoot, C.hCharRoot, C.hWorldRoot,
|
||||
C.hTitlePage, C.hChapterDir, C.hChapterDoc, C.hSceneDoc,
|
||||
"0000000000010",
|
||||
@@ -342,7 +342,7 @@ def testCoreTools_DocDuplicator(mockGUI, fncPath, tstPaths, mockRnd):
|
||||
("0000000000017", None), # The Chapter
|
||||
("0000000000018", None), # The Scene
|
||||
]
|
||||
assert theProject.tree._treeOrder == [
|
||||
assert theProject.tree._order == [
|
||||
C.hNovelRoot, C.hPlotRoot, C.hCharRoot, C.hWorldRoot,
|
||||
C.hTitlePage, C.hChapterDir, C.hChapterDoc, C.hSceneDoc,
|
||||
"0000000000010",
|
||||
@@ -410,7 +410,7 @@ def testCoreTools_NewMinimal(monkeypatch, fncPath, tstPaths, mockGUI, mockRnd):
|
||||
testFile = tstPaths.outDir / "coreTools_NewMinimal_nwProject.nwx"
|
||||
compFile = tstPaths.refDir / "coreTools_NewMinimal_nwProject.nwx"
|
||||
|
||||
projBuild = ProjectBuilder(mockGUI)
|
||||
projBuild = ProjectBuilder()
|
||||
|
||||
# Setting no data should fail
|
||||
assert projBuild.buildProject({}) is False
|
||||
@@ -432,7 +432,7 @@ def testCoreTools_NewMinimal(monkeypatch, fncPath, tstPaths, mockGUI, mockRnd):
|
||||
|
||||
|
||||
@pytest.mark.core
|
||||
def testCoreTools_NewCustomA(monkeypatch, fncPath, tstPaths, mockGUI, mockRnd):
|
||||
def testCoreTools_NewCustomA(monkeypatch, fncPath, tstPaths, mockRnd):
|
||||
"""Create a new project from a project wizard dictionary.
|
||||
Custom type with chapters and scenes.
|
||||
"""
|
||||
@@ -460,7 +460,7 @@ def testCoreTools_NewCustomA(monkeypatch, fncPath, tstPaths, mockGUI, mockRnd):
|
||||
"numScenes": 3,
|
||||
}
|
||||
|
||||
projBuild = ProjectBuilder(mockGUI)
|
||||
projBuild = ProjectBuilder()
|
||||
assert projBuild.buildProject(projData) is True
|
||||
|
||||
copyfile(projFile, testFile)
|
||||
@@ -470,7 +470,7 @@ def testCoreTools_NewCustomA(monkeypatch, fncPath, tstPaths, mockGUI, mockRnd):
|
||||
|
||||
|
||||
@pytest.mark.core
|
||||
def testCoreTools_NewCustomB(monkeypatch, fncPath, tstPaths, mockGUI, mockRnd):
|
||||
def testCoreTools_NewCustomB(monkeypatch, fncPath, tstPaths, mockRnd):
|
||||
"""Create a new project from a project wizard dictionary.
|
||||
Custom type without chapters, but with scenes.
|
||||
"""
|
||||
@@ -498,7 +498,7 @@ def testCoreTools_NewCustomB(monkeypatch, fncPath, tstPaths, mockGUI, mockRnd):
|
||||
"numScenes": 6,
|
||||
}
|
||||
|
||||
projBuild = ProjectBuilder(mockGUI)
|
||||
projBuild = ProjectBuilder()
|
||||
assert projBuild.buildProject(projData) is True
|
||||
|
||||
copyfile(projFile, testFile)
|
||||
@@ -508,7 +508,7 @@ def testCoreTools_NewCustomB(monkeypatch, fncPath, tstPaths, mockGUI, mockRnd):
|
||||
|
||||
|
||||
@pytest.mark.core
|
||||
def testCoreTools_NewSample(monkeypatch, fncPath, tstPaths, mockGUI):
|
||||
def testCoreTools_NewSample(monkeypatch, fncPath, tstPaths):
|
||||
"""Check that we can create a new project can be created from the
|
||||
provided sample project via a zip file.
|
||||
"""
|
||||
@@ -522,7 +522,7 @@ def testCoreTools_NewSample(monkeypatch, fncPath, tstPaths, mockGUI):
|
||||
"popCustom": False,
|
||||
}
|
||||
|
||||
projBuild = ProjectBuilder(mockGUI)
|
||||
projBuild = ProjectBuilder()
|
||||
|
||||
# No path set
|
||||
assert projBuild.buildProject({"popSample": True}) is False
|
||||
|
||||
@@ -76,7 +76,7 @@ BUILD_CONF = {
|
||||
@pytest.mark.core
|
||||
def testCoreDocBuild_OpenDocument(monkeypatch, mockGUI, prjLipsum, fncPath, tstPaths):
|
||||
"""Test building an open document manuscript."""
|
||||
project = NWProject(mockGUI)
|
||||
project = NWProject()
|
||||
project.openProject(prjLipsum)
|
||||
|
||||
build = BuildSettings()
|
||||
@@ -180,7 +180,7 @@ def testCoreDocBuild_OpenDocument(monkeypatch, mockGUI, prjLipsum, fncPath, tstP
|
||||
@pytest.mark.core
|
||||
def testCoreDocBuild_HTML(monkeypatch, mockGUI, prjLipsum, fncPath, tstPaths):
|
||||
"""Test building an HTML manuscript."""
|
||||
project = NWProject(mockGUI)
|
||||
project = NWProject()
|
||||
project.openProject(prjLipsum)
|
||||
|
||||
build = BuildSettings()
|
||||
@@ -250,7 +250,7 @@ def testCoreDocBuild_HTML(monkeypatch, mockGUI, prjLipsum, fncPath, tstPaths):
|
||||
@pytest.mark.core
|
||||
def testCoreDocBuild_Markdown(monkeypatch, mockGUI, prjLipsum, fncPath, tstPaths):
|
||||
"""Test building an Markdown manuscript."""
|
||||
project = NWProject(mockGUI)
|
||||
project = NWProject()
|
||||
project.openProject(prjLipsum)
|
||||
|
||||
build = BuildSettings()
|
||||
@@ -320,7 +320,7 @@ def testCoreDocBuild_Markdown(monkeypatch, mockGUI, prjLipsum, fncPath, tstPaths
|
||||
@pytest.mark.core
|
||||
def testCoreDocBuild_NWD(monkeypatch, mockGUI, prjLipsum, fncPath, tstPaths):
|
||||
"""Test building a NWD manuscript."""
|
||||
project = NWProject(mockGUI)
|
||||
project = NWProject()
|
||||
project.openProject(prjLipsum)
|
||||
|
||||
build = BuildSettings()
|
||||
@@ -390,7 +390,7 @@ def testCoreDocBuild_NWD(monkeypatch, mockGUI, prjLipsum, fncPath, tstPaths):
|
||||
@pytest.mark.core
|
||||
def testCoreDocBuild_Custom(mockGUI, fncPath: Path):
|
||||
"""Test custom builds and some error handling."""
|
||||
project = NWProject(mockGUI)
|
||||
project = NWProject()
|
||||
buildTestProject(project, fncPath)
|
||||
|
||||
build = BuildSettings()
|
||||
@@ -421,8 +421,8 @@ def testCoreDocBuild_Custom(mockGUI, fncPath: Path):
|
||||
|
||||
# Add an invalid item to the project
|
||||
nHandle = "0123456789def"
|
||||
project.tree._treeOrder.append(nHandle)
|
||||
project.tree._projTree[nHandle] = None # type: ignore
|
||||
project.tree._order.append(nHandle)
|
||||
project.tree._tree[nHandle] = None # type: ignore
|
||||
|
||||
docBuild.queueAll()
|
||||
assert len(docBuild) == 8
|
||||
@@ -455,7 +455,7 @@ def testCoreDocBuild_Custom(mockGUI, fncPath: Path):
|
||||
@pytest.mark.core
|
||||
def testCoreDocBuild_IterBuild(mockGUI, fncPath: Path, mockRnd):
|
||||
"""Test iter build wrapper."""
|
||||
project = NWProject(mockGUI)
|
||||
project = NWProject()
|
||||
buildTestProject(project, fncPath)
|
||||
build = BuildSettings()
|
||||
build.unpack(BUILD_CONF)
|
||||
|
||||
@@ -32,7 +32,7 @@ from novelwriter.core.document import NWDocument
|
||||
@pytest.mark.core
|
||||
def testCoreDocument_LoadSave(monkeypatch, mockGUI, fncPath, mockRnd):
|
||||
"""Test loading and saving a document with the NWDocument class."""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
mockRnd.reset()
|
||||
buildTestProject(theProject, fncPath)
|
||||
|
||||
@@ -173,7 +173,7 @@ def testCoreDocument_LoadSave(monkeypatch, mockGUI, fncPath, mockRnd):
|
||||
def testCoreDocument_Methods(mockGUI, fncPath, mockRnd):
|
||||
"""Test other methods of the NWDocument class.
|
||||
"""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
mockRnd.reset()
|
||||
buildTestProject(theProject, fncPath)
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ def testCoreIndex_LoadSave(monkeypatch, prjLipsum, mockGUI, tstPaths):
|
||||
testFile = tstPaths.outDir / "coreIndex_LoadSave_tagsIndex.json"
|
||||
compFile = tstPaths.refDir / "coreIndex_LoadSave_tagsIndex.json"
|
||||
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
assert theProject.openProject(prjLipsum)
|
||||
|
||||
theIndex = NWIndex(theProject)
|
||||
@@ -155,7 +155,7 @@ def testCoreIndex_LoadSave(monkeypatch, prjLipsum, mockGUI, tstPaths):
|
||||
@pytest.mark.core
|
||||
def testCoreIndex_ScanThis(mockGUI):
|
||||
"""Test the tag scanner function scanThis."""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
theIndex = theProject.index
|
||||
|
||||
isValid, theBits, thePos = theIndex.scanThis("tag: this, and this")
|
||||
@@ -204,7 +204,7 @@ def testCoreIndex_ScanThis(mockGUI):
|
||||
def testCoreIndex_CheckThese(mockGUI, fncPath, mockRnd):
|
||||
"""Test the tag checker function checkThese.
|
||||
"""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
mockRnd.reset()
|
||||
buildTestProject(theProject, fncPath)
|
||||
theIndex = theProject.index
|
||||
@@ -281,7 +281,7 @@ def testCoreIndex_CheckThese(mockGUI, fncPath, mockRnd):
|
||||
@pytest.mark.core
|
||||
def testCoreIndex_ScanText(mockGUI, fncPath, mockRnd):
|
||||
"""Check the index text scanner."""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
mockRnd.reset()
|
||||
buildTestProject(theProject, fncPath)
|
||||
theIndex = theProject.index
|
||||
@@ -502,7 +502,7 @@ def testCoreIndex_ScanText(mockGUI, fncPath, mockRnd):
|
||||
@pytest.mark.core
|
||||
def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd):
|
||||
"""Check the index data extraction functions."""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
mockRnd.reset()
|
||||
buildTestProject(theProject, fncPath)
|
||||
|
||||
@@ -727,7 +727,7 @@ def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd):
|
||||
]
|
||||
|
||||
# Add a fake handle to the tree and check that it's ignored
|
||||
theProject.tree._treeOrder.append("0000000000000")
|
||||
theProject.tree._order.append("0000000000000")
|
||||
assert [(h, t) for h, t, _ in theIndex._itemIndex.iterNovelStructure(skipExcl=False)] == [
|
||||
(C.hTitlePage, "T0001"),
|
||||
(C.hChapterDoc, "T0001"),
|
||||
@@ -738,7 +738,7 @@ def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd):
|
||||
(sHandle, "T0001"),
|
||||
(tHandle, "T0001"),
|
||||
]
|
||||
theProject.tree._treeOrder.remove("0000000000000")
|
||||
theProject.tree._order.remove("0000000000000")
|
||||
|
||||
# Extract stats
|
||||
assert theIndex.getNovelWordCount(skipExcl=False) == 43
|
||||
@@ -941,7 +941,7 @@ def testCoreIndex_TagsIndex():
|
||||
@pytest.mark.core
|
||||
def testCoreIndex_ItemIndex(mockGUI, fncPath, mockRnd):
|
||||
"""Check the ItemIndex class."""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
mockRnd.reset()
|
||||
buildTestProject(theProject, fncPath)
|
||||
theProject.index.clearIndex()
|
||||
@@ -1077,7 +1077,7 @@ def testCoreIndex_ItemIndex(mockGUI, fncPath, mockRnd):
|
||||
assert nStruct[0][0] == uHandle
|
||||
|
||||
# Inject garbage into tree
|
||||
theProject.tree._treeOrder.append("stuff")
|
||||
theProject.tree._order.append("stuff")
|
||||
nStruct = list(itemIndex.iterNovelStructure())
|
||||
assert len(nStruct) == 4
|
||||
assert nStruct[0][0] == nHandle
|
||||
|
||||
@@ -34,7 +34,7 @@ from novelwriter.core.project import NWProject
|
||||
@pytest.mark.core
|
||||
def testCoreItem_Setters(mockGUI, mockRnd, fncPath):
|
||||
"""Test all the simple setters for the NWItem class."""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
mockRnd.reset()
|
||||
buildTestProject(theProject, fncPath)
|
||||
theItem = NWItem(theProject, "0000000000000")
|
||||
@@ -185,7 +185,7 @@ def testCoreItem_Setters(mockGUI, mockRnd, fncPath):
|
||||
@pytest.mark.core
|
||||
def testCoreItem_Methods(mockGUI, mockRnd, fncPath):
|
||||
"""Test the simple methods of the NWItem class."""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
mockRnd.reset()
|
||||
buildTestProject(theProject, fncPath)
|
||||
theItem = NWItem(theProject, "0000000000000")
|
||||
@@ -333,7 +333,7 @@ def testCoreItem_TypeSetter(mockGUI):
|
||||
"""Test the setter for all the nwItemType values for the NWItem
|
||||
class.
|
||||
"""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
theItem = NWItem(theProject, "0000000000000")
|
||||
|
||||
# Type
|
||||
@@ -362,7 +362,7 @@ def testCoreItem_ClassSetter(mockGUI):
|
||||
"""Test the setter for all the nwItemClass values for the NWItem
|
||||
class.
|
||||
"""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
theItem = NWItem(theProject, "0000000000000")
|
||||
|
||||
# Class
|
||||
@@ -449,7 +449,7 @@ def testCoreItem_LayoutSetter(mockGUI):
|
||||
"""Test the setter for all the nwItemLayout values for the NWItem
|
||||
class.
|
||||
"""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
theItem = NWItem(theProject, "0000000000000")
|
||||
|
||||
# Faulty Layouts
|
||||
@@ -477,7 +477,7 @@ def testCoreItem_LayoutSetter(mockGUI):
|
||||
def testCoreItem_ClassDefaults(mockGUI):
|
||||
"""Test the setter for the default values.
|
||||
"""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
theItem = NWItem(theProject, "0000000000000")
|
||||
|
||||
# Root items should not have their class updated
|
||||
@@ -532,7 +532,7 @@ def testCoreItem_ClassDefaults(mockGUI):
|
||||
@pytest.mark.core
|
||||
def testCoreItem_PackUnpack(mockGUI, caplog, mockRnd):
|
||||
"""Test packing and unpacking entries for the NWItem class."""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
theProject.data.itemStatus.write(None, "New", (100, 100, 100))
|
||||
theProject.data.itemImport.write(None, "New", (100, 100, 100))
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ from novelwriter.gui.noveltree import NovelTreeColumn
|
||||
@pytest.mark.core
|
||||
def testCoreOptions_LoadSave(monkeypatch, mockGUI, fncPath):
|
||||
"""Test loading and saving from the OptionState class."""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
theOpts = OptionState(theProject)
|
||||
|
||||
metaDir = fncPath / "meta"
|
||||
@@ -106,7 +106,7 @@ def testCoreOptions_LoadSave(monkeypatch, mockGUI, fncPath):
|
||||
@pytest.mark.core
|
||||
def testCoreOptions_SetGet(mockGUI):
|
||||
"""Test setting and getting values from the OptionState class."""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
theOpts = OptionState(theProject)
|
||||
|
||||
nwColHidden = NovelTreeColumn.HIDDEN
|
||||
|
||||
@@ -19,6 +19,7 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from PyQt5.QtWidgets import QMessageBox
|
||||
import pytest
|
||||
|
||||
from shutil import copyfile
|
||||
@@ -27,7 +28,7 @@ from zipfile import ZipFile
|
||||
from mocked import causeOSError
|
||||
from tools import C, cmpFiles, buildTestProject, XML_IGNORE
|
||||
|
||||
from novelwriter import CONFIG
|
||||
from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.enum import nwItemClass
|
||||
from novelwriter.constants import nwFiles
|
||||
from novelwriter.core.tree import NWTree
|
||||
@@ -44,7 +45,7 @@ def testCoreProject_NewRoot(fncPath, tstPaths, mockGUI, mockRnd):
|
||||
testFile = tstPaths.outDir / "coreProject_NewRoot_nwProject.nwx"
|
||||
compFile = tstPaths.refDir / "coreProject_NewRoot_nwProject.nwx"
|
||||
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
mockRnd.reset()
|
||||
buildTestProject(theProject, fncPath)
|
||||
|
||||
@@ -94,7 +95,7 @@ def testCoreProject_NewFileFolder(monkeypatch, fncPath, tstPaths, mockGUI, mockR
|
||||
testFile = tstPaths.outDir / "coreProject_NewFileFolder_nwProject.nwx"
|
||||
compFile = tstPaths.refDir / "coreProject_NewFileFolder_nwProject.nwx"
|
||||
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
mockRnd.reset()
|
||||
buildTestProject(theProject, fncPath)
|
||||
|
||||
@@ -163,7 +164,7 @@ def testCoreProject_NewFileFolder(monkeypatch, fncPath, tstPaths, mockGUI, mockR
|
||||
@pytest.mark.core
|
||||
def testCoreProject_Open(monkeypatch, caplog, mockGUI, fncPath, mockRnd):
|
||||
"""Test opening a project."""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
mockRnd.reset()
|
||||
buildTestProject(theProject, fncPath)
|
||||
|
||||
@@ -176,7 +177,7 @@ def testCoreProject_Open(monkeypatch, caplog, mockGUI, fncPath, mockRnd):
|
||||
theProject.storage._lockFilePath = fncPath / nwFiles.PROJ_LOCK
|
||||
assert theProject.storage.writeLockFile() is True
|
||||
assert theProject.openProject(fncPath) is False
|
||||
assert isinstance(theProject.getLockStatus(), list)
|
||||
assert isinstance(theProject.lockStatus, list)
|
||||
|
||||
# Fail to read lockfile (which still opens the project)
|
||||
with monkeypatch.context() as mp:
|
||||
@@ -189,9 +190,9 @@ def testCoreProject_Open(monkeypatch, caplog, mockGUI, fncPath, mockRnd):
|
||||
# Force open with lockfile
|
||||
theProject.storage._lockFilePath = fncPath / nwFiles.PROJ_LOCK
|
||||
assert theProject.storage.writeLockFile() is True
|
||||
assert theProject.openProject(fncPath, overrideLock=True) is True
|
||||
assert theProject.openProject(fncPath, clearLock=True) is True
|
||||
theProject.closeProject()
|
||||
assert theProject.getLockStatus() is None
|
||||
assert theProject.lockStatus is None
|
||||
|
||||
# Fail getting xml reader
|
||||
with monkeypatch.context() as mp:
|
||||
@@ -203,37 +204,40 @@ def testCoreProject_Open(monkeypatch, caplog, mockGUI, fncPath, mockRnd):
|
||||
mp.setattr(ProjectXMLReader, "read", lambda *a: False)
|
||||
mp.setattr(ProjectXMLReader, "state", property(lambda *a: XMLReadState.NOT_NWX_FILE))
|
||||
assert theProject.openProject(fncPath) is False
|
||||
assert "Project file does not appear" in mockGUI.lastAlert
|
||||
lastMsg = SHARED.alert.logMessage if SHARED.alert else ""
|
||||
assert "Project file does not appear" in lastMsg
|
||||
|
||||
# Unknown project file version
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr(ProjectXMLReader, "read", lambda *a: False)
|
||||
mp.setattr(ProjectXMLReader, "state", property(lambda *a: XMLReadState.UNKNOWN_VERSION))
|
||||
assert theProject.openProject(fncPath) is False
|
||||
assert "Unknown or unsupported novelWriter project file" in mockGUI.lastAlert
|
||||
lastMsg = SHARED.alert.logMessage if SHARED.alert else ""
|
||||
assert "Unknown or unsupported novelWriter project file" in lastMsg
|
||||
|
||||
# Other parse error
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr(ProjectXMLReader, "read", lambda *a: False)
|
||||
mp.setattr(ProjectXMLReader, "state", property(lambda *a: XMLReadState.CANNOT_PARSE))
|
||||
assert theProject.openProject(fncPath) is False
|
||||
assert "Failed to parse project xml" in mockGUI.lastAlert
|
||||
lastMsg = SHARED.alert.logMessage if SHARED.alert else ""
|
||||
assert "Failed to parse project xml" in lastMsg
|
||||
|
||||
# Won't convert legacy file
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr(ProjectXMLReader, "state", property(lambda *a: XMLReadState.WAS_LEGACY))
|
||||
mockGUI.askResponse = False
|
||||
mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.No)
|
||||
assert theProject.openProject(fncPath) is False
|
||||
assert "The file format of your project is about to be" in mockGUI.lastQuestion
|
||||
mockGUI.askResponse = True
|
||||
lastMsg = SHARED.alert.logMessage if SHARED.alert else ""
|
||||
assert "The file format of your project is about to be" in lastMsg
|
||||
|
||||
# Won't open project from newer version
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr(ProjectXMLReader, "hexVersion", property(lambda *a: 0x99999999))
|
||||
mockGUI.askResponse = False
|
||||
mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.No)
|
||||
assert theProject.openProject(fncPath) is False
|
||||
assert "This project was saved by a newer version" in mockGUI.lastQuestion
|
||||
mockGUI.askResponse = True
|
||||
lastMsg = SHARED.alert.logMessage if SHARED.alert else ""
|
||||
assert "This project was saved by a newer version" in lastMsg
|
||||
|
||||
# Fail checking items should still pass
|
||||
with monkeypatch.context() as mp:
|
||||
@@ -246,10 +250,10 @@ def testCoreProject_Open(monkeypatch, caplog, mockGUI, fncPath, mockRnd):
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr(ProjectXMLReader, "state", property(lambda *a: XMLReadState.WAS_LEGACY))
|
||||
mp.setattr("novelwriter.core.index.NWIndex.loadIndex", lambda *a: True)
|
||||
mockGUI.askResponse = True
|
||||
theProject.index._indexBroken = True
|
||||
assert theProject.openProject(fncPath) is True
|
||||
assert "The file format of your project is about to be" in mockGUI.lastQuestion
|
||||
lastMsg = SHARED.alert.logMessage if SHARED.alert else ""
|
||||
assert "The file format of your project is about to be" in lastMsg
|
||||
assert theProject.index._indexBroken is False
|
||||
|
||||
theProject.closeProject()
|
||||
@@ -260,7 +264,7 @@ def testCoreProject_Open(monkeypatch, caplog, mockGUI, fncPath, mockRnd):
|
||||
@pytest.mark.core
|
||||
def testCoreProject_Save(monkeypatch, mockGUI, mockRnd, fncPath):
|
||||
"""Test saving a project."""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
|
||||
# Nothing to save
|
||||
assert theProject.saveProject() is False
|
||||
@@ -289,7 +293,7 @@ def testCoreProject_Save(monkeypatch, mockGUI, mockRnd, fncPath):
|
||||
@pytest.mark.core
|
||||
def testCoreProject_AccessItems(mockGUI, fncPath, mockRnd):
|
||||
"""Test helper functions for the project folder."""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
buildTestProject(theProject, fncPath)
|
||||
|
||||
# Storage Objects
|
||||
@@ -323,7 +327,7 @@ def testCoreProject_AccessItems(mockGUI, fncPath, mockRnd):
|
||||
assert theProject.tree.handles() == newOrder
|
||||
|
||||
# Add a non-existing item
|
||||
theProject.tree._treeOrder.append(C.hInvalid)
|
||||
theProject.tree._order.append(C.hInvalid)
|
||||
|
||||
# Add an item with a non-existent parent
|
||||
nHandle = theProject.newFile("Test File", C.hChapterDir)
|
||||
@@ -331,7 +335,7 @@ def testCoreProject_AccessItems(mockGUI, fncPath, mockRnd):
|
||||
assert theProject.tree[nHandle].itemParent == "cba9876543210"
|
||||
|
||||
retOrder = []
|
||||
for tItem in theProject.getProjectItems():
|
||||
for tItem in theProject.iterProjectItems():
|
||||
retOrder.append(tItem.itemHandle)
|
||||
|
||||
assert retOrder == [
|
||||
@@ -353,7 +357,7 @@ def testCoreProject_AccessItems(mockGUI, fncPath, mockRnd):
|
||||
@pytest.mark.core
|
||||
def testCoreProject_StatusImport(mockGUI, fncPath, mockRnd):
|
||||
"""Test the status and importance flag handling."""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
mockRnd.reset()
|
||||
buildTestProject(theProject, fncPath)
|
||||
|
||||
@@ -462,7 +466,7 @@ def testCoreProject_StatusImport(mockGUI, fncPath, mockRnd):
|
||||
@pytest.mark.core
|
||||
def testCoreProject_Methods(monkeypatch, mockGUI, fncPath, mockRnd):
|
||||
"""Test other project class methods and functions."""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
buildTestProject(theProject, fncPath)
|
||||
|
||||
# Project Name
|
||||
@@ -482,7 +486,7 @@ def testCoreProject_Methods(monkeypatch, mockGUI, fncPath, mockRnd):
|
||||
theProject._session._start = 1600000000
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr("novelwriter.core.project.time", lambda: 1600005600)
|
||||
assert theProject.getCurrentEditTime() == 6834
|
||||
assert theProject.currentEditTime == 6834
|
||||
|
||||
# Trash folder
|
||||
# Should create on first call, and just returned on later calls
|
||||
@@ -580,7 +584,7 @@ def testCoreProject_Backup(monkeypatch, mockGUI, fncPath, tstPaths):
|
||||
backup file and checks that the project XML file is identical to
|
||||
the original file.
|
||||
"""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
|
||||
# No Project
|
||||
assert theProject.backupProject(doNotify=False) is False
|
||||
|
||||
@@ -35,7 +35,7 @@ from novelwriter.core.sessions import NWSessionLog
|
||||
@pytest.mark.core
|
||||
def testCoreSessions_Main(monkeypatch, mockGUI, fncPath):
|
||||
"""Test log file handling of the NWSessionLog class."""
|
||||
project = NWProject(mockGUI)
|
||||
project = NWProject()
|
||||
buildTestProject(project, fncPath)
|
||||
|
||||
logFile = project.storage.getMetaFile(nwFiles.SESS_FILE)
|
||||
|
||||
@@ -36,7 +36,7 @@ from novelwriter.core.spellcheck import FakeEnchant, NWSpellEnchant, UserDiction
|
||||
@pytest.mark.core
|
||||
def testCoreSpell_UserDictionary(monkeypatch, mockGUI, fncPath):
|
||||
"""Test the UserDictionary class."""
|
||||
project = NWProject(mockGUI)
|
||||
project = NWProject()
|
||||
buildTestProject(project, fncPath)
|
||||
|
||||
# Check that there is no file before we start
|
||||
@@ -114,7 +114,7 @@ def testCoreSpell_UserDictionary(monkeypatch, mockGUI, fncPath):
|
||||
@pytest.mark.core
|
||||
def testCoreSpell_FakeEnchant(monkeypatch, mockGUI, fncPath):
|
||||
"""Test the FakeEnchant spell checker fallback."""
|
||||
project = NWProject(mockGUI)
|
||||
project = NWProject()
|
||||
buildTestProject(project, fncPath)
|
||||
|
||||
# Make package import fail
|
||||
@@ -149,7 +149,7 @@ def testCoreSpell_FakeEnchant(monkeypatch, mockGUI, fncPath):
|
||||
@pytest.mark.core
|
||||
def testCoreSpell_Enchant(monkeypatch, mockGUI, fncPath):
|
||||
"""Test the pyenchant spell checker."""
|
||||
project = NWProject(mockGUI)
|
||||
project = NWProject()
|
||||
buildTestProject(project, fncPath)
|
||||
|
||||
# Break the enchant package, and check error handling
|
||||
|
||||
@@ -43,7 +43,7 @@ class MockProject:
|
||||
@pytest.mark.core
|
||||
def testCoreStorage_OpenProjectInPlace(mockGUI, fncPath, mockRnd):
|
||||
"""Test opening a project in a folder."""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
mockRnd.reset()
|
||||
buildTestProject(theProject, fncPath)
|
||||
theProject.closeProject()
|
||||
@@ -180,7 +180,7 @@ def testCoreStorage_ZipIt(monkeypatch, mockGUI, fncPath, tstPaths, mockRnd):
|
||||
"""Test making a zip archive of a project."""
|
||||
zipFile = tstPaths.tmpDir / "project.zip"
|
||||
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
storage = theProject.storage
|
||||
assert storage.zipIt(zipFile) is False
|
||||
|
||||
@@ -365,7 +365,7 @@ def testCoreStorage_DeprecatedFiles(monkeypatch, fncPath):
|
||||
@pytest.mark.core
|
||||
def testCoreStorage_OldFormatConvert(monkeypatch, mockGUI, fncPath):
|
||||
"""Test cleanup of deprecated files that needs to be converted."""
|
||||
project = NWProject(mockGUI)
|
||||
project = NWProject()
|
||||
buildTestProject(project, fncPath)
|
||||
legacy = _LegacyStorage(project)
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ from novelwriter.core.project import NWProject
|
||||
def testCoreToHtml_ConvertFormat(mockGUI):
|
||||
"""Test the tokenizer and converter chain using the ToHtml class.
|
||||
"""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
theHtml = ToHtml(theProject)
|
||||
|
||||
# Novel Files Headers
|
||||
@@ -233,7 +233,7 @@ def testCoreToHtml_ConvertFormat(mockGUI):
|
||||
def testCoreToHtml_ConvertDirect(mockGUI):
|
||||
"""Test the converter directly using the ToHtml class.
|
||||
"""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
theHtml = ToHtml(theProject)
|
||||
|
||||
theHtml._isNovel = True
|
||||
@@ -380,7 +380,7 @@ def testCoreToHtml_ConvertDirect(mockGUI):
|
||||
def testCoreToHtml_SpecialCases(mockGUI):
|
||||
"""Test some special cases that have caused errors in the past.
|
||||
"""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
theHtml = ToHtml(theProject)
|
||||
theHtml._isNovel = True
|
||||
|
||||
@@ -454,7 +454,7 @@ def testCoreToHtml_SpecialCases(mockGUI):
|
||||
def testCoreToHtml_Complex(mockGUI, fncPath):
|
||||
"""Test the save method of the ToHtml class.
|
||||
"""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
theHtml = ToHtml(theProject)
|
||||
theHtml._isNovel = True
|
||||
|
||||
@@ -549,7 +549,7 @@ def testCoreToHtml_Complex(mockGUI, fncPath):
|
||||
def testCoreToHtml_Methods(mockGUI):
|
||||
"""Test all the other methods of the ToHtml class.
|
||||
"""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
theHtml = ToHtml(theProject)
|
||||
theHtml.setKeepMarkdown(True)
|
||||
|
||||
@@ -609,7 +609,7 @@ def testCoreToHtml_Methods(mockGUI):
|
||||
def testCoreToHtml_Format(mockGUI):
|
||||
"""Test all the formatters for the ToHtml class.
|
||||
"""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
theHtml = ToHtml(theProject)
|
||||
|
||||
# Export Mode
|
||||
|
||||
@@ -36,7 +36,7 @@ class BareTokenizer(Tokenizer):
|
||||
@pytest.mark.core
|
||||
def testCoreToken_Setters(mockGUI):
|
||||
"""Test all the setters for the Tokenizer class."""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
theToken = BareTokenizer(theProject)
|
||||
|
||||
# Verify defaults
|
||||
@@ -133,7 +133,7 @@ def testCoreToken_Setters(mockGUI):
|
||||
@pytest.mark.core
|
||||
def testCoreToken_TextOps(monkeypatch, mockGUI, mockRnd, fncPath):
|
||||
"""Test handling files and text in the Tokenizer class."""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
mockRnd.reset()
|
||||
buildTestProject(theProject, fncPath)
|
||||
|
||||
@@ -231,7 +231,7 @@ def testCoreToken_StripEscape():
|
||||
@pytest.mark.core
|
||||
def testCoreToken_HeaderFormat(mockGUI):
|
||||
"""Test the tokenization of header formats in the Tokenizer class."""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
theToken = BareTokenizer(theProject)
|
||||
theToken.setKeepMarkdown(True)
|
||||
|
||||
@@ -434,7 +434,7 @@ def testCoreToken_HeaderFormat(mockGUI):
|
||||
@pytest.mark.core
|
||||
def testCoreToken_MetaFormat(mockGUI):
|
||||
"""Test the tokenization of meta formats in the Tokenizer class."""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
theToken = BareTokenizer(theProject)
|
||||
theToken.setKeepMarkdown(True)
|
||||
|
||||
@@ -502,7 +502,7 @@ def testCoreToken_MetaFormat(mockGUI):
|
||||
@pytest.mark.core
|
||||
def testCoreToken_MarginFormat(mockGUI):
|
||||
"""Test the tokenization of margin formats in the Tokenizer class."""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
theToken = BareTokenizer(theProject)
|
||||
theToken.setKeepMarkdown(True)
|
||||
|
||||
@@ -556,7 +556,7 @@ def testCoreToken_MarginFormat(mockGUI):
|
||||
@pytest.mark.core
|
||||
def testCoreToken_TextFormat(mockGUI):
|
||||
"""Test the tokenization of text formats in the Tokenizer class."""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
theToken = BareTokenizer(theProject)
|
||||
theToken.setKeepMarkdown(True)
|
||||
|
||||
@@ -677,7 +677,7 @@ def testCoreToken_TextFormat(mockGUI):
|
||||
@pytest.mark.core
|
||||
def testCoreToken_SpecialFormat(mockGUI):
|
||||
"""Test the tokenization of special formats in the Tokenizer class."""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
theToken = BareTokenizer(theProject)
|
||||
|
||||
theToken._isNovel = True
|
||||
@@ -879,7 +879,7 @@ def testCoreToken_SpecialFormat(mockGUI):
|
||||
@pytest.mark.core
|
||||
def testCoreToken_ProcessHeaders(mockGUI):
|
||||
"""Test the header and page parser of the Tokenizer class."""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
theProject.data.setLanguage("en")
|
||||
theProject._loadProjectLocalisation()
|
||||
theToken = BareTokenizer(theProject)
|
||||
|
||||
@@ -32,7 +32,7 @@ def testCoreToMarkdown_ConvertFormat(mockGUI):
|
||||
"""Test the tokenizer and converter chain using the ToMarkdown
|
||||
class.
|
||||
"""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
theMD = ToMarkdown(theProject)
|
||||
|
||||
# Headers
|
||||
@@ -159,7 +159,7 @@ def testCoreToMarkdown_ConvertFormat(mockGUI):
|
||||
@pytest.mark.core
|
||||
def testCoreToMarkdown_ConvertDirect(mockGUI):
|
||||
"""Test the converter directly using the ToMarkdown class."""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
theMD = ToMarkdown(theProject)
|
||||
|
||||
theMD._isNovel = True
|
||||
@@ -209,7 +209,7 @@ def testCoreToMarkdown_ConvertDirect(mockGUI):
|
||||
@pytest.mark.core
|
||||
def testCoreToMarkdown_Complex(mockGUI, fncPath):
|
||||
"""Test the save method of the ToMarkdown class."""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
theMD = ToMarkdown(theProject)
|
||||
theMD._isNovel = True
|
||||
|
||||
@@ -261,7 +261,7 @@ def testCoreToMarkdown_Complex(mockGUI, fncPath):
|
||||
@pytest.mark.core
|
||||
def testCoreToMarkdown_Format(mockGUI):
|
||||
"""Test all the formatters for the ToMarkdown class."""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
theMD = ToMarkdown(theProject)
|
||||
|
||||
assert theMD._formatKeywords("", theMD.A_NONE) == ""
|
||||
|
||||
@@ -53,7 +53,7 @@ def xmlToText(xElem):
|
||||
@pytest.mark.core
|
||||
def testCoreToOdt_Init(mockGUI):
|
||||
"""Test initialisation of the ODT document."""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
|
||||
# Flat Doc
|
||||
# ========
|
||||
@@ -108,7 +108,7 @@ def testCoreToOdt_Init(mockGUI):
|
||||
@pytest.mark.core
|
||||
def testCoreToOdt_TextFormatting(mockGUI):
|
||||
"""Test formatting of paragraphs."""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
theDoc = ToOdt(theProject, isFlat=True)
|
||||
|
||||
theDoc.initDocument()
|
||||
@@ -242,7 +242,7 @@ def testCoreToOdt_TextFormatting(mockGUI):
|
||||
@pytest.mark.core
|
||||
def testCoreToOdt_Convert(mockGUI):
|
||||
"""Test the converter of the ToOdt class."""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
theDoc = ToOdt(theProject, isFlat=True)
|
||||
|
||||
theDoc._isNovel = True
|
||||
@@ -573,7 +573,7 @@ def testCoreToOdt_ConvertDirect(mockGUI):
|
||||
"""Test the converter directly using the ToOdt class to reach some
|
||||
otherwise hard to reach conditions.
|
||||
"""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
theDoc = ToOdt(theProject, isFlat=True)
|
||||
|
||||
theDoc._isNovel = True
|
||||
@@ -626,7 +626,7 @@ def testCoreToOdt_ConvertDirect(mockGUI):
|
||||
@pytest.mark.core
|
||||
def testCoreToOdt_SaveFlat(mockGUI, fncPath, tstPaths):
|
||||
"""Test the document save functions."""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
theProject.data.setAuthor("Jane Smith")
|
||||
theProject.data.setName("Test Project")
|
||||
theProject.data.setSaveCount(1234)
|
||||
@@ -668,7 +668,7 @@ def testCoreToOdt_SaveFlat(mockGUI, fncPath, tstPaths):
|
||||
@pytest.mark.core
|
||||
def testCoreToOdt_SaveFull(mockGUI, fncPath, tstPaths):
|
||||
"""Test the document save functions."""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
theProject.data.setAuthor("Jane Smith")
|
||||
theProject.data.setName("Test Project")
|
||||
theProject.data.setSaveCount(1234)
|
||||
@@ -745,7 +745,7 @@ def testCoreToOdt_SaveFull(mockGUI, fncPath, tstPaths):
|
||||
@pytest.mark.core
|
||||
def testCoreToOdt_Format(mockGUI):
|
||||
"""Test the formatters for the ToOdt class."""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
theDoc = ToOdt(theProject, isFlat=True)
|
||||
|
||||
assert theDoc._formatSynopsis("synopsis text") == (
|
||||
|
||||
@@ -39,7 +39,7 @@ from novelwriter.core.project import NWProject
|
||||
@pytest.fixture(scope="function")
|
||||
def mockItems(mockGUI, mockRnd):
|
||||
"""Create a list of mock items."""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
|
||||
itemA = NWItem(theProject, "a000000000001")
|
||||
itemA._name = "Novel"
|
||||
@@ -112,7 +112,7 @@ def mockItems(mockGUI, mockRnd):
|
||||
@pytest.mark.core
|
||||
def testCoreTree_BuildTree(mockGUI, mockItems):
|
||||
"""Test building a project tree from a list of items."""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
theTree = NWTree(theProject)
|
||||
|
||||
# Check that tree is empty (calls NWTree.__bool__)
|
||||
@@ -127,7 +127,7 @@ def testCoreTree_BuildTree(mockGUI, mockItems):
|
||||
assert theTree.append(nwItem) is True
|
||||
assert theTree.updateItemData(nwItem.itemHandle) is True
|
||||
|
||||
assert theTree._treeChanged is True
|
||||
assert theTree._changed is True
|
||||
|
||||
# Check that tree is not empty (calls __bool__)
|
||||
assert bool(theTree) is True
|
||||
@@ -269,7 +269,7 @@ def testCoreTree_BuildTree(mockGUI, mockItems):
|
||||
@pytest.mark.core
|
||||
def testCoreTree_PackUnpack(mockGUI, mockItems):
|
||||
"""Test packing and unpacking data."""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
theTree = NWTree(theProject)
|
||||
|
||||
aHandles = []
|
||||
@@ -298,7 +298,7 @@ def testCoreTree_PackUnpack(mockGUI, mockItems):
|
||||
@pytest.mark.core
|
||||
def testCoreTree_CheckConsistency(caplog: pytest.LogCaptureFixture, mockGUI, fncPath, mockRnd):
|
||||
"""Check the project consistency."""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
buildTestProject(theProject, fncPath)
|
||||
|
||||
# By default, all is well
|
||||
@@ -354,10 +354,12 @@ def testCoreTree_CheckConsistency(caplog: pytest.LogCaptureFixture, mockGUI, fnc
|
||||
assert itemX.itemClass == nwItemClass.NOVEL
|
||||
assert itemX.itemName == "[Recovered] Stuff"
|
||||
|
||||
# If the tree is empty, there is nowhere to add any of the 4 files
|
||||
# If the tree is empty, a new root folder is created
|
||||
theProject.tree.clear()
|
||||
assert theProject.tree.checkConsistency("Recovered") == (4, 0)
|
||||
assert len(theProject.tree) == 0
|
||||
assert theProject.tree.checkConsistency("Recovered") == (4, 4)
|
||||
assert len(theProject.tree) == 5
|
||||
nHandle = theProject.tree.findRoot(nwItemClass.NOVEL)
|
||||
assert theProject.tree[nHandle].itemName == "Recovered" # type: ignore
|
||||
|
||||
# END Test testCoreTree_CheckConsistency
|
||||
|
||||
@@ -365,7 +367,7 @@ def testCoreTree_CheckConsistency(caplog: pytest.LogCaptureFixture, mockGUI, fnc
|
||||
@pytest.mark.core
|
||||
def testCoreTree_Methods(mockGUI, mockItems):
|
||||
"""Test various class methods."""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
theTree = NWTree(theProject)
|
||||
|
||||
for nwItem in mockItems:
|
||||
@@ -411,9 +413,9 @@ def testCoreTree_Methods(mockGUI, mockItems):
|
||||
assert roots[3][0] == "a000000000004"
|
||||
|
||||
# Add a fake item to root and check that it can handle it
|
||||
theTree._treeRoots["0000000000000"] = NWItem(theProject, "0000000000000")
|
||||
theTree._roots["0000000000000"] = NWItem(theProject, "0000000000000")
|
||||
assert theTree.findRoot(nwItemClass.WORLD) is None
|
||||
del theTree._treeRoots["0000000000000"]
|
||||
del theTree._roots["0000000000000"]
|
||||
|
||||
# Get item path
|
||||
assert theTree.getItemPath("stuff") == []
|
||||
@@ -446,7 +448,7 @@ def testCoreTree_Methods(mockGUI, mockItems):
|
||||
def testCoreTree_MakeHandles(mockGUI):
|
||||
"""Test generating item handles."""
|
||||
random.seed(42)
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
theTree = NWTree(theProject)
|
||||
|
||||
handles = ["1c803a3b1799d", "bdd6406671ad1", "3eb1346685257", "23b8c392456de"]
|
||||
@@ -454,13 +456,13 @@ def testCoreTree_MakeHandles(mockGUI):
|
||||
random.seed(42)
|
||||
tHandle = theTree._makeHandle()
|
||||
assert tHandle == handles[0]
|
||||
theTree._projTree[handles[0]] = None # type: ignore
|
||||
theTree._tree[handles[0]] = None # type: ignore
|
||||
|
||||
# Add the next in line to the project to force duplicate
|
||||
theTree._projTree[handles[1]] = None # type: ignore
|
||||
theTree._tree[handles[1]] = None # type: ignore
|
||||
tHandle = theTree._makeHandle()
|
||||
assert tHandle == handles[2]
|
||||
theTree._projTree[handles[2]] = None # type: ignore
|
||||
theTree._tree[handles[2]] = None # type: ignore
|
||||
|
||||
# Reset the seed to force collissions, which should still end up
|
||||
# returning the next handle in the sequence
|
||||
@@ -474,14 +476,14 @@ def testCoreTree_MakeHandles(mockGUI):
|
||||
@pytest.mark.core
|
||||
def testCoreTree_Stats(mockGUI, mockItems):
|
||||
"""Test project stats methods."""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
theTree = NWTree(theProject)
|
||||
|
||||
for nwItem in mockItems:
|
||||
theTree.append(nwItem)
|
||||
|
||||
assert len(theTree) == len(mockItems)
|
||||
theTree._treeOrder.append("stuff")
|
||||
theTree._order.append("stuff")
|
||||
|
||||
# Count Words
|
||||
novelWords, noteWords = theTree.sumWords()
|
||||
@@ -494,7 +496,7 @@ def testCoreTree_Stats(mockGUI, mockItems):
|
||||
@pytest.mark.core
|
||||
def testCoreTree_Reorder(caplog, mockGUI, mockItems):
|
||||
"""Test changing tree order."""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
theTree = NWTree(theProject)
|
||||
|
||||
aHandle = []
|
||||
@@ -518,7 +520,7 @@ def testCoreTree_Reorder(caplog, mockGUI, mockItems):
|
||||
assert "Handle 'stuff' in new tree order is not in old order" in caplog.text
|
||||
|
||||
caplog.clear()
|
||||
theTree._treeOrder.append("stuff")
|
||||
theTree._order.append("stuff")
|
||||
theTree.setOrder(bHandle)
|
||||
assert theTree.handles() == bHandle
|
||||
assert "Handle 'stuff' in old tree order is not in new order" in caplog.text
|
||||
@@ -529,7 +531,7 @@ def testCoreTree_Reorder(caplog, mockGUI, mockItems):
|
||||
@pytest.mark.core
|
||||
def testCoreTree_ToCFile(monkeypatch, fncPath, mockGUI, mockItems):
|
||||
"""Test writing the ToC.txt file."""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject = NWProject()
|
||||
theTree = NWTree(theProject)
|
||||
|
||||
for nwItem in mockItems:
|
||||
@@ -537,7 +539,7 @@ def testCoreTree_ToCFile(monkeypatch, fncPath, mockGUI, mockItems):
|
||||
theTree.updateItemData(nwItem.itemHandle)
|
||||
|
||||
assert len(theTree) == len(mockItems)
|
||||
theTree._treeOrder.append("stuff")
|
||||
theTree._order.append("stuff")
|
||||
|
||||
def mockIsFile(fileName):
|
||||
"""Return True for items that are files in novelWriter and
|
||||
|
||||
@@ -23,6 +23,7 @@ import pytest
|
||||
|
||||
from tools import C, buildTestProject
|
||||
|
||||
from novelwriter import SHARED
|
||||
from novelwriter.dialogs.docsplit import GuiDocSplit
|
||||
from novelwriter.dialogs.editlabel import GuiEditLabel
|
||||
|
||||
@@ -35,7 +36,7 @@ def testDlgSplit_Main(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
|
||||
# Create a new project
|
||||
buildTestProject(nwGUI, projPath)
|
||||
|
||||
theProject = nwGUI.project
|
||||
theProject = SHARED.project
|
||||
projTree = nwGUI.projView.projTree
|
||||
|
||||
docText = (
|
||||
|
||||
@@ -39,8 +39,7 @@ KEY_DELAY = 1
|
||||
|
||||
@pytest.mark.gui
|
||||
def testDlgPreferences_Main(qtbot, monkeypatch, nwGUI, tstPaths):
|
||||
"""Test the load project wizard.
|
||||
"""
|
||||
"""Test the preferences dialog."""
|
||||
monkeypatch.setattr(GuiPreferences, "exec_", lambda *a: None)
|
||||
monkeypatch.setattr(GuiPreferences, "result", lambda *a: QDialog.Accepted)
|
||||
monkeypatch.setattr(nwGUI.docEditor.spEnchant, "listDictionaries", lambda: [("en", "none")])
|
||||
|
||||
@@ -25,6 +25,7 @@ from tools import getGuiItem
|
||||
|
||||
from PyQt5.QtWidgets import QAction
|
||||
|
||||
from novelwriter import SHARED
|
||||
from novelwriter.dialogs.projdetails import GuiProjectDetails
|
||||
|
||||
|
||||
@@ -54,7 +55,7 @@ def testDlgProjDetails_Dialog(qtbot, nwGUI, prjLipsum):
|
||||
assert projDet.tabMain.wordCountVal.text() == f"{3000:n}"
|
||||
assert projDet.tabMain.chapCountVal.text() == f"{3:n}"
|
||||
assert projDet.tabMain.sceneCountVal.text() == f"{5:n}"
|
||||
assert projDet.tabMain.revCountVal.text() == f"{nwGUI.project.data.saveCount:n}"
|
||||
assert projDet.tabMain.revCountVal.text() == f"{SHARED.project.data.saveCount:n}"
|
||||
|
||||
assert projDet.tabMain.projPathVal.text() == str(prjLipsum)
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ from PyQt5.QtGui import QColor
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtWidgets import QDialog, QAction, QColorDialog
|
||||
|
||||
from novelwriter import CONFIG
|
||||
from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.enum import nwItemType
|
||||
from novelwriter.dialogs.editlabel import GuiEditLabel
|
||||
from novelwriter.dialogs.projsettings import GuiProjectSettings
|
||||
@@ -50,8 +50,8 @@ def testDlgProjSettings_Dialog(qtbot, monkeypatch, nwGUI):
|
||||
assert getGuiItem("GuiProjectSettings") is None
|
||||
|
||||
# Pretend we have a project
|
||||
nwGUI.hasProject = True
|
||||
nwGUI.project.data.setSpellLang("en")
|
||||
SHARED.project._valid = True
|
||||
SHARED.project.data.setSpellLang("en")
|
||||
|
||||
# Get the dialog object
|
||||
nwGUI.mainMenu.aProjectSettings.activate(QAction.Trigger)
|
||||
@@ -95,7 +95,7 @@ def testDlgProjSettings_Main(qtbot, monkeypatch, nwGUI, fncPath, projPath, mockR
|
||||
CONFIG.setBackupPath(fncPath)
|
||||
|
||||
# Set some values
|
||||
theProject = nwGUI.project
|
||||
theProject = SHARED.project
|
||||
theProject.data.setSpellLang("en")
|
||||
theProject.data.setAuthor("Jane Smith")
|
||||
theProject.data.setAutoReplace({"A": "B", "C": "D"})
|
||||
@@ -160,7 +160,7 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, fncPath, projPat
|
||||
CONFIG.setBackupPath(fncPath)
|
||||
|
||||
# Set some values
|
||||
theProject = nwGUI.project
|
||||
theProject = SHARED.project
|
||||
theProject.tree[C.hTitlePage].setStatus(C.sFinished)
|
||||
theProject.tree[C.hChapterDoc].setStatus(C.sDraft)
|
||||
theProject.tree[C.hSceneDoc].setStatus(C.sDraft)
|
||||
@@ -361,7 +361,7 @@ def testDlgProjSettings_Replace(qtbot, monkeypatch, nwGUI, fncPath, projPath, mo
|
||||
CONFIG.setBackupPath(fncPath)
|
||||
|
||||
# Set some values
|
||||
theProject = nwGUI.project
|
||||
theProject = SHARED.project
|
||||
theProject.data.setAutoReplace({
|
||||
"A": "B", "C": "D"
|
||||
})
|
||||
|
||||
@@ -26,6 +26,7 @@ from PyQt5.QtWidgets import QDialog, QAction
|
||||
|
||||
from tools import buildTestProject, getGuiItem
|
||||
|
||||
from novelwriter import SHARED
|
||||
from novelwriter.core.spellcheck import UserDictionary
|
||||
from novelwriter.dialogs.wordlist import GuiWordList
|
||||
|
||||
@@ -55,7 +56,7 @@ def testDlgWordList_Dialog(qtbot, monkeypatch, nwGUI, projPath):
|
||||
assert wList.listBox.count() == 0
|
||||
|
||||
# Add words
|
||||
userDict = UserDictionary(nwGUI.project)
|
||||
userDict = UserDictionary(SHARED.project)
|
||||
userDict.add("word_a")
|
||||
userDict.add("word_c")
|
||||
userDict.add("word_g")
|
||||
|
||||
@@ -28,7 +28,7 @@ from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtGui import QTextBlock, QTextCursor, QTextOption
|
||||
from PyQt5.QtWidgets import QAction, qApp
|
||||
|
||||
from novelwriter import CONFIG
|
||||
from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.enum import nwDocAction, nwDocInsert, nwItemLayout
|
||||
from novelwriter.constants import nwKeyWords, nwUnicode
|
||||
from novelwriter.core.index import countWords
|
||||
@@ -163,10 +163,10 @@ def testGuiEditor_SaveText(qtbot, monkeypatch, caplog, nwGUI, projPath, ipsumTex
|
||||
assert "Could not save document." in caplog.text
|
||||
|
||||
# Change header level
|
||||
assert nwGUI.project.tree[C.hSceneDoc].itemLayout == nwItemLayout.DOCUMENT
|
||||
assert SHARED.project.tree[C.hSceneDoc].itemLayout == nwItemLayout.DOCUMENT
|
||||
nwGUI.docEditor.replaceText(longText[1:])
|
||||
assert nwGUI.docEditor.saveText() is True
|
||||
assert nwGUI.project.tree[C.hSceneDoc].itemLayout == nwItemLayout.DOCUMENT
|
||||
assert SHARED.project.tree[C.hSceneDoc].itemLayout == nwItemLayout.DOCUMENT
|
||||
|
||||
# Regular save
|
||||
assert nwGUI.docEditor.saveText() is True
|
||||
@@ -194,18 +194,18 @@ def testGuiEditor_MetaData(qtbot, nwGUI, projPath, mockRnd):
|
||||
assert nwGUI.docEditor.getText() == "### New Scene\n\nSome\ntext.\nMore\u00a0text.\n"
|
||||
|
||||
# Check Propertoes
|
||||
assert nwGUI.docEditor.docChanged() is True
|
||||
assert nwGUI.docEditor.docHandle() == C.hSceneDoc
|
||||
assert nwGUI.docEditor.lastActive() > 0.0
|
||||
assert nwGUI.docEditor.isEmpty() is False
|
||||
assert nwGUI.docEditor.docChanged is True
|
||||
assert nwGUI.docEditor.docHandle == C.hSceneDoc
|
||||
assert nwGUI.docEditor.lastActive > 0.0
|
||||
assert nwGUI.docEditor.isEmpty is False
|
||||
|
||||
# Cursor Position
|
||||
assert nwGUI.docEditor.setCursorPosition(None) is False
|
||||
assert nwGUI.docEditor.setCursorPosition(10) is True
|
||||
assert nwGUI.docEditor.getCursorPosition() == 10
|
||||
assert nwGUI.project.tree[C.hSceneDoc].cursorPos != 10
|
||||
assert SHARED.project.tree[C.hSceneDoc].cursorPos != 10
|
||||
nwGUI.docEditor.saveCursorPosition()
|
||||
assert nwGUI.project.tree[C.hSceneDoc].cursorPos == 10
|
||||
assert SHARED.project.tree[C.hSceneDoc].cursorPos == 10
|
||||
|
||||
assert nwGUI.docEditor.setCursorLine(None) is False
|
||||
assert nwGUI.docEditor.setCursorLine(3) is True
|
||||
@@ -213,7 +213,7 @@ def testGuiEditor_MetaData(qtbot, nwGUI, projPath, mockRnd):
|
||||
|
||||
# Document Changed Signal
|
||||
nwGUI.docEditor._docChanged = False
|
||||
with qtbot.waitSignal(nwGUI.docEditor.docEditedStatusChanged, raising=True, timeout=100):
|
||||
with qtbot.waitSignal(nwGUI.docEditor.editedStatusChanged, raising=True, timeout=100):
|
||||
nwGUI.docEditor.setDocumentChanged(True)
|
||||
assert nwGUI.docEditor._docChanged is True
|
||||
|
||||
@@ -1067,7 +1067,7 @@ def testGuiEditor_Tags(qtbot, nwGUI, projPath, ipsumText, mockRnd):
|
||||
|
||||
# Create Character
|
||||
theText = "### Jane Doe\n\n@tag: Jane\n\n" + ipsumText[1] + "\n\n"
|
||||
cHandle = nwGUI.project.newFile("Jane Doe", C.hCharRoot)
|
||||
cHandle = SHARED.project.newFile("Jane Doe", C.hCharRoot)
|
||||
assert nwGUI.openDocument(cHandle) is True
|
||||
assert nwGUI.docEditor.replaceText(theText) is True
|
||||
assert nwGUI.saveDocument() is True
|
||||
@@ -1145,8 +1145,8 @@ def testGuiEditor_WordCounters(qtbot, monkeypatch, nwGUI, projPath, ipsumText, m
|
||||
assert nwGUI.docEditor.docFooter.wordsText.text() == "Words: 0 (+0)"
|
||||
|
||||
# Open a document and populate it
|
||||
nwGUI.project.tree[C.hSceneDoc]._initCount = 0 # Clear item's count
|
||||
nwGUI.project.tree[C.hSceneDoc]._wordCount = 0 # Clear item's count
|
||||
SHARED.project.tree[C.hSceneDoc]._initCount = 0 # Clear item's count
|
||||
SHARED.project.tree[C.hSceneDoc]._wordCount = 0 # Clear item's count
|
||||
assert nwGUI.openDocument(C.hSceneDoc) is True
|
||||
|
||||
theText = "\n\n".join(ipsumText)
|
||||
@@ -1170,9 +1170,9 @@ def testGuiEditor_WordCounters(qtbot, monkeypatch, nwGUI, projPath, ipsumText, m
|
||||
|
||||
nwGUI.docEditor.wCounterDoc.run()
|
||||
# nwGUI.docEditor._updateDocCounts(cC, wC, pC)
|
||||
assert nwGUI.project.tree[C.hSceneDoc]._charCount == cC
|
||||
assert nwGUI.project.tree[C.hSceneDoc]._wordCount == wC
|
||||
assert nwGUI.project.tree[C.hSceneDoc]._paraCount == pC
|
||||
assert SHARED.project.tree[C.hSceneDoc]._charCount == cC
|
||||
assert SHARED.project.tree[C.hSceneDoc]._wordCount == wC
|
||||
assert SHARED.project.tree[C.hSceneDoc]._paraCount == pC
|
||||
assert nwGUI.docEditor.docFooter.wordsText.text() == f"Words: {wC} (+{wC})"
|
||||
|
||||
# Select all text
|
||||
@@ -1361,7 +1361,7 @@ def testGuiEditor_Search(qtbot, monkeypatch, nwGUI, prjLipsum):
|
||||
|
||||
# Next match
|
||||
nwGUI.mainMenu.aFindNext.activate(QAction.Trigger)
|
||||
assert nwGUI.docEditor.docHandle() == "2426c6f0ca922" # Next document
|
||||
assert nwGUI.docEditor.docHandle == "2426c6f0ca922" # Next document
|
||||
nwGUI.mainMenu.aFindNext.activate(QAction.Trigger)
|
||||
assert abs(nwGUI.docEditor.getCursorPosition() - 620) < 3
|
||||
nwGUI.mainMenu.aFindNext.activate(QAction.Trigger)
|
||||
@@ -1371,11 +1371,11 @@ def testGuiEditor_Search(qtbot, monkeypatch, nwGUI, prjLipsum):
|
||||
assert nwGUI.docEditor.docSearch.doNextFile is True
|
||||
assert nwGUI.docEditor.docSearch.setSearchText("abcdef")
|
||||
nwGUI.mainMenu.aFindNext.activate(QAction.Trigger)
|
||||
assert nwGUI.docEditor.docHandle() != "2426c6f0ca922"
|
||||
assert nwGUI.docEditor.docHandle() == "04468803b92e1"
|
||||
assert nwGUI.docEditor.docHandle != "2426c6f0ca922"
|
||||
assert nwGUI.docEditor.docHandle == "04468803b92e1"
|
||||
nwGUI.mainMenu.aFindNext.activate(QAction.Trigger)
|
||||
assert nwGUI.docEditor.docHandle() != "04468803b92e1"
|
||||
assert nwGUI.docEditor.docHandle() == "7a992350f3eb6"
|
||||
assert nwGUI.docEditor.docHandle != "04468803b92e1"
|
||||
assert nwGUI.docEditor.docHandle == "7a992350f3eb6"
|
||||
|
||||
# Toggle Replace
|
||||
nwGUI.docEditor.beginReplace()
|
||||
|
||||
@@ -27,7 +27,7 @@ from PyQt5.QtCore import Qt, QUrl
|
||||
from PyQt5.QtGui import QTextCursor
|
||||
from PyQt5.QtWidgets import qApp, QAction
|
||||
|
||||
from novelwriter import CONFIG
|
||||
from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.enum import nwDocAction
|
||||
from novelwriter.core.tohtml import ToHtml
|
||||
|
||||
@@ -40,8 +40,8 @@ def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, prjLipsum):
|
||||
|
||||
# Rebuild the index
|
||||
nwGUI.mainMenu.aRebuildIndex.activate(QAction.Trigger)
|
||||
assert nwGUI.project.index._tagsIndex._tags != {}
|
||||
assert nwGUI.project.index._itemIndex._items != {}
|
||||
assert SHARED.project.index._tagsIndex._tags != {}
|
||||
assert SHARED.project.index._itemIndex._items != {}
|
||||
|
||||
# Select a document in the project tree
|
||||
nwGUI.projView.setSelectedHandle("88243afbe5ed8")
|
||||
@@ -50,7 +50,7 @@ def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, prjLipsum):
|
||||
theItem = nwGUI.projView.projTree._getTreeItem("88243afbe5ed8")
|
||||
theRect = nwGUI.projView.projTree.visualItemRect(theItem)
|
||||
qtbot.mouseClick(nwGUI.projView.projTree.viewport(), Qt.MidButton, pos=theRect.center())
|
||||
assert nwGUI.docViewer.docHandle() == "88243afbe5ed8"
|
||||
assert nwGUI.docViewer.docHandle == "88243afbe5ed8"
|
||||
|
||||
# Reload the text
|
||||
origText = nwGUI.docViewer.toPlainText()
|
||||
@@ -97,7 +97,7 @@ def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, prjLipsum):
|
||||
|
||||
# Close document
|
||||
nwGUI.docViewer.docHeader._closeDocument()
|
||||
assert nwGUI.docViewer.docHandle() is None
|
||||
assert nwGUI.docViewer.docHandle is None
|
||||
|
||||
# Action on no document
|
||||
assert nwGUI.docViewer.docAction(nwDocAction.COPY) is False
|
||||
@@ -114,21 +114,21 @@ def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, prjLipsum):
|
||||
theRect = nwGUI.docViewer.cursorRect()
|
||||
# qtbot.mouseClick(nwGUI.docViewer.viewport(), Qt.LeftButton, pos=theRect.center(), delay=100)
|
||||
nwGUI.docViewer._linkClicked(QUrl("#char=Bod"))
|
||||
assert nwGUI.docViewer.docHandle() == "4c4f28287af27"
|
||||
assert nwGUI.docViewer.docHandle == "4c4f28287af27"
|
||||
|
||||
# Click mouse nav buttons
|
||||
qtbot.mouseClick(nwGUI.docViewer.viewport(), Qt.BackButton, pos=theRect.center(), delay=100)
|
||||
assert nwGUI.docViewer.docHandle() == "88243afbe5ed8"
|
||||
assert nwGUI.docViewer.docHandle == "88243afbe5ed8"
|
||||
qtbot.mouseClick(nwGUI.docViewer.viewport(), Qt.ForwardButton, pos=theRect.center(), delay=100)
|
||||
assert nwGUI.docViewer.docHandle() == "4c4f28287af27"
|
||||
assert nwGUI.docViewer.docHandle == "4c4f28287af27"
|
||||
|
||||
# Scroll bar default on empty document
|
||||
nwGUI.docViewer.clear()
|
||||
assert nwGUI.docViewer.getScrollPosition() == 0
|
||||
assert nwGUI.docViewer.scrollPosition == 0
|
||||
nwGUI.docViewer.reloadText()
|
||||
|
||||
# Change document title
|
||||
nwItem = nwGUI.project.tree["4c4f28287af27"]
|
||||
nwItem = SHARED.project.tree["4c4f28287af27"]
|
||||
nwItem.setName("Test Title")
|
||||
assert nwItem.itemName == "Test Title"
|
||||
nwGUI.docViewer.updateDocInfo("4c4f28287af27")
|
||||
|
||||
@@ -30,7 +30,7 @@ from tools import (
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtWidgets import QDialog, QMessageBox, QInputDialog
|
||||
|
||||
from novelwriter import CONFIG
|
||||
from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.enum import nwItemType, nwView, nwWidget
|
||||
from novelwriter.constants import nwFiles
|
||||
from novelwriter.gui.outline import GuiOutlineView
|
||||
@@ -104,18 +104,18 @@ def testGuiMain_Launch(qtbot, monkeypatch, nwGUI, prjLipsum):
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiMain_NewProject(monkeypatch, nwGUI, projPath):
|
||||
"""Test creating a new project.
|
||||
"""
|
||||
# No data
|
||||
"""Test creating a new project."""
|
||||
# Open wizard, but return no data
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr(GuiProjectWizard, "exec_", lambda *a: None)
|
||||
assert nwGUI.newProject(projData=None) is False
|
||||
|
||||
# Close project
|
||||
with monkeypatch.context() as mp:
|
||||
nwGUI.hasProject = True
|
||||
SHARED.project._valid = True
|
||||
mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.No)
|
||||
assert nwGUI.newProject(projData={"projPath": projPath}) is False
|
||||
SHARED.project._valid = False
|
||||
|
||||
# No project path
|
||||
assert nwGUI.newProject(projData={}) is False
|
||||
@@ -153,10 +153,10 @@ def testGuiMain_ProjectTreeItems(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
|
||||
nwGUI.projStack.setCurrentIndex(0)
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr(GuiProjectTree, "hasFocus", lambda *a: True)
|
||||
assert nwGUI.docEditor.docHandle() is None
|
||||
assert nwGUI.docEditor.docHandle is None
|
||||
nwGUI.projView.projTree._getTreeItem(sHandle).setSelected(True)
|
||||
nwGUI._keyPressReturn()
|
||||
assert nwGUI.docEditor.docHandle() == sHandle
|
||||
assert nwGUI.docEditor.docHandle == sHandle
|
||||
assert nwGUI.closeDocument() is True
|
||||
|
||||
# Novel Tree has focus
|
||||
@@ -164,11 +164,11 @@ def testGuiMain_ProjectTreeItems(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
|
||||
nwGUI.novelView.novelTree.refreshTree(rootHandle=None, overRide=True)
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr(GuiNovelView, "treeHasFocus", lambda *a: True)
|
||||
assert nwGUI.docEditor.docHandle() is None
|
||||
assert nwGUI.docEditor.docHandle is None
|
||||
selItem = nwGUI.novelView.novelTree.topLevelItem(2)
|
||||
nwGUI.novelView.novelTree.setCurrentItem(selItem)
|
||||
nwGUI._keyPressReturn()
|
||||
assert nwGUI.docEditor.docHandle() == sHandle
|
||||
assert nwGUI.docEditor.docHandle == sHandle
|
||||
assert nwGUI.closeDocument() is True
|
||||
|
||||
# Project Outline has focus
|
||||
@@ -176,11 +176,11 @@ def testGuiMain_ProjectTreeItems(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
|
||||
nwGUI.switchFocus(nwWidget.OUTLINE)
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr(GuiOutlineView, "treeHasFocus", lambda *a: True)
|
||||
assert nwGUI.docEditor.docHandle() is None
|
||||
assert nwGUI.docEditor.docHandle is None
|
||||
selItem = nwGUI.outlineView.outlineTree.topLevelItem(2)
|
||||
nwGUI.outlineView.outlineTree.setCurrentItem(selItem)
|
||||
nwGUI._keyPressReturn()
|
||||
assert nwGUI.docEditor.docHandle() == sHandle
|
||||
assert nwGUI.docEditor.docHandle == sHandle
|
||||
assert nwGUI.closeDocument() is True
|
||||
|
||||
# qtbot.stop()
|
||||
@@ -202,14 +202,14 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd):
|
||||
assert nwGUI.saveProject()
|
||||
assert nwGUI.closeProject()
|
||||
|
||||
assert len(nwGUI.project.tree) == 0
|
||||
assert len(nwGUI.project.tree._treeOrder) == 0
|
||||
assert len(nwGUI.project.tree._treeRoots) == 0
|
||||
assert nwGUI.project.tree.trashRoot() is None
|
||||
assert nwGUI.project.data.name == ""
|
||||
assert nwGUI.project.data.title == ""
|
||||
assert nwGUI.project.data.author == ""
|
||||
assert nwGUI.project.data.spellCheck is False
|
||||
assert len(SHARED.project.tree) == 0
|
||||
assert len(SHARED.project.tree._order) == 0
|
||||
assert len(SHARED.project.tree._roots) == 0
|
||||
assert SHARED.project.tree.trashRoot() is None
|
||||
assert SHARED.project.data.name == ""
|
||||
assert SHARED.project.data.title == ""
|
||||
assert SHARED.project.data.author == ""
|
||||
assert SHARED.project.data.spellCheck is False
|
||||
|
||||
# Check the files
|
||||
projFile = projPath / "nwProject.nwx"
|
||||
@@ -222,14 +222,14 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd):
|
||||
assert nwGUI.openProject(projPath)
|
||||
|
||||
# Check that we loaded the data
|
||||
assert len(nwGUI.project.tree) == 8
|
||||
assert len(nwGUI.project.tree._treeOrder) == 8
|
||||
assert len(nwGUI.project.tree._treeRoots) == 4
|
||||
assert nwGUI.project.tree.trashRoot() is None
|
||||
assert nwGUI.project.data.name == "New Project"
|
||||
assert nwGUI.project.data.title == "New Novel"
|
||||
assert nwGUI.project.data.author == "Jane Doe"
|
||||
assert nwGUI.project.data.spellCheck is False
|
||||
assert len(SHARED.project.tree) == 8
|
||||
assert len(SHARED.project.tree._order) == 8
|
||||
assert len(SHARED.project.tree._roots) == 4
|
||||
assert SHARED.project.tree.trashRoot() is None
|
||||
assert SHARED.project.data.name == "New Project"
|
||||
assert SHARED.project.data.title == "New Novel"
|
||||
assert SHARED.project.data.author == "Jane Doe"
|
||||
assert SHARED.project.data.spellCheck is False
|
||||
|
||||
# Check that tree items have been created
|
||||
assert nwGUI.projView.projTree._getTreeItem(C.hNovelRoot) is not None
|
||||
@@ -506,9 +506,9 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd):
|
||||
nwGUI.docEditor.wCounterDoc.run()
|
||||
|
||||
# Save the document
|
||||
assert nwGUI.docEditor.docChanged()
|
||||
assert nwGUI.docEditor.docChanged
|
||||
assert nwGUI.saveDocument()
|
||||
assert not nwGUI.docEditor.docChanged()
|
||||
assert not nwGUI.docEditor.docChanged
|
||||
nwGUI.rebuildIndex()
|
||||
|
||||
# Open and view the edited document
|
||||
|
||||
@@ -27,7 +27,7 @@ from PyQt5.QtWidgets import QAction, QFileDialog, QMessageBox
|
||||
|
||||
from tools import C, writeFile, buildTestProject
|
||||
|
||||
from novelwriter import CONFIG
|
||||
from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.enum import nwDocAction, nwDocInsert
|
||||
from novelwriter.constants import nwKeyWords, nwUnicode
|
||||
from novelwriter.gui.doceditor import GuiDocEditor
|
||||
@@ -206,7 +206,7 @@ def testGuiMenu_EditFormat(qtbot, monkeypatch, nwGUI, prjLipsum):
|
||||
|
||||
# Clear the Text
|
||||
nwGUI.docEditor.clear()
|
||||
assert nwGUI.docEditor.isEmpty()
|
||||
assert nwGUI.docEditor.isEmpty
|
||||
|
||||
# Alignment & Indent
|
||||
# ==================
|
||||
@@ -403,17 +403,17 @@ def testGuiMenu_ContextMenus(qtbot, nwGUI, prjLipsum):
|
||||
|
||||
# Navigation History
|
||||
assert nwGUI.viewDocument("04468803b92e1")
|
||||
assert nwGUI.docViewer.docHandle() == "04468803b92e1"
|
||||
assert nwGUI.docViewer.docHandle == "04468803b92e1"
|
||||
assert nwGUI.docViewer.docHeader.backButton.isEnabled()
|
||||
assert not nwGUI.docViewer.docHeader.forwardButton.isEnabled()
|
||||
|
||||
qtbot.mouseClick(nwGUI.docViewer.docHeader.backButton, Qt.LeftButton)
|
||||
assert nwGUI.docViewer.docHandle() == "4c4f28287af27"
|
||||
assert nwGUI.docViewer.docHandle == "4c4f28287af27"
|
||||
assert not nwGUI.docViewer.docHeader.backButton.isEnabled()
|
||||
assert nwGUI.docViewer.docHeader.forwardButton.isEnabled()
|
||||
|
||||
qtbot.mouseClick(nwGUI.docViewer.docHeader.forwardButton, Qt.LeftButton)
|
||||
assert nwGUI.docViewer.docHandle() == "04468803b92e1"
|
||||
assert nwGUI.docViewer.docHandle == "04468803b92e1"
|
||||
assert nwGUI.docViewer.docHeader.backButton.isEnabled()
|
||||
assert not nwGUI.docViewer.docHeader.forwardButton.isEnabled()
|
||||
|
||||
@@ -438,10 +438,10 @@ def testGuiMenu_Insert(qtbot, monkeypatch, nwGUI, fncPath, projPath, mockRnd):
|
||||
nwGUI.docEditor.clear()
|
||||
|
||||
assert nwGUI.docEditor.insertText(nwDocInsert.NO_INSERT) is False
|
||||
assert nwGUI.docEditor.isEmpty()
|
||||
assert nwGUI.docEditor.isEmpty
|
||||
|
||||
assert nwGUI.docEditor.insertText(None) is False
|
||||
assert nwGUI.docEditor.isEmpty()
|
||||
assert nwGUI.docEditor.isEmpty
|
||||
|
||||
# qtbot.stop()
|
||||
|
||||
@@ -653,21 +653,10 @@ def testGuiMenu_Insert(qtbot, monkeypatch, nwGUI, fncPath, projPath, mockRnd):
|
||||
# Reveal File Location
|
||||
# ====================
|
||||
|
||||
theMessage = ""
|
||||
|
||||
def recordMsg(*args, **kwargs):
|
||||
nonlocal theMessage
|
||||
theMessage = "%s|%s" % (args[0], kwargs["info"])
|
||||
return None
|
||||
|
||||
assert not theMessage
|
||||
monkeypatch.setattr(nwGUI, "makeAlert", recordMsg)
|
||||
nwGUI.mainMenu.aFileDetails.activate(QAction.Trigger)
|
||||
|
||||
theBits = theMessage.split("|")
|
||||
assert len(theBits) == 2
|
||||
assert theBits[0] == "The currently open file is saved in:"
|
||||
assert theBits[1] == str(projPath / "content" / "000000000000f.nwd")
|
||||
path = str(projPath / "content" / "000000000000f.nwd")
|
||||
logMsg = SHARED.alert.logMessage if SHARED.alert else ""
|
||||
assert logMsg == f"The currently open file is saved in: {path}"
|
||||
|
||||
# qtbot.stop()
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ from PyQt5.QtGui import QFocusEvent
|
||||
from PyQt5.QtCore import Qt, QEvent
|
||||
from PyQt5.QtWidgets import QInputDialog, QToolTip
|
||||
|
||||
from novelwriter import CONFIG
|
||||
from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.enum import nwWidget, nwItemType
|
||||
from novelwriter.gui.noveltree import NovelTreeColumn
|
||||
from novelwriter.dialogs.editlabel import GuiEditLabel
|
||||
@@ -48,7 +48,7 @@ def testGuiNovelTree_TreeItems(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
|
||||
nwGUI.projView.projTree._getTreeItem(C.hCharRoot).setSelected(True)
|
||||
nwGUI.projView.projTree.newTreeItem(nwItemType.FILE)
|
||||
|
||||
contentPath = nwGUI.project.storage.contentPath
|
||||
contentPath = SHARED.project.storage.contentPath
|
||||
assert isinstance(contentPath, Path)
|
||||
|
||||
(contentPath / "0000000000010.nwd").write_text(
|
||||
@@ -118,26 +118,26 @@ def testGuiNovelTree_TreeItems(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
|
||||
# Double-click item
|
||||
scItem.setSelected(True)
|
||||
assert scItem.isSelected()
|
||||
assert nwGUI.docEditor.docHandle() is None
|
||||
assert nwGUI.docEditor.docHandle is None
|
||||
novelTree._treeDoubleClick(scItem, 0)
|
||||
assert nwGUI.docEditor.docHandle() == C.hSceneDoc
|
||||
assert nwGUI.docEditor.docHandle == C.hSceneDoc
|
||||
|
||||
# Open item with middle mouse button
|
||||
scItem.setSelected(True)
|
||||
assert scItem.isSelected()
|
||||
assert nwGUI.docViewer.docHandle() is None
|
||||
assert nwGUI.docViewer.docHandle is None
|
||||
qtbot.mouseClick(vPort, Qt.MiddleButton, pos=vPort.rect().center(), delay=10)
|
||||
assert nwGUI.docViewer.docHandle() is None
|
||||
assert nwGUI.docViewer.docHandle is None
|
||||
|
||||
scRect = novelTree.visualItemRect(scItem)
|
||||
oldData = scItem.data(novelTree.C_TITLE, novelTree.D_HANDLE)
|
||||
scItem.setData(novelTree.C_TITLE, novelTree.D_HANDLE, None)
|
||||
qtbot.mouseClick(vPort, Qt.MiddleButton, pos=scRect.center(), delay=10)
|
||||
assert nwGUI.docViewer.docHandle() is None
|
||||
assert nwGUI.docViewer.docHandle is None
|
||||
|
||||
scItem.setData(novelTree.C_TITLE, novelTree.D_HANDLE, oldData)
|
||||
qtbot.mouseClick(vPort, Qt.MiddleButton, pos=scRect.center(), delay=10)
|
||||
assert nwGUI.docViewer.docHandle() == C.hSceneDoc
|
||||
assert nwGUI.docViewer.docHandle == C.hSceneDoc
|
||||
|
||||
# Last Column
|
||||
# ===========
|
||||
|
||||
@@ -27,7 +27,7 @@ from tools import buildTestProject, writeFile
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtWidgets import QWidget, QAction
|
||||
|
||||
from novelwriter import CONFIG
|
||||
from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.enum import nwItemClass, nwOutline, nwView
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ def testGuiOutline_Main(qtbot, monkeypatch, nwGUI, projPath):
|
||||
|
||||
# Option State
|
||||
# ============
|
||||
pOptions = nwGUI.project.options
|
||||
pOptions = SHARED.project.options
|
||||
colNames = [h.name for h in nwOutline]
|
||||
colItems = [h for h in nwOutline]
|
||||
colWidth = {h: outlineTree.DEF_WIDTH[h] for h in nwOutline}
|
||||
@@ -181,7 +181,7 @@ def testGuiOutline_Content(qtbot, nwGUI, prjLipsum):
|
||||
assert outlineBar.novelValue.itemData(2) == "" # All novels
|
||||
|
||||
# Add a second novel folder
|
||||
newHandle = nwGUI.project.newRoot(nwItemClass.NOVEL)
|
||||
newHandle = SHARED.project.newRoot(nwItemClass.NOVEL)
|
||||
nwGUI.projView.projTree.revealNewTreeItem(newHandle)
|
||||
|
||||
# Check new values in dropdown list
|
||||
@@ -198,7 +198,7 @@ def testGuiOutline_Content(qtbot, nwGUI, prjLipsum):
|
||||
("Section 4", 4),
|
||||
]
|
||||
for dTitle, hLevel in docList:
|
||||
aHandle = nwGUI.project.newFile(dTitle, newHandle)
|
||||
aHandle = SHARED.project.newFile(dTitle, newHandle)
|
||||
hHash = "#"*hLevel
|
||||
writeFile(prjLipsum / "content" / f"{aHandle}.nwd", f"{hHash} {dTitle}\n\n")
|
||||
nwGUI.projView.projTree.revealNewTreeItem(aHandle)
|
||||
@@ -246,7 +246,7 @@ def testGuiOutline_Content(qtbot, nwGUI, prjLipsum):
|
||||
# Click POV Link
|
||||
assert outlineData.povKeyValue.text() == "<a href='Bod'>Bod</a>"
|
||||
outlineView._tagClicked("Bod")
|
||||
assert nwGUI.docViewer.docHandle() == "4c4f28287af27"
|
||||
assert nwGUI.docViewer.docHandle == "4c4f28287af27"
|
||||
|
||||
# Scene One, Section Two
|
||||
selItem = outlineTree.topLevelItem(5)
|
||||
@@ -262,7 +262,7 @@ def testGuiOutline_Content(qtbot, nwGUI, prjLipsum):
|
||||
assert outlineData.itemValue.text() == "Finished"
|
||||
|
||||
outlineTree._treeDoubleClick(selItem, 0)
|
||||
assert nwGUI.docEditor.docHandle() == "88243afbe5ed8"
|
||||
assert nwGUI.docEditor.docHandle == "88243afbe5ed8"
|
||||
|
||||
# qtbot.stop()
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ from mocked import causeOSError
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtWidgets import QMessageBox, QMenu, QTreeWidgetItem, QDialog
|
||||
|
||||
from novelwriter import CONFIG
|
||||
from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.enum import nwItemLayout, nwItemType, nwItemClass
|
||||
from novelwriter.guimain import GuiMain
|
||||
from novelwriter.gui.projtree import GuiProjectTree, GuiProjectView
|
||||
@@ -46,7 +46,7 @@ def testGuiProjTree_NewItems(qtbot, caplog, monkeypatch, nwGUI, projPath, mockRn
|
||||
|
||||
projView = nwGUI.projView
|
||||
projTree = nwGUI.projView.projTree
|
||||
theProject = nwGUI.project
|
||||
theProject = SHARED.project
|
||||
|
||||
# Try to add item with no project
|
||||
assert projView.projTree.newTreeItem(nwItemType.FILE) is False
|
||||
@@ -260,19 +260,19 @@ def testGuiProjTree_MoveItems(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
|
||||
# ===========
|
||||
|
||||
projView.setSelectedHandle(C.hNovelRoot)
|
||||
assert nwGUI.project.tree._treeOrder.index(C.hNovelRoot) == 0
|
||||
assert SHARED.project.tree._order.index(C.hNovelRoot) == 0
|
||||
|
||||
# Move novel folder up
|
||||
assert projTree.moveTreeItem(-1) is False
|
||||
assert nwGUI.project.tree._treeOrder.index(C.hNovelRoot) == 0
|
||||
assert SHARED.project.tree._order.index(C.hNovelRoot) == 0
|
||||
|
||||
# Move novel folder down
|
||||
assert projTree.moveTreeItem(1) is True
|
||||
assert nwGUI.project.tree._treeOrder.index(C.hNovelRoot) == 1
|
||||
assert SHARED.project.tree._order.index(C.hNovelRoot) == 1
|
||||
|
||||
# Move novel folder up again
|
||||
assert projTree.moveTreeItem(-1) is True
|
||||
assert nwGUI.project.tree._treeOrder.index(C.hNovelRoot) == 0
|
||||
assert SHARED.project.tree._order.index(C.hNovelRoot) == 0
|
||||
|
||||
# Clean up
|
||||
# qtbot.stop()
|
||||
@@ -348,7 +348,7 @@ def testGuiProjTree_RequestDeleteItem(qtbot, caplog, monkeypatch, nwGUI, projPat
|
||||
C.hChapterDir, C.hChapterDoc, C.hSceneDoc,
|
||||
"0000000000010"
|
||||
]
|
||||
trashHandle = nwGUI.project.tree.trashRoot()
|
||||
trashHandle = SHARED.project.tree.trashRoot()
|
||||
assert projTree.getTreeFromHandle(trashHandle) == [
|
||||
trashHandle, "0000000000012", "0000000000011"
|
||||
]
|
||||
@@ -368,7 +368,7 @@ def testGuiProjTree_MoveItemToTrash(qtbot, caplog, monkeypatch, nwGUI, projPath,
|
||||
"""Test moving items to Trash."""
|
||||
monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True))
|
||||
|
||||
theProject = nwGUI.project
|
||||
theProject = SHARED.project
|
||||
projTree = nwGUI.projView.projTree
|
||||
|
||||
# Create a project
|
||||
@@ -420,7 +420,7 @@ def testGuiProjTree_PermanentlyDeleteItem(qtbot, caplog, monkeypatch, nwGUI, pro
|
||||
"""Test permanently deleting items."""
|
||||
monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True))
|
||||
|
||||
theProject = nwGUI.project
|
||||
theProject = SHARED.project
|
||||
projTree = nwGUI.projView.projTree
|
||||
|
||||
# Create a project
|
||||
@@ -450,10 +450,10 @@ def testGuiProjTree_PermanentlyDeleteItem(qtbot, caplog, monkeypatch, nwGUI, pro
|
||||
|
||||
# Deleting file is OK, and if it is open, it should close
|
||||
assert nwGUI.openDocument(C.hTitlePage) is True
|
||||
assert nwGUI.docEditor.docHandle() == C.hTitlePage
|
||||
assert nwGUI.docEditor.docHandle == C.hTitlePage
|
||||
assert projTree.permDeleteItem(C.hTitlePage) is True
|
||||
assert C.hTitlePage not in theProject.tree
|
||||
assert nwGUI.docEditor.docHandle() is None
|
||||
assert nwGUI.docEditor.docHandle is None
|
||||
|
||||
# Deleting folder + files recursively is ok
|
||||
assert projTree.permDeleteItem(C.hChapterDir) is True
|
||||
@@ -471,7 +471,7 @@ def testGuiProjTree_EmptyTrash(qtbot, caplog, monkeypatch, nwGUI, projPath, mock
|
||||
"""Test emptying Trash."""
|
||||
monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True))
|
||||
|
||||
theProject = nwGUI.project
|
||||
theProject = SHARED.project
|
||||
projTree = nwGUI.projView.projTree
|
||||
|
||||
# No project open
|
||||
@@ -541,16 +541,16 @@ def testGuiProjTree_ContextMenu(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
|
||||
projTree.setExpandedFromHandle(None, True)
|
||||
|
||||
projTree._addTrashRoot()
|
||||
hTrashRoot = nwGUI.project.tree.trashRoot()
|
||||
hTrashRoot = SHARED.project.tree.trashRoot()
|
||||
|
||||
projTree.setSelectedHandle(C.hCharRoot)
|
||||
projTree.newTreeItem(nwItemType.FILE)
|
||||
projTree.setSelectedHandle(C.hNovelRoot)
|
||||
projTree.newTreeItem(nwItemType.FILE, isNote=True)
|
||||
|
||||
nwGUI.project.newFile("SubNote", hNovelNote)
|
||||
SHARED.project.newFile("SubNote", hNovelNote)
|
||||
projTree.revealNewTreeItem(hSubNote)
|
||||
assert nwGUI.project.tree[hSubNote].itemParent == hNovelNote
|
||||
assert SHARED.project.tree[hSubNote].itemParent == hNovelNote
|
||||
|
||||
def itemPos(tHandle):
|
||||
return projTree.visualItemRect(projTree._getTreeItem(tHandle)).center()
|
||||
@@ -578,7 +578,7 @@ def testGuiProjTree_ContextMenu(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
|
||||
# Direct Edit Functions
|
||||
# =====================
|
||||
# Trigger the dedicated functions the menu entries connect to
|
||||
nwItem = nwGUI.project.tree[hNovelNote]
|
||||
nwItem = SHARED.project.tree[hNovelNote]
|
||||
|
||||
# Toggle active flag
|
||||
assert nwItem.isActive is True
|
||||
@@ -619,17 +619,17 @@ def testGuiProjTree_ContextMenu(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.No)
|
||||
projTree._covertFolderToFile(hNewFolderOne, nwItemLayout.DOCUMENT)
|
||||
assert nwGUI.project.tree[hNewFolderOne].isFolderType()
|
||||
assert SHARED.project.tree[hNewFolderOne].isFolderType()
|
||||
|
||||
# Convert the first folder to a document
|
||||
projTree._covertFolderToFile(hNewFolderOne, nwItemLayout.DOCUMENT)
|
||||
assert nwGUI.project.tree[hNewFolderOne].isFileType()
|
||||
assert nwGUI.project.tree[hNewFolderOne].isDocumentLayout()
|
||||
assert SHARED.project.tree[hNewFolderOne].isFileType()
|
||||
assert SHARED.project.tree[hNewFolderOne].isDocumentLayout()
|
||||
|
||||
# Convert the second folder to a note
|
||||
projTree._covertFolderToFile(hNewFolderTwo, nwItemLayout.NOTE)
|
||||
assert nwGUI.project.tree[hNewFolderTwo].isFileType()
|
||||
assert nwGUI.project.tree[hNewFolderTwo].isNoteLayout()
|
||||
assert SHARED.project.tree[hNewFolderTwo].isFileType()
|
||||
assert SHARED.project.tree[hNewFolderTwo].isNoteLayout()
|
||||
|
||||
# qtbot.stop()
|
||||
|
||||
@@ -649,7 +649,7 @@ def testGuiProjTree_MergeDocuments(qtbot, monkeypatch, nwGUI, projPath, mockRnd,
|
||||
# Create a project
|
||||
buildTestProject(nwGUI, projPath)
|
||||
|
||||
theProject = nwGUI.project
|
||||
theProject = SHARED.project
|
||||
projTree = nwGUI.projView.projTree
|
||||
|
||||
mergedDoc1 = "0000000000014"
|
||||
@@ -751,7 +751,7 @@ def testGuiProjTree_SplitDocument(qtbot, monkeypatch, nwGUI, projPath, mockRnd,
|
||||
# Create a project
|
||||
buildTestProject(nwGUI, projPath)
|
||||
|
||||
theProject = nwGUI.project
|
||||
theProject = SHARED.project
|
||||
projTree = nwGUI.projView.projTree
|
||||
|
||||
docText = (
|
||||
@@ -852,7 +852,7 @@ def testGuiProjTree_Duplicate(qtbot, monkeypatch, nwGUI: GuiMain, projPath, mock
|
||||
"""Test the duplicate items function."""
|
||||
# Create a project
|
||||
buildTestProject(nwGUI, projPath)
|
||||
assert len(nwGUI.project.tree) == 8
|
||||
assert len(SHARED.project.tree) == 8
|
||||
|
||||
projTree = nwGUI.projView.projTree
|
||||
projTree._getTreeItem(C.hNovelRoot).setExpanded(True) # type: ignore
|
||||
@@ -860,28 +860,28 @@ def testGuiProjTree_Duplicate(qtbot, monkeypatch, nwGUI: GuiMain, projPath, mock
|
||||
|
||||
# Nothing to do
|
||||
assert projTree._duplicateFromHandle(C.hInvalid) is False
|
||||
assert len(nwGUI.project.tree) == 8
|
||||
assert len(SHARED.project.tree) == 8
|
||||
|
||||
# Duplicate title page, but select no
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.No)
|
||||
assert projTree._duplicateFromHandle(C.hTitlePage) is False
|
||||
assert len(nwGUI.project.tree) == 8
|
||||
assert len(SHARED.project.tree) == 8
|
||||
|
||||
# Duplicate title page
|
||||
assert projTree._duplicateFromHandle(C.hTitlePage) is True
|
||||
assert len(nwGUI.project.tree) == 9
|
||||
assert len(SHARED.project.tree) == 9
|
||||
|
||||
# Duplicate folder
|
||||
assert projTree._duplicateFromHandle(C.hChapterDir) is True
|
||||
assert len(nwGUI.project.tree) == 12
|
||||
assert len(SHARED.project.tree) == 12
|
||||
|
||||
# Duplicate novel root
|
||||
assert projTree._duplicateFromHandle(C.hNovelRoot) is True
|
||||
assert len(nwGUI.project.tree) == 21
|
||||
assert len(SHARED.project.tree) == 21
|
||||
|
||||
# Check tree order that all items are next to eachother
|
||||
assert nwGUI.project.tree._treeOrder == [
|
||||
assert SHARED.project.tree._order == [
|
||||
C.hNovelRoot, C.hTitlePage, "0000000000010", C.hChapterDir, C.hChapterDoc, C.hSceneDoc,
|
||||
"0000000000011", "0000000000012", "0000000000013", "0000000000014", "0000000000015",
|
||||
"0000000000016", "0000000000017", "0000000000018", "0000000000019", "000000000001a",
|
||||
@@ -889,7 +889,7 @@ def testGuiProjTree_Duplicate(qtbot, monkeypatch, nwGUI: GuiMain, projPath, mock
|
||||
]
|
||||
|
||||
# Make the duplicator stop early
|
||||
content = nwGUI.project.storage.contentPath
|
||||
content = SHARED.project.storage.contentPath
|
||||
assert isinstance(content, Path)
|
||||
(content / "000000000001e.nwd").touch()
|
||||
assert (content / "000000000001e.nwd").exists()
|
||||
@@ -897,7 +897,7 @@ def testGuiProjTree_Duplicate(qtbot, monkeypatch, nwGUI: GuiMain, projPath, mock
|
||||
# Should only create the folder, and skip the two files because the
|
||||
# next handle is already a file
|
||||
assert projTree._duplicateFromHandle(C.hChapterDir) is True
|
||||
assert len(nwGUI.project.tree) == 22
|
||||
assert len(SHARED.project.tree) == 22
|
||||
|
||||
# qtbot.stop()
|
||||
|
||||
@@ -938,13 +938,13 @@ def testGuiProjTree_Other(qtbot, monkeypatch, nwGUI: GuiMain, projPath, mockRnd)
|
||||
assert projTree.revealNewTreeItem(C.hInvalid) is False
|
||||
|
||||
# Try to add an orphaned file to the tree
|
||||
nHandle = nwGUI.project.newFile("Test", C.hNovelRoot)
|
||||
nwGUI.project.tree[nHandle].setParent(None) # type: ignore
|
||||
nHandle = SHARED.project.newFile("Test", C.hNovelRoot)
|
||||
SHARED.project.tree[nHandle].setParent(None) # type: ignore
|
||||
assert projTree.revealNewTreeItem(nHandle) is False
|
||||
|
||||
# Try to add an item with unknown parent to the tree
|
||||
nHandle = nwGUI.project.newFile("Test", C.hNovelRoot)
|
||||
nwGUI.project.tree[nHandle].setParent(C.hInvalid) # type: ignore
|
||||
nHandle = SHARED.project.newFile("Test", C.hNovelRoot)
|
||||
SHARED.project.tree[nHandle].setParent(C.hInvalid) # type: ignore
|
||||
assert projTree.revealNewTreeItem(nHandle) is False
|
||||
|
||||
# Method: undoLastMove
|
||||
@@ -969,25 +969,25 @@ def testGuiProjTree_Other(qtbot, monkeypatch, nwGUI: GuiMain, projPath, mockRnd)
|
||||
# Try to open a file with nothings selected
|
||||
projTree.clearSelection()
|
||||
projTree._treeDoubleClick(QTreeWidgetItem(), 0)
|
||||
assert nwGUI.docEditor.docHandle() is None
|
||||
assert nwGUI.docEditor.docHandle is None
|
||||
|
||||
# When the item cannot be found
|
||||
projTree._getTreeItem(C.hTitlePage).setSelected(True) # type: ignore
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr("novelwriter.core.tree.NWTree.__getitem__", lambda *a: None)
|
||||
projTree._treeDoubleClick(QTreeWidgetItem(), 0)
|
||||
assert nwGUI.docEditor.docHandle() is None
|
||||
assert nwGUI.docEditor.docHandle is None
|
||||
|
||||
# Successfully open a file
|
||||
projTree._treeDoubleClick(projTree._getTreeItem(C.hTitlePage), 0)
|
||||
assert nwGUI.docEditor.docHandle() == C.hTitlePage
|
||||
assert nwGUI.docEditor.docHandle == C.hTitlePage
|
||||
projTree._getTreeItem(C.hTitlePage).setSelected(False) # type: ignore
|
||||
|
||||
# A non-file item should be expanded instead
|
||||
projTree._getTreeItem(C.hNovelRoot).setExpanded(False) # type: ignore
|
||||
projTree._getTreeItem(C.hNovelRoot).setSelected(True) # type: ignore
|
||||
projTree._treeDoubleClick(projTree._getTreeItem(C.hNovelRoot), 1)
|
||||
assert nwGUI.docEditor.docHandle() == C.hTitlePage
|
||||
assert nwGUI.docEditor.docHandle == C.hTitlePage
|
||||
assert projTree._getTreeItem(C.hNovelRoot).isExpanded() is True # type: ignore
|
||||
|
||||
# Navigate the Tree
|
||||
|
||||
@@ -24,17 +24,16 @@ import pytest
|
||||
|
||||
from tools import C, buildTestProject
|
||||
|
||||
from novelwriter import CONFIG
|
||||
from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.extensions.statusled import StatusLED
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiStatusBar_Main(qtbot, nwGUI, projPath, mockRnd):
|
||||
"""Test the the various features of the status bar.
|
||||
"""
|
||||
"""Test the the various features of the status bar."""
|
||||
buildTestProject(nwGUI, projPath)
|
||||
cHandle = nwGUI.project.newFile("A Note", C.hCharRoot)
|
||||
newDoc = nwGUI.project.storage.getDocument(cHandle)
|
||||
cHandle = SHARED.project.newFile("A Note", C.hCharRoot)
|
||||
newDoc = SHARED.project.storage.getDocument(cHandle)
|
||||
newDoc.writeDocument("# A Note\n\n")
|
||||
nwGUI.projView.projTree.revealNewTreeItem(cHandle)
|
||||
nwGUI.rebuildIndex(beQuiet=True)
|
||||
@@ -42,7 +41,7 @@ def testGuiStatusBar_Main(qtbot, nwGUI, projPath, mockRnd):
|
||||
# Reference Time
|
||||
refTime = time.time()
|
||||
nwGUI.mainStatus.setRefTime(refTime)
|
||||
assert nwGUI.mainStatus.refTime == refTime
|
||||
assert nwGUI.mainStatus._refTime == refTime
|
||||
|
||||
# Project Status
|
||||
nwGUI.mainStatus.setProjectStatus(StatusLED.S_NONE)
|
||||
@@ -64,18 +63,18 @@ def testGuiStatusBar_Main(qtbot, nwGUI, projPath, mockRnd):
|
||||
CONFIG.stopWhenIdle = False
|
||||
nwGUI.mainStatus.setUserIdle(True)
|
||||
nwGUI.mainStatus.updateTime()
|
||||
assert nwGUI.mainStatus.userIdle is False
|
||||
assert nwGUI.mainStatus._userIdle is False
|
||||
assert nwGUI.mainStatus.timeText.text() == "00:00:00"
|
||||
|
||||
CONFIG.stopWhenIdle = True
|
||||
nwGUI.mainStatus.setUserIdle(True)
|
||||
nwGUI.mainStatus.updateTime(5)
|
||||
assert nwGUI.mainStatus.userIdle is True
|
||||
assert nwGUI.mainStatus._userIdle is True
|
||||
assert nwGUI.mainStatus.timeText.text() != "00:00:00"
|
||||
|
||||
nwGUI.mainStatus.setUserIdle(False)
|
||||
nwGUI.mainStatus.updateTime(5)
|
||||
assert nwGUI.mainStatus.userIdle is False
|
||||
assert nwGUI.mainStatus._userIdle is False
|
||||
assert nwGUI.mainStatus.timeText.text() != "00:00:00"
|
||||
|
||||
# Language
|
||||
|
||||
@@ -30,7 +30,7 @@ from tools import writeFile
|
||||
from PyQt5.QtGui import QIcon, QPalette, QPixmap
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
|
||||
from novelwriter import CONFIG
|
||||
from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
|
||||
from novelwriter.constants import nwLabels
|
||||
|
||||
@@ -38,7 +38,7 @@ from novelwriter.constants import nwLabels
|
||||
@pytest.mark.gui
|
||||
def testGuiTheme_Main(qtbot, nwGUI, tstPaths):
|
||||
"""Test the theme class init."""
|
||||
mainTheme = CONFIG.theme
|
||||
mainTheme = SHARED.theme
|
||||
|
||||
# Methods
|
||||
# =======
|
||||
@@ -121,7 +121,7 @@ def testGuiTheme_Main(qtbot, nwGUI, tstPaths):
|
||||
@pytest.mark.gui
|
||||
def testGuiTheme_Theme(qtbot, monkeypatch, nwGUI):
|
||||
"""Test the theme part of the class."""
|
||||
mainTheme = CONFIG.theme
|
||||
mainTheme = SHARED.theme
|
||||
|
||||
# List Themes
|
||||
# ===========
|
||||
@@ -199,7 +199,7 @@ def testGuiTheme_Theme(qtbot, monkeypatch, nwGUI):
|
||||
@pytest.mark.gui
|
||||
def testGuiTheme_Syntax(qtbot, monkeypatch, nwGUI):
|
||||
"""Test the syntax part of the class."""
|
||||
mainTheme = CONFIG.theme
|
||||
mainTheme = SHARED.theme
|
||||
|
||||
# List Themes
|
||||
# ===========
|
||||
@@ -264,7 +264,7 @@ def testGuiTheme_Syntax(qtbot, monkeypatch, nwGUI):
|
||||
@pytest.mark.gui
|
||||
def testGuiTheme_Icons(qtbot, caplog, monkeypatch, nwGUI, tstPaths):
|
||||
"""Test the icon cache class."""
|
||||
iconCache = CONFIG.theme.iconCache
|
||||
iconCache = SHARED.theme.iconCache
|
||||
|
||||
# Load Theme
|
||||
# ==========
|
||||
|
||||
@@ -45,7 +45,7 @@ def testManuscriptBuild_Main(
|
||||
build = BuildSettings()
|
||||
build.setLastPath(fncPath)
|
||||
|
||||
manus = GuiManuscriptBuild(nwGUI, nwGUI, build)
|
||||
manus = GuiManuscriptBuild(nwGUI, build)
|
||||
manus.show()
|
||||
|
||||
# Check initial values
|
||||
@@ -101,7 +101,7 @@ def testManuscriptBuild_Main(
|
||||
# Error Handling
|
||||
# ==============
|
||||
|
||||
manus = GuiManuscriptBuild(nwGUI, nwGUI, build)
|
||||
manus = GuiManuscriptBuild(nwGUI, build)
|
||||
manus.show()
|
||||
|
||||
# Name, path and format should be remembered
|
||||
|
||||
@@ -32,7 +32,7 @@ from PyQt5.QtCore import Qt, pyqtSlot
|
||||
from PyQt5.QtWidgets import QDialogButtonBox
|
||||
from PyQt5.QtPrintSupport import QPrintPreviewDialog
|
||||
|
||||
from novelwriter import CONFIG
|
||||
from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.guimain import GuiMain
|
||||
from novelwriter.core.buildsettings import BuildSettings
|
||||
from novelwriter.tools.manuscript import GuiManuscript
|
||||
@@ -45,7 +45,7 @@ def testManuscript_Init(monkeypatch, qtbot: QtBot, nwGUI: GuiMain, projPath: Pat
|
||||
"""Test the init/main functionality of the GuiManuscript dialog."""
|
||||
buildTestProject(nwGUI, projPath)
|
||||
nwGUI.openProject(projPath)
|
||||
nwGUI.project.storage.getDocument(C.hChapterDoc).writeDocument("## A Chapter\n\n\t\tHi")
|
||||
SHARED.project.storage.getDocument(C.hChapterDoc).writeDocument("## A Chapter\n\n\t\tHi")
|
||||
allText = "New Novel\nBy Jane Doe\nA Chapter\n\t\tHi\n* * *"
|
||||
|
||||
manus = GuiManuscript(nwGUI)
|
||||
@@ -159,7 +159,7 @@ def testManuscript_Features(monkeypatch, qtbot: QtBot, nwGUI: GuiMain, projPath:
|
||||
manus.show()
|
||||
manus.loadContent()
|
||||
|
||||
cacheFile = CONFIG.dataPath("cache") / f"build_{nwGUI.project.data.uuid}.json"
|
||||
cacheFile = CONFIG.dataPath("cache") / f"build_{SHARED.project.data.uuid}.json"
|
||||
manus.buildList.setCurrentRow(0)
|
||||
build = manus._getSelectedBuild()
|
||||
assert isinstance(build, BuildSettings)
|
||||
|
||||
@@ -21,16 +21,16 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import pytest
|
||||
|
||||
from tools import C, buildTestProject
|
||||
|
||||
from pathlib import Path
|
||||
from pytestqt.qtbot import QtBot
|
||||
|
||||
from tools import C, buildTestProject
|
||||
|
||||
from PyQt5.QtGui import QFont
|
||||
from PyQt5.QtCore import pyqtSlot
|
||||
from PyQt5.QtWidgets import QDialogButtonBox, QFontDialog
|
||||
|
||||
from novelwriter import CONFIG
|
||||
from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.guimain import GuiMain
|
||||
from novelwriter.constants import nwHeadFmt
|
||||
from novelwriter.core.buildsettings import BuildSettings, FilterMode
|
||||
@@ -128,11 +128,11 @@ def testBuildSettings_Filter(qtbot: QtBot, nwGUI: GuiMain, projPath: Path, mockR
|
||||
"worldRoot": 9,
|
||||
}
|
||||
|
||||
hPlotDoc = nwGUI.project.newFile("Main Plot", C.hPlotRoot)
|
||||
hCharDoc = nwGUI.project.newFile("Jane Doe", C.hCharRoot)
|
||||
hPlotDoc = SHARED.project.newFile("Main Plot", C.hPlotRoot)
|
||||
hCharDoc = SHARED.project.newFile("Jane Doe", C.hCharRoot)
|
||||
nwGUI.projView.projTree.revealNewTreeItem(hPlotDoc)
|
||||
nwGUI.projView.projTree.revealNewTreeItem(hCharDoc)
|
||||
nwGUI.project.tree[hPlotDoc].setActive(False) # type: ignore
|
||||
SHARED.project.tree[hPlotDoc].setActive(False) # type: ignore
|
||||
|
||||
# Create the dialog and populate it
|
||||
bSettings = GuiBuildSettings(nwGUI, build)
|
||||
@@ -167,7 +167,7 @@ def testBuildSettings_Filter(qtbot: QtBot, nwGUI: GuiMain, projPath: Path, mockR
|
||||
|
||||
# Switch off novel docs
|
||||
filterTab.filterOpt._widgets[switchMap["incNovel"]].setChecked(False)
|
||||
assert build.buildItemFilter(nwGUI.project) == {
|
||||
assert build.buildItemFilter(SHARED.project) == {
|
||||
C.hNovelRoot: (False, FilterMode.SKIPPED),
|
||||
C.hTitlePage: (False, FilterMode.FILTERED),
|
||||
C.hChapterDir: (False, FilterMode.SKIPPED),
|
||||
@@ -182,7 +182,7 @@ def testBuildSettings_Filter(qtbot: QtBot, nwGUI: GuiMain, projPath: Path, mockR
|
||||
|
||||
# Switch on note docs
|
||||
filterTab.filterOpt._widgets[switchMap["incNotes"]].setChecked(True)
|
||||
assert build.buildItemFilter(nwGUI.project) == {
|
||||
assert build.buildItemFilter(SHARED.project) == {
|
||||
C.hNovelRoot: (False, FilterMode.SKIPPED),
|
||||
C.hTitlePage: (False, FilterMode.FILTERED),
|
||||
C.hChapterDir: (False, FilterMode.SKIPPED),
|
||||
@@ -197,7 +197,7 @@ def testBuildSettings_Filter(qtbot: QtBot, nwGUI: GuiMain, projPath: Path, mockR
|
||||
|
||||
# Switch on inactive docs
|
||||
filterTab.filterOpt._widgets[switchMap["incInactive"]].setChecked(True)
|
||||
assert build.buildItemFilter(nwGUI.project) == {
|
||||
assert build.buildItemFilter(SHARED.project) == {
|
||||
C.hNovelRoot: (False, FilterMode.SKIPPED),
|
||||
C.hTitlePage: (False, FilterMode.FILTERED),
|
||||
C.hChapterDir: (False, FilterMode.SKIPPED),
|
||||
@@ -214,7 +214,7 @@ def testBuildSettings_Filter(qtbot: QtBot, nwGUI: GuiMain, projPath: Path, mockR
|
||||
filterTab._treeMap[C.hChapterDoc].setSelected(True)
|
||||
filterTab._treeMap[C.hSceneDoc].setSelected(True)
|
||||
filterTab.includedButton.click()
|
||||
assert build.buildItemFilter(nwGUI.project) == {
|
||||
assert build.buildItemFilter(SHARED.project) == {
|
||||
C.hNovelRoot: (False, FilterMode.SKIPPED),
|
||||
C.hTitlePage: (False, FilterMode.FILTERED),
|
||||
C.hChapterDir: (False, FilterMode.SKIPPED),
|
||||
@@ -232,7 +232,7 @@ def testBuildSettings_Filter(qtbot: QtBot, nwGUI: GuiMain, projPath: Path, mockR
|
||||
filterTab._treeMap[hPlotDoc].setSelected(True) # type: ignore
|
||||
filterTab._treeMap[hCharDoc].setSelected(True) # type: ignore
|
||||
filterTab.excludedButton.click()
|
||||
assert build.buildItemFilter(nwGUI.project) == {
|
||||
assert build.buildItemFilter(SHARED.project) == {
|
||||
C.hNovelRoot: (False, FilterMode.SKIPPED),
|
||||
C.hTitlePage: (False, FilterMode.FILTERED),
|
||||
C.hChapterDir: (False, FilterMode.SKIPPED),
|
||||
@@ -247,7 +247,7 @@ def testBuildSettings_Filter(qtbot: QtBot, nwGUI: GuiMain, projPath: Path, mockR
|
||||
|
||||
# Switch on novel docs
|
||||
filterTab.filterOpt._widgets[switchMap["incNovel"]].setChecked(True)
|
||||
assert build.buildItemFilter(nwGUI.project) == {
|
||||
assert build.buildItemFilter(SHARED.project) == {
|
||||
C.hNovelRoot: (False, FilterMode.SKIPPED),
|
||||
C.hTitlePage: (True, FilterMode.FILTERED), # Now enabled
|
||||
C.hChapterDir: (False, FilterMode.SKIPPED),
|
||||
@@ -264,7 +264,7 @@ def testBuildSettings_Filter(qtbot: QtBot, nwGUI: GuiMain, projPath: Path, mockR
|
||||
filterTab.optTree.clearSelection()
|
||||
filterTab._treeMap[C.hNovelRoot].setSelected(True)
|
||||
filterTab.resetButton.click()
|
||||
assert build.buildItemFilter(nwGUI.project) == {
|
||||
assert build.buildItemFilter(SHARED.project) == {
|
||||
C.hNovelRoot: (False, FilterMode.SKIPPED),
|
||||
C.hTitlePage: (True, FilterMode.FILTERED),
|
||||
C.hChapterDir: (False, FilterMode.SKIPPED),
|
||||
@@ -284,7 +284,7 @@ def testBuildSettings_Filter(qtbot: QtBot, nwGUI: GuiMain, projPath: Path, mockR
|
||||
filterTab._treeMap[hPlotDoc].setSelected(True) # type: ignore
|
||||
filterTab._treeMap[hCharDoc].setSelected(True) # type: ignore
|
||||
filterTab.resetButton.click()
|
||||
assert build.buildItemFilter(nwGUI.project) == {
|
||||
assert build.buildItemFilter(SHARED.project) == {
|
||||
C.hNovelRoot: (False, FilterMode.SKIPPED),
|
||||
C.hTitlePage: (True, FilterMode.FILTERED),
|
||||
C.hChapterDir: (False, FilterMode.SKIPPED),
|
||||
@@ -302,8 +302,8 @@ def testBuildSettings_Filter(qtbot: QtBot, nwGUI: GuiMain, projPath: Path, mockR
|
||||
C.hNovelRoot, C.hTitlePage, C.hChapterDir, C.hChapterDoc, C.hSceneDoc,
|
||||
C.hPlotRoot, hPlotDoc, C.hCharRoot, hCharDoc,
|
||||
]
|
||||
nwGUI.project.tree[hCharDoc].setRoot(None) # type: ignore
|
||||
nwGUI.project.tree[hPlotDoc].setParent(None) # type: ignore
|
||||
SHARED.project.tree[hCharDoc].setRoot(None) # type: ignore
|
||||
SHARED.project.tree[hPlotDoc].setParent(None) # type: ignore
|
||||
filterTab._populateTree()
|
||||
assert list(filterTab._treeMap.keys()) == [
|
||||
C.hNovelRoot, C.hTitlePage, C.hChapterDir, C.hChapterDoc, C.hSceneDoc,
|
||||
|
||||
@@ -20,15 +20,17 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
import pytest
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from tools import getGuiItem, buildTestProject
|
||||
from mocked import causeOSError
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtWidgets import QAction, QFileDialog
|
||||
|
||||
from novelwriter import SHARED
|
||||
from novelwriter.constants import nwFiles
|
||||
from novelwriter.tools.writingstats import GuiWritingStats
|
||||
|
||||
@@ -39,7 +41,7 @@ def testToolWritingStats_Main(qtbot, monkeypatch, nwGUI, projPath, tstPaths):
|
||||
"""
|
||||
# Create a project to work on
|
||||
buildTestProject(nwGUI, projPath)
|
||||
project = nwGUI.project
|
||||
project = SHARED.project
|
||||
|
||||
qtbot.wait(100)
|
||||
assert nwGUI.saveProject()
|
||||
@@ -377,13 +379,14 @@ def testToolWritingStats_Main(qtbot, monkeypatch, nwGUI, projPath, tstPaths):
|
||||
|
||||
# IOError
|
||||
# =======
|
||||
monkeypatch.setattr("builtins.open", causeOSError)
|
||||
assert not sessLog._loadLogFile()
|
||||
assert not sessLog._saveData(sessLog.FMT_CSV)
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr("builtins.open", causeOSError)
|
||||
assert not sessLog._loadLogFile()
|
||||
assert not sessLog._saveData(sessLog.FMT_CSV)
|
||||
|
||||
# qtbot.stop()
|
||||
|
||||
sessLog._doClose()
|
||||
assert nwGUI.closeProject()
|
||||
assert nwGUI.closeProject() is True
|
||||
|
||||
# END Test testToolWritingStats_Main
|
||||
|
||||
+3
-3
@@ -165,10 +165,10 @@ def buildTestProject(obj, projPath):
|
||||
nwGUI = None
|
||||
project = obj
|
||||
else:
|
||||
from novelwriter import SHARED
|
||||
nwGUI = obj
|
||||
project = obj.project
|
||||
project = SHARED.project
|
||||
|
||||
project.clearProject()
|
||||
project.storage.openProjectInPlace(projPath)
|
||||
project.setDefaultStatusImport()
|
||||
|
||||
@@ -204,9 +204,9 @@ def buildTestProject(obj, projPath):
|
||||
project.session.startSession()
|
||||
project.setProjectChanged(True)
|
||||
project.saveProject(autoSave=True)
|
||||
project._valid = True
|
||||
|
||||
if nwGUI is not None:
|
||||
nwGUI.hasProject = True
|
||||
nwGUI.rebuildTrees()
|
||||
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user