@@ -99,6 +99,12 @@ python3 -m pip install lxml
|
||||
python3 -m pip install pyenchant
|
||||
```
|
||||
|
||||
PyQt/Qt should be at least 5.2.1, but ideally 5.10 or higher for nearly all features to work.
|
||||
Exporting to markdown requires PyQt/Qt 5.14.
|
||||
There are no known minimum for lxml, but the code was originally written with 4.2.
|
||||
The optional spell check library must be at least 3.0.0 to work with Windows.
|
||||
On Linux, 2.0.0 also works fine.
|
||||
|
||||
If no external spell checking tool is installed, novelWriter will use a basic spell checker based on standard Python package `difflib`.
|
||||
Currently, only English dictionaries are available for this spell checker, but more can be added to the `nw/assets/dict` folder.
|
||||
See the [nw/assets/dict/README.md](README.md) file in that folder for how to generate more dictionaries.
|
||||
|
||||
@@ -35,6 +35,11 @@ The following are optional, but recommended:
|
||||
|
||||
* ``pyenchant`` for spell checking
|
||||
|
||||
PyQt/Qt should be at least 5.2.1, but ideally 5.10 or higher for nearly all features to work.
|
||||
Exporting to markdown requires PyQt/Qt 5.14.
|
||||
There are no known minimum for lxml, but the code was originally written with 4.2.
|
||||
The optional spell check library must be at least 3.0.0 to work with Windows.
|
||||
On Linux, 2.0.0 also works fine.
|
||||
|
||||
Running novelWriter
|
||||
===================
|
||||
|
||||
+2
-2
@@ -136,7 +136,6 @@ class NWDoc():
|
||||
"""Save the document via temp file in case of save failure, and
|
||||
in any case keep a backup of the file.
|
||||
"""
|
||||
|
||||
if self.docHandle is None or not self.docEditable:
|
||||
return False
|
||||
|
||||
@@ -202,7 +201,6 @@ class NWDoc():
|
||||
"""Parses the document meta tag and returns the path and name as
|
||||
a list and a string.
|
||||
"""
|
||||
|
||||
if len(self.docMeta) < 14:
|
||||
# Not enough information
|
||||
return "", []
|
||||
@@ -232,6 +230,8 @@ class NWDoc():
|
||||
|
||||
@staticmethod
|
||||
def _assemblePath(tHandle, docExt):
|
||||
"""Assemble the file path for a given handle.
|
||||
"""
|
||||
if tHandle is None:
|
||||
return None, None
|
||||
docDir = "data_"+tHandle[0]
|
||||
|
||||
+7
-3
@@ -244,7 +244,6 @@ class NWIndex():
|
||||
and text as separate inputs as we want to primarily scan the
|
||||
files before we save them, unless we're rebuilding the index.
|
||||
"""
|
||||
|
||||
theItem = self.theProject.projTree[tHandle]
|
||||
if theItem is None:
|
||||
return False
|
||||
@@ -529,13 +528,18 @@ class NWIndex():
|
||||
# Extract Data
|
||||
##
|
||||
|
||||
def getNovelStructure(self):
|
||||
def getNovelStructure(self, skipExcluded=True):
|
||||
"""Builds a list of all titles in the novel, in the correct
|
||||
order as they appear in the tree view and in the respective
|
||||
document files, but skipping all note files.
|
||||
"""
|
||||
theStructure = []
|
||||
for tHandle in self.theProject.projTree.handles():
|
||||
for tItem in self.theProject.projTree:
|
||||
if tItem is None:
|
||||
continue
|
||||
if not tItem.isExported and skipExcluded:
|
||||
continue
|
||||
tHandle = tItem.itemHandle
|
||||
if tHandle not in self.novelIndex:
|
||||
continue
|
||||
for sTitle in sorted(self.novelIndex[tHandle].keys()):
|
||||
|
||||
+70
-17
@@ -181,7 +181,6 @@ class NWProject():
|
||||
"""Clear the data for the current project, and set them to
|
||||
default values.
|
||||
"""
|
||||
|
||||
# Project Status
|
||||
self.projOpened = 0
|
||||
self.projChanged = False
|
||||
@@ -236,7 +235,6 @@ class NWProject():
|
||||
parse the XML of the file and populate the project variables and
|
||||
build the tree of project items.
|
||||
"""
|
||||
|
||||
if not path.isfile(fileName):
|
||||
fileName = path.join(fileName, nwFiles.PROJ_FILE)
|
||||
if not path.isfile(fileName):
|
||||
@@ -404,7 +402,6 @@ class NWProject():
|
||||
make sure if the save fails, we're not left with a truncated
|
||||
file.
|
||||
"""
|
||||
|
||||
if self.projPath is None:
|
||||
self.makeAlert("Project path not set, cannot save.", nwAlert.ERROR)
|
||||
return False
|
||||
@@ -522,7 +519,6 @@ class NWProject():
|
||||
def zipIt(self, doNotify):
|
||||
"""Create a zip file of the entire project.
|
||||
"""
|
||||
|
||||
logger.info("Backing up project")
|
||||
self.theParent.statusBar.setStatus("Backing up project ...")
|
||||
|
||||
@@ -836,7 +832,6 @@ class NWProject():
|
||||
def _readLockFile(self):
|
||||
"""Reads the lock file in the project folder.
|
||||
"""
|
||||
|
||||
if self.projPath is None:
|
||||
return ["ERROR"]
|
||||
|
||||
@@ -863,7 +858,6 @@ class NWProject():
|
||||
def _writeLockFile(self):
|
||||
"""Writes a lock file to the project folder.
|
||||
"""
|
||||
|
||||
if self.projPath is None:
|
||||
return False
|
||||
|
||||
@@ -901,6 +895,8 @@ class NWProject():
|
||||
return None
|
||||
|
||||
def _checkFolder(self, thePath):
|
||||
"""Check if a folder exists, and if it doesn't, create it.
|
||||
"""
|
||||
if not path.isdir(thePath):
|
||||
try:
|
||||
mkdir(thePath)
|
||||
@@ -911,6 +907,8 @@ class NWProject():
|
||||
return True
|
||||
|
||||
def _packProjectValue(self, xParent, theName, theValue, allowNone=True):
|
||||
"""Pack a list of values into an xml element.
|
||||
"""
|
||||
if not isinstance(theValue, list):
|
||||
theValue = [theValue]
|
||||
for aValue in theValue:
|
||||
@@ -927,7 +925,6 @@ class NWProject():
|
||||
orphaned files so the user can either delete them, or put them
|
||||
back into the project tree.
|
||||
"""
|
||||
|
||||
if self.projPath is None:
|
||||
return
|
||||
|
||||
@@ -992,7 +989,6 @@ class NWProject():
|
||||
def _appendSessionStats(self):
|
||||
"""Append session statistics to the sessions log file.
|
||||
"""
|
||||
|
||||
if self.projMeta is None:
|
||||
return False
|
||||
|
||||
@@ -1234,9 +1230,13 @@ class NWTree():
|
||||
##
|
||||
|
||||
def __len__(self):
|
||||
"""Return the length counter. Does not check that it is correct!
|
||||
"""
|
||||
return self._theLength
|
||||
|
||||
def __bool__(self):
|
||||
"""Returns True if the tree has any entries.
|
||||
"""
|
||||
return self._theLength > 0
|
||||
|
||||
##
|
||||
@@ -1382,7 +1382,6 @@ class NWItem():
|
||||
def unpackXML(self, xItem):
|
||||
"""Sets the values from an XML entry of type 'item'.
|
||||
"""
|
||||
|
||||
if xItem.tag != "item":
|
||||
logger.error("XML entry is not an NWItem")
|
||||
return False
|
||||
@@ -1420,9 +1419,11 @@ class NWItem():
|
||||
|
||||
@staticmethod
|
||||
def _subPack(xParent, name, attrib=None, text=None, none=True):
|
||||
"""Packs the values into an xml element.
|
||||
"""
|
||||
if not none and (text == None or text == "None"):
|
||||
return None
|
||||
xSub = etree.SubElement(xParent,name,attrib=attrib)
|
||||
xSub = etree.SubElement(xParent, name, attrib=attrib)
|
||||
if text is not None:
|
||||
xSub.text = text
|
||||
return xSub
|
||||
@@ -1432,10 +1433,14 @@ class NWItem():
|
||||
##
|
||||
|
||||
def setName(self, theName):
|
||||
"""Set the item name.
|
||||
"""
|
||||
self.itemName = theName.strip()
|
||||
return
|
||||
|
||||
def setHandle(self, theHandle):
|
||||
"""Set the item handle, and ensure it is valid.
|
||||
"""
|
||||
if isinstance(theHandle, str):
|
||||
if len(theHandle) == 13:
|
||||
self.itemHandle = theHandle
|
||||
@@ -1446,6 +1451,8 @@ class NWItem():
|
||||
return
|
||||
|
||||
def setParent(self, theParent):
|
||||
"""Set the parent handle, and ensure that it is valid.
|
||||
"""
|
||||
if theParent is None:
|
||||
self.parHandle = None
|
||||
elif isinstance(theParent, str):
|
||||
@@ -1458,10 +1465,16 @@ class NWItem():
|
||||
return
|
||||
|
||||
def setOrder(self, theOrder):
|
||||
"""Set the item order, and ensure that it is valid. This value
|
||||
is purely a meta value, not actually used by novelWriter.
|
||||
"""
|
||||
self.itemOrder = checkInt(theOrder, 0)
|
||||
return
|
||||
|
||||
def setType(self, theType):
|
||||
"""Set the item type from either a proper nwItemType, or set it
|
||||
from a string representing a nwItemType.
|
||||
"""
|
||||
if isinstance(theType, nwItemType):
|
||||
self.itemType = theType
|
||||
elif theType in nwItemType.__members__:
|
||||
@@ -1472,6 +1485,9 @@ class NWItem():
|
||||
return
|
||||
|
||||
def setClass(self, theClass):
|
||||
"""Set the item class from either a proper nwItemClass, or set
|
||||
it from a string representing a nwItemClass.
|
||||
"""
|
||||
if isinstance(theClass, nwItemClass):
|
||||
self.itemClass = theClass
|
||||
elif theClass in nwItemClass.__members__:
|
||||
@@ -1482,6 +1498,9 @@ class NWItem():
|
||||
return
|
||||
|
||||
def setLayout(self, theLayout):
|
||||
"""Set the item layout from either a proper nwItemLayout, or set
|
||||
it from a string representing a nwItemLayout.
|
||||
"""
|
||||
if isinstance(theLayout, nwItemLayout):
|
||||
self.itemLayout = theLayout
|
||||
elif theLayout in nwItemLayout.__members__:
|
||||
@@ -1492,6 +1511,9 @@ class NWItem():
|
||||
return
|
||||
|
||||
def setStatus(self, theStatus):
|
||||
"""Set the item status by looking it up in the valid status
|
||||
items of the current project.
|
||||
"""
|
||||
if self.itemClass == nwItemClass.NOVEL:
|
||||
self.itemStatus = self.theProject.statusItems.checkEntry(theStatus)
|
||||
else:
|
||||
@@ -1499,6 +1521,8 @@ class NWItem():
|
||||
return
|
||||
|
||||
def setExpanded(self, expState):
|
||||
"""Save the expanded status of an item in the project tree.
|
||||
"""
|
||||
if isinstance(expState, str):
|
||||
self.isExpanded = expState == str(True)
|
||||
else:
|
||||
@@ -1506,6 +1530,8 @@ class NWItem():
|
||||
return
|
||||
|
||||
def setExported(self, expState):
|
||||
"""Save the export flag.
|
||||
"""
|
||||
if isinstance(expState, str):
|
||||
self.isExported = expState == str(True)
|
||||
else:
|
||||
@@ -1517,19 +1543,27 @@ class NWItem():
|
||||
##
|
||||
|
||||
def setCharCount(self, theCount):
|
||||
self.charCount = checkInt(theCount,0)
|
||||
"""Set the character count, and ensure that it is an integer.
|
||||
"""
|
||||
self.charCount = checkInt(theCount, 0)
|
||||
return
|
||||
|
||||
def setWordCount(self, theCount):
|
||||
self.wordCount = checkInt(theCount,0)
|
||||
"""Set the word count, and ensure that it is an integer.
|
||||
"""
|
||||
self.wordCount = checkInt(theCount, 0)
|
||||
return
|
||||
|
||||
def setParaCount(self, theCount):
|
||||
self.paraCount = checkInt(theCount,0)
|
||||
"""Set the paragraph count, and ensure that it is an integer.
|
||||
"""
|
||||
self.paraCount = checkInt(theCount, 0)
|
||||
return
|
||||
|
||||
def setCursorPos(self, thePosition):
|
||||
self.cursorPos = checkInt(thePosition,0)
|
||||
"""Set the cursor position, and ensure that it is an integer.
|
||||
"""
|
||||
self.cursorPos = checkInt(thePosition, 0)
|
||||
return
|
||||
|
||||
# END Class NWItem
|
||||
@@ -1551,6 +1585,9 @@ class NWStatus():
|
||||
return
|
||||
|
||||
def addEntry(self, theLabel, theColours):
|
||||
"""Add a status entry to the status object, but ensure it isn't
|
||||
a duplicate.
|
||||
"""
|
||||
theLabel = theLabel.strip()
|
||||
if self.lookupEntry(theLabel) is None:
|
||||
self.theLabels.append(theLabel)
|
||||
@@ -1561,6 +1598,9 @@ class NWStatus():
|
||||
return True
|
||||
|
||||
def lookupEntry(self, theLabel):
|
||||
"""Look up a status entry in the object lists, and return it if
|
||||
it exists.
|
||||
"""
|
||||
if theLabel is None:
|
||||
return None
|
||||
theLabel = theLabel.strip()
|
||||
@@ -1569,6 +1609,9 @@ class NWStatus():
|
||||
return None
|
||||
|
||||
def checkEntry(self, theStatus):
|
||||
"""Check if a status value is valid, and returns the safe
|
||||
reference to be used internally.
|
||||
"""
|
||||
if isinstance(theStatus, str):
|
||||
theStatus = theStatus.strip()
|
||||
if self.lookupEntry(theStatus) is not None:
|
||||
@@ -1578,11 +1621,12 @@ class NWStatus():
|
||||
return self.theLabels[theStatus]
|
||||
|
||||
def setNewEntries(self, newList):
|
||||
|
||||
"""Update the list of entries after they have been modified by
|
||||
the GUI tool.
|
||||
"""
|
||||
replaceMap = {}
|
||||
|
||||
if newList is not None:
|
||||
|
||||
self.theLabels = []
|
||||
self.theColours = []
|
||||
self.theCounts = []
|
||||
@@ -1598,10 +1642,14 @@ class NWStatus():
|
||||
return replaceMap
|
||||
|
||||
def resetCounts(self):
|
||||
"""Clear the counts of references to the status entries.
|
||||
"""
|
||||
self.theCounts = [0]*self.theLength
|
||||
return
|
||||
|
||||
def countEntry(self, theLabel):
|
||||
"""Lookup the usage count of a given entry.
|
||||
"""
|
||||
theIndex = self.lookupEntry(theLabel)
|
||||
if theIndex is not None:
|
||||
self.theCounts[theIndex] += 1
|
||||
@@ -1623,7 +1671,6 @@ class NWStatus():
|
||||
def unpackEntries(self, xParent):
|
||||
"""Unpack an XML tree and set the class values.
|
||||
"""
|
||||
|
||||
theLabels = []
|
||||
theColours = []
|
||||
|
||||
@@ -1661,15 +1708,21 @@ class NWStatus():
|
||||
##
|
||||
|
||||
def __getitem__(self, n):
|
||||
"""Return an entry by its index.
|
||||
"""
|
||||
if n >= 0 and n < self.theLength:
|
||||
return self.theLabels[n], self.theColours[n], self.theCounts[n]
|
||||
return None, None, None
|
||||
|
||||
def __iter__(self):
|
||||
"""Initialise the iterator.
|
||||
"""
|
||||
self.theIndex = 0
|
||||
return self
|
||||
|
||||
def __next__(self):
|
||||
"""Return the next entry for the iterator.
|
||||
"""
|
||||
if self.theIndex < self.theLength:
|
||||
theLabel, theColour, theCount = self.__getitem__(self.theIndex)
|
||||
self.theIndex += 1
|
||||
|
||||
+36
-2
@@ -55,15 +55,23 @@ class NWSpellCheck():
|
||||
return
|
||||
|
||||
def setLanguage(self, theLang, projectDict=None):
|
||||
"""Dummy function.
|
||||
"""
|
||||
return
|
||||
|
||||
def checkWord(self, theWord):
|
||||
"""Dummy function.
|
||||
"""
|
||||
return True
|
||||
|
||||
def suggestWords(self, theWord):
|
||||
"""Dummy function.
|
||||
"""
|
||||
return []
|
||||
|
||||
def addWord(self, newWord):
|
||||
"""Add a word to the project dictionary.
|
||||
"""
|
||||
if self.projectDict is not None and newWord not in self.PROJW:
|
||||
newWord = newWord.strip()
|
||||
self.PROJW.append(newWord)
|
||||
@@ -76,10 +84,14 @@ class NWSpellCheck():
|
||||
return
|
||||
|
||||
def listDictionaries(self):
|
||||
"""Dummy function.
|
||||
"""
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
def expandLanguage(spTag):
|
||||
"""Translate a language tag to something more suer friendly.
|
||||
"""
|
||||
spBits = spTag.split("_")
|
||||
if spBits[0] in isoLanguage.ISO_639_1:
|
||||
spLang = isoLanguage.ISO_639_1[spBits[0]]
|
||||
@@ -94,6 +106,9 @@ class NWSpellCheck():
|
||||
##
|
||||
|
||||
def _readProjectDictionary(self, projectDict):
|
||||
"""Read the content of the project dictionary, and add it to the
|
||||
lookup lists.
|
||||
"""
|
||||
self.PROJW = []
|
||||
if projectDict is not None:
|
||||
self.projectDict = projectDict
|
||||
@@ -147,17 +162,25 @@ class NWSpellEnchant(NWSpellCheck):
|
||||
return
|
||||
|
||||
def checkWord(self, theWord):
|
||||
"""Wrapper function for pyenchant.
|
||||
"""
|
||||
return self.theDict.check(theWord)
|
||||
|
||||
def suggestWords(self, theWord):
|
||||
"""Wrapper function for pyenchant.
|
||||
"""
|
||||
return self.theDict.suggest(theWord)
|
||||
|
||||
def addWord(self, newWord):
|
||||
"""Wrapper function for pyenchant.
|
||||
"""
|
||||
self.theDict.add_to_session(newWord)
|
||||
NWSpellCheck.addWord(self, newWord)
|
||||
return
|
||||
|
||||
def listDictionaries(self):
|
||||
"""Wrapper function for pyenchant.
|
||||
"""
|
||||
retList = []
|
||||
try:
|
||||
import enchant
|
||||
@@ -171,6 +194,8 @@ class NWSpellEnchant(NWSpellCheck):
|
||||
# END Class NWSpellEnchant
|
||||
|
||||
class NWSpellEnchantDummy:
|
||||
"""Fallback for when Enchant is selected, but not installed.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
return
|
||||
@@ -191,6 +216,11 @@ class NWSpellEnchantDummy:
|
||||
# ================================================================================================ #
|
||||
|
||||
class NWSpellSimple(NWSpellCheck):
|
||||
"""Internal spell check tool that uses standard Python packages with
|
||||
no other external dependencies. This is the fallback spell checker
|
||||
when no other is available. This method is fairly slow compared to
|
||||
other implementations.
|
||||
"""
|
||||
|
||||
WORDS = []
|
||||
|
||||
@@ -200,7 +230,8 @@ class NWSpellSimple(NWSpellCheck):
|
||||
return
|
||||
|
||||
def setLanguage(self, theLang, projectDict=None):
|
||||
|
||||
"""Load a dictionary as a list from the app assets folder.
|
||||
"""
|
||||
self.WORDS = []
|
||||
dictFile = path.join(self.mainConf.dictPath,theLang+".dict")
|
||||
try:
|
||||
@@ -259,6 +290,8 @@ class NWSpellSimple(NWSpellCheck):
|
||||
return theOptions
|
||||
|
||||
def addWord(self, newWord):
|
||||
"""Wrapper for the internal project dictionary feature.
|
||||
"""
|
||||
newWord = newWord.strip().lower()
|
||||
if newWord not in self.WORDS:
|
||||
self.WORDS.append(newWord)
|
||||
@@ -266,7 +299,8 @@ class NWSpellSimple(NWSpellCheck):
|
||||
return
|
||||
|
||||
def listDictionaries(self):
|
||||
|
||||
"""Lists the dictionary files in the app assets folder.
|
||||
"""
|
||||
retList = []
|
||||
for dictFile in listdir(self.mainConf.dictPath):
|
||||
|
||||
|
||||
@@ -540,6 +540,22 @@ class Tokenizer():
|
||||
tToken[0], tToken[1], tToken[2], tToken[3] | self.A_PBA
|
||||
)
|
||||
|
||||
# A single page is always left-aligned and starts on a fresh
|
||||
# page, unless it's empty.
|
||||
if self.isPage:
|
||||
for n, tToken in enumerate(self.theTokens):
|
||||
tType = tToken[0]
|
||||
tText = tToken[1]
|
||||
tFormat = tToken[2]
|
||||
if n == 0:
|
||||
self.theTokens[n] = (
|
||||
tType, tText, tFormat, self.A_LEFT | self.A_PBB
|
||||
)
|
||||
else:
|
||||
self.theTokens[n] = (
|
||||
tType, tText, tFormat, self.A_LEFT
|
||||
)
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
|
||||
@@ -39,7 +39,6 @@ def countWords(theText):
|
||||
"""Count words in a piece of text, skipping special syntax and
|
||||
comments.
|
||||
"""
|
||||
|
||||
charCount = 0
|
||||
wordCount = 0
|
||||
paraCount = 0
|
||||
@@ -86,7 +85,6 @@ def projectMaintenance(theProject):
|
||||
"""Wrapper class for handling various tasks related to managing old
|
||||
projects with content from older versions of novelWriter.
|
||||
"""
|
||||
|
||||
# Remove no longer used project cache folder
|
||||
if path.isdir(theProject.projPath):
|
||||
cacheDir = path.join(theProject.projPath, "cache")
|
||||
@@ -141,7 +139,6 @@ def numberToWord(numVal, theLanguage):
|
||||
def _numberToWordEN(numVal):
|
||||
"""Convert numbers to English words.
|
||||
"""
|
||||
|
||||
numWord = ""
|
||||
oneWord = ""
|
||||
tenWord = ""
|
||||
|
||||
@@ -69,10 +69,14 @@ class PagedDialog(QDialog):
|
||||
return
|
||||
|
||||
def addTab(self, tabWidget, tabLabel):
|
||||
"""Forwards the adding of tabs to the QTabWidget.
|
||||
"""
|
||||
self._tabBox.addTab(tabWidget, tabLabel)
|
||||
return
|
||||
|
||||
def addControls(self, buttonBar):
|
||||
"""Adds a button bar to the dialog.
|
||||
"""
|
||||
self._buttonBox.addWidget(buttonBar)
|
||||
return
|
||||
|
||||
@@ -85,12 +89,16 @@ class VerticalTabBar(QTabBar):
|
||||
return
|
||||
|
||||
def tabSizeHint(self, theIndex):
|
||||
"""Returns a transposed size hint for the rotated bar.
|
||||
"""
|
||||
tSize = QTabBar.tabSizeHint(self, theIndex)
|
||||
tSize.transpose()
|
||||
return tSize
|
||||
|
||||
def paintEvent(self, theEvent):
|
||||
|
||||
"""Custom implementation of the label painter that rotates the
|
||||
label 90 degrees.
|
||||
"""
|
||||
pObj = QStylePainter(self)
|
||||
oObj = QStyleOptionTab()
|
||||
|
||||
|
||||
@@ -69,11 +69,15 @@ class QConfigLayout(QGridLayout):
|
||||
return
|
||||
|
||||
def setHelpText(self, intRow, theText):
|
||||
"""Set the text for the help label.
|
||||
"""
|
||||
if intRow in self._itemMap:
|
||||
self._itemMap[intRow]["help"].setText(theText)
|
||||
return
|
||||
|
||||
def setLabelText(self, intRow, theText):
|
||||
"""Set the text for the main label.
|
||||
"""
|
||||
if intRow in self._itemMap:
|
||||
self._itemMap[intRow]["label"].setText(theText)
|
||||
return
|
||||
@@ -85,7 +89,6 @@ class QConfigLayout(QGridLayout):
|
||||
def addGroupLabel(self, theLabel):
|
||||
"""Adds a text label to separate groups of settings.
|
||||
"""
|
||||
|
||||
if isinstance(theLabel, QLabel):
|
||||
qLabel = theLabel
|
||||
elif isinstance(theLabel, str):
|
||||
@@ -107,7 +110,6 @@ class QConfigLayout(QGridLayout):
|
||||
def addRow(self, theLabel, theWidget, helpText=None, theUnit=None):
|
||||
"""Add a label and a widget as a new row of the grid.
|
||||
"""
|
||||
|
||||
thisEntry = {
|
||||
"label" : None,
|
||||
"help" : None,
|
||||
|
||||
@@ -97,7 +97,6 @@ class QSwitch(QAbstractButton):
|
||||
def paintEvent(self, event):
|
||||
"""Drawing the switch itself.
|
||||
"""
|
||||
|
||||
qPaint = QPainter(self)
|
||||
qPaint.setRenderHint(QPainter.Antialiasing, True)
|
||||
qPaint.setPen(Qt.NoPen)
|
||||
|
||||
@@ -143,31 +143,33 @@ class GuiDocDetails(QFrame):
|
||||
self.pCountData.setAlignment(Qt.AlignRight)
|
||||
|
||||
# Assemble
|
||||
self.mainBox.addWidget(self.labelName, 0, 0, 1, 1)
|
||||
self.mainBox.addWidget(self.labelFlag, 0, 1, 1, 1)
|
||||
self.mainBox.addWidget(self.labelData, 0, 2, 1, 3)
|
||||
self.mainBox.addWidget(self.labelName, 0, 0, 1, 1, Qt.AlignTop)
|
||||
self.mainBox.addWidget(self.labelFlag, 0, 1, 1, 1, Qt.AlignTop)
|
||||
self.mainBox.addWidget(self.labelData, 0, 2, 1, 3, Qt.AlignTop)
|
||||
|
||||
self.mainBox.addWidget(self.statusName, 1, 0, 1, 1)
|
||||
self.mainBox.addWidget(self.statusFlag, 1, 1, 1, 1)
|
||||
self.mainBox.addWidget(self.statusData, 1, 2, 1, 1)
|
||||
self.mainBox.addWidget(self.cCountName, 1, 3, 1, 1)
|
||||
self.mainBox.addWidget(self.cCountData, 1, 4, 1, 1)
|
||||
self.mainBox.addWidget(self.statusName, 1, 0, 1, 1, Qt.AlignTop)
|
||||
self.mainBox.addWidget(self.statusFlag, 1, 1, 1, 1, Qt.AlignTop)
|
||||
self.mainBox.addWidget(self.statusData, 1, 2, 1, 1, Qt.AlignTop)
|
||||
self.mainBox.addWidget(self.cCountName, 1, 3, 1, 1, Qt.AlignTop)
|
||||
self.mainBox.addWidget(self.cCountData, 1, 4, 1, 1, Qt.AlignTop)
|
||||
|
||||
self.mainBox.addWidget(self.className, 2, 0, 1, 1)
|
||||
self.mainBox.addWidget(self.classFlag, 2, 1, 1, 1)
|
||||
self.mainBox.addWidget(self.classData, 2, 2, 1, 1)
|
||||
self.mainBox.addWidget(self.wCountName, 2, 3, 1, 1)
|
||||
self.mainBox.addWidget(self.wCountData, 2, 4, 1, 1)
|
||||
self.mainBox.addWidget(self.className, 2, 0, 1, 1, Qt.AlignTop)
|
||||
self.mainBox.addWidget(self.classFlag, 2, 1, 1, 1, Qt.AlignTop)
|
||||
self.mainBox.addWidget(self.classData, 2, 2, 1, 1, Qt.AlignTop)
|
||||
self.mainBox.addWidget(self.wCountName, 2, 3, 1, 1, Qt.AlignTop)
|
||||
self.mainBox.addWidget(self.wCountData, 2, 4, 1, 1, Qt.AlignTop)
|
||||
|
||||
self.mainBox.addWidget(self.layoutName, 3, 0, 1, 1)
|
||||
self.mainBox.addWidget(self.layoutFlag, 3, 1, 1, 1)
|
||||
self.mainBox.addWidget(self.layoutData, 3, 2, 1, 1)
|
||||
self.mainBox.addWidget(self.pCountName, 3, 3, 1, 1)
|
||||
self.mainBox.addWidget(self.pCountData, 3, 4, 1, 1)
|
||||
self.mainBox.addWidget(self.layoutName, 3, 0, 1, 1, Qt.AlignTop)
|
||||
self.mainBox.addWidget(self.layoutFlag, 3, 1, 1, 1, Qt.AlignTop)
|
||||
self.mainBox.addWidget(self.layoutData, 3, 2, 1, 1, Qt.AlignTop)
|
||||
self.mainBox.addWidget(self.pCountName, 3, 3, 1, 1, Qt.AlignTop)
|
||||
self.mainBox.addWidget(self.pCountData, 3, 4, 1, 1, Qt.AlignTop)
|
||||
|
||||
self.mainBox.setColumnStretch(0,0)
|
||||
self.mainBox.setColumnStretch(1,0)
|
||||
self.mainBox.setColumnStretch(2,1)
|
||||
self.mainBox.setColumnStretch(3,0)
|
||||
self.mainBox.setColumnStretch(4,0)
|
||||
|
||||
logger.debug("DocDetails initialisation complete")
|
||||
|
||||
@@ -180,7 +182,6 @@ class GuiDocDetails(QFrame):
|
||||
def updateViewBox(self, tHandle):
|
||||
"""Populate the details box from a given handle.
|
||||
"""
|
||||
|
||||
nwItem = self.theProject.projTree[tHandle]
|
||||
|
||||
if nwItem is None:
|
||||
|
||||
@@ -141,7 +141,6 @@ class GuiDocEditor(QTextEdit):
|
||||
"""Clear the current document and reset all document related
|
||||
flags and counters.
|
||||
"""
|
||||
|
||||
self.nwDocument.clearDocument()
|
||||
self.setReadOnly(True)
|
||||
self.clear()
|
||||
@@ -167,7 +166,6 @@ class GuiDocEditor(QTextEdit):
|
||||
settings. This function is both called when the editor is
|
||||
created, and when the user changes the main editor preferences.
|
||||
"""
|
||||
|
||||
# Some Constants
|
||||
self.nonWord = "\"'"
|
||||
self.nonWord += "".join(self.mainConf.fmtDoubleQuotes)
|
||||
@@ -241,7 +239,6 @@ class GuiDocEditor(QTextEdit):
|
||||
document is new (empty string), we set up the editor for editing
|
||||
the file.
|
||||
"""
|
||||
|
||||
theDoc = self.nwDocument.openDocument(tHandle, showStatus=showStatus)
|
||||
if theDoc is None:
|
||||
# There was an io error
|
||||
@@ -319,7 +316,6 @@ class GuiDocEditor(QTextEdit):
|
||||
Config.textFixedW is enabled or we're in Zen mode. Otherwise,
|
||||
just ensure the margins are set correctly.
|
||||
"""
|
||||
|
||||
if self.mainConf.textFixedW or self.theParent.isZenMode:
|
||||
vBar = self.verticalScrollBar()
|
||||
if vBar.isVisible():
|
||||
@@ -376,6 +372,10 @@ class GuiDocEditor(QTextEdit):
|
||||
##
|
||||
|
||||
def setDocumentChanged(self, bValue):
|
||||
"""Keeps track of the document changed variable, and ensures
|
||||
that the corresponding icon on the status bar shows the same
|
||||
status.
|
||||
"""
|
||||
self.docChanged = bValue
|
||||
self.theParent.statusBar.setDocumentStatus(self.docChanged)
|
||||
return self.docChanged
|
||||
@@ -437,7 +437,6 @@ class GuiDocEditor(QTextEdit):
|
||||
If the spell check mode (theMode) is not defined (None), then
|
||||
toggle the current status saved in this class.
|
||||
"""
|
||||
|
||||
if theMode is None:
|
||||
theMode = not self.spellCheck
|
||||
|
||||
@@ -460,7 +459,6 @@ class GuiDocEditor(QTextEdit):
|
||||
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.
|
||||
"""
|
||||
|
||||
logger.verbose("Running spell checker")
|
||||
if self.spellCheck:
|
||||
bfTime = time()
|
||||
@@ -481,6 +479,12 @@ class GuiDocEditor(QTextEdit):
|
||||
##
|
||||
|
||||
def docAction(self, theAction):
|
||||
"""Perform an action on the current document based on an action
|
||||
flag. This is just a single entry point wrapper function to
|
||||
ensure all the feature functions get the correct information
|
||||
passed to it without having to consider the internal logic of
|
||||
this class when calling these actions from other classes.
|
||||
"""
|
||||
logger.verbose("Requesting action: %s" % theAction.name)
|
||||
if not self.theParent.hasProject:
|
||||
logger.error("No project open")
|
||||
@@ -537,9 +541,14 @@ class GuiDocEditor(QTextEdit):
|
||||
return True
|
||||
|
||||
def isEmpty(self):
|
||||
"""Wrapper function to check if the current document is empty.
|
||||
"""
|
||||
return self.qDocument.isEmpty()
|
||||
|
||||
def revealLocation(self):
|
||||
"""Tell the user where on the file system the file in the editor
|
||||
is saved.
|
||||
"""
|
||||
if self.theHandle is not None:
|
||||
msgBox = QMessageBox()
|
||||
msgBox.information(self, "File Location", (
|
||||
@@ -568,7 +577,6 @@ class GuiDocEditor(QTextEdit):
|
||||
However, we don't want to spend a lot of time in this function
|
||||
as it is triggered on every keypress when typing.
|
||||
"""
|
||||
|
||||
self.hasSelection = self.textCursor().hasSelection()
|
||||
|
||||
if keyEvent.modifiers() == Qt.ShiftModifier:
|
||||
@@ -628,7 +636,6 @@ class GuiDocEditor(QTextEdit):
|
||||
"""Triggered by right click to open the context menu. Also
|
||||
triggered by the Ctrl+. shortcut.
|
||||
"""
|
||||
|
||||
if not self.spellCheck:
|
||||
return
|
||||
|
||||
@@ -667,7 +674,11 @@ class GuiDocEditor(QTextEdit):
|
||||
|
||||
return
|
||||
|
||||
@pyqtSlot("QTextCursor", str)
|
||||
def _correctWord(self, theCursor, theWord):
|
||||
"""Slot for the spell check context menu triggering the
|
||||
replacement of a word with the word from the dictionary.
|
||||
"""
|
||||
xPos = theCursor.selectionStart()
|
||||
theCursor.beginEditBlock()
|
||||
theCursor.removeSelectedText()
|
||||
@@ -677,7 +688,11 @@ class GuiDocEditor(QTextEdit):
|
||||
self.setTextCursor(theCursor)
|
||||
return
|
||||
|
||||
@pyqtSlot("QTextCursor")
|
||||
def _addWord(self, theCursor):
|
||||
"""Slot for the spell check context menu triggered when the user
|
||||
wants to add a word to the project dictionary.
|
||||
"""
|
||||
theWord = theCursor.selectedText().strip().strip(self.nonWord)
|
||||
logger.debug("Added '%s' to project dictionary" % theWord)
|
||||
self.theDict.addWord(theWord)
|
||||
@@ -729,7 +744,6 @@ class GuiDocEditor(QTextEdit):
|
||||
tag and can tell the document viewer to try and find and load
|
||||
the file where the tag is defined.
|
||||
"""
|
||||
|
||||
if theCursor is None:
|
||||
theCursor = self.textCursor()
|
||||
|
||||
@@ -772,13 +786,15 @@ class GuiDocEditor(QTextEdit):
|
||||
return
|
||||
|
||||
def _openSpellContext(self):
|
||||
"""Opens the spell check context menu at the current point of
|
||||
the cursor.
|
||||
"""
|
||||
self._openContextMenu(self.cursorRect().center())
|
||||
return
|
||||
|
||||
def _docAutoReplace(self, theBlock):
|
||||
"""Autoreplace text elements based on main configuration.
|
||||
"""
|
||||
|
||||
if not theBlock.isValid():
|
||||
return
|
||||
|
||||
@@ -870,7 +886,6 @@ class GuiDocEditor(QTextEdit):
|
||||
def _formatBlock(self, docAction):
|
||||
"""Changes the block format of the block under the cursor.
|
||||
"""
|
||||
|
||||
theCursor = self.textCursor()
|
||||
theBlock = theCursor.block()
|
||||
if not theBlock.isValid():
|
||||
@@ -949,6 +964,8 @@ class GuiDocEditor(QTextEdit):
|
||||
return
|
||||
|
||||
def _makeSelection(self, selMode):
|
||||
"""Wrapper function to select a word based on a selection mode.
|
||||
"""
|
||||
theCursor = self.textCursor()
|
||||
theCursor.clearSelection()
|
||||
theCursor.select(selMode)
|
||||
@@ -1025,7 +1042,6 @@ class GuiDocEditor(QTextEdit):
|
||||
"""Create the spell checking object based on the spellTool
|
||||
setting in config.
|
||||
"""
|
||||
|
||||
if self.mainConf.spellTool == "enchant":
|
||||
from nw.core.spellcheck import NWSpellEnchant
|
||||
self.theDict = NWSpellEnchant()
|
||||
|
||||
@@ -79,7 +79,6 @@ class GuiDocTitleBar(QLabel):
|
||||
"""Sets the document title from the handle, or alternatively,
|
||||
set the whole document path.
|
||||
"""
|
||||
|
||||
self.setText("")
|
||||
self.theHandle = tHandle
|
||||
if tHandle is None:
|
||||
|
||||
@@ -105,13 +105,19 @@ class GuiDocTree(QTreeWidget):
|
||||
##
|
||||
|
||||
def clearTree(self):
|
||||
"""Clear the GUI content and the related maps.
|
||||
"""
|
||||
self.clear()
|
||||
self.theMap = {}
|
||||
self.orphRoot = None
|
||||
return
|
||||
|
||||
def newTreeItem(self, itemType, itemClass):
|
||||
|
||||
"""Add new item to the tree, with a given itemType and
|
||||
itemClass, and attach it to the selected handle. Also 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.
|
||||
"""
|
||||
pHandle = self.getSelectedHandle()
|
||||
|
||||
if not self.theParent.hasProject:
|
||||
@@ -274,7 +280,6 @@ class GuiDocTree(QTreeWidget):
|
||||
function only asks for confirmation once, and calls the regular
|
||||
deleteItem function for each document in the Trash folder.
|
||||
"""
|
||||
|
||||
trashHandle = self.theProject.projTree.trashRoot()
|
||||
|
||||
logger.debug("Emptying Trash folder")
|
||||
@@ -315,7 +320,6 @@ class GuiDocTree(QTreeWidget):
|
||||
that to save memory. Items not in the tree are not saved to the
|
||||
project file, so a loaded project will be clean anyway.
|
||||
"""
|
||||
|
||||
if tHandle is None:
|
||||
tHandle = self.getSelectedHandle()
|
||||
|
||||
@@ -446,6 +450,13 @@ class GuiDocTree(QTreeWidget):
|
||||
return
|
||||
|
||||
def propagateCount(self, tHandle, theCount, nDepth=0):
|
||||
"""Recursive function setting the word count for a given item,
|
||||
and propagating that count upwards in the tree until reaching a
|
||||
root item. This function is more efficient than recalculating
|
||||
everything each time the word count is updated, but is also
|
||||
prone to diverging from the true values if the counts are not
|
||||
properly reported to the function.
|
||||
"""
|
||||
tItem = self._getTreeItem(tHandle)
|
||||
if tItem is not None:
|
||||
tItem.setText(self.C_COUNT,str(theCount))
|
||||
@@ -460,6 +471,12 @@ class GuiDocTree(QTreeWidget):
|
||||
return
|
||||
|
||||
def projectWordCount(self):
|
||||
"""Sum up the word counts for all root items and set the
|
||||
relevant values in the project and on the status bar. This call
|
||||
is a fast way of getting this number, and depends on the
|
||||
propagateCount function being called when it should to maintain
|
||||
the correct count.
|
||||
"""
|
||||
nWords = 0
|
||||
for n in range(self.topLevelItemCount()):
|
||||
tItem = self.topLevelItem(n)
|
||||
@@ -472,6 +489,11 @@ class GuiDocTree(QTreeWidget):
|
||||
return
|
||||
|
||||
def buildTree(self):
|
||||
"""Build the entire project tree from scratch. This depends on
|
||||
the save project item iterator in the project class which will
|
||||
always make sure items with a parent have had their parent item
|
||||
sent first.
|
||||
"""
|
||||
self.clear()
|
||||
for nwItem in self.theProject.getProjectItems():
|
||||
self._addTreeItem(nwItem)
|
||||
@@ -517,11 +539,16 @@ class GuiDocTree(QTreeWidget):
|
||||
##
|
||||
|
||||
def _getTreeItem(self, tHandle):
|
||||
"""Returns the QTreeWidgetItem of a given item handle.
|
||||
"""
|
||||
if tHandle in self.theMap.keys():
|
||||
return self.theMap[tHandle]
|
||||
return None
|
||||
|
||||
def _scanChildren(self, theList, theItem, theIndex):
|
||||
"""This is a recursive function returning all items in a tree
|
||||
starting at a given QTreeWidgetItem.
|
||||
"""
|
||||
tHandle = theItem.text(self.C_HANDLE)
|
||||
nwItem = self.theProject.projTree[tHandle]
|
||||
nwItem.setExpanded(theItem.isExpanded())
|
||||
@@ -532,7 +559,9 @@ class GuiDocTree(QTreeWidget):
|
||||
return theList
|
||||
|
||||
def _addTreeItem(self, nwItem):
|
||||
|
||||
"""Create a QTreeWidgetItem from an NWItem and add it to the
|
||||
project tree.
|
||||
"""
|
||||
tHandle = nwItem.itemHandle
|
||||
pHandle = nwItem.parHandle
|
||||
tClass = nwItem.itemClass
|
||||
@@ -591,6 +620,9 @@ class GuiDocTree(QTreeWidget):
|
||||
return trItem
|
||||
|
||||
def _addOrphanedRoot(self):
|
||||
"""Add the special Orphaned Files root item to hold non-root
|
||||
items with no parent set.
|
||||
"""
|
||||
if self.orphRoot is None:
|
||||
newItem = QTreeWidgetItem([""]*4)
|
||||
newItem.setText(self.C_NAME, "Orphaned Files")
|
||||
@@ -604,6 +636,8 @@ class GuiDocTree(QTreeWidget):
|
||||
return
|
||||
|
||||
def _cleanOrphanedRoot(self):
|
||||
"""Remove the special Orphaned Files root folder if it is empty.
|
||||
"""
|
||||
if self.orphRoot is not None:
|
||||
if self.orphRoot.childCount() == 0:
|
||||
self.takeTopLevelItem(self.indexOfTopLevelItem(self.orphRoot))
|
||||
@@ -615,7 +649,6 @@ class GuiDocTree(QTreeWidget):
|
||||
in the project is consistent with the treeView. Also move the
|
||||
word count over to the new parent tree.
|
||||
"""
|
||||
|
||||
trItemS = self._getTreeItem(tHandle)
|
||||
nwItemS = self.theProject.projTree[tHandle]
|
||||
trItemP = trItemS.parent()
|
||||
@@ -636,6 +669,10 @@ class GuiDocTree(QTreeWidget):
|
||||
return True
|
||||
|
||||
def _moveOrphanedItem(self, tHandle, dHandle):
|
||||
"""Move an Orphaned Item to a new dHandle parent item. This
|
||||
function will set all the missing meta data based on the meta
|
||||
data of the destination item.
|
||||
"""
|
||||
trItemS = self._getTreeItem(tHandle)
|
||||
nwItemS = self.theProject.projTree[tHandle]
|
||||
nwItemD = self.theProject.projTree[dHandle]
|
||||
|
||||
@@ -88,7 +88,6 @@ class GuiDocViewer(QTextBrowser):
|
||||
def initViewer(self):
|
||||
"""Set editor settings from main config.
|
||||
"""
|
||||
|
||||
self._makeStyleSheet()
|
||||
|
||||
# Set Font
|
||||
@@ -122,7 +121,6 @@ class GuiDocViewer(QTextBrowser):
|
||||
def loadText(self, tHandle):
|
||||
"""Load text into the viewer from an item handle.
|
||||
"""
|
||||
|
||||
tItem = self.theProject.projTree[tHandle]
|
||||
if tItem is None:
|
||||
logger.warning("Item not found")
|
||||
@@ -153,11 +151,16 @@ class GuiDocViewer(QTextBrowser):
|
||||
return True
|
||||
|
||||
def reloadText(self):
|
||||
"""Reload the text in the current document.
|
||||
"""
|
||||
self.loadText(self.theHandle)
|
||||
return
|
||||
|
||||
def loadFromTag(self, theTag):
|
||||
|
||||
"""Load text in the document from a reference given by a meta
|
||||
tag rather than a known handle. This function depends on the
|
||||
index being up to date.
|
||||
"""
|
||||
logger.debug("Loading document from tag '%s'" % theTag)
|
||||
|
||||
if theTag in self.theParent.theIndex.tagIndex.keys():
|
||||
@@ -175,6 +178,9 @@ class GuiDocViewer(QTextBrowser):
|
||||
return True
|
||||
|
||||
def docAction(self, theAction):
|
||||
"""Wrapper function for various document actions on the current
|
||||
document.
|
||||
"""
|
||||
logger.verbose("Requesting action: %s" % theAction.name)
|
||||
if self.theHandle is None:
|
||||
logger.error("No document open")
|
||||
@@ -225,6 +231,9 @@ class GuiDocViewer(QTextBrowser):
|
||||
##
|
||||
|
||||
def _makeSelection(self, selMode):
|
||||
"""Wrapper function for making a selection based on a specific
|
||||
selection mode.
|
||||
"""
|
||||
theCursor = self.textCursor()
|
||||
theCursor.clearSelection()
|
||||
theCursor.select(selMode)
|
||||
@@ -232,7 +241,8 @@ class GuiDocViewer(QTextBrowser):
|
||||
return
|
||||
|
||||
def _linkClicked(self, theURL):
|
||||
|
||||
"""Slot for a link in the document being clicked.
|
||||
"""
|
||||
theLink = theURL.url()
|
||||
tHandle = None
|
||||
onLine = 0
|
||||
@@ -256,7 +266,9 @@ class GuiDocViewer(QTextBrowser):
|
||||
return
|
||||
|
||||
def _makeStyleSheet(self):
|
||||
|
||||
"""Generate an appropriate style sheet for the document viewer,
|
||||
based on the current syntax highlighter theme,
|
||||
"""
|
||||
styleSheet = (
|
||||
"body {{"
|
||||
" color: rgb({tColR},{tColG},{tColB});"
|
||||
|
||||
@@ -67,11 +67,15 @@ class GuiNoticeBar(QFrame):
|
||||
return
|
||||
|
||||
def showNote(self, theNote):
|
||||
"""Show the note on the noticebar.
|
||||
"""
|
||||
self.noteLabel.setText("<b>Note:</b> %s" % theNote)
|
||||
self.setVisible(True)
|
||||
return
|
||||
|
||||
def hideNote(self):
|
||||
"""Clear the noticebar and hide it.
|
||||
"""
|
||||
self.noteLabel.setText("")
|
||||
self.setVisible(False)
|
||||
return
|
||||
|
||||
@@ -125,7 +125,6 @@ class GuiProjectOutline(QTreeWidget):
|
||||
"""Clear the tree and header and set the default values for the
|
||||
columns arrays.
|
||||
"""
|
||||
|
||||
self.clear()
|
||||
self.setColumnCount(1)
|
||||
self.setHeaderLabel(nwLabels.OUTLINE_COLS[nwOutline.TITLE])
|
||||
@@ -150,7 +149,6 @@ class GuiProjectOutline(QTreeWidget):
|
||||
what data to load, and if necessary, force a rebuild of the
|
||||
tree.
|
||||
"""
|
||||
|
||||
# If it's the first time, we always build
|
||||
if self.firstView or self.firstView and overRide:
|
||||
self._loadHeaderState()
|
||||
@@ -229,7 +227,6 @@ class GuiProjectOutline(QTreeWidget):
|
||||
"""Load the state of the main tree header, that is, column order
|
||||
and column width.
|
||||
"""
|
||||
|
||||
# Load whatever we saved last time, regardless of wether it
|
||||
# contains the correct names or number of columns. The names
|
||||
# must be valid though.
|
||||
@@ -280,7 +277,6 @@ class GuiProjectOutline(QTreeWidget):
|
||||
save the current width of hidden columns though. This preserves
|
||||
the last known width in case they're unhidden again.
|
||||
"""
|
||||
|
||||
# If we haven't built the tree, there is nothing to save.
|
||||
if self.lastBuild == 0:
|
||||
return
|
||||
@@ -319,7 +315,6 @@ class GuiProjectOutline(QTreeWidget):
|
||||
if they are hidden. This ensures that showing and hiding columns
|
||||
is fast and doesn't require a rebuild of the tree.
|
||||
"""
|
||||
|
||||
self.clear()
|
||||
|
||||
if self.firstView:
|
||||
@@ -347,7 +342,7 @@ class GuiProjectOutline(QTreeWidget):
|
||||
currChapter = None
|
||||
currScene = None
|
||||
|
||||
for titleKey in self.theIndex.getNovelStructure():
|
||||
for titleKey in self.theIndex.getNovelStructure(skipExcluded=True):
|
||||
|
||||
if len(titleKey) < 16:
|
||||
continue
|
||||
@@ -404,7 +399,6 @@ class GuiProjectOutline(QTreeWidget):
|
||||
def _createTreeItem(self, tHandle, sTitle):
|
||||
"""Populate a tree item with all the column values.
|
||||
"""
|
||||
|
||||
nwItem = self.theProject.projTree[tHandle]
|
||||
novIdx = self.theIndex.novelIndex[tHandle][sTitle]
|
||||
|
||||
|
||||
@@ -98,6 +98,9 @@ class GuiSearchBar(QFrame):
|
||||
##
|
||||
|
||||
def setSearchText(self, theText):
|
||||
"""Open the search bar and set the search text to the text
|
||||
provided, if any.
|
||||
"""
|
||||
if not self.isVisible():
|
||||
self.setVisible(True)
|
||||
self.searchBox.setText(theText)
|
||||
@@ -106,15 +109,21 @@ class GuiSearchBar(QFrame):
|
||||
return True
|
||||
|
||||
def setReplaceText(self, theText):
|
||||
"""Set the replace text.
|
||||
"""
|
||||
self._replaceVisible(True)
|
||||
self.replaceBox.setFocus()
|
||||
self.replaceBox.setText(theText)
|
||||
return True
|
||||
|
||||
def getSearchText(self):
|
||||
"""Return the current search text.
|
||||
"""
|
||||
return self.searchBox.text()
|
||||
|
||||
def getReplaceText(self):
|
||||
"""Return the current replace text.
|
||||
"""
|
||||
return self.replaceBox.text()
|
||||
|
||||
##
|
||||
@@ -122,11 +131,15 @@ class GuiSearchBar(QFrame):
|
||||
##
|
||||
|
||||
def _doClose(self):
|
||||
"""Hide the search/replace bar.
|
||||
"""
|
||||
self._replaceVisible(False)
|
||||
self.setVisible(False)
|
||||
return
|
||||
|
||||
def _doSearch(self):
|
||||
"""Call the search action function for the document editor.
|
||||
"""
|
||||
modKey = qApp.keyboardModifiers()
|
||||
if modKey == Qt.ShiftModifier:
|
||||
self.theParent.docEditor.docAction(nwDocAction.GO_PREV)
|
||||
@@ -135,10 +148,14 @@ class GuiSearchBar(QFrame):
|
||||
return
|
||||
|
||||
def _doReplace(self):
|
||||
"""Call the replace action function for the document editor.
|
||||
"""
|
||||
self.theParent.docEditor.docAction(nwDocAction.REPL_NEXT)
|
||||
return
|
||||
|
||||
def _replaceVisible(self, isVisible):
|
||||
"""Set the visibility of all the replace widgets.
|
||||
"""
|
||||
self.replaceLabel.setVisible(isVisible)
|
||||
self.replaceBox.setVisible(isVisible)
|
||||
self.replaceButton.setVisible(isVisible)
|
||||
|
||||
@@ -94,7 +94,9 @@ class GuiDocViewDetails(QWidget):
|
||||
return
|
||||
|
||||
def refreshReferences(self, tHandle):
|
||||
|
||||
"""Update the current list of document references from the
|
||||
project index.
|
||||
"""
|
||||
self.currHandle = tHandle
|
||||
|
||||
if self.isSticky.isChecked():
|
||||
@@ -117,12 +119,17 @@ class GuiDocViewDetails(QWidget):
|
||||
##
|
||||
|
||||
def _linkClicked(self, theLink):
|
||||
"""Capture the link-click and forward it to the document viewer
|
||||
class for handling.
|
||||
"""
|
||||
if len(theLink) == 18:
|
||||
tHandle = theLink[-13:]
|
||||
self.theParent.viewDocument(tHandle)
|
||||
return
|
||||
|
||||
def _doShowHide(self, chState):
|
||||
"""Toggle the expand/collapse of the panel.
|
||||
"""
|
||||
self.scrollBox.setVisible(chState)
|
||||
self.mainConf.setShowRefPanel(chState)
|
||||
if chState:
|
||||
@@ -132,6 +139,8 @@ class GuiDocViewDetails(QWidget):
|
||||
return
|
||||
|
||||
def _doSticky(self, chState):
|
||||
"""Toggle the sticky feature of the references.
|
||||
"""
|
||||
if not chState and self.currHandle is not None:
|
||||
self.refreshReferences(self.currHandle)
|
||||
return
|
||||
|
||||
@@ -75,6 +75,9 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
||||
return
|
||||
|
||||
def initHighlighter(self):
|
||||
"""Initialise the syntax highlighter, setting all the colour
|
||||
rules and building the regexes.
|
||||
"""
|
||||
|
||||
logger.debug("Setting up highlighting rules")
|
||||
|
||||
@@ -205,14 +208,21 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
||||
##
|
||||
|
||||
def setDict(self, theDict):
|
||||
"""Set the dictionary object for spell check underlines lookup.
|
||||
"""
|
||||
self.theDict = theDict
|
||||
return True
|
||||
|
||||
def setSpellCheck(self, theMode):
|
||||
"""Enable/disable the real time spell checker.
|
||||
"""
|
||||
self.spellCheck = theMode
|
||||
return True
|
||||
|
||||
def setHandle(self, theHandle):
|
||||
"""Set the handle of the currently highlighted document. This is
|
||||
needed for the index lookup for validating tags and references.
|
||||
"""
|
||||
self.theHandle = theHandle
|
||||
return True
|
||||
|
||||
@@ -226,7 +236,6 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
||||
is significantly faster than running the regex checks we use for
|
||||
text paragraphs.
|
||||
"""
|
||||
|
||||
if self.theHandle is None or not theText:
|
||||
return
|
||||
|
||||
@@ -317,7 +326,6 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
||||
"""Generate a valid character format to be applied to the text
|
||||
that is to be highlighted.
|
||||
"""
|
||||
|
||||
theFormat = QTextCharFormat()
|
||||
|
||||
if fmtCol is not None:
|
||||
|
||||
@@ -51,7 +51,6 @@ class OptionState():
|
||||
def loadSettings(self):
|
||||
"""Load the options dictionary from the project settings file.
|
||||
"""
|
||||
|
||||
if self.theProject.projMeta is None:
|
||||
return False
|
||||
|
||||
@@ -76,7 +75,6 @@ class OptionState():
|
||||
def saveSettings(self):
|
||||
"""Save the options dictionary to the project settings file.
|
||||
"""
|
||||
|
||||
if self.theProject.projMeta is None:
|
||||
return False
|
||||
|
||||
|
||||
@@ -45,7 +45,9 @@ class WordCounter(QThread):
|
||||
return
|
||||
|
||||
def run(self):
|
||||
|
||||
"""Overloaded run function for the word counter, forwarding the
|
||||
call to the function that does the actual counting.
|
||||
"""
|
||||
theText = self.theParent.getText()
|
||||
cC, wC, pC = countWords(theText)
|
||||
|
||||
|
||||
+1
-1
@@ -57,7 +57,7 @@ class GuiMain(QMainWindow):
|
||||
|
||||
logger.info("Starting %s" % nw.__package__)
|
||||
logger.debug("Initialising GUI ...")
|
||||
self.mainConf = nw.CONFIG
|
||||
self.mainConf = nw.CONFIG
|
||||
|
||||
# Some runtime info useful for debugging
|
||||
logger.info("OS: %s" % self.mainConf.osType)
|
||||
|
||||
+3
-3
@@ -1,3 +1,3 @@
|
||||
pyqt5
|
||||
lxml
|
||||
pyenchant
|
||||
pyqt5>=5.2.1
|
||||
lxml>=4.2.0
|
||||
pyenchant>=3.0.0
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
%%~ 974e400180a99:7031beac91f75:Page
|
||||
This is a plain page with some text on it.
|
||||
|
||||
This file should receive no special formatting, but the text will always be left aligned and the content will always start on a fresh page when the project is exported.
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="0.5.2" hexVersion="0x000502f0" fileVersion="1.0" saveCount="158" autoCount="21" timeStamp="2020-05-23 21:12:53">
|
||||
<novelWriterXML appVersion="0.6" hexVersion="0x000600f0" fileVersion="1.0" saveCount="168" autoCount="26" timeStamp="2020-05-24 18:33:34">
|
||||
<project>
|
||||
<name>Sample Project</name>
|
||||
<title>Sample Project</title>
|
||||
@@ -11,8 +11,8 @@
|
||||
<spellCheck>True</spellCheck>
|
||||
<autoOutline>True</autoOutline>
|
||||
<lastEdited>636b6aa9b697b</lastEdited>
|
||||
<lastViewed>6a2d6d5f4f401</lastViewed>
|
||||
<lastWordCount>875</lastWordCount>
|
||||
<lastViewed>ba8a28a246524</lastViewed>
|
||||
<lastWordCount>914</lastWordCount>
|
||||
<autoReplace>
|
||||
<A>B</A>
|
||||
<B>E</B>
|
||||
@@ -44,7 +44,7 @@
|
||||
<entry blue="175" green="0" red="117">Main</entry>
|
||||
</importance>
|
||||
</settings>
|
||||
<content count="21">
|
||||
<content count="22">
|
||||
<item handle="7031beac91f75" order="0" parent="None">
|
||||
<name>Novel</name>
|
||||
<type>ROOT</type>
|
||||
@@ -65,20 +65,33 @@
|
||||
<paraCount>2</paraCount>
|
||||
<cursorPos>78</cursorPos>
|
||||
</item>
|
||||
<item handle="edca4be2fcaf8" order="1" parent="7031beac91f75">
|
||||
<name>Part 1</name>
|
||||
<item handle="974e400180a99" order="1" parent="7031beac91f75">
|
||||
<name>Page</name>
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
<exported>True</exported>
|
||||
<layout>PAGE</layout>
|
||||
<charCount>208</charCount>
|
||||
<wordCount>40</wordCount>
|
||||
<paraCount>2</paraCount>
|
||||
<cursorPos>213</cursorPos>
|
||||
</item>
|
||||
<item handle="edca4be2fcaf8" order="2" parent="7031beac91f75">
|
||||
<name>Part One</name>
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
<exported>True</exported>
|
||||
<layout>PARTITION</layout>
|
||||
<charCount>0</charCount>
|
||||
<wordCount>0</wordCount>
|
||||
<paraCount>0</paraCount>
|
||||
<charCount>23</charCount>
|
||||
<wordCount>5</wordCount>
|
||||
<paraCount>1</paraCount>
|
||||
<cursorPos>0</cursorPos>
|
||||
</item>
|
||||
<item handle="e7ded148d6e4a" order="2" parent="7031beac91f75">
|
||||
<item handle="e7ded148d6e4a" order="3" parent="7031beac91f75">
|
||||
<name>A Folder</name>
|
||||
<type>FOLDER</type>
|
||||
<class>NOVEL</class>
|
||||
@@ -109,7 +122,7 @@
|
||||
<charCount>1199</charCount>
|
||||
<wordCount>216</wordCount>
|
||||
<paraCount>7</paraCount>
|
||||
<cursorPos>0</cursorPos>
|
||||
<cursorPos>527</cursorPos>
|
||||
</item>
|
||||
<item handle="bc0cbd2a407f3" order="2" parent="e7ded148d6e4a">
|
||||
<name>Another Scene</name>
|
||||
@@ -277,9 +290,9 @@
|
||||
<expanded>False</expanded>
|
||||
<exported>True</exported>
|
||||
<layout>SCENE</layout>
|
||||
<charCount>30</charCount>
|
||||
<wordCount>6</wordCount>
|
||||
<paraCount>1</paraCount>
|
||||
<charCount>0</charCount>
|
||||
<wordCount>0</wordCount>
|
||||
<paraCount>0</paraCount>
|
||||
<cursorPos>36</cursorPos>
|
||||
</item>
|
||||
</content>
|
||||
Reference in New Issue
Block a user