Merge pull request #460 from vkbo/editor_performance
Editor Performance Improvements
This commit is contained in:
@@ -29,6 +29,8 @@ import logging
|
|||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
from PyQt5.QtWidgets import qApp
|
||||||
|
|
||||||
from nw.constants import nwConst, nwUnicode
|
from nw.constants import nwConst, nwUnicode
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -251,3 +253,11 @@ def makeFileNameSafe(theText):
|
|||||||
if c.isalpha() or c.isdigit() or c == " ":
|
if c.isalpha() or c.isdigit() or c == " ":
|
||||||
cleanName += c
|
cleanName += c
|
||||||
return cleanName
|
return cleanName
|
||||||
|
|
||||||
|
def getGuiItem(theName):
|
||||||
|
"""Returns a QtWidget based on its objectName.
|
||||||
|
"""
|
||||||
|
for qWidget in qApp.topLevelWidgets():
|
||||||
|
if qWidget.objectName() == theName:
|
||||||
|
return qWidget
|
||||||
|
return None
|
||||||
|
|||||||
@@ -33,7 +33,9 @@ class nwConst():
|
|||||||
fStampFmt = "%Y-%m-%d %H.%M.%S" # FileName safe format
|
fStampFmt = "%Y-%m-%d %H.%M.%S" # FileName safe format
|
||||||
dStampFmt = "%Y-%m-%d" # Date only format
|
dStampFmt = "%Y-%m-%d" # Date only format
|
||||||
|
|
||||||
maxDepth = 30 # Maximum folder depth of a project
|
maxDepth = 30 # Maximum folder depth of a project
|
||||||
|
maxDocSize = 5000000 # Maxium size of a single document
|
||||||
|
maxBuildSize = 10000000 # Maxium size of a project build
|
||||||
|
|
||||||
# END Class nwConst
|
# END Class nwConst
|
||||||
|
|
||||||
|
|||||||
+6
-6
@@ -271,16 +271,16 @@ class NWIndex():
|
|||||||
theRoot = self.theProject.projTree.getRootItem(tHandle)
|
theRoot = self.theProject.projTree.getRootItem(tHandle)
|
||||||
|
|
||||||
if theItem is None:
|
if theItem is None:
|
||||||
logger.error("Not indexing unknown item %s" % tHandle)
|
logger.info("Not indexing unknown item %s" % tHandle)
|
||||||
return False
|
return False
|
||||||
if theItem.itemType != nwItemType.FILE:
|
if theItem.itemType != nwItemType.FILE:
|
||||||
logger.error("Not indexing non-file item %s" % tHandle)
|
logger.info("Not indexing non-file item %s" % tHandle)
|
||||||
return False
|
return False
|
||||||
if theItem.itemLayout == nwItemLayout.NO_LAYOUT:
|
if theItem.itemLayout == nwItemLayout.NO_LAYOUT:
|
||||||
logger.error("Not indexing no-layout item %s" % tHandle)
|
logger.info("Not indexing no-layout item %s" % tHandle)
|
||||||
return False
|
return False
|
||||||
if theItem.parHandle is None:
|
if theItem.parHandle is None:
|
||||||
logger.error("Not indexing orphaned item %s" % tHandle)
|
logger.info("Not indexing orphaned item %s" % tHandle)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Run word counter for the whole text
|
# Run word counter for the whole text
|
||||||
@@ -289,10 +289,10 @@ class NWIndex():
|
|||||||
|
|
||||||
# If the file is archived or trashed, we don't index the file itself
|
# If the file is archived or trashed, we don't index the file itself
|
||||||
if self.theProject.projTree.isTrashRoot(theItem.parHandle):
|
if self.theProject.projTree.isTrashRoot(theItem.parHandle):
|
||||||
logger.error("Not indexing trash item %s" % tHandle)
|
logger.info("Not indexing trash item %s" % tHandle)
|
||||||
return False
|
return False
|
||||||
if theRoot.itemClass == nwItemClass.ARCHIVE:
|
if theRoot.itemClass == nwItemClass.ARCHIVE:
|
||||||
logger.error("Not indexing archived item %s" % tHandle)
|
logger.info("Not indexing archived item %s" % tHandle)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
itemClass = theItem.itemClass
|
itemClass = theItem.itemClass
|
||||||
|
|||||||
+17
-1
@@ -33,7 +33,7 @@ from PyQt5.QtCore import QRegularExpression
|
|||||||
|
|
||||||
from nw.core.document import NWDoc
|
from nw.core.document import NWDoc
|
||||||
from nw.core.tools import numberToWord, numberToRoman
|
from nw.core.tools import numberToWord, numberToRoman
|
||||||
from nw.constants import nwItemLayout, nwItemType, nwRegEx
|
from nw.constants import nwConst, nwItemLayout, nwItemType, nwRegEx
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -120,6 +120,9 @@ class Tokenizer():
|
|||||||
self.isNote = False
|
self.isNote = False
|
||||||
self.isNovel = False
|
self.isNovel = False
|
||||||
|
|
||||||
|
# Error Handling
|
||||||
|
self.errData = []
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
##
|
##
|
||||||
@@ -212,6 +215,14 @@ class Tokenizer():
|
|||||||
theDocument = NWDoc(self.theProject, self.theParent)
|
theDocument = NWDoc(self.theProject, self.theParent)
|
||||||
self.theText = theDocument.openDocument(theHandle)
|
self.theText = theDocument.openDocument(theHandle)
|
||||||
|
|
||||||
|
docSize = len(self.theText)
|
||||||
|
if docSize > nwConst.maxDocSize:
|
||||||
|
errVal = "Document '%s' is too big (%.2f MB). Skipping." % (
|
||||||
|
self.theItem.itemName, docSize/1.0e6
|
||||||
|
)
|
||||||
|
self.theText = "# ERROR\n\n%s\n\n" % errVal
|
||||||
|
self.errData.append(errVal)
|
||||||
|
|
||||||
self.isNone = self.theItem.itemLayout == nwItemLayout.NO_LAYOUT
|
self.isNone = self.theItem.itemLayout == nwItemLayout.NO_LAYOUT
|
||||||
self.isTitle = self.theItem.itemLayout == nwItemLayout.TITLE
|
self.isTitle = self.theItem.itemLayout == nwItemLayout.TITLE
|
||||||
self.isBook = self.theItem.itemLayout == nwItemLayout.BOOK
|
self.isBook = self.theItem.itemLayout == nwItemLayout.BOOK
|
||||||
@@ -230,6 +241,11 @@ class Tokenizer():
|
|||||||
"""
|
"""
|
||||||
return self.theResult
|
return self.theResult
|
||||||
|
|
||||||
|
def getResultSize(self):
|
||||||
|
"""Return the size of the result from the conversion.
|
||||||
|
"""
|
||||||
|
return len(self.theResult)
|
||||||
|
|
||||||
def getFilteredMarkdown(self):
|
def getFilteredMarkdown(self):
|
||||||
"""Return the novelWriter markdown after the filters have been applied.
|
"""Return the novelWriter markdown after the filters have been applied.
|
||||||
"""
|
"""
|
||||||
|
|||||||
+12
-2
@@ -29,6 +29,8 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from nw.constants import nwUnicode
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# =============================================================================================== #
|
# =============================================================================================== #
|
||||||
@@ -44,6 +46,15 @@ def countWords(theText):
|
|||||||
paraCount = 0
|
paraCount = 0
|
||||||
prevEmpty = True
|
prevEmpty = True
|
||||||
|
|
||||||
|
# We need to treat dashes as word separators for counting words.
|
||||||
|
# The check+replace apprach is much faster that direct replace for
|
||||||
|
# large texts, and a bit slower for small texts, but in the latter
|
||||||
|
# case it doesn't matter.
|
||||||
|
if nwUnicode.U_ENDASH in theText:
|
||||||
|
theText = theText.replace(nwUnicode.U_ENDASH, " ")
|
||||||
|
if nwUnicode.U_EMDASH in theText:
|
||||||
|
theText = theText.replace(nwUnicode.U_EMDASH, " ")
|
||||||
|
|
||||||
for aLine in theText.splitlines():
|
for aLine in theText.splitlines():
|
||||||
|
|
||||||
countPara = True
|
countPara = True
|
||||||
@@ -72,8 +83,7 @@ def countWords(theText):
|
|||||||
charCount -= 2
|
charCount -= 2
|
||||||
countPara = False
|
countPara = False
|
||||||
|
|
||||||
theBuff = aLine.replace("–", " ").replace("—", " ")
|
wordCount += len(aLine.split())
|
||||||
wordCount += len(theBuff.split())
|
|
||||||
charCount += theLen
|
charCount += theLen
|
||||||
if countPara and prevEmpty:
|
if countPara and prevEmpty:
|
||||||
paraCount += 1
|
paraCount += 1
|
||||||
|
|||||||
+62
-15
@@ -36,10 +36,10 @@ from datetime import datetime
|
|||||||
from PyQt5.QtCore import Qt, QByteArray, QTimer
|
from PyQt5.QtCore import Qt, QByteArray, QTimer
|
||||||
from PyQt5.QtPrintSupport import QPrinter, QPrintPreviewDialog
|
from PyQt5.QtPrintSupport import QPrinter, QPrintPreviewDialog
|
||||||
from PyQt5.QtGui import (
|
from PyQt5.QtGui import (
|
||||||
QPalette, QColor, QTextDocumentWriter, QFont
|
QPalette, QColor, QTextDocumentWriter, QFont, QCursor
|
||||||
)
|
)
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
QDialog, QVBoxLayout, QHBoxLayout, QTextBrowser, QPushButton, QLabel,
|
qApp, QDialog, QVBoxLayout, QHBoxLayout, QTextBrowser, QPushButton, QLabel,
|
||||||
QLineEdit, QGroupBox, QGridLayout, QProgressBar, QMenu, QAction,
|
QLineEdit, QGroupBox, QGridLayout, QProgressBar, QMenu, QAction,
|
||||||
QFileDialog, QFontDialog, QSpinBox, QScrollArea, QSplitter, QWidget,
|
QFileDialog, QFontDialog, QSpinBox, QScrollArea, QSplitter, QWidget,
|
||||||
QSizePolicy
|
QSizePolicy
|
||||||
@@ -49,7 +49,7 @@ from nw.common import fuzzyTime, makeFileNameSafe
|
|||||||
from nw.gui.custom import QSwitch
|
from nw.gui.custom import QSwitch
|
||||||
from nw.core import ToHtml
|
from nw.core import ToHtml
|
||||||
from nw.constants import (
|
from nw.constants import (
|
||||||
nwAlert, nwFiles, nwItemType, nwItemLayout, nwItemClass
|
nwConst, nwAlert, nwFiles, nwItemType, nwItemLayout, nwItemClass
|
||||||
)
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -77,7 +77,7 @@ class GuiBuildNovel(QDialog):
|
|||||||
self.theTheme = theParent.theTheme
|
self.theTheme = theParent.theTheme
|
||||||
self.optState = self.theProject.optState
|
self.optState = self.theProject.optState
|
||||||
|
|
||||||
self.htmlText = [] # List of html document
|
self.htmlText = [] # List of html documents
|
||||||
self.htmlStyle = [] # List of html styles
|
self.htmlStyle = [] # List of html styles
|
||||||
self.nwdText = [] # List of markdown documents
|
self.nwdText = [] # List of markdown documents
|
||||||
self.buildTime = 0 # The timestamp of the last build
|
self.buildTime = 0 # The timestamp of the last build
|
||||||
@@ -483,7 +483,11 @@ class GuiBuildNovel(QDialog):
|
|||||||
|
|
||||||
logger.debug("GuiBuildNovel initialisation complete")
|
logger.debug("GuiBuildNovel initialisation complete")
|
||||||
|
|
||||||
# Load from Cache
|
return
|
||||||
|
|
||||||
|
def viewCachedDoc(self):
|
||||||
|
"""Load the previously generated document from cache.
|
||||||
|
"""
|
||||||
if self._loadCache():
|
if self._loadCache():
|
||||||
textFont = self.textFont.text()
|
textFont = self.textFont.text()
|
||||||
textSize = self.textSize.value()
|
textSize = self.textSize.value()
|
||||||
@@ -494,14 +498,25 @@ class GuiBuildNovel(QDialog):
|
|||||||
self.docView.clearStyleSheet()
|
self.docView.clearStyleSheet()
|
||||||
else:
|
else:
|
||||||
self.docView.setStyleSheet(self.htmlStyle)
|
self.docView.setStyleSheet(self.htmlStyle)
|
||||||
self.docView.setContent(self.htmlText, self.buildTime)
|
|
||||||
|
htmlSize = sum([len(x) for x in self.htmlText])
|
||||||
|
if htmlSize < nwConst.maxBuildSize:
|
||||||
|
qApp.processEvents()
|
||||||
|
self.docView.setContent(self.htmlText, self.buildTime)
|
||||||
|
else:
|
||||||
|
self.docView.setText(
|
||||||
|
"Failed to generate preview. The result is too big."
|
||||||
|
)
|
||||||
|
self._enableQtSave(False)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.htmlText = []
|
self.htmlText = []
|
||||||
self.htmlStyle = []
|
self.htmlStyle = []
|
||||||
self.nwdText = []
|
self.nwdText = []
|
||||||
self.buildTime = 0
|
self.buildTime = 0
|
||||||
|
return False
|
||||||
|
|
||||||
return
|
return True
|
||||||
|
|
||||||
##
|
##
|
||||||
# Slots
|
# Slots
|
||||||
@@ -554,6 +569,8 @@ class GuiBuildNovel(QDialog):
|
|||||||
self.htmlStyle = []
|
self.htmlStyle = []
|
||||||
self.nwdText = []
|
self.nwdText = []
|
||||||
|
|
||||||
|
htmlSize = 0
|
||||||
|
|
||||||
for nItt, tItem in enumerate(self.theProject.projTree):
|
for nItt, tItem in enumerate(self.theProject.projTree):
|
||||||
|
|
||||||
noteRoot = noteFiles
|
noteRoot = noteFiles
|
||||||
@@ -568,6 +585,7 @@ class GuiBuildNovel(QDialog):
|
|||||||
makeHtml.doConvert()
|
makeHtml.doConvert()
|
||||||
self.htmlText.append(makeHtml.getResult())
|
self.htmlText.append(makeHtml.getResult())
|
||||||
self.nwdText.append(makeHtml.getFilteredMarkdown())
|
self.nwdText.append(makeHtml.getFilteredMarkdown())
|
||||||
|
htmlSize += makeHtml.getResultSize()
|
||||||
|
|
||||||
elif self._checkInclude(tItem, noteFiles, novelFiles, ignoreFlag):
|
elif self._checkInclude(tItem, noteFiles, novelFiles, ignoreFlag):
|
||||||
makeHtml.setText(tItem.itemHandle)
|
makeHtml.setText(tItem.itemHandle)
|
||||||
@@ -578,6 +596,7 @@ class GuiBuildNovel(QDialog):
|
|||||||
makeHtml.doPostProcessing()
|
makeHtml.doPostProcessing()
|
||||||
self.htmlText.append(makeHtml.getResult())
|
self.htmlText.append(makeHtml.getResult())
|
||||||
self.nwdText.append(makeHtml.getFilteredMarkdown())
|
self.nwdText.append(makeHtml.getFilteredMarkdown())
|
||||||
|
htmlSize += makeHtml.getResultSize()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Failed to generate html of document '%s'" % tItem.itemHandle)
|
logger.error("Failed to generate html of document '%s'" % tItem.itemHandle)
|
||||||
@@ -591,6 +610,12 @@ class GuiBuildNovel(QDialog):
|
|||||||
# Update progress bar, also for skipped items
|
# Update progress bar, also for skipped items
|
||||||
self.buildProgress.setValue(nItt+1)
|
self.buildProgress.setValue(nItt+1)
|
||||||
|
|
||||||
|
if makeHtml.errData:
|
||||||
|
self.theParent.makeAlert((
|
||||||
|
"There were problems when building the project:"
|
||||||
|
"<br>- %s"
|
||||||
|
) % "<br>- ".join(makeHtml.errData), nwAlert.ERROR)
|
||||||
|
|
||||||
if replaceTabs:
|
if replaceTabs:
|
||||||
htmlText = []
|
htmlText = []
|
||||||
eightSpace = " "*8
|
eightSpace = " "*8
|
||||||
@@ -615,7 +640,15 @@ class GuiBuildNovel(QDialog):
|
|||||||
self.docView.clearStyleSheet()
|
self.docView.clearStyleSheet()
|
||||||
else:
|
else:
|
||||||
self.docView.setStyleSheet(self.htmlStyle)
|
self.docView.setStyleSheet(self.htmlStyle)
|
||||||
self.docView.setContent(self.htmlText, self.buildTime)
|
|
||||||
|
if htmlSize < nwConst.maxBuildSize:
|
||||||
|
self.docView.setContent(self.htmlText, self.buildTime)
|
||||||
|
self._enableQtSave(True)
|
||||||
|
else:
|
||||||
|
self.docView.setText(
|
||||||
|
"Failed to generate preview. The result is too big."
|
||||||
|
)
|
||||||
|
self._enableQtSave(False)
|
||||||
|
|
||||||
self._saveCache()
|
self._saveCache()
|
||||||
|
|
||||||
@@ -955,13 +988,24 @@ class GuiBuildNovel(QDialog):
|
|||||||
"""Capture the user closing the window so we can save settings.
|
"""Capture the user closing the window so we can save settings.
|
||||||
"""
|
"""
|
||||||
self._saveSettings()
|
self._saveSettings()
|
||||||
QDialog.closeEvent(self, theEvent)
|
self.docView.clear()
|
||||||
|
theEvent.accept()
|
||||||
return
|
return
|
||||||
|
|
||||||
##
|
##
|
||||||
# Internal Functions
|
# Internal Functions
|
||||||
##
|
##
|
||||||
|
|
||||||
|
def _enableQtSave(self, theState):
|
||||||
|
"""Set the enabled status of Save menu entries that depend on
|
||||||
|
the QTextDocument.
|
||||||
|
"""
|
||||||
|
self.saveODT.setEnabled(theState)
|
||||||
|
self.savePDF.setEnabled(theState)
|
||||||
|
self.saveMD.setEnabled(theState)
|
||||||
|
self.saveTXT.setEnabled(theState)
|
||||||
|
return
|
||||||
|
|
||||||
def _saveSettings(self):
|
def _saveSettings(self):
|
||||||
"""Save the various user settings.
|
"""Save the various user settings.
|
||||||
"""
|
"""
|
||||||
@@ -1063,6 +1107,12 @@ class GuiBuildNovelDocView(QTextBrowser):
|
|||||||
theFont.setPointSize(self.mainConf.textSize)
|
theFont.setPointSize(self.mainConf.textSize)
|
||||||
self.setFont(theFont)
|
self.setFont(theFont)
|
||||||
|
|
||||||
|
# Set the tab stops
|
||||||
|
if self.mainConf.verQtValue >= 51000:
|
||||||
|
self.setTabStopDistance(self.mainConf.getTabWidth())
|
||||||
|
else:
|
||||||
|
self.setTabStopWidth(self.mainConf.getTabWidth())
|
||||||
|
|
||||||
docPalette = self.palette()
|
docPalette = self.palette()
|
||||||
docPalette.setColor(QPalette.Base, QColor(255, 255, 255))
|
docPalette.setColor(QPalette.Base, QColor(255, 255, 255))
|
||||||
docPalette.setColor(QPalette.Text, QColor(0, 0, 0))
|
docPalette.setColor(QPalette.Text, QColor(0, 0, 0))
|
||||||
@@ -1126,17 +1176,13 @@ class GuiBuildNovelDocView(QTextBrowser):
|
|||||||
|
|
||||||
self.buildTime = timeStamp
|
self.buildTime = timeStamp
|
||||||
sPos = self.verticalScrollBar().value()
|
sPos = self.verticalScrollBar().value()
|
||||||
|
qApp.setOverrideCursor(QCursor(Qt.WaitCursor))
|
||||||
# Refresh the tab stops
|
|
||||||
if self.mainConf.verQtValue >= 51000:
|
|
||||||
self.setTabStopDistance(self.mainConf.getTabWidth())
|
|
||||||
else:
|
|
||||||
self.setTabStopWidth(self.mainConf.getTabWidth())
|
|
||||||
|
|
||||||
theText = theText.replace("\t", "!!tab!!")
|
theText = theText.replace("\t", "!!tab!!")
|
||||||
theText = theText.replace("<del>", "<span style='text-decoration: line-through;'>")
|
theText = theText.replace("<del>", "<span style='text-decoration: line-through;'>")
|
||||||
theText = theText.replace("</del>", "</span>")
|
theText = theText.replace("</del>", "</span>")
|
||||||
self.setHtml(theText)
|
self.setHtml(theText)
|
||||||
|
qApp.processEvents()
|
||||||
|
|
||||||
while self.find("!!tab!!"):
|
while self.find("!!tab!!"):
|
||||||
theCursor = self.textCursor()
|
theCursor = self.textCursor()
|
||||||
@@ -1148,6 +1194,7 @@ class GuiBuildNovelDocView(QTextBrowser):
|
|||||||
# Since we change the content while it may still be rendering, we mark
|
# Since we change the content while it may still be rendering, we mark
|
||||||
# the document dirty again to make sure it's re-rendered properly.
|
# the document dirty again to make sure it's re-rendered properly.
|
||||||
self.qDocument.markContentsDirty(0, self.qDocument.characterCount())
|
self.qDocument.markContentsDirty(0, self.qDocument.characterCount())
|
||||||
|
qApp.restoreOverrideCursor()
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
+136
-48
@@ -36,7 +36,7 @@ import logging
|
|||||||
from time import time
|
from time import time
|
||||||
|
|
||||||
from PyQt5.QtCore import (
|
from PyQt5.QtCore import (
|
||||||
Qt, QSize, QThread, QTimer, pyqtSlot, QRegExp, QRegularExpression
|
Qt, QSize, QThread, QTimer, pyqtSlot, QRegExp, QRegularExpression, QPointF
|
||||||
)
|
)
|
||||||
from PyQt5.QtGui import (
|
from PyQt5.QtGui import (
|
||||||
QTextCursor, QTextOption, QKeySequence, QFont, QColor, QPalette,
|
QTextCursor, QTextOption, QKeySequence, QFont, QColor, QPalette,
|
||||||
@@ -52,7 +52,7 @@ from nw.core import NWDoc, NWSpellCheck, NWSpellSimple, countWords
|
|||||||
from nw.gui.dochighlight import GuiDocHighlighter
|
from nw.gui.dochighlight import GuiDocHighlighter
|
||||||
from nw.common import transferCase
|
from nw.common import transferCase
|
||||||
from nw.constants import (
|
from nw.constants import (
|
||||||
nwAlert, nwUnicode, nwDocAction, nwDocInsert, nwItemClass
|
nwConst, nwAlert, nwUnicode, nwDocAction, nwDocInsert, nwItemClass
|
||||||
)
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -69,21 +69,23 @@ class GuiDocEditor(QTextEdit):
|
|||||||
self.theParent = theParent
|
self.theParent = theParent
|
||||||
self.theTheme = theParent.theTheme
|
self.theTheme = theParent.theTheme
|
||||||
self.theProject = theParent.theProject
|
self.theProject = theParent.theProject
|
||||||
self.docChanged = False
|
|
||||||
self.spellCheck = False
|
|
||||||
self.nwDocument = NWDoc(self.theProject, self.theParent)
|
self.nwDocument = NWDoc(self.theProject, self.theParent)
|
||||||
self.theHandle = None
|
|
||||||
self.theDict = None
|
self.docChanged = False # Flag for changed status of document
|
||||||
|
self.spellCheck = False # Flag for spell checking enabled
|
||||||
|
self.theHandle = None # The handle of the open file
|
||||||
|
self.theDict = None # The current spell check dictionary
|
||||||
|
self.nonWord = "\"'" # Characters to not include in spell checking
|
||||||
|
|
||||||
# Document Variables
|
# Document Variables
|
||||||
self.charCount = 0
|
self.charCount = 0 # Character count
|
||||||
self.wordCount = 0
|
self.wordCount = 0 # Word count
|
||||||
self.paraCount = 0
|
self.paraCount = 0 # Paragraph count
|
||||||
self.lastEdit = 0
|
self.lastEdit = 0 # Time stamp of last edit
|
||||||
self.lastFind = None
|
self.lastFind = None # Position of the last found search word
|
||||||
self.bigDoc = False
|
self.bigDoc = False # Flag for very large document size
|
||||||
self.doReplace = False
|
self.doReplace = False # Switch to temporarily disable auto-replace
|
||||||
self.nonWord = "\"'"
|
self.queuePos = None # Used for delayed change of cursor position
|
||||||
|
|
||||||
# Typography
|
# Typography
|
||||||
self.typDQOpen = self.mainConf.fmtDoubleQuotes[0]
|
self.typDQOpen = self.mainConf.fmtDoubleQuotes[0]
|
||||||
@@ -94,6 +96,7 @@ class GuiDocEditor(QTextEdit):
|
|||||||
# Core Elements and Signals
|
# Core Elements and Signals
|
||||||
self.qDocument = self.document()
|
self.qDocument = self.document()
|
||||||
self.qDocument.contentsChange.connect(self._docChange)
|
self.qDocument.contentsChange.connect(self._docChange)
|
||||||
|
self.qDocument.documentLayout().documentSizeChanged.connect(self._docSizeChanged)
|
||||||
|
|
||||||
# Document Title
|
# Document Title
|
||||||
self.docHeader = GuiDocEditHeader(self)
|
self.docHeader = GuiDocEditHeader(self)
|
||||||
@@ -161,8 +164,10 @@ class GuiDocEditor(QTextEdit):
|
|||||||
self.wordCount = 0
|
self.wordCount = 0
|
||||||
self.paraCount = 0
|
self.paraCount = 0
|
||||||
self.lastEdit = 0
|
self.lastEdit = 0
|
||||||
|
self.lastFind = None
|
||||||
self.bigDoc = False
|
self.bigDoc = False
|
||||||
self.doReplace = False
|
self.doReplace = False
|
||||||
|
self.queuePos = None
|
||||||
|
|
||||||
self.setDocumentChanged(False)
|
self.setDocumentChanged(False)
|
||||||
self.docHeader.setTitleFromHandle(self.theHandle)
|
self.docHeader.setTitleFromHandle(self.theHandle)
|
||||||
@@ -216,6 +221,12 @@ class GuiDocEditor(QTextEdit):
|
|||||||
|
|
||||||
self.qDocument.setDefaultTextOption(theOpt)
|
self.qDocument.setDefaultTextOption(theOpt)
|
||||||
|
|
||||||
|
# Refresh the tab stops
|
||||||
|
if self.mainConf.verQtValue >= 51000:
|
||||||
|
self.setTabStopDistance(self.mainConf.getTabWidth())
|
||||||
|
else:
|
||||||
|
self.setTabStopWidth(self.mainConf.getTabWidth())
|
||||||
|
|
||||||
# Initialise the syntax highlighter
|
# Initialise the syntax highlighter
|
||||||
self.hLight.initHighlighter()
|
self.hLight.initHighlighter()
|
||||||
|
|
||||||
@@ -223,21 +234,12 @@ class GuiDocEditor(QTextEdit):
|
|||||||
# font changed, otherwise we just clear the editor entirely,
|
# font changed, otherwise we just clear the editor entirely,
|
||||||
# which makes it read only.
|
# which makes it read only.
|
||||||
if self.theHandle is not None:
|
if self.theHandle is not None:
|
||||||
self.reloadText()
|
self.redrawText()
|
||||||
else:
|
else:
|
||||||
self.clearEditor()
|
self.clearEditor()
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def reloadText(self):
|
|
||||||
"""Reloads the document currently being edited.
|
|
||||||
"""
|
|
||||||
if self.theHandle is not None:
|
|
||||||
tHandle = self.theHandle
|
|
||||||
self.clearEditor()
|
|
||||||
self.loadText(tHandle, showStatus=False)
|
|
||||||
return
|
|
||||||
|
|
||||||
def loadText(self, tHandle, tLine=None, showStatus=True):
|
def loadText(self, tHandle, tLine=None, showStatus=True):
|
||||||
"""Load text from a document into the editor. If we have an io
|
"""Load text from a document into the editor. If we have an io
|
||||||
error, we must handle this and clear the editor so that we don't
|
error, we must handle this and clear the editor so that we don't
|
||||||
@@ -253,12 +255,22 @@ class GuiDocEditor(QTextEdit):
|
|||||||
self.clearEditor()
|
self.clearEditor()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
docSize = len(theDoc)
|
||||||
|
if docSize > nwConst.maxDocSize:
|
||||||
|
self.theParent.makeAlert((
|
||||||
|
"The document you are trying to open is too big. "
|
||||||
|
"The document size is %.2f\u202fMB. "
|
||||||
|
"The maximum size allowed is %.2f\u202fMB."
|
||||||
|
) % (docSize/1.0e6, nwConst.maxDocSize/1.0e6), nwAlert.ERROR)
|
||||||
|
self.clearEditor()
|
||||||
|
return False
|
||||||
|
|
||||||
qApp.setOverrideCursor(QCursor(Qt.WaitCursor))
|
qApp.setOverrideCursor(QCursor(Qt.WaitCursor))
|
||||||
self.hLight.setHandle(tHandle)
|
self.hLight.setHandle(tHandle)
|
||||||
|
|
||||||
# Check that the document is not too big for full, initial spell
|
# Check that the document is not too big for full, initial spell
|
||||||
# checking. If it is too big, we switch to only check as we type
|
# checking. If it is too big, we switch to only check as we type
|
||||||
self._checkDocSize(len(theDoc))
|
self._checkDocSize(docSize)
|
||||||
spTemp = self.hLight.spellCheck
|
spTemp = self.hLight.spellCheck
|
||||||
if self.bigDoc:
|
if self.bigDoc:
|
||||||
self.hLight.spellCheck = False
|
self.hLight.spellCheck = False
|
||||||
@@ -266,16 +278,12 @@ class GuiDocEditor(QTextEdit):
|
|||||||
bfTime = time()
|
bfTime = time()
|
||||||
self._allowAutoReplace(False)
|
self._allowAutoReplace(False)
|
||||||
self.setPlainText(theDoc)
|
self.setPlainText(theDoc)
|
||||||
|
qApp.processEvents()
|
||||||
|
|
||||||
self._allowAutoReplace(True)
|
self._allowAutoReplace(True)
|
||||||
afTime = time()
|
afTime = time()
|
||||||
logger.debug("Document highlighted in %.3f milliseconds" % (1000*(afTime-bfTime)))
|
logger.debug("Document highlighted in %.3f milliseconds" % (1000*(afTime-bfTime)))
|
||||||
|
|
||||||
theItem = self.nwDocument.getCurrentItem()
|
|
||||||
if tLine is None and theItem is not None:
|
|
||||||
self.setCursorPosition(theItem.cursorPos)
|
|
||||||
else:
|
|
||||||
self.setCursorLine(tLine)
|
|
||||||
|
|
||||||
self.lastEdit = time()
|
self.lastEdit = time()
|
||||||
self._runCounter()
|
self._runCounter()
|
||||||
self.wcTimer.start()
|
self.wcTimer.start()
|
||||||
@@ -287,24 +295,54 @@ class GuiDocEditor(QTextEdit):
|
|||||||
self.docFooter.setHandle(self.theHandle)
|
self.docFooter.setHandle(self.theHandle)
|
||||||
self.updateDocMargins()
|
self.updateDocMargins()
|
||||||
self.hLight.spellCheck = spTemp
|
self.hLight.spellCheck = spTemp
|
||||||
|
|
||||||
|
theItem = self.nwDocument.getCurrentItem()
|
||||||
|
if tLine is None and theItem is not None:
|
||||||
|
# For large documents we queue the repositioning until the
|
||||||
|
# document layout has grown past the point we want to move
|
||||||
|
# the cursor to. This makes the loading significantly
|
||||||
|
# faster.
|
||||||
|
if docSize > 50000:
|
||||||
|
self.queuePos = theItem.cursorPos
|
||||||
|
else:
|
||||||
|
self.setCursorPosition(theItem.cursorPos)
|
||||||
|
else:
|
||||||
|
self.setCursorLine(tLine)
|
||||||
|
|
||||||
qApp.restoreOverrideCursor()
|
qApp.restoreOverrideCursor()
|
||||||
|
|
||||||
# Refresh the tab stops
|
|
||||||
if self.mainConf.verQtValue >= 51000:
|
|
||||||
self.setTabStopDistance(self.mainConf.getTabWidth())
|
|
||||||
else:
|
|
||||||
self.setTabStopWidth(self.mainConf.getTabWidth())
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def updateTagHighLighting(self, forceBigDoc=False):
|
||||||
|
"""Rerun the syntax highlighter on all meta data lines.
|
||||||
|
"""
|
||||||
|
self.hLight.rehighlightByType(GuiDocHighlighter.BLOCK_META)
|
||||||
|
return
|
||||||
|
|
||||||
|
def redrawText(self):
|
||||||
|
"""Redraw the text by marking the document content as "dirty".
|
||||||
|
"""
|
||||||
|
self.qDocument.markContentsDirty(0, self.qDocument.characterCount())
|
||||||
|
return
|
||||||
|
|
||||||
def replaceText(self, theText):
|
def replaceText(self, theText):
|
||||||
"""Replaces the text of the current document with the provided
|
"""Replaces the text of the current document with the provided
|
||||||
text. This also clears undo history.
|
text. This also clears undo history.
|
||||||
"""
|
"""
|
||||||
|
docSize = len(theText)
|
||||||
|
if docSize > nwConst.maxDocSize:
|
||||||
|
self.theParent.makeAlert((
|
||||||
|
"The text you are trying to add is too big. "
|
||||||
|
"The text size is %.2f\u202fMB. "
|
||||||
|
"The maximum size allowed is %.2f\u202fMB."
|
||||||
|
) % (docSize/1.0e6, nwConst.maxDocSize/1.0e6), nwAlert.ERROR)
|
||||||
|
return False
|
||||||
|
|
||||||
self.setPlainText(theText)
|
self.setPlainText(theText)
|
||||||
self.setDocumentChanged(True)
|
self.setDocumentChanged(True)
|
||||||
self.updateDocMargins()
|
self.updateDocMargins()
|
||||||
return
|
|
||||||
|
return True
|
||||||
|
|
||||||
def saveText(self):
|
def saveText(self):
|
||||||
"""Save the text currently in the editor to the NWDoc object,
|
"""Save the text currently in the editor to the NWDoc object,
|
||||||
@@ -315,11 +353,10 @@ class GuiDocEditor(QTextEdit):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
docText = self.getText()
|
docText = self.getText()
|
||||||
cursPos = self.getCursorPosition()
|
|
||||||
theItem.setCharCount(self.charCount)
|
theItem.setCharCount(self.charCount)
|
||||||
theItem.setWordCount(self.wordCount)
|
theItem.setWordCount(self.wordCount)
|
||||||
theItem.setParaCount(self.paraCount)
|
theItem.setParaCount(self.paraCount)
|
||||||
theItem.setCursorPos(cursPos)
|
self.saveCursorPosition()
|
||||||
self.nwDocument.saveDocument(docText)
|
self.nwDocument.saveDocument(docText)
|
||||||
self.setDocumentChanged(False)
|
self.setDocumentChanged(False)
|
||||||
|
|
||||||
@@ -428,6 +465,15 @@ class GuiDocEditor(QTextEdit):
|
|||||||
"""
|
"""
|
||||||
return self.textCursor().selectionEnd()
|
return self.textCursor().selectionEnd()
|
||||||
|
|
||||||
|
def saveCursorPosition(self):
|
||||||
|
"""Save the cursor position to the current project item object.
|
||||||
|
"""
|
||||||
|
theItem = self.nwDocument.getCurrentItem()
|
||||||
|
if theItem is not None:
|
||||||
|
cursPos = self.getCursorPosition()
|
||||||
|
theItem.setCursorPos(cursPos)
|
||||||
|
return
|
||||||
|
|
||||||
def setCursorLine(self, theLine):
|
def setCursorLine(self, theLine):
|
||||||
"""Move the cursor to a given line in the document.
|
"""Move the cursor to a given line in the document.
|
||||||
"""
|
"""
|
||||||
@@ -727,6 +773,13 @@ class GuiDocEditor(QTextEdit):
|
|||||||
"""
|
"""
|
||||||
self.lastEdit = time()
|
self.lastEdit = time()
|
||||||
self.lastFind = None
|
self.lastFind = None
|
||||||
|
if self.qDocument.characterCount() > nwConst.maxDocSize:
|
||||||
|
self.theParent.makeAlert((
|
||||||
|
"The document has grown too big and you cannot add more text to it. "
|
||||||
|
"The maximum size of a single novelWriter document is %.2f\u202fMB."
|
||||||
|
) % (nwConst.maxDocSize/1.0e6), nwAlert.ERROR)
|
||||||
|
self.undo()
|
||||||
|
return
|
||||||
if not self.docChanged:
|
if not self.docChanged:
|
||||||
self.setDocumentChanged(True)
|
self.setDocumentChanged(True)
|
||||||
if not self.wcTimer.isActive():
|
if not self.wcTimer.isActive():
|
||||||
@@ -895,6 +948,29 @@ class GuiDocEditor(QTextEdit):
|
|||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@pyqtSlot("QSizeF")
|
||||||
|
def _docSizeChanged(self, theSize):
|
||||||
|
"""Called whenever the underlying document layout size changes.
|
||||||
|
This is used to queue the repositioning of the cursor for very
|
||||||
|
large documents to ensure the region where the cursor is being
|
||||||
|
moved to has been drawn before the move is made.
|
||||||
|
"""
|
||||||
|
if self.queuePos is not None:
|
||||||
|
thePos = self.qDocument.documentLayout().hitTest(
|
||||||
|
QPointF(theSize.width(), theSize.height()), Qt.FuzzyHit
|
||||||
|
)
|
||||||
|
if self.queuePos <= thePos:
|
||||||
|
logger.verbose(
|
||||||
|
"Allowed cursor move to %d <= %d" % (self.queuePos, thePos)
|
||||||
|
)
|
||||||
|
self.setCursorPosition(self.queuePos)
|
||||||
|
self.queuePos = None
|
||||||
|
else:
|
||||||
|
logger.verbose(
|
||||||
|
"Denied cursor move to %d > %d" % (self.queuePos, thePos)
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
##
|
##
|
||||||
# Internal Functions
|
# Internal Functions
|
||||||
##
|
##
|
||||||
@@ -1050,15 +1126,24 @@ class GuiDocEditor(QTextEdit):
|
|||||||
"""Check if document size crosses the big document limit set in
|
"""Check if document size crosses the big document limit set in
|
||||||
config. If so, we will set the big document flag to True.
|
config. If so, we will set the big document flag to True.
|
||||||
"""
|
"""
|
||||||
if theSize > self.mainConf.bigDocLimit*1000:
|
newState = theSize > self.mainConf.bigDocLimit*1000
|
||||||
logger.info(
|
|
||||||
"The document size is %d > %d, big doc mode is enabled" % (
|
if newState != self.bigDoc:
|
||||||
theSize, self.mainConf.bigDocLimit*1000
|
if newState:
|
||||||
|
logger.info(
|
||||||
|
"The document size is {:n} > {:n}, big doc mode has been enabled".format(
|
||||||
|
theSize, self.mainConf.bigDocLimit*1000
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
else:
|
||||||
self.bigDoc = True
|
logger.info(
|
||||||
else:
|
"The document size is {:n} <= {:n}, big doc mode has been disabled".format(
|
||||||
self.bigDoc = False
|
theSize, self.mainConf.bigDocLimit*1000
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.bigDoc = newState
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def _wrapSelection(self, tBefore, tAfter=None):
|
def _wrapSelection(self, tBefore, tAfter=None):
|
||||||
@@ -2178,6 +2263,9 @@ class GuiDocEditFooter(QWidget):
|
|||||||
|
|
||||||
self.wordsText.setText("Words: {:n} ({:+n})".format(wCount, wDiff))
|
self.wordsText.setText("Words: {:n} ({:+n})".format(wCount, wDiff))
|
||||||
|
|
||||||
|
byteSize = self.docEditor.qDocument.characterCount()
|
||||||
|
self.wordsText.setToolTip("Document size is {:n} bytes".format(byteSize))
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# END Class GuiDocEditFooter
|
# END Class GuiDocEditFooter
|
||||||
|
|||||||
@@ -39,6 +39,11 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
class GuiDocHighlighter(QSyntaxHighlighter):
|
class GuiDocHighlighter(QSyntaxHighlighter):
|
||||||
|
|
||||||
|
BLOCK_NONE = 0
|
||||||
|
BLOCK_TEXT = 1
|
||||||
|
BLOCK_META = 2
|
||||||
|
BLOCK_TITLE = 4
|
||||||
|
|
||||||
def __init__(self, theDoc, theParent):
|
def __init__(self, theDoc, theParent):
|
||||||
QSyntaxHighlighter.__init__(self, theDoc)
|
QSyntaxHighlighter.__init__(self, theDoc)
|
||||||
|
|
||||||
@@ -229,6 +234,22 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|||||||
self.theHandle = theHandle
|
self.theHandle = theHandle
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
##
|
||||||
|
# Methods
|
||||||
|
##
|
||||||
|
|
||||||
|
def rehighlightByType(self, theType):
|
||||||
|
"""Loop through all blocks and rehighlight those of a given
|
||||||
|
content type.
|
||||||
|
"""
|
||||||
|
qDocument = self.document()
|
||||||
|
nBlocks = qDocument.blockCount()
|
||||||
|
for i in range(nBlocks):
|
||||||
|
theBlock = qDocument.findBlockByNumber(i)
|
||||||
|
if theBlock.userState() & theType == theType:
|
||||||
|
self.rehighlightBlock(theBlock)
|
||||||
|
return
|
||||||
|
|
||||||
##
|
##
|
||||||
# Highlight Block
|
# Highlight Block
|
||||||
##
|
##
|
||||||
@@ -239,10 +260,12 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|||||||
is significantly faster than running the regex checks used for
|
is significantly faster than running the regex checks used for
|
||||||
text paragraphs.
|
text paragraphs.
|
||||||
"""
|
"""
|
||||||
|
self.setCurrentBlockState(self.BLOCK_NONE)
|
||||||
if self.theHandle is None or not theText:
|
if self.theHandle is None or not theText:
|
||||||
return
|
return
|
||||||
|
|
||||||
if theText.startswith("@"): # Keywords and commands
|
if theText.startswith("@"): # Keywords and commands
|
||||||
|
self.setCurrentBlockState(self.BLOCK_META)
|
||||||
tItem = self.theParent.theProject.projTree[self.theHandle]
|
tItem = self.theParent.theProject.projTree[self.theHandle]
|
||||||
isValid, theBits, thePos = self.theIndex.scanThis(theText)
|
isValid, theBits, thePos = self.theIndex.scanThis(theText)
|
||||||
isGood = self.theIndex.checkThese(theBits, tItem)
|
isGood = self.theIndex.checkThese(theBits, tItem)
|
||||||
@@ -266,22 +289,27 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|||||||
return
|
return
|
||||||
|
|
||||||
elif theText.startswith("# "): # Header 1
|
elif theText.startswith("# "): # Header 1
|
||||||
|
self.setCurrentBlockState(self.BLOCK_TITLE)
|
||||||
self.setFormat(0, 1, self.hStyles["header1h"])
|
self.setFormat(0, 1, self.hStyles["header1h"])
|
||||||
self.setFormat(1, len(theText), self.hStyles["header1"])
|
self.setFormat(1, len(theText), self.hStyles["header1"])
|
||||||
|
|
||||||
elif theText.startswith("## "): # Header 2
|
elif theText.startswith("## "): # Header 2
|
||||||
|
self.setCurrentBlockState(self.BLOCK_TITLE)
|
||||||
self.setFormat(0, 2, self.hStyles["header2h"])
|
self.setFormat(0, 2, self.hStyles["header2h"])
|
||||||
self.setFormat(2, len(theText), self.hStyles["header2"])
|
self.setFormat(2, len(theText), self.hStyles["header2"])
|
||||||
|
|
||||||
elif theText.startswith("### "): # Header 3
|
elif theText.startswith("### "): # Header 3
|
||||||
|
self.setCurrentBlockState(self.BLOCK_TITLE)
|
||||||
self.setFormat(0, 3, self.hStyles["header3h"])
|
self.setFormat(0, 3, self.hStyles["header3h"])
|
||||||
self.setFormat(3, len(theText), self.hStyles["header3"])
|
self.setFormat(3, len(theText), self.hStyles["header3"])
|
||||||
|
|
||||||
elif theText.startswith("#### "): # Header 4
|
elif theText.startswith("#### "): # Header 4
|
||||||
|
self.setCurrentBlockState(self.BLOCK_TITLE)
|
||||||
self.setFormat(0, 4, self.hStyles["header4h"])
|
self.setFormat(0, 4, self.hStyles["header4h"])
|
||||||
self.setFormat(4, len(theText), self.hStyles["header4"])
|
self.setFormat(4, len(theText), self.hStyles["header4"])
|
||||||
|
|
||||||
elif theText.startswith("%"): # Comments
|
elif theText.startswith("%"): # Comments
|
||||||
|
self.setCurrentBlockState(self.BLOCK_TEXT)
|
||||||
toCheck = theText[1:].lstrip()
|
toCheck = theText[1:].lstrip()
|
||||||
synTag = toCheck[:9].lower()
|
synTag = toCheck[:9].lower()
|
||||||
tLen = len(theText)
|
tLen = len(theText)
|
||||||
@@ -294,6 +322,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|||||||
self.setFormat(0, tLen, self.hStyles["hidden"])
|
self.setFormat(0, tLen, self.hStyles["hidden"])
|
||||||
|
|
||||||
else: # Text Paragraph
|
else: # Text Paragraph
|
||||||
|
self.setCurrentBlockState(self.BLOCK_TEXT)
|
||||||
for rX, xFmt in self.rxRules:
|
for rX, xFmt in self.rxRules:
|
||||||
rxItt = rX.globalMatch(theText, 0)
|
rxItt = rX.globalMatch(theText, 0)
|
||||||
while rxItt.hasNext():
|
while rxItt.hasNext():
|
||||||
|
|||||||
+18
-6
@@ -125,7 +125,7 @@ class GuiProjectLoad(QDialog):
|
|||||||
|
|
||||||
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Open | QDialogButtonBox.Cancel)
|
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Open | QDialogButtonBox.Cancel)
|
||||||
self.buttonBox.accepted.connect(self._doOpenRecent)
|
self.buttonBox.accepted.connect(self._doOpenRecent)
|
||||||
self.buttonBox.rejected.connect(self._doClose)
|
self.buttonBox.rejected.connect(self._doCancel)
|
||||||
|
|
||||||
self.newButton = self.buttonBox.addButton("New", QDialogButtonBox.ActionRole)
|
self.newButton = self.buttonBox.addButton("New", QDialogButtonBox.ActionRole)
|
||||||
self.newButton.clicked.connect(self._doNewProject)
|
self.newButton.clicked.connect(self._doNewProject)
|
||||||
@@ -153,7 +153,7 @@ class GuiProjectLoad(QDialog):
|
|||||||
"""Close the dialog window with a recent project selected.
|
"""Close the dialog window with a recent project selected.
|
||||||
"""
|
"""
|
||||||
logger.verbose("GuiProjectLoad open button clicked")
|
logger.verbose("GuiProjectLoad open button clicked")
|
||||||
self._saveDialogState()
|
self._saveSettings()
|
||||||
|
|
||||||
selItems = self.listBox.selectedItems()
|
selItems = self.listBox.selectedItems()
|
||||||
if selItems:
|
if selItems:
|
||||||
@@ -194,11 +194,12 @@ class GuiProjectLoad(QDialog):
|
|||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def _doClose(self):
|
def _doCancel(self):
|
||||||
"""Close the dialog window without doing anything.
|
"""Close the dialog window without doing anything.
|
||||||
"""
|
"""
|
||||||
logger.verbose("GuiProjectLoad close button clicked")
|
logger.verbose("GuiProjectLoad close button clicked")
|
||||||
self._saveDialogState()
|
self.openPath = None
|
||||||
|
self.openState = self.NONE_STATE
|
||||||
self.close()
|
self.close()
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -206,7 +207,7 @@ class GuiProjectLoad(QDialog):
|
|||||||
"""Create a new project.
|
"""Create a new project.
|
||||||
"""
|
"""
|
||||||
logger.verbose("GuiProjectLoad new project button clicked")
|
logger.verbose("GuiProjectLoad new project button clicked")
|
||||||
self._saveDialogState()
|
self._saveSettings()
|
||||||
self.openPath = None
|
self.openPath = None
|
||||||
self.openState = self.NEW_STATE
|
self.openState = self.NEW_STATE
|
||||||
self.accept()
|
self.accept()
|
||||||
@@ -230,11 +231,22 @@ class GuiProjectLoad(QDialog):
|
|||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
##
|
||||||
|
# Events
|
||||||
|
##
|
||||||
|
|
||||||
|
def closeEvent(self, theEvent):
|
||||||
|
"""Capture the user closing the dialog so we can save settings.
|
||||||
|
"""
|
||||||
|
self._saveSettings()
|
||||||
|
theEvent.accept()
|
||||||
|
return
|
||||||
|
|
||||||
##
|
##
|
||||||
# Internal Functions
|
# Internal Functions
|
||||||
##
|
##
|
||||||
|
|
||||||
def _saveDialogState(self):
|
def _saveSettings(self):
|
||||||
"""Save the changes made to the dialog.
|
"""Save the changes made to the dialog.
|
||||||
"""
|
"""
|
||||||
colWidths = [0, 0, 0]
|
colWidths = [0, 0, 0]
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ import os
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt
|
from PyQt5.QtCore import Qt
|
||||||
from PyQt5.QtGui import QPixmap
|
from PyQt5.QtGui import QPixmap, QCursor
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
qApp, QDialog, QTreeWidget, QTreeWidgetItem, QDialogButtonBox, QGridLayout,
|
qApp, QDialog, QTreeWidget, QTreeWidgetItem, QDialogButtonBox, QGridLayout,
|
||||||
QLabel, QGroupBox, QMenu, QAction, QFileDialog, QSpinBox, QHBoxLayout
|
QLabel, QGroupBox, QMenu, QAction, QFileDialog, QSpinBox, QHBoxLayout
|
||||||
@@ -253,10 +253,15 @@ class GuiWritingStats(QDialog):
|
|||||||
|
|
||||||
logger.debug("GuiWritingStats initialisation complete")
|
logger.debug("GuiWritingStats initialisation complete")
|
||||||
|
|
||||||
qApp.processEvents()
|
return
|
||||||
|
|
||||||
|
def populateGUI(self):
|
||||||
|
"""Populate list box with data from the log file.
|
||||||
|
"""
|
||||||
|
qApp.setOverrideCursor(QCursor(Qt.WaitCursor))
|
||||||
self._loadLogFile()
|
self._loadLogFile()
|
||||||
self._updateListBox()
|
self._updateListBox()
|
||||||
|
qApp.restoreOverrideCursor()
|
||||||
return
|
return
|
||||||
|
|
||||||
##
|
##
|
||||||
|
|||||||
+17
-4
@@ -48,6 +48,7 @@ from nw.gui import (
|
|||||||
)
|
)
|
||||||
from nw.core import NWProject, NWDoc, NWIndex
|
from nw.core import NWProject, NWDoc, NWIndex
|
||||||
from nw.constants import nwItemType, nwItemClass, nwAlert
|
from nw.constants import nwItemType, nwItemClass, nwAlert
|
||||||
|
from nw.common import getGuiItem
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -448,6 +449,7 @@ class GuiMain(QMainWindow):
|
|||||||
"""Close the document and clear the editor and title field.
|
"""Close the document and clear the editor and title field.
|
||||||
"""
|
"""
|
||||||
if self.hasProject:
|
if self.hasProject:
|
||||||
|
self.docEditor.saveCursorPosition()
|
||||||
if self.docEditor.docChanged:
|
if self.docEditor.docChanged:
|
||||||
self.saveDocument()
|
self.saveDocument()
|
||||||
self.docEditor.clearEditor()
|
self.docEditor.clearEditor()
|
||||||
@@ -716,8 +718,7 @@ class GuiMain(QMainWindow):
|
|||||||
|
|
||||||
tEnd = time()
|
tEnd = time()
|
||||||
self.statusBar.setStatus("Indexing completed in %.1f ms" % ((tEnd - tStart)*1000.0))
|
self.statusBar.setStatus("Indexing completed in %.1f ms" % ((tEnd - tStart)*1000.0))
|
||||||
self.docEditor.reloadText()
|
self.docEditor.updateTagHighLighting()
|
||||||
|
|
||||||
qApp.restoreOverrideCursor()
|
qApp.restoreOverrideCursor()
|
||||||
|
|
||||||
if not beQuiet:
|
if not beQuiet:
|
||||||
@@ -816,9 +817,15 @@ class GuiMain(QMainWindow):
|
|||||||
logger.error("No project open")
|
logger.error("No project open")
|
||||||
return
|
return
|
||||||
|
|
||||||
dlgBuild = GuiBuildNovel(self, self.theProject)
|
dlgBuild = getGuiItem("GuiBuildNovel")
|
||||||
|
if dlgBuild is None:
|
||||||
|
dlgBuild = GuiBuildNovel(self, self.theProject)
|
||||||
|
|
||||||
dlgBuild.setModal(False)
|
dlgBuild.setModal(False)
|
||||||
dlgBuild.show()
|
dlgBuild.show()
|
||||||
|
qApp.processEvents()
|
||||||
|
dlgBuild.viewCachedDoc()
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def showWritingStatsDialog(self):
|
def showWritingStatsDialog(self):
|
||||||
@@ -828,9 +835,15 @@ class GuiMain(QMainWindow):
|
|||||||
logger.error("No project open")
|
logger.error("No project open")
|
||||||
return
|
return
|
||||||
|
|
||||||
dlgStats = GuiWritingStats(self, self.theProject)
|
dlgStats = getGuiItem("GuiWritingStats")
|
||||||
|
if dlgStats is None:
|
||||||
|
dlgStats = GuiWritingStats(self, self.theProject)
|
||||||
|
|
||||||
dlgStats.setModal(False)
|
dlgStats.setModal(False)
|
||||||
dlgStats.show()
|
dlgStats.show()
|
||||||
|
qApp.processEvents()
|
||||||
|
dlgStats.populateGUI()
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def showAboutNWDialog(self):
|
def showAboutNWDialog(self):
|
||||||
|
|||||||
+38
-5
@@ -114,7 +114,12 @@ def testProjectSettings(qtbot, monkeypatch, yesToAll, nwFuncTemp, nwTempGUI, nwR
|
|||||||
projEdit._doSave()
|
projEdit._doSave()
|
||||||
|
|
||||||
# Open again, and check project settings
|
# Open again, and check project settings
|
||||||
projEdit = GuiProjectSettings(nwGUI, nwGUI.theProject)
|
nwGUI.mainMenu.aProjectSettings.activate(QAction.Trigger)
|
||||||
|
qtbot.waitUntil(lambda: getGuiItem("GuiProjectSettings") is not None, timeout=1000)
|
||||||
|
|
||||||
|
projEdit = getGuiItem("GuiProjectSettings")
|
||||||
|
assert isinstance(projEdit, GuiProjectSettings)
|
||||||
|
|
||||||
qtbot.addWidget(projEdit)
|
qtbot.addWidget(projEdit)
|
||||||
assert projEdit.tabMain.editName.text() == "Project Name"
|
assert projEdit.tabMain.editName.text() == "Project Name"
|
||||||
assert projEdit.tabMain.editTitle.text() == "Project Title"
|
assert projEdit.tabMain.editTitle.text() == "Project Title"
|
||||||
@@ -582,8 +587,13 @@ def testBuildTool(qtbot, yesToAll, nwTempBuild, nwLipsum, nwRef, nwTemp):
|
|||||||
nwBuild._doClose()
|
nwBuild._doClose()
|
||||||
|
|
||||||
# Re-open build dialog from cahce
|
# Re-open build dialog from cahce
|
||||||
nwBuild = GuiBuildNovel(nwGUI, nwGUI.theProject)
|
nwGUI.mainMenu.aBuildProject.activate(QAction.Trigger)
|
||||||
|
qtbot.waitUntil(lambda: getGuiItem("GuiBuildNovel") is not None, timeout=1000)
|
||||||
|
|
||||||
|
nwBuild = getGuiItem("GuiBuildNovel")
|
||||||
|
assert isinstance(nwBuild, GuiBuildNovel)
|
||||||
|
|
||||||
|
assert nwBuild.viewCachedDoc()
|
||||||
assert nwBuild.htmlText == htmlText
|
assert nwBuild.htmlText == htmlText
|
||||||
assert nwBuild.htmlStyle == htmlStyle
|
assert nwBuild.htmlStyle == htmlStyle
|
||||||
assert nwBuild.nwdText == nwdText
|
assert nwBuild.nwdText == nwdText
|
||||||
@@ -659,7 +669,11 @@ def testMergeSplitTools(qtbot, monkeypatch, yesToAll, nwTempGUI, nwLipsum, nwRef
|
|||||||
# Split By Scene
|
# Split By Scene
|
||||||
assert nwGUI.treeView.setSelectedHandle("73475cb40a568")
|
assert nwGUI.treeView.setSelectedHandle("73475cb40a568")
|
||||||
qtbot.wait(stepDelay)
|
qtbot.wait(stepDelay)
|
||||||
nwSplit = GuiDocSplit(nwGUI, nwGUI.theProject)
|
nwGUI.mainMenu.aSplitDoc.activate(QAction.Trigger)
|
||||||
|
qtbot.waitUntil(lambda: getGuiItem("GuiDocSplit") is not None, timeout=1000)
|
||||||
|
|
||||||
|
nwSplit = getGuiItem("GuiDocSplit")
|
||||||
|
assert isinstance(nwSplit, GuiDocSplit)
|
||||||
qtbot.wait(stepDelay)
|
qtbot.wait(stepDelay)
|
||||||
nwSplit.splitLevel.setCurrentIndex(2)
|
nwSplit.splitLevel.setCurrentIndex(2)
|
||||||
qtbot.wait(stepDelay)
|
qtbot.wait(stepDelay)
|
||||||
@@ -691,7 +705,11 @@ def testMergeSplitTools(qtbot, monkeypatch, yesToAll, nwTempGUI, nwLipsum, nwRef
|
|||||||
# Split By Section
|
# Split By Section
|
||||||
assert nwGUI.treeView.setSelectedHandle("73475cb40a568")
|
assert nwGUI.treeView.setSelectedHandle("73475cb40a568")
|
||||||
qtbot.wait(stepDelay)
|
qtbot.wait(stepDelay)
|
||||||
nwSplit = GuiDocSplit(nwGUI, nwGUI.theProject)
|
nwGUI.mainMenu.aSplitDoc.activate(QAction.Trigger)
|
||||||
|
qtbot.waitUntil(lambda: getGuiItem("GuiDocSplit") is not None, timeout=1000)
|
||||||
|
|
||||||
|
nwSplit = getGuiItem("GuiDocSplit")
|
||||||
|
assert isinstance(nwSplit, GuiDocSplit)
|
||||||
qtbot.wait(stepDelay)
|
qtbot.wait(stepDelay)
|
||||||
nwSplit.splitLevel.setCurrentIndex(3)
|
nwSplit.splitLevel.setCurrentIndex(3)
|
||||||
qtbot.wait(stepDelay)
|
qtbot.wait(stepDelay)
|
||||||
@@ -945,6 +963,7 @@ def testLoadProject(qtbot, monkeypatch, yesToAll, nwMinimal, nwTemp):
|
|||||||
assert nwGUI.openProject(nwMinimal)
|
assert nwGUI.openProject(nwMinimal)
|
||||||
assert nwGUI.closeProject()
|
assert nwGUI.closeProject()
|
||||||
|
|
||||||
|
qtbot.wait(stepDelay)
|
||||||
monkeypatch.setattr(GuiProjectLoad, "exec_", lambda *args: None)
|
monkeypatch.setattr(GuiProjectLoad, "exec_", lambda *args: None)
|
||||||
monkeypatch.setattr(GuiProjectLoad, "result", lambda *args: QDialog.Accepted)
|
monkeypatch.setattr(GuiProjectLoad, "result", lambda *args: QDialog.Accepted)
|
||||||
nwGUI.mainMenu.aOpenProject.activate(QAction.Trigger)
|
nwGUI.mainMenu.aOpenProject.activate(QAction.Trigger)
|
||||||
@@ -954,36 +973,50 @@ def testLoadProject(qtbot, monkeypatch, yesToAll, nwMinimal, nwTemp):
|
|||||||
assert isinstance(nwLoad, GuiProjectLoad)
|
assert isinstance(nwLoad, GuiProjectLoad)
|
||||||
nwLoad.show()
|
nwLoad.show()
|
||||||
|
|
||||||
|
qtbot.wait(stepDelay)
|
||||||
recentCount = nwLoad.listBox.topLevelItemCount()
|
recentCount = nwLoad.listBox.topLevelItemCount()
|
||||||
assert recentCount > 0
|
assert recentCount > 0
|
||||||
|
|
||||||
|
qtbot.wait(stepDelay)
|
||||||
selItem = nwLoad.listBox.topLevelItem(0)
|
selItem = nwLoad.listBox.topLevelItem(0)
|
||||||
selPath = selItem.data(nwLoad.C_NAME, Qt.UserRole)
|
selPath = selItem.data(nwLoad.C_NAME, Qt.UserRole)
|
||||||
assert isinstance(selItem, QTreeWidgetItem)
|
assert isinstance(selItem, QTreeWidgetItem)
|
||||||
|
|
||||||
|
qtbot.wait(stepDelay)
|
||||||
nwLoad.selPath.setText("")
|
nwLoad.selPath.setText("")
|
||||||
nwLoad.listBox.setCurrentItem(selItem)
|
nwLoad.listBox.setCurrentItem(selItem)
|
||||||
nwLoad._doSelectRecent()
|
nwLoad._doSelectRecent()
|
||||||
assert nwLoad.selPath.text() == selPath
|
assert nwLoad.selPath.text() == selPath
|
||||||
|
|
||||||
|
qtbot.wait(stepDelay)
|
||||||
qtbot.mouseClick(nwLoad.buttonBox.button(QDialogButtonBox.Open), Qt.LeftButton)
|
qtbot.mouseClick(nwLoad.buttonBox.button(QDialogButtonBox.Open), Qt.LeftButton)
|
||||||
assert nwLoad.openPath == selPath
|
assert nwLoad.openPath == selPath
|
||||||
assert nwLoad.openState == nwLoad.OPEN_STATE
|
assert nwLoad.openState == nwLoad.OPEN_STATE
|
||||||
|
|
||||||
# Just create a new project load from scratch for the rest of the test
|
# Just create a new project load from scratch for the rest of the test
|
||||||
del nwLoad
|
del nwLoad
|
||||||
nwLoad = GuiProjectLoad(nwGUI)
|
|
||||||
|
qtbot.wait(stepDelay)
|
||||||
|
nwGUI.mainMenu.aOpenProject.activate(QAction.Trigger)
|
||||||
|
qtbot.waitUntil(lambda: getGuiItem("GuiProjectLoad") is not None, timeout=1000)
|
||||||
|
|
||||||
|
qtbot.wait(stepDelay)
|
||||||
|
nwLoad = getGuiItem("GuiProjectLoad")
|
||||||
|
assert isinstance(nwLoad, GuiProjectLoad)
|
||||||
nwLoad.show()
|
nwLoad.show()
|
||||||
|
|
||||||
|
qtbot.wait(stepDelay)
|
||||||
qtbot.mouseClick(nwLoad.buttonBox.button(QDialogButtonBox.Cancel), Qt.LeftButton)
|
qtbot.mouseClick(nwLoad.buttonBox.button(QDialogButtonBox.Cancel), Qt.LeftButton)
|
||||||
assert nwLoad.openPath is None
|
assert nwLoad.openPath is None
|
||||||
assert nwLoad.openState == nwLoad.NONE_STATE
|
assert nwLoad.openState == nwLoad.NONE_STATE
|
||||||
|
|
||||||
|
qtbot.wait(stepDelay)
|
||||||
nwLoad.show()
|
nwLoad.show()
|
||||||
qtbot.mouseClick(nwLoad.newButton, Qt.LeftButton)
|
qtbot.mouseClick(nwLoad.newButton, Qt.LeftButton)
|
||||||
assert nwLoad.openPath is None
|
assert nwLoad.openPath is None
|
||||||
assert nwLoad.openState == nwLoad.NEW_STATE
|
assert nwLoad.openState == nwLoad.NEW_STATE
|
||||||
|
|
||||||
|
qtbot.wait(stepDelay)
|
||||||
nwLoad.show()
|
nwLoad.show()
|
||||||
nwLoad._keyPressDelete()
|
nwLoad._keyPressDelete()
|
||||||
assert nwLoad.listBox.topLevelItemCount() == recentCount - 1
|
assert nwLoad.listBox.topLevelItemCount() == recentCount - 1
|
||||||
|
|||||||
Reference in New Issue
Block a user