Merge pull request #816 from vkbo/style

Style
This commit is contained in:
Veronica Berglyd Olsen
2021-06-25 21:56:03 +02:00
committed by GitHub
93 changed files with 986 additions and 725 deletions
+2 -2
View File
@@ -25,5 +25,5 @@ jobs:
flake8 tests --count --select=E9,F63,F7,F82 --show-source --statistics
- name: Coding Style Violations
run: |
flake8 nw --count --max-line-length=99 --ignore E203,E221,E226,E228,E241,E251,E261,E266,E302,E305 --show-source --statistics
flake8 tests --count --max-line-length=99 --ignore E203,E221,E226,E228,E241,E251,E261,E266,E302,E305 --show-source --statistics
flake8 nw --count --max-line-length=99 --ignore E203,E221,E226,E228,E241,E251 --show-source --statistics
flake8 tests --count --max-line-length=99 --ignore E203,E221,E226,E228,E241,E251 --show-source --statistics
+14 -27
View File
@@ -77,28 +77,28 @@ The `setup.cfg` file in the root of this project has the following settings for
matches the coding standard:
```conf
[flake8]
ignore = E203,E221,E226,E228,E241,E251,E261,E266,E302,E305
ignore = E203,E221,E226,E228,E241,E251
max-line-length = 99
exclude = docs/*
```
The command line equivalent, with reporting, is:
```bash
flake8 . --count --ignore E203,E221,E226,E228,E241,E251,E261,E266,E302,E305 --max-line-length=99 --show-source --statistics
flake8 . --count --ignore E203,E221,E226,E228,E241,E251 --max-line-length=99 --show-source --statistics
```
Passing this check is required before contributions are merged into the `main` branch. This is
checked automatically when you make a pull request. You can run the `flake8` command locally to
check beforehand. The full command will give you a detailed description of the code lines that do
not conform to the standard.
Passing this check is required before contributions are merged into the `main`, `testing` or `dev`
branches. This is checked automatically when you make a pull request. You can run the `flake8`
command locally to check beforehand. The full command will give you a detailed description of the
code lines that do not conform to the standard.
## Ignored Errors
Some `flake8` error codes are ignored for this project for various reasons. The source also uses
camelCase function and variable names. This is the standard for the Qt libraries novelWriter
integrates with. It also happens to be the author's personal preferences.
integrates with. It also happens to be the author's personal preferences. (Yay!)
The reason behind the other ignored error codes are listed below. Many of them are due to PEP8 not
The reason behind the other ignored error codes are listed below. Most of them are due to PEP8 not
permitting column alignment as opposed to many other coding styles, like for instance for Go. I
find them useful in regions of bulk value assignments. There's a reason why tables are more
readable than lists. They should be used sparingly though.
@@ -113,10 +113,12 @@ The ignored errors are all `pycodestyle` errors, and they are documented
**Reason:** Column alignment.
**E226:** missing whitespace around arithmetic operator
**Reason:** This doesn't actually follow the PEP8 recommendation of grouping longer equations by
operator precedence like `2*a + 3*b` instead of `a * a + 3 * b`. Generally, don't use spaces around
`*`, `/` and `**`, but _do_ use spaces around `+` and `-`. For appending strings, the spaces can be
dropped. Don't use the `+` operator for appending multiple strings. Use formatting instead.
**Reason:** This doesn't actually follow the
[PEP8 recommendation](https://www.python.org/dev/peps/pep-0008/#other-recommendations)
of grouping longer equations by operator precedence like `2*a + 3*b` instead of `a * a + 3 * b`.
Generally, don't use spaces around `*`, `/` and `**`, but _do_ use spaces around `+` and `-`.
For appending strings, the spaces can be dropped. Don't use the `+` operator for appending multiple
strings. Use formatting instead.
**E228** missing whitespace around modulo operator
**Reason:** See reason for E226. Formatting `%` like `/` and `*` should be possible.
@@ -126,18 +128,3 @@ dropped. Don't use the `+` operator for appending multiple strings. Use formatti
**E251:** unexpected spaces around keyword / parameter equals
**Reason:** Column alignment.
**E261:** at least two spaces before inline comment
**Reason:** With syntax highlighting, this one doesn't make much sense.
**E266:** too many leading # for block comment
**Reason:** In the source multiple `#`s is sometimes used to indicate importance or heading level,
like markdown headers.
**E302:** expected 2 blank lines, found 0
**Reason:** Applies to classes. Instead, end classes with a comment like `# END Class ClassName` to
make it easier to see which class just ended. The double line break is then redundant.
**E305:** expected 2 blank lines after end of function or class
**Reason:** Instead, _always_ end a function with a `return`, preferably indented at function
level. The end of the function is then clear.
-1
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
#
# Configuration file for the Sphinx documentation builder.
#
-1
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
Qt Base Translation File
========================
+3 -4
View File
@@ -1,5 +1,4 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
novelWriter Start Script
==========================
@@ -8,9 +7,9 @@ import os
import sys
try:
import PyQt5.QtWidgets # noqa: F401
import PyQt5.QtGui # noqa: F401
import PyQt5.QtCore # noqa: F401
import PyQt5.QtWidgets # noqa: F401
import PyQt5.QtGui # noqa: F401
import PyQt5.QtCore # noqa: F401
except Exception:
print("ERROR: Failed to load dependency PyQt5")
sys.exit(1)
+6 -2
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter Init File
=======================
@@ -90,15 +89,19 @@ __docurl__ = "https://novelwriter.readthedocs.io"
# Add verbose logging level
VERBOSE = 5
logging.addLevelName(VERBOSE, "VERBOSE")
def logVerbose(self, message, *args, **kws):
if self.isEnabledFor(VERBOSE):
self._log(VERBOSE, message, args, **kws)
logging.Logger.verbose = logVerbose
# Initiating logging
logger = logging.getLogger(__name__)
##
# Main Program
##
@@ -106,6 +109,7 @@ logger = logging.getLogger(__name__)
# Load the main config as a global object
CONFIG = Config()
def main(sysArgs=None):
"""Parses command line, sets up logging, and launches main GUI.
"""
@@ -229,7 +233,7 @@ def main(sysArgs=None):
errorCode |= 16
try:
import lxml # noqa: F401
import lxml # noqa: F401
except ImportError:
errorData.append("Python module 'lxml' is missing.")
errorCode |= 32
+29 -11
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter Common Functions
==============================
@@ -36,6 +35,7 @@ from nw.constants import nwConst, nwUnicode
logger = logging.getLogger(__name__)
def checkString(value, default, allowNone=False):
"""Check if a variable is a string or a none.
"""
@@ -48,6 +48,7 @@ def checkString(value, default, allowNone=False):
return str(value)
return default
def checkInt(value, default, allowNone=False):
"""Check if a variable is an integer or a none.
"""
@@ -61,6 +62,7 @@ def checkInt(value, default, allowNone=False):
except Exception:
return default
def checkBool(value, default, allowNone=False):
"""Check if a variable is a boolean or a none.
"""
@@ -85,6 +87,7 @@ def checkBool(value, default, allowNone=False):
return default
return default
def checkHandle(value, default, allowNone=False):
"""Check if a value is a handle.
"""
@@ -97,6 +100,7 @@ def checkHandle(value, default, allowNone=False):
return str(value)
return default
def isHandle(theString):
"""Check if a string is a valid novelWriter handle.
Note: This is case sensitive. Must be lower case!
@@ -110,6 +114,7 @@ def isHandle(theString):
return False
return True
def isTitleTag(theString):
"""Check if a string is a valid title string.
"""
@@ -124,21 +129,25 @@ def isTitleTag(theString):
return False
return True
def isItemClass(theString):
"""Check if an item is a calid nwItemClass identifier.
"""
return theString in nwItemClass.__members__
def isItemType(theString):
"""Check if an item is a calid nwItemType identifier.
"""
return theString in nwItemType.__members__
def isItemLayout(theString):
"""Check if an item is a calid nwItemLayout identifier.
"""
return theString in nwItemLayout.__members__
def hexToInt(value, default=0):
"""Convert a hex string to an integer.
"""
@@ -149,6 +158,7 @@ def hexToInt(value, default=0):
return default
return default
def formatInt(theInt):
"""Formats an integer with k, M, G etc.
"""
@@ -168,6 +178,7 @@ def formatInt(theInt):
return str(theInt)
def formatTimeStamp(theTime, fileSafe=False):
"""Take a number (on the format returned by time.time()) and convert
it to a timestamp string.
@@ -177,6 +188,7 @@ def formatTimeStamp(theTime, fileSafe=False):
else:
return datetime.fromtimestamp(theTime).strftime(nwConst.FMT_TSTAMP)
def formatTime(tS):
"""Format a time in seconds in HH:MM:SS format or d-HH:MM:SS format
if a full day or longer.
@@ -188,6 +200,7 @@ def formatTime(tS):
return f"{tS//3600:02d}:{tS%3600//60:02d}:{tS%60:02d}"
return "ERROR"
def splitVersionNumber(vString):
""" Splits a version string on the form aa.bb.cc into major, minor
and patch, and computes an integer value aabbcc.
@@ -211,6 +224,7 @@ def splitVersionNumber(vString):
return [vMajor, vMinor, vPatch, vInt]
def transferCase(theSource, theTarget):
"""Transfers the case of the source word to the target word. This
will consider all upper or lower, and first char capitalisation.
@@ -232,6 +246,7 @@ def transferCase(theSource, theTarget):
return theResult
def fuzzyTime(secDiff):
"""Converts a time difference in seconds into a fuzzy time string.
"""
@@ -247,43 +262,43 @@ def fuzzyTime(secDiff):
return QCoreApplication.translate(
"Common", "a minute ago"
)
elif secDiff < 3300: # 55 minutes
elif secDiff < 3300: # 55 minutes
return QCoreApplication.translate(
"Common", "{0} minutes ago"
).format(int(round(secDiff/60)))
elif secDiff < 5400: # 90 minutes
elif secDiff < 5400: # 90 minutes
return QCoreApplication.translate(
"Common", "an hour ago"
)
elif secDiff < 84600: # 23.5 hours
elif secDiff < 84600: # 23.5 hours
return QCoreApplication.translate(
"Common", "{0} hours ago"
).format(int(round(secDiff/3600)))
elif secDiff < 129600: # 1.5 days
elif secDiff < 129600: # 1.5 days
return QCoreApplication.translate(
"Common", "a day ago"
)
elif secDiff < 561600: # 6.5 days
elif secDiff < 561600: # 6.5 days
return QCoreApplication.translate(
"Common", "{0} days ago"
).format(int(round(secDiff/86400)))
elif secDiff < 907200: # 10.5 days
elif secDiff < 907200: # 10.5 days
return QCoreApplication.translate(
"Common", "a week ago"
)
elif secDiff < 2419200: # 28 days
elif secDiff < 2419200: # 28 days
return QCoreApplication.translate(
"Common", "{0} weeks ago"
).format(int(round(secDiff/604800)))
elif secDiff < 3888000: # 45 days
elif secDiff < 3888000: # 45 days
return QCoreApplication.translate(
"Common", "a month ago"
)
elif secDiff < 29808000: # 345 days
elif secDiff < 29808000: # 345 days
return QCoreApplication.translate(
"Common", "{0} months ago"
).format(int(round(secDiff/2592000)))
elif secDiff < 47336400: # 1.5 years
elif secDiff < 47336400: # 1.5 years
return QCoreApplication.translate(
"Common", "a year ago"
)
@@ -292,6 +307,7 @@ def fuzzyTime(secDiff):
"Common", "{0} years ago"
).format(int(round(secDiff/31557600)))
def makeFileNameSafe(theText):
"""Returns a filename safe version of the text.
"""
@@ -301,6 +317,7 @@ def makeFileNameSafe(theText):
cleanName += c
return cleanName
def getGuiItem(theName):
"""Returns a QtWidget based on its objectName.
"""
@@ -309,6 +326,7 @@ def getGuiItem(theName):
return qWidget
return None
def numberToRoman(numVal, isLower=False):
"""Convert an integer to a roman number.
"""
+73 -73
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter Config Class
==========================
@@ -45,6 +44,7 @@ from nw.constants import nwConst, nwFiles, nwUnicode
logger = logging.getLogger(__name__)
class Config:
CNF_STR = 0
@@ -82,27 +82,27 @@ class Config:
self.helpPath = None # The full path to the novelwriter .qhc help file
# Runtime Settings and Variables
self.confChanged = False # True whenever the config has chenged, false after save
self.hasHelp = False # True if the Qt help files are present in the assets folder
self.confChanged = False # True whenever the config has chenged, false after save
self.hasHelp = False # True if the Qt help files are present in the assets folder
## General
# General
self.guiTheme = "default"
self.guiSyntax = "default_light"
self.guiIcons = "typicons_colour_light"
self.guiDark = False # Load icons for dark backgrounds, if available
self.guiFont = "" # Defaults to system default font
self.guiFontSize = 11 # Is overridden if system default is loaded
self.guiScale = 1.0 # Set automatically by Theme class
self.lastNotes = "0x0" # The latest release notes that have been shown
self.guiDark = False # Load icons for dark backgrounds, if available
self.guiFont = "" # Defaults to system default font
self.guiFontSize = 11 # Is overridden if system default is loaded
self.guiScale = 1.0 # Set automatically by Theme class
self.lastNotes = "0x0" # The latest release notes that have been shown
## Localisation
# Localisation
self.qLocal = QLocale.system()
self.guiLang = self.qLocal.name()
self.qtLangPath = QLibraryInfo.location(QLibraryInfo.TranslationsPath)
self.nwLangPath = None
self.qtTrans = {}
## Sizes
# Sizes
self.winGeometry = [1200, 650]
self.prefGeometry = [700, 615]
self.treeColWidth = [200, 50, 30]
@@ -114,54 +114,54 @@ class Config:
self.outlnPanePos = [500, 150]
self.isFullScreen = False
## Features
self.hideVScroll = False # Hide vertical scroll bars on main widgets
self.hideHScroll = False # Hide horizontal scroll bars on main widgets
# Features
self.hideVScroll = False # Hide vertical scroll bars on main widgets
self.hideHScroll = False # Hide horizontal scroll bars on main widgets
## Project
self.autoSaveProj = 60 # Interval for auto-saving project in seconds
self.autoSaveDoc = 30 # Interval for auto-saving document in seconds
# Project
self.autoSaveProj = 60 # Interval for auto-saving project in seconds
self.autoSaveDoc = 30 # Interval for auto-saving document in seconds
## Text Editor
self.textFont = None # Editor font
self.textSize = 12 # Editor font size
self.textFixedW = True # Keep editor text fixed width
self.textWidth = 600 # Editor text width
self.textMargin = 40 # Editor/viewer text margin
self.tabWidth = 40 # Editor tabulator width
# Text Editor
self.textFont = None # Editor font
self.textSize = 12 # Editor font size
self.textFixedW = True # Keep editor text fixed width
self.textWidth = 600 # Editor text width
self.textMargin = 40 # Editor/viewer text margin
self.tabWidth = 40 # Editor tabulator width
self.focusWidth = 800 # Focus Mode text width
self.hideFocusFooter = False # Hide document footer in Focus Mode
self.showFullPath = True # Show full document path in editor header
self.autoSelect = True # Auto-select word when applying format with no selection
self.focusWidth = 800 # Focus Mode text width
self.hideFocusFooter = False # Hide document footer in Focus Mode
self.showFullPath = True # Show full document path in editor header
self.autoSelect = True # Auto-select word when applying format with no selection
self.doJustify = False # Justify text
self.showTabsNSpaces = False # Show tabs and spaces in edior
self.showLineEndings = False # Show line endings in editor
self.showMultiSpaces = True # Highlight multiple spaces in the text
self.doJustify = False # Justify text
self.showTabsNSpaces = False # Show tabs and spaces in edior
self.showLineEndings = False # Show line endings in editor
self.showMultiSpaces = True # Highlight multiple spaces in the text
self.doReplace = True # Enable auto-replace as you type
self.doReplaceSQuote = True # Smart single quotes
self.doReplaceDQuote = True # Smart double quotes
self.doReplaceDash = True # Replace multiple hyphens with dashes
self.doReplaceDots = True # Replace three dots with ellipsis
self.doReplace = True # Enable auto-replace as you type
self.doReplaceSQuote = True # Smart single quotes
self.doReplaceDQuote = True # Smart double quotes
self.doReplaceDash = True # Replace multiple hyphens with dashes
self.doReplaceDots = True # Replace three dots with ellipsis
self.scrollPastEnd = True # Allow scrolling past end of document
self.autoScroll = False # Typewriter-like scrolling
self.autoScrollPos = 30 # Start point for typewriter-like scrolling
self.scrollPastEnd = True # Allow scrolling past end of document
self.autoScroll = False # Typewriter-like scrolling
self.autoScrollPos = 30 # Start point for typewriter-like scrolling
self.wordCountTimer = 5.0 # Interval for word count update in seconds
self.bigDocLimit = 800 # Size threshold for heavy editor features in kilobytes
self.wordCountTimer = 5.0 # Interval for word count update in seconds
self.bigDocLimit = 800 # Size threshold for heavy editor features in kilobytes
self.highlightQuotes = True # Highlight text in quotes
self.allowOpenSQuote = False # Allow open-ended single quotes
self.allowOpenDQuote = True # Allow open-ended double quotes
self.highlightEmph = True # Add colour to text emphasis
self.highlightQuotes = True # Highlight text in quotes
self.allowOpenSQuote = False # Allow open-ended single quotes
self.allowOpenDQuote = True # Allow open-ended double quotes
self.highlightEmph = True # Add colour to text emphasis
self.stopWhenIdle = True # Stop the status bar clock when the user is idle
self.userIdleTime = 300 # Time of inactivity to consider user idle
self.stopWhenIdle = True # Stop the status bar clock when the user is idle
self.userIdleTime = 300 # Time of inactivity to consider user idle
## User-Selected Symbols
# User-Selected Symbols
self.fmtApostrophe = nwUnicode.U_RSQUO
self.fmtSingleQuotes = [nwUnicode.U_LSQUO, nwUnicode.U_RSQUO]
self.fmtDoubleQuotes = [nwUnicode.U_LDQUO, nwUnicode.U_RDQUO]
@@ -169,11 +169,11 @@ class Config:
self.fmtPadAfter = ""
self.fmtPadThin = False
## Spell Checking
# Spell Checking
self.spellTool = None
self.spellLanguage = None
## Search Bar Switches
# Search Bar Switches
self.searchCase = False
self.searchWord = False
self.searchRegEx = False
@@ -181,12 +181,12 @@ class Config:
self.searchNextFile = False
self.searchMatchCap = False
## Backup
# Backup
self.backupPath = ""
self.backupOnClose = False
self.askBeforeBackup = True
## State
# State
self.showRefPanel = True
self.viewComments = True
self.viewSynopsis = True
@@ -235,8 +235,8 @@ class Config:
self.kernelVer = "Unknown"
# Packages
self.hasEnchant = False # The pyenchant package
self.hasAssistant = False # The Qt Assistant executable
self.hasEnchant = False # The pyenchant package
self.hasAssistant = False # The Qt Assistant executable
# Recent Cache
self.recentProj = {}
@@ -382,9 +382,9 @@ class Config:
self.qtTrans = {}
langList = [
(self.qtLangPath, "qtbase"), # Qt 5.x
(self.nwLangPath, "qtbase"), # Alternative Qt 5.x
(self.nwLangPath, "nw"), # novelWriter
(self.qtLangPath, "qtbase"), # Qt 5.x
(self.nwLangPath, "qtbase"), # Alternative Qt 5.x
(self.nwLangPath, "nw"), # novelWriter
]
for lngPath, lngBase in langList:
for lngCode in self.qLocal.uiLanguages():
@@ -445,7 +445,7 @@ class Config:
self.errData.append(str(e))
return False
## Main
# Main
cnfSec = "Main"
self.guiTheme = self._parseLine(
cnfParse, cnfSec, "theme", self.CNF_STR, self.guiTheme
@@ -472,7 +472,7 @@ class Config:
cnfParse, cnfSec, "guilang", self.CNF_STR, self.guiLang
)
## Sizes
# Sizes
cnfSec = "Sizes"
self.winGeometry = self._parseLine(
cnfParse, cnfSec, "geometry", self.CNF_I_LST, self.winGeometry
@@ -511,7 +511,7 @@ class Config:
cnfParse, cnfSec, "hidehscroll", self.CNF_BOOL, self.hideHScroll
)
## Project
# Project
cnfSec = "Project"
self.autoSaveProj = self._parseLine(
cnfParse, cnfSec, "autosaveproject", self.CNF_INT, self.autoSaveProj
@@ -520,7 +520,7 @@ class Config:
cnfParse, cnfSec, "autosavedoc", self.CNF_INT, self.autoSaveDoc
)
## Editor
# Editor
cnfSec = "Editor"
self.textFont = self._parseLine(
cnfParse, cnfSec, "textfont", self.CNF_STR, self.textFont
@@ -631,7 +631,7 @@ class Config:
cnfParse, cnfSec, "useridletime", self.CNF_INT, self.userIdleTime
)
## Backup
# Backup
cnfSec = "Backup"
self.backupPath = self._parseLine(
cnfParse, cnfSec, "backuppath", self.CNF_STR, self.backupPath
@@ -643,7 +643,7 @@ class Config:
cnfParse, cnfSec, "askbeforebackup", self.CNF_BOOL, self.askBeforeBackup
)
## State
# State
cnfSec = "State"
self.showRefPanel = self._parseLine(
cnfParse, cnfSec, "showrefpanel", self.CNF_BOOL, self.showRefPanel
@@ -673,7 +673,7 @@ class Config:
cnfParse, cnfSec, "searchmatchcap", self.CNF_BOOL, self.searchMatchCap
)
## Path
# Path
cnfSec = "Path"
self.lastPath = self._parseLine(
cnfParse, cnfSec, "lastpath", self.CNF_STR, self.lastPath
@@ -704,7 +704,7 @@ class Config:
# Set options
## Main
# Main
cnfSec = "Main"
cnfParse.add_section(cnfSec)
cnfParse.set(cnfSec, "timestamp", formatTimeStamp(time()))
@@ -717,7 +717,7 @@ class Config:
cnfParse.set(cnfSec, "lastnotes", str(self.lastNotes))
cnfParse.set(cnfSec, "guilang", str(self.guiLang))
## Sizes
# Sizes
cnfSec = "Sizes"
cnfParse.add_section(cnfSec)
cnfParse.set(cnfSec, "geometry", self._packList(self.winGeometry))
@@ -733,13 +733,13 @@ class Config:
cnfParse.set(cnfSec, "hidevscroll", str(self.hideVScroll))
cnfParse.set(cnfSec, "hidehscroll", str(self.hideHScroll))
## Project
# Project
cnfSec = "Project"
cnfParse.add_section(cnfSec)
cnfParse.set(cnfSec, "autosaveproject", str(self.autoSaveProj))
cnfParse.set(cnfSec, "autosavedoc", str(self.autoSaveDoc))
## Editor
# Editor
cnfSec = "Editor"
cnfParse.add_section(cnfSec)
cnfParse.set(cnfSec, "textfont", str(self.textFont))
@@ -779,14 +779,14 @@ class Config:
cnfParse.set(cnfSec, "stopwhenidle", str(self.stopWhenIdle))
cnfParse.set(cnfSec, "useridletime", str(self.userIdleTime))
## Backup
# Backup
cnfSec = "Backup"
cnfParse.add_section(cnfSec)
cnfParse.set(cnfSec, "backuppath", str(self.backupPath))
cnfParse.set(cnfSec, "backuponclose", str(self.backupOnClose))
cnfParse.set(cnfSec, "askbeforebackup", str(self.askBeforeBackup))
## State
# State
cnfSec = "State"
cnfParse.add_section(cnfSec)
cnfParse.set(cnfSec, "showrefpanel", str(self.showRefPanel))
@@ -799,7 +799,7 @@ class Config:
cnfParse.set(cnfSec, "searchnextfile", str(self.searchNextFile))
cnfParse.set(cnfSec, "searchmatchcap", str(self.searchMatchCap))
## Path
# Path
cnfSec = "Path"
cnfParse.add_section(cnfSec)
cnfParse.set(cnfSec, "lastpath", str(self.lastPath))
@@ -1119,7 +1119,7 @@ class Config:
"""Cheks if we have the optional packages used by some features.
"""
try:
import enchant # noqa: F401
import enchant # noqa: F401
self.hasEnchant = True
logger.debug("Checking package 'pyenchant': OK")
except Exception:
+83 -74
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter Constants
=======================
@@ -28,22 +27,24 @@ from PyQt5.QtCore import QCoreApplication, QT_TRANSLATE_NOOP
from nw.enum import nwItemClass, nwItemLayout, nwItemType, nwOutline
def trConst(tString):
"""Wrapper function for locally translating constants.
"""
return QCoreApplication.translate("Constant", tString)
class nwConst():
# Date and Time Formats
FMT_TSTAMP = "%Y-%m-%d %H:%M:%S" # Default format
FMT_FSTAMP = "%Y-%m-%d %H.%M.%S" # FileName safe format
FMT_DSTAMP = "%Y-%m-%d" # Date only format
FMT_TSTAMP = "%Y-%m-%d %H:%M:%S" # Default format
FMT_FSTAMP = "%Y-%m-%d %H.%M.%S" # FileName safe format
FMT_DSTAMP = "%Y-%m-%d" # Date only format
# Various Hard Limits
MAX_DEPTH = 30 # Maximum folder depth of a project
MAX_DOCSIZE = 5000000 # Maxium size of a single document
MAX_BUILDSIZE = 10000000 # Maxium size of a project build
MAX_DEPTH = 30 # Maximum folder depth of a project
MAX_DOCSIZE = 5000000 # Maxium size of a single document
MAX_BUILDSIZE = 10000000 # Maxium size of a project build
# Spell Check Providers
SP_INTERNAL = "internal"
@@ -51,6 +52,7 @@ class nwConst():
# END Class nwConst
class nwLists():
"""Lists used for grouping various other constants.
"""
@@ -65,6 +67,7 @@ class nwLists():
# END Class nwLists
class nwRegEx():
FMT_EI = r"(?<![\w\\])(_)(?![\s_])(.+?)(?<![\s\\])(\1)(?!\w)"
@@ -73,6 +76,7 @@ class nwRegEx():
# END Class nwRegEx
class nwFiles():
PROJ_FILE = "nwProject.nwx"
@@ -87,6 +91,7 @@ class nwFiles():
# END Class nwFiles
class nwKeyWords:
TAG_KEY = "@tag"
@@ -121,6 +126,7 @@ class nwKeyWords:
# END Class nwKeyWords
class nwLabels():
CLASS_NAME = {
@@ -218,6 +224,7 @@ class nwLabels():
# END Class nwLabels
class nwQuotes():
"""Allowed quotation marks.
Source: https://en.wikipedia.org/wiki/Quotation_mark
@@ -249,79 +256,80 @@ class nwQuotes():
# END Class nwQuotes
class nwUnicode:
"""Supported unicode character constants and their HTML equivalents.
"""
# Unicode Constants
# =================
## Quotation Marks
U_QUOT = "\u0022" # Quotation mark
U_APOS = "\u0027" # Apostrophe
U_LAQUO = "\u00ab" # Left-pointing double angle quotation mark
U_RAQUO = "\u00bb" # Right-pointing double angle quotation mark
U_LSQUO = "\u2018" # Left single quotation mark
U_RSQUO = "\u2019" # Right single quotation mark
U_SBQUO = "\u201a" # Single low-9 quotation mark
U_SUQUO = "\u201b" # Single high-reversed-9 quotation mark
U_LDQUO = "\u201c" # Left double quotation mark
U_RDQUO = "\u201d" # Right double quotation mark
U_BDQUO = "\u201e" # Double low-9 quotation mark
U_UDQUO = "\u201f" # Double high-reversed-9 quotation mark
U_LSAQUO = "\u2039" # Single left-pointing angle quotation mark
U_RSAQUO = "\u203a" # Single right-pointing angle quotation mark
U_BDRQUO = "\u2e42" # Double low-reversed-9 quotation mark
U_LCQUO = "\u300c" # Left corner bracket
U_RCQUO = "\u300d" # Right corner bracket
U_LWCQUO = "\u300e" # Left white corner bracket
U_RWCQUO = "\u300f" # Right white corner bracket
# Quotation Marks
U_QUOT = "\u0022" # Quotation mark
U_APOS = "\u0027" # Apostrophe
U_LAQUO = "\u00ab" # Left-pointing double angle quotation mark
U_RAQUO = "\u00bb" # Right-pointing double angle quotation mark
U_LSQUO = "\u2018" # Left single quotation mark
U_RSQUO = "\u2019" # Right single quotation mark
U_SBQUO = "\u201a" # Single low-9 quotation mark
U_SUQUO = "\u201b" # Single high-reversed-9 quotation mark
U_LDQUO = "\u201c" # Left double quotation mark
U_RDQUO = "\u201d" # Right double quotation mark
U_BDQUO = "\u201e" # Double low-9 quotation mark
U_UDQUO = "\u201f" # Double high-reversed-9 quotation mark
U_LSAQUO = "\u2039" # Single left-pointing angle quotation mark
U_RSAQUO = "\u203a" # Single right-pointing angle quotation mark
U_BDRQUO = "\u2e42" # Double low-reversed-9 quotation mark
U_LCQUO = "\u300c" # Left corner bracket
U_RCQUO = "\u300d" # Right corner bracket
U_LWCQUO = "\u300e" # Left white corner bracket
U_RWCQUO = "\u300f" # Right white corner bracket
## Punctuation
U_FGDASH = "\u2012" # Figure dash
U_ENDASH = "\u2013" # Short dash
U_EMDASH = "\u2014" # Long dash
U_HBAR = "\u2015" # Horizontal bar
U_HELLIP = "\u2026" # Ellipsis
U_MAPOSS = "\u02bc" # Modifier letter single apostrophe
U_PRIME = "\u2032" # Prime
U_DPRIME = "\u2033" # Double prime
# Punctuation
U_FGDASH = "\u2012" # Figure dash
U_ENDASH = "\u2013" # Short dash
U_EMDASH = "\u2014" # Long dash
U_HBAR = "\u2015" # Horizontal bar
U_HELLIP = "\u2026" # Ellipsis
U_MAPOSS = "\u02bc" # Modifier letter single apostrophe
U_PRIME = "\u2032" # Prime
U_DPRIME = "\u2033" # Double prime
## Spaces and Lines
U_NBSP = "\u00a0" # Non-breaking space
U_THSP = "\u2009" # Thin space
U_THNBSP = "\u202f" # Thin non-breaking space
U_ENSP = "\u2002" # Short (en) space
U_EMSP = "\u2003" # Long (em) space
U_LSEP = "\u2028" # Line separator
U_PSEP = "\u2029" # Paragraph separator
# Spaces and Lines
U_NBSP = "\u00a0" # Non-breaking space
U_THSP = "\u2009" # Thin space
U_THNBSP = "\u202f" # Thin non-breaking space
U_ENSP = "\u2002" # Short (en) space
U_EMSP = "\u2003" # Long (em) space
U_LSEP = "\u2028" # Line separator
U_PSEP = "\u2029" # Paragraph separator
## Symbols
U_CHECK = "\u2714" # Heavy check mark
U_CROSS = "\u2715" # Heavy cross mark
U_BULL = "\u2022" # List bullet
U_TRBULL = "\u2023" # Triangle bullet
U_HYBULL = "\u2043" # Hyphen bullet
U_FLOWER = "\u2055" # Flower punctuation mark
U_PERMIL = "\u2030" # Per mille sign
U_DEGREE = "\u00b0" # Degree symbol
U_MINUS = "\u2212" # Minus sign
U_TIMES = "\u00d7" # Multiplaction sign
U_DIVIDE = "\u00f7" # Division sign
# Symbols
U_CHECK = "\u2714" # Heavy check mark
U_CROSS = "\u2715" # Heavy cross mark
U_BULL = "\u2022" # List bullet
U_TRBULL = "\u2023" # Triangle bullet
U_HYBULL = "\u2043" # Hyphen bullet
U_FLOWER = "\u2055" # Flower punctuation mark
U_PERMIL = "\u2030" # Per mille sign
U_DEGREE = "\u00b0" # Degree symbol
U_MINUS = "\u2212" # Minus sign
U_TIMES = "\u00d7" # Multiplaction sign
U_DIVIDE = "\u00f7" # Division sign
## Arrows
U_UTRI = "\u25b2" # Up-pointing triangle
U_UTRIS = "\u25b4" # Up-pointing triangle, small
U_RTRI = "\u25b6" # Right-pointing triangle
U_RTRIS = "\u25b8" # Right-pointing triangle, small
U_DTRI = "\u25bc" # Down-pointing triangle
U_DTRIS = "\u25be" # Down-pointing triangle, small
U_LTRI = "\u25c0" # Left-pointing triangle
U_LTRIS = "\u25c2" # Left-pointing triangle, small
# Arrows
U_UTRI = "\u25b2" # Up-pointing triangle
U_UTRIS = "\u25b4" # Up-pointing triangle, small
U_RTRI = "\u25b6" # Right-pointing triangle
U_RTRIS = "\u25b8" # Right-pointing triangle, small
U_DTRI = "\u25bc" # Down-pointing triangle
U_DTRIS = "\u25be" # Down-pointing triangle, small
U_LTRI = "\u25c0" # Left-pointing triangle
U_LTRIS = "\u25c2" # Left-pointing triangle, small
# HTML Equivalents
# ================
## Quotes
# Quotes
H_QUOT = "&quot;"
H_APOS = "&#39;"
H_LAQUO = "&laquo;"
@@ -342,7 +350,7 @@ class nwUnicode:
H_LWCQUO = "&#12302;"
H_RWCQUO = "&#12303;"
## Punctuation
# Punctuation
H_FGDASH = "&#8210;"
H_ENDASH = "&ndash;"
H_EMDASH = "&mdash;"
@@ -352,14 +360,14 @@ class nwUnicode:
H_PRIME = "&prime;"
H_DPRIME = "&#8243;"
## Spaces
# Spaces
H_NBSP = "&nbsp;"
H_THSP = "&thinsp;"
H_THNBSP = "&#8239;"
H_ENSP = "&ensp;"
H_EMSP = "&emsp;"
## Symbols
# Symbols
H_CHECK = "&#10004;"
H_CROSS = "&#10005;"
H_BULL = "&bull;"
@@ -372,7 +380,7 @@ class nwUnicode:
H_TIMES = "&times;"
H_DIVIDE = "&divide;"
## Arrows
# Arrows
H_UTRI = "&#9650;"
H_UTRIS = "&#9652;"
H_RTRI = "&#9654;"
@@ -384,10 +392,11 @@ class nwUnicode:
# END Class nwUnicode
class nwHtmlUnicode():
U_TO_H = {
## Quotes
# Quotes
nwUnicode.U_QUOT : nwUnicode.H_QUOT,
nwUnicode.U_APOS : nwUnicode.H_APOS,
nwUnicode.U_LAQUO : nwUnicode.H_LAQUO,
@@ -408,7 +417,7 @@ class nwHtmlUnicode():
nwUnicode.U_LWCQUO : nwUnicode.H_LWCQUO,
nwUnicode.U_RWCQUO : nwUnicode.H_RWCQUO,
## Punctuation
# Punctuation
nwUnicode.U_FGDASH : nwUnicode.H_FGDASH,
nwUnicode.U_ENDASH : nwUnicode.H_ENDASH,
nwUnicode.U_EMDASH : nwUnicode.H_EMDASH,
@@ -418,14 +427,14 @@ class nwHtmlUnicode():
nwUnicode.U_PRIME : nwUnicode.H_PRIME,
nwUnicode.U_DPRIME : nwUnicode.H_DPRIME,
## Spaces
# Spaces
nwUnicode.U_NBSP : nwUnicode.H_NBSP,
nwUnicode.U_THSP : nwUnicode.H_THSP,
nwUnicode.U_THNBSP : nwUnicode.H_THNBSP,
nwUnicode.U_ENSP : nwUnicode.H_ENSP,
nwUnicode.U_EMSP : nwUnicode.H_EMSP,
## Symbols
# Symbols
nwUnicode.U_CHECK : nwUnicode.H_CHECK,
nwUnicode.U_CROSS : nwUnicode.H_CROSS,
nwUnicode.U_BULL : nwUnicode.H_BULL,
+20 -1
View File
@@ -1,4 +1,23 @@
# -*- coding: utf-8 -*-
"""
novelWriter Core Init
=======================
This file is a part of novelWriter
Copyright 20182021, Veronica Berglyd Olsen
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
from nw.core.document import NWDoc
from nw.core.index import NWIndex, countWords
+6 -6
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter Project Document
==============================
@@ -32,6 +31,7 @@ from nw.common import isHandle
logger = logging.getLogger(__name__)
class NWDoc():
def __init__(self, theProject, theHandle):
@@ -39,11 +39,11 @@ class NWDoc():
self.theProject = theProject
# Internal Variables
self._theItem = None # The currently open item
self._docHandle = None # The handle of the currently open item
self._fileLoc = None # The file location of the currently open item
self._docMeta = {} # The meta data of the currently open item
self._docError = "" # The latest encountered IO error
self._theItem = None # The currently open item
self._docHandle = None # The handle of the currently open item
self._fileLoc = None # The file location of the currently open item
self._docMeta = {} # The meta data of the currently open item
self._docError = "" # The latest encountered IO error
if isHandle(theHandle):
self._docHandle = theHandle
+5 -4
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter Project Index
===========================
@@ -39,6 +38,7 @@ from nw.core.document import NWDoc
logger = logging.getLogger(__name__)
class NWIndex():
H_VALID = ("H0", "H1", "H2", "H3", "H4")
@@ -482,10 +482,10 @@ class NWIndex():
"""Scan a line starting with @ to check that it's valid. Then
split it up into its elements and positions as two arrays.
"""
theBits = [] # The elements of the string
thePos = [] # The absolute position of each element
theBits = [] # The elements of the string
thePos = [] # The absolute position of each element
aLine = aLine.rstrip() # Remove all trailing white spaces
aLine = aLine.rstrip() # Remove all trailing white spaces
nChar = len(aLine)
if nChar < 2:
return False, theBits, thePos
@@ -895,6 +895,7 @@ class NWIndex():
# END Class NWIndex
# =============================================================================================== #
# Simple Word Counter
# =============================================================================================== #
+8 -8
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter Project Item Class
================================
@@ -33,6 +32,7 @@ from nw.common import checkInt, isHandle, isItemClass, isItemLayout, isItemType
logger = logging.getLogger(__name__)
class NWItem():
def __init__(self, theProject):
@@ -51,11 +51,11 @@ class NWItem():
self.isExported = True
# Document Meta Data
self.charCount = 0 # Current character count
self.wordCount = 0 # Current word count
self.paraCount = 0 # Current paragraph count
self.initCount = 0 # Initial word count
self.cursorPos = 0 # Last cursor position
self.charCount = 0 # Current character count
self.wordCount = 0 # Current word count
self.paraCount = 0 # Current paragraph count
self.initCount = 0 # Initial word count
self.cursorPos = 0 # Last cursor position
return
@@ -251,7 +251,7 @@ class NWItem():
if isinstance(expState, str):
self.isExpanded = (expState == str(True))
else:
self.isExpanded = (expState == True) # noqa: E712
self.isExpanded = (expState == True) # noqa: E712
return
def setExported(self, expState):
@@ -260,7 +260,7 @@ class NWItem():
if isinstance(expState, str):
self.isExported = (expState == str(True))
else:
self.isExported = (expState == True) # noqa: E712
self.isExported = (expState == True) # noqa: E712
return
##
+1 -1
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter Project Options Cache
===================================
@@ -34,6 +33,7 @@ from nw.constants import nwFiles
logger = logging.getLogger(__name__)
class OptionState():
def __init__(self, theProject):
+36 -36
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter Project Wrapper
=============================
@@ -50,6 +49,7 @@ from nw.constants import trConst, nwFiles, nwLabels
logger = logging.getLogger(__name__)
class NWProject():
def __init__(self, theParent):
@@ -59,48 +59,48 @@ class NWProject():
self.mainConf = nw.CONFIG
# Core Elements
self.optState = OptionState(self) # Project-specific GUI options
self.projTree = NWTree(self) # The project tree
self.langData = {} # Localisation data
self.optState = OptionState(self) # Project-specific GUI options
self.projTree = NWTree(self) # The project tree
self.langData = {} # Localisation data
# Project Status
self.projOpened = 0 # The time stamp of when the project file was opened
self.projChanged = False # The project has unsaved changes
self.projAltered = False # The project has been altered this session
self.lockedBy = None # Data on which computer has the project open
self.saveCount = 0 # Meta data: number of saves
self.autoCount = 0 # Meta data: number of automatic saves
self.editTime = 0 # The accumulated edit time read from the project file
self.projOpened = 0 # The time stamp of when the project file was opened
self.projChanged = False # The project has unsaved changes
self.projAltered = False # The project has been altered this session
self.lockedBy = None # Data on which computer has the project open
self.saveCount = 0 # Meta data: number of saves
self.autoCount = 0 # Meta data: number of automatic saves
self.editTime = 0 # The accumulated edit time read from the project file
# Class Settings
self.projPath = None # The full path to where the currently open project is saved
self.projMeta = None # The full path to the project's meta data folder
self.projCache = None # The full path to the project's cache folder
self.projContent = None # The full path to the project's content folder
self.projDict = None # The spell check dictionary
self.projSpell = None # The spell check language, if different than default
self.projLang = None # The project language, used for builds
self.projFile = None # The file name of the project main XML file
self.projPath = None # The full path to where the currently open project is saved
self.projMeta = None # The full path to the project's meta data folder
self.projCache = None # The full path to the project's cache folder
self.projContent = None # The full path to the project's content folder
self.projDict = None # The spell check dictionary
self.projSpell = None # The spell check language, if different than default
self.projLang = None # The project language, used for builds
self.projFile = None # The file name of the project main XML file
# Project Meta
self.projName = "" # Project name (working title)
self.bookTitle = "" # The final title; should only be used for exports
self.bookAuthors = [] # A list of book authors
self.projName = "" # Project name (working title)
self.bookTitle = "" # The final title; should only be used for exports
self.bookAuthors = [] # A list of book authors
# Project Settings
self.autoReplace = {} # Text to auto-replace on exports
self.titleFormat = {} # The formatting of titles for exports
self.spellCheck = False # Controls the spellcheck-as-you-type feature
self.autoOutline = True # If true, the Project Outline is updated automatically
self.statusItems = None # Novel file progress status values
self.importItems = None # Note file importance values
self.lastEdited = None # The handle of the last file to be edited
self.lastViewed = None # The handle of the last file to be viewed
self.lastWCount = 0 # The project word count from last session
self.currWCount = 0 # The project word count in current session
self.novelWCount = 0 # Total number of words in novel files
self.notesWCount = 0 # Total number of words in note files
self.doBackup = True # Run project backup on exit
self.autoReplace = {} # Text to auto-replace on exports
self.titleFormat = {} # The formatting of titles for exports
self.spellCheck = False # Controls the spellcheck-as-you-type feature
self.autoOutline = True # If true, the Project Outline is updated automatically
self.statusItems = None # Novel file progress status values
self.importItems = None # Note file importance values
self.lastEdited = None # The handle of the last file to be edited
self.lastViewed = None # The handle of the last file to be viewed
self.lastWCount = 0 # The project word count from last session
self.currWCount = 0 # The project word count in current session
self.novelWCount = 0 # Total number of words in novel files
self.notesWCount = 0 # Total number of words in note files
self.doBackup = True # Run project backup on exit
# Internal Mapping
self.makeAlert = self.theParent.makeAlert
@@ -383,7 +383,7 @@ class NWProject():
# Check for Old Legacy Data
# =========================
legacyList = [] # Cleanup is done later
legacyList = [] # Cleanup is done later
for projItem in os.listdir(self.projPath):
logger.verbose("Project contains: %s" % projItem)
if projItem.startswith("data_"):
+4 -1
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter Spell Check Classes
=================================
@@ -31,6 +30,7 @@ import difflib
logger = logging.getLogger(__name__)
# =============================================================================================== #
# SpellChecking SuperClass
# =============================================================================================== #
@@ -122,6 +122,7 @@ class NWSpellCheck():
# END Class NWSpellCheck
# =============================================================================================== #
# Enchant Based SpellChecking
# =============================================================================================== #
@@ -208,6 +209,7 @@ class NWSpellEnchant(NWSpellCheck):
# END Class NWSpellEnchant
class FakeEnchant:
"""Fallback for when Enchant is selected, but not installed.
"""
@@ -225,6 +227,7 @@ class FakeEnchant:
# END Class FakeEnchant
# =============================================================================================== #
# Fallback SpellChecking Using difflib
# =============================================================================================== #
+1 -1
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter Project Item Status Class
=======================================
@@ -32,6 +31,7 @@ from nw.common import checkInt
logger = logging.getLogger(__name__)
class NWStatus():
def __init__(self):
+4 -4
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter HTML Text Converter
=================================
@@ -31,11 +30,12 @@ from nw.constants import nwKeyWords, nwLabels, nwHtmlUnicode
logger = logging.getLogger(__name__)
class ToHtml(Tokenizer):
M_PREVIEW = 0 # Tweak output for the DocViewer
M_EXPORT = 1 # Tweak output for saving to HTML or printing
M_EBOOK = 2 # Tweak output for converting to epub
M_PREVIEW = 0 # Tweak output for the DocViewer
M_EXPORT = 1 # Tweak output for saving to HTML or printing
M_EBOOK = 2 # Tweak output for converting to epub
def __init__(self, theProject):
Tokenizer.__init__(self, theProject)
+57 -57
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter Text Tokenizer
============================
@@ -40,6 +39,7 @@ from nw.core.document import NWDoc
logger = logging.getLogger(__name__)
class Tokenizer():
# In-Text Format
@@ -51,33 +51,33 @@ class Tokenizer():
FMT_D_E = 6 # End strikeout
# Block Type
T_EMPTY = 1 # Empty line (new paragraph)
T_SYNOPSIS = 2 # Synopsis comment
T_COMMENT = 3 # Comment line
T_KEYWORD = 4 # Command line
T_TITLE = 5 # Title
T_HEAD1 = 6 # Header 1
T_HEAD2 = 7 # Header 2
T_HEAD3 = 8 # Header 3
T_HEAD4 = 9 # Header 4
T_TEXT = 10 # Text line
T_SEP = 11 # Scene separator
T_SKIP = 12 # Paragraph break
T_EMPTY = 1 # Empty line (new paragraph)
T_SYNOPSIS = 2 # Synopsis comment
T_COMMENT = 3 # Comment line
T_KEYWORD = 4 # Command line
T_TITLE = 5 # Title
T_HEAD1 = 6 # Header 1
T_HEAD2 = 7 # Header 2
T_HEAD3 = 8 # Header 3
T_HEAD4 = 9 # Header 4
T_TEXT = 10 # Text line
T_SEP = 11 # Scene separator
T_SKIP = 12 # Paragraph break
# Block Style
A_NONE = 0x0000 # No special style
A_LEFT = 0x0001 # Left aligned
A_RIGHT = 0x0002 # Right aligned
A_CENTRE = 0x0004 # Centred
A_JUSTIFY = 0x0008 # Justified
A_PBB = 0x0010 # Page break before always
A_PBB_AUT = 0x0020 # Page break before auto
A_PBA = 0x0040 # Page break after always
A_PBA_AUT = 0x0080 # Page break after auto
A_Z_TOPMRG = 0x0100 # Zero top margin
A_Z_BTMMRG = 0x0200 # Zero bottom margin
A_IND_L = 0x0400 # Left indentation
A_IND_R = 0x0800 # Right indentation
A_NONE = 0x0000 # No special style
A_LEFT = 0x0001 # Left aligned
A_RIGHT = 0x0002 # Right aligned
A_CENTRE = 0x0004 # Centred
A_JUSTIFY = 0x0008 # Justified
A_PBB = 0x0010 # Page break before always
A_PBB_AUT = 0x0020 # Page break before auto
A_PBA = 0x0040 # Page break after always
A_PBA_AUT = 0x0080 # Page break after auto
A_Z_TOPMRG = 0x0100 # Zero top margin
A_Z_BTMMRG = 0x0200 # Zero bottom margin
A_IND_L = 0x0400 # Left indentation
A_IND_R = 0x0800 # Right indentation
def __init__(self, theProject):
@@ -86,28 +86,28 @@ class Tokenizer():
self.mainConf = nw.CONFIG
# Data Variables
self.theText = "" # The raw text to be tokenized
self.theHandle = None # The handle associated with the text
self.theItem = None # The NWItem associated with the handle
self.theTokens = [] # The list of the processed tokens
self.theResult = "" # The result of the last document
self.theText = "" # The raw text to be tokenized
self.theHandle = None # The handle associated with the text
self.theItem = None # The NWItem associated with the handle
self.theTokens = [] # The list of the processed tokens
self.theResult = "" # The result of the last document
self.keepMarkdown = False # Whether to keep the markdown text
self.theMarkdown = [] # The result novelWriter markdown of all documents
self.keepMarkdown = False # Whether to keep the markdown text
self.theMarkdown = [] # The result novelWriter markdown of all documents
# User Settings
self.textFont = "Serif" # Output text font
self.textSize = 11 # Output text size
self.textFixed = False # Fixed width text
self.lineHeight = 1.15 # Line height in units of em
self.blockIndent = 4.00 # Block indent in units of em
self.doJustify = False # Justify text
self.doBodyText = True # Include body text
self.doSynopsis = False # Also process synopsis comments
self.doComments = False # Also process comments
self.doKeywords = False # Also process keywords like tags and references
self.textFont = "Serif" # Output text font
self.textSize = 11 # Output text size
self.textFixed = False # Fixed width text
self.lineHeight = 1.15 # Line height in units of em
self.blockIndent = 4.00 # Block indent in units of em
self.doJustify = False # Justify text
self.doBodyText = True # Include body text
self.doSynopsis = False # Also process synopsis comments
self.doComments = False # Also process comments
self.doKeywords = False # Also process keywords like tags and references
## Margins
# Margins
self.marginTitle = (1.000, 0.500)
self.marginHead1 = (1.000, 0.500)
self.marginHead2 = (0.834, 0.500)
@@ -116,23 +116,23 @@ class Tokenizer():
self.marginText = (0.000, 0.584)
self.marginMeta = (0.000, 0.584)
## Title Formats
self.fmtTitle = "%title%" # Formatting for titles
self.fmtChapter = "%title%" # Formatting for numbered chapters
self.fmtUnNum = "%title%" # Formatting for unnumbered chapters
self.fmtScene = "%title%" # Formatting for scenes
self.fmtSection = "%title%" # Formatting for sections
# Title Formats
self.fmtTitle = "%title%" # Formatting for titles
self.fmtChapter = "%title%" # Formatting for numbered chapters
self.fmtUnNum = "%title%" # Formatting for unnumbered chapters
self.fmtScene = "%title%" # Formatting for scenes
self.fmtSection = "%title%" # Formatting for sections
self.hideScene = False # Do not include scene headers
self.hideSection = False # Do not include section headers
self.hideScene = False # Do not include scene headers
self.hideSection = False # Do not include section headers
self.linkHeaders = False # Add an anchor before headers
self.linkHeaders = False # Add an anchor before headers
# Instance Variables
self.numChapter = 0 # Counter for chapter numbers
self.numChScene = 0 # Counter for scene number within chapter
self.numAbsScene = 0 # Counter for scene number within novel
self.firstScene = False # Flag to indicate that the first scene of the chapter
self.numChapter = 0 # Counter for chapter numbers
self.numChScene = 0 # Counter for scene number within chapter
self.numAbsScene = 0 # Counter for scene number within novel
self.firstScene = False # Flag to indicate that the first scene of the chapter
# This File
self.isNone = False
+3 -3
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter Markdown Text Converter
=====================================
@@ -31,10 +30,11 @@ from nw.core.tokenizer import Tokenizer
logger = logging.getLogger(__name__)
class ToMarkdown(Tokenizer):
M_STD = 0 # Standard Markdown
M_GH = 1 # GitHub Markdown
M_STD = 0 # Standard Markdown
M_GH = 1 # GitHub Markdown
def __init__(self, theProject):
Tokenizer.__init__(self, theProject)
+34 -31
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter ODT Text Converter
================================
@@ -57,38 +56,39 @@ TAG_TAB = "{%s}tab" % XML_NS["text"]
TAG_SPAN = "{%s}span" % XML_NS["text"]
TAG_STNM = "{%s}style-name" % XML_NS["text"]
class ToOdt(Tokenizer):
X_BLD = 0x01 # Bold format
X_ITA = 0x02 # Italic format
X_DEL = 0x04 # Strikethrough format
X_BRK = 0x08 # Line break
X_TAB = 0x10 # Tab
X_BLD = 0x01 # Bold format
X_ITA = 0x02 # Italic format
X_DEL = 0x04 # Strikethrough format
X_BRK = 0x08 # Line break
X_TAB = 0x10 # Tab
def __init__(self, theProject, isFlat):
Tokenizer.__init__(self, theProject)
self.mainConf = nw.CONFIG
self._isFlat = isFlat # Flat: .fodt, otherwise .odt
self._isFlat = isFlat # Flat: .fodt, otherwise .odt
self._dFlat = None # FODT file XML root
self._dCont = None # ODT content.xml root
self._dMeta = None # ODT meta.xml root
self._dStyl = None # ODT styles.xml root
self._dFlat = None # FODT file XML root
self._dCont = None # ODT content.xml root
self._dMeta = None # ODT meta.xml root
self._dStyl = None # ODT styles.xml root
self._xMeta = None # Office meta root
self._xStyl = None # Office styles root
self._xAuto = None # Office auto-styles root
self._xMast = None # Office master-styles root
self._xBody = None # Office body root
self._xText = None # Office text root
self._xMeta = None # Office meta root
self._xStyl = None # Office styles root
self._xAuto = None # Office auto-styles root
self._xMast = None # Office master-styles root
self._xBody = None # Office body root
self._xText = None # Office text root
self._xAut2 = None # Page layout auto-styles for ODT file
self._xAut2 = None # Page layout auto-styles for ODT file
self._mainPara = {} # User-accessible paragraph styles
self._autoPara = {} # Auto-generated paragraph styles
self._autoText = {} # Auto-generated text styles
self._mainPara = {} # User-accessible paragraph styles
self._autoPara = {} # Auto-generated paragraph styles
self._autoText = {} # Auto-generated text styles
# Properties
self.textFont = "Liberation Serif"
@@ -114,7 +114,7 @@ class ToOdt(Tokenizer):
self._dLanguage = "en"
self._dCountry = "GB"
## Text Margings in Units of em
# Text Margings in Units of em
self._mTopTitle = "0.423cm"
self._mTopHead1 = "0.423cm"
self._mTopHead2 = "0.353cm"
@@ -133,13 +133,13 @@ class ToOdt(Tokenizer):
self._mBotText = "0.247cm"
self._mBotMeta = "0.106cm"
## Document Margins
# Document Margins
self._mDocTop = "2.000cm"
self._mDocBtm = "2.000cm"
self._mDocLeft = "2.000cm"
self._mDocRight = "2.000cm"
## Colour
# Colour
self._colHead12 = None
self._opaHead12 = None
self._colHead34 = None
@@ -315,15 +315,15 @@ class ToOdt(Tokenizer):
def doConvert(self):
"""Convert the list of text tokens into XML elements.
"""
self.theResult = "" # Not used, but cleared just in case
self.theResult = "" # Not used, but cleared just in case
odtTags = {
self.FMT_B_B : "_B", # Bold open format
self.FMT_B_E : "b_", # Bold close format
self.FMT_I_B : "I", # Italic open format
self.FMT_I_E : "i", # Italic close format
self.FMT_D_B : "_S", # Strikethrough open format
self.FMT_D_E : "s_", # Strikethrough close format
self.FMT_B_B : "_B", # Bold open format
self.FMT_B_E : "b_", # Bold close format
self.FMT_I_B : "I", # Italic open format
self.FMT_I_E : "i", # Italic close format
self.FMT_D_B : "_S", # Strikethrough open format
self.FMT_D_E : "s_", # Strikethrough close format
}
thisPar = []
@@ -985,6 +985,7 @@ class ToOdt(Tokenizer):
# END Class ToOdt
# =============================================================================================== #
# Auto-Style Classes
# =============================================================================================== #
@@ -1212,6 +1213,7 @@ class ODTParagraphStyle():
# END Class ODTParagraphStyle
class ODTTextStyle():
"""Wrapper class for the text style setting used by the exporter.
Only the used settings are exposed here to keep the class minimal
@@ -1282,6 +1284,7 @@ class ODTTextStyle():
# END Class ODTTextStyle
# =============================================================================================== #
# Local Functions
# =============================================================================================== #
+10 -10
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter Project Tree Class
================================
@@ -62,22 +61,23 @@ LAYOUT_MAP = {
},
}
class NWTree():
def __init__(self, theProject):
self.theProject = theProject
self._projTree = {} # Holds all the items of the project
self._treeOrder = [] # The order of the tree items on the tree view
self._treeRoots = [] # The root items of the tree
self._trashRoot = None # The handle of the trash root folder
self._archRoot = None # The handle of the archive root folder
self._theIndex = 0 # The current iterator index
self._treeChanged = False # True if tree structure has changed
self._projTree = {} # Holds all the items of the project
self._treeOrder = [] # The order of the tree items on the tree view
self._treeRoots = [] # The root items of the tree
self._trashRoot = None # The handle of the trash root folder
self._archRoot = None # The handle of the archive root folder
self._theIndex = 0 # The current iterator index
self._treeChanged = False # True if tree structure has changed
self._handleSeed = None # Used for generating handles for testing
self._handleCount = 0 # A counter that is added to the handle generator
self._handleSeed = None # Used for generating handles for testing
self._handleCount = 0 # A counter that is added to the handle generator
return
+20 -1
View File
@@ -1,4 +1,23 @@
# -*- coding: utf-8 -*-
"""
novelWriter Dialogs Init
==========================
This file is a part of novelWriter
Copyright 20182021, Veronica Berglyd Olsen
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
from nw.dialogs.about import GuiAbout
from nw.dialogs.docmerge import GuiDocMerge
+1 -1
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter GUI About Box
===========================
@@ -39,6 +38,7 @@ from PyQt5.QtWidgets import (
logger = logging.getLogger(__name__)
class GuiAbout(QDialog):
def __init__(self, theParent):
+1 -1
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter GUI Doc Merge Tool
================================
@@ -39,6 +38,7 @@ from nw.gui.custom import QHelpLabel
logger = logging.getLogger(__name__)
class GuiDocMerge(QDialog):
def __init__(self, theParent):
+1 -1
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter GUI Doc Split Tool
================================
@@ -40,6 +39,7 @@ from nw.gui.custom import QHelpLabel
logger = logging.getLogger(__name__)
class GuiDocSplit(QDialog):
def __init__(self, theParent):
+1 -1
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter GUI Item Editor
=============================
@@ -39,6 +38,7 @@ from nw.gui.custom import QSwitch
logger = logging.getLogger(__name__)
class GuiItemEditor(QDialog):
def __init__(self, theParent, tHandle):
+50 -43
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter GUI Preferences
=============================
@@ -43,6 +42,7 @@ from nw.dialogs.quotes import GuiQuoteSelect
logger = logging.getLogger(__name__)
class GuiPreferences(PagedDialog):
def __init__(self, theParent):
@@ -134,6 +134,7 @@ class GuiPreferences(PagedDialog):
# END Class GuiPreferences
class GuiPreferencesGeneral(QWidget):
def __init__(self, theParent):
@@ -153,7 +154,7 @@ class GuiPreferencesGeneral(QWidget):
self.mainForm.addGroupLabel(self.tr("Look and Feel"))
minWidth = self.mainConf.pxInt(200)
## Select Locale
# Select Locale
self.guiLang = QComboBox()
self.guiLang.setMinimumWidth(minWidth)
theLangs = self.mainConf.listLanguages(self.mainConf.LANG_NW)
@@ -169,7 +170,7 @@ class GuiPreferencesGeneral(QWidget):
self.tr("Changing this requires restarting novelWriter.")
)
## Select Theme
# Select Theme
self.guiTheme = QComboBox()
self.guiTheme.setMinimumWidth(minWidth)
self.theThemes = self.theTheme.listThemes()
@@ -185,7 +186,7 @@ class GuiPreferencesGeneral(QWidget):
self.tr("Changing this requires restarting novelWriter.")
)
## Select Icon Theme
# Select Icon Theme
self.guiIcons = QComboBox()
self.guiIcons.setMinimumWidth(minWidth)
self.theIcons = self.theTheme.theIcons.listThemes()
@@ -201,7 +202,7 @@ class GuiPreferencesGeneral(QWidget):
self.tr("Changing this requires restarting novelWriter.")
)
## Dark Icons
# Dark Icons
self.guiDark = QSwitch()
self.guiDark.setChecked(self.mainConf.guiDark)
self.mainForm.addRow(
@@ -210,7 +211,7 @@ class GuiPreferencesGeneral(QWidget):
self.tr("May improve the look of icons on dark themes.")
)
## Font Family
# Font Family
self.guiFont = QLineEdit()
self.guiFont.setReadOnly(True)
self.guiFont.setFixedWidth(self.mainConf.pxInt(162))
@@ -225,7 +226,7 @@ class GuiPreferencesGeneral(QWidget):
theButton = self.fontButton
)
## Font Size
# Font Size
self.guiFontSize = QSpinBox(self)
self.guiFontSize.setMinimum(8)
self.guiFontSize.setMaximum(60)
@@ -319,6 +320,7 @@ class GuiPreferencesGeneral(QWidget):
# END Class GuiPreferencesGeneral
class GuiPreferencesProjects(QWidget):
def __init__(self, theParent):
@@ -337,7 +339,7 @@ class GuiPreferencesProjects(QWidget):
# ==============
self.mainForm.addGroupLabel(self.tr("Automatic Save"))
## Document Save Timer
# Document Save Timer
self.autoSaveDoc = QSpinBox(self)
self.autoSaveDoc.setMinimum(5)
self.autoSaveDoc.setMaximum(600)
@@ -350,7 +352,7 @@ class GuiPreferencesProjects(QWidget):
theUnit=self.tr("seconds")
)
## Project Save Timer
# Project Save Timer
self.autoSaveProj = QSpinBox(self)
self.autoSaveProj.setMinimum(5)
self.autoSaveProj.setMaximum(600)
@@ -367,7 +369,7 @@ class GuiPreferencesProjects(QWidget):
# ==============
self.mainForm.addGroupLabel(self.tr("Project Backup"))
## Backup Path
# Backup Path
self.backupPath = self.mainConf.backupPath
self.backupGetPath = QPushButton(self.tr("Browse"))
self.backupGetPath.clicked.connect(self._backupFolder)
@@ -377,7 +379,7 @@ class GuiPreferencesProjects(QWidget):
self.tr("Path: {0}").format(self.backupPath)
)
## Run when closing
# Run when closing
self.backupOnClose = QSwitch()
self.backupOnClose.setChecked(self.mainConf.backupOnClose)
self.backupOnClose.toggled.connect(self._toggledBackupOnClose)
@@ -387,8 +389,8 @@ class GuiPreferencesProjects(QWidget):
self.tr("Can be overridden for individual projects in Project Settings.")
)
## Ask before backup
## Only enabled when "Run when closing" is checked
# Ask before backup
# Only enabled when "Run when closing" is checked
self.askBeforeBackup = QSwitch()
self.askBeforeBackup.setChecked(self.mainConf.askBeforeBackup)
self.askBeforeBackup.setEnabled(self.mainConf.backupOnClose)
@@ -402,7 +404,7 @@ class GuiPreferencesProjects(QWidget):
# =============
self.mainForm.addGroupLabel(self.tr("Session Timer"))
## Pause when idle
# Pause when idle
self.stopWhenIdle = QSwitch()
self.stopWhenIdle.setChecked(self.mainConf.stopWhenIdle)
self.mainForm.addRow(
@@ -411,7 +413,7 @@ class GuiPreferencesProjects(QWidget):
self.tr("Also pauses when the application window does not have focus.")
)
## Inactive time for idle
# Inactive time for idle
self.userIdleTime = QDoubleSpinBox()
self.userIdleTime.setMinimum(0.5)
self.userIdleTime.setMaximum(600.0)
@@ -479,6 +481,7 @@ class GuiPreferencesProjects(QWidget):
# END Class GuiPreferencesProjects
class GuiPreferencesDocuments(QWidget):
def __init__(self, theParent):
@@ -497,7 +500,7 @@ class GuiPreferencesDocuments(QWidget):
# ==========
self.mainForm.addGroupLabel(self.tr("Text Style"))
## Font Family
# Font Family
self.textFont = QLineEdit()
self.textFont.setReadOnly(True)
self.textFont.setFixedWidth(self.mainConf.pxInt(162))
@@ -512,7 +515,7 @@ class GuiPreferencesDocuments(QWidget):
theButton = self.fontButton
)
## Font Size
# Font Size
self.textSize = QSpinBox(self)
self.textSize.setMinimum(8)
self.textSize.setMaximum(60)
@@ -529,7 +532,7 @@ class GuiPreferencesDocuments(QWidget):
# =========
self.mainForm.addGroupLabel(self.tr("Text Flow"))
## Max Text Width in Normal Mode
# Max Text Width in Normal Mode
self.textWidth = QSpinBox(self)
self.textWidth.setMinimum(300)
self.textWidth.setMaximum(10000)
@@ -542,7 +545,7 @@ class GuiPreferencesDocuments(QWidget):
theUnit=self.tr("px")
)
## Max Text Width in Focus Mode
# Max Text Width in Focus Mode
self.focusWidth = QSpinBox(self)
self.focusWidth.setMinimum(300)
self.focusWidth.setMaximum(10000)
@@ -555,7 +558,7 @@ class GuiPreferencesDocuments(QWidget):
theUnit=self.tr("px")
)
## Document Fixed Width
# Document Fixed Width
self.textFixedW = QSwitch()
self.textFixedW.setChecked(not self.mainConf.textFixedW)
self.mainForm.addRow(
@@ -564,7 +567,7 @@ class GuiPreferencesDocuments(QWidget):
self.tr("Text width is defined by the margins only.")
)
## Focus Mode Footer
# Focus Mode Footer
self.hideFocusFooter = QSwitch()
self.hideFocusFooter.setChecked(self.mainConf.hideFocusFooter)
self.mainForm.addRow(
@@ -573,7 +576,7 @@ class GuiPreferencesDocuments(QWidget):
self.tr("Hide the information bar at the bottom of the document.")
)
## Justify Text
# Justify Text
self.doJustify = QSwitch()
self.doJustify.setChecked(self.mainConf.doJustify)
self.mainForm.addRow(
@@ -582,7 +585,7 @@ class GuiPreferencesDocuments(QWidget):
self.tr("Lay out text with straight edges in the editor and viewer.")
)
## Document Margins
# Document Margins
self.textMargin = QSpinBox(self)
self.textMargin.setMinimum(0)
self.textMargin.setMaximum(900)
@@ -595,7 +598,7 @@ class GuiPreferencesDocuments(QWidget):
theUnit=self.tr("px")
)
## Tab Width
# Tab Width
self.tabWidth = QSpinBox(self)
self.tabWidth.setMinimum(0)
self.tabWidth.setMaximum(200)
@@ -649,6 +652,7 @@ class GuiPreferencesDocuments(QWidget):
# END Class GuiPreferencesDocuments
class GuiPreferencesEditor(QWidget):
def __init__(self, theParent):
@@ -669,7 +673,7 @@ class GuiPreferencesEditor(QWidget):
# ==============
self.mainForm.addGroupLabel(self.tr("Spell Checking"))
## Spell Check Provider and Language
# Spell Check Provider and Language
self.spellLangList = QComboBox(self)
self.spellLangList.setMaximumWidth(mW)
@@ -699,7 +703,7 @@ class GuiPreferencesEditor(QWidget):
self.tr("Available languages are determined by your system.")
)
## Big Document Size Limit
# Big Document Size Limit
self.bigDocLimit = QSpinBox(self)
self.bigDocLimit.setMinimum(10)
self.bigDocLimit.setMaximum(10000)
@@ -716,7 +720,7 @@ class GuiPreferencesEditor(QWidget):
# ==========
self.mainForm.addGroupLabel(self.tr("Word Count"))
## Word Count Timer
# Word Count Timer
self.wordCountTimer = QDoubleSpinBox(self)
self.wordCountTimer.setDecimals(1)
self.wordCountTimer.setMinimum(2.0)
@@ -734,7 +738,7 @@ class GuiPreferencesEditor(QWidget):
# ==============
self.mainForm.addGroupLabel(self.tr("Writing Guides"))
## Show Tabs and Spaces
# Show Tabs and Spaces
self.showTabsNSpaces = QSwitch()
self.showTabsNSpaces.setChecked(self.mainConf.showTabsNSpaces)
self.mainForm.addRow(
@@ -743,7 +747,7 @@ class GuiPreferencesEditor(QWidget):
self.tr("Add symbols to indicate tabs and spaces in the editor.")
)
## Show Line Endings
# Show Line Endings
self.showLineEndings = QSwitch()
self.showLineEndings.setChecked(self.mainConf.showLineEndings)
self.mainForm.addRow(
@@ -756,7 +760,7 @@ class GuiPreferencesEditor(QWidget):
# ================
self.mainForm.addGroupLabel(self.tr("Scroll Behaviour"))
## Scroll Past End
# Scroll Past End
self.scrollPastEnd = QSwitch()
self.scrollPastEnd.setChecked(self.mainConf.scrollPastEnd)
self.mainForm.addRow(
@@ -765,7 +769,7 @@ class GuiPreferencesEditor(QWidget):
self.tr("Also improves trypewriter scrolling for short documents.")
)
## Typewriter Scrolling
# Typewriter Scrolling
self.autoScroll = QSwitch()
self.autoScroll.setChecked(self.mainConf.autoScroll)
self.mainForm.addRow(
@@ -774,7 +778,7 @@ class GuiPreferencesEditor(QWidget):
self.tr("Try to keep the cursor at a fixed vertical position.")
)
## Typewriter Position
# Typewriter Position
self.autoScrollPos = QSpinBox(self)
self.autoScrollPos.setMinimum(10)
self.autoScrollPos.setMaximum(90)
@@ -849,6 +853,7 @@ class GuiPreferencesEditor(QWidget):
# END Class GuiPreferencesEditor
class GuiPreferencesSyntax(QWidget):
def __init__(self, theParent):
@@ -973,6 +978,7 @@ class GuiPreferencesSyntax(QWidget):
# END Class GuiPreferencesSyntax
class GuiPreferencesAutomation(QWidget):
def __init__(self, theParent):
@@ -991,7 +997,7 @@ class GuiPreferencesAutomation(QWidget):
# ==================
self.mainForm.addGroupLabel(self.tr("Automatic Features"))
## Auto-Select Word Under Cursor
# Auto-Select Word Under Cursor
self.autoSelect = QSwitch()
self.autoSelect.setChecked(self.mainConf.autoSelect)
self.mainForm.addRow(
@@ -1000,7 +1006,7 @@ class GuiPreferencesAutomation(QWidget):
self.tr("Apply formatting to word under cursor if no selection is made.")
)
## Auto-Replace as You Type Main Switch
# Auto-Replace as You Type Main Switch
self.doReplace = QSwitch()
self.doReplace.setChecked(self.mainConf.doReplace)
self.doReplace.toggled.connect(self._toggleAutoReplaceMain)
@@ -1014,7 +1020,7 @@ class GuiPreferencesAutomation(QWidget):
# ===================
self.mainForm.addGroupLabel(self.tr("Replace as You Type"))
## Auto-Replace Single Quotes
# Auto-Replace Single Quotes
self.doReplaceSQuote = QSwitch()
self.doReplaceSQuote.setChecked(self.mainConf.doReplaceSQuote)
self.doReplaceSQuote.setEnabled(self.mainConf.doReplace)
@@ -1024,7 +1030,7 @@ class GuiPreferencesAutomation(QWidget):
self.tr("Try to guess which is an opening or a closing single quote.")
)
## Auto-Replace Double Quotes
# Auto-Replace Double Quotes
self.doReplaceDQuote = QSwitch()
self.doReplaceDQuote.setChecked(self.mainConf.doReplaceDQuote)
self.doReplaceDQuote.setEnabled(self.mainConf.doReplace)
@@ -1034,7 +1040,7 @@ class GuiPreferencesAutomation(QWidget):
self.tr("Try to guess which is an opening or a closing double quote.")
)
## Auto-Replace Hyphens
# Auto-Replace Hyphens
self.doReplaceDash = QSwitch()
self.doReplaceDash.setChecked(self.mainConf.doReplaceDash)
self.doReplaceDash.setEnabled(self.mainConf.doReplace)
@@ -1044,7 +1050,7 @@ class GuiPreferencesAutomation(QWidget):
self.tr("Double and triple hyphens become short and long dashes.")
)
## Auto-Replace Dots
# Auto-Replace Dots
self.doReplaceDots = QSwitch()
self.doReplaceDots.setChecked(self.mainConf.doReplaceDots)
self.doReplaceDots.setEnabled(self.mainConf.doReplace)
@@ -1058,7 +1064,7 @@ class GuiPreferencesAutomation(QWidget):
# =================
self.mainForm.addGroupLabel(self.tr("Automatic Padding"))
## Pad Before
# Pad Before
self.fmtPadBefore = QLineEdit()
self.fmtPadBefore.setMaxLength(32)
self.fmtPadBefore.setText(self.mainConf.fmtPadBefore)
@@ -1068,7 +1074,7 @@ class GuiPreferencesAutomation(QWidget):
self.tr("Automatically add space before any of these symbols."),
)
## Pad After
# Pad After
self.fmtPadAfter = QLineEdit()
self.fmtPadAfter.setMaxLength(32)
self.fmtPadAfter.setText(self.mainConf.fmtPadAfter)
@@ -1078,7 +1084,7 @@ class GuiPreferencesAutomation(QWidget):
self.tr("Automatically add space after any of these symbols."),
)
## Use Thin Space
# Use Thin Space
self.fmtPadThin = QSwitch()
self.fmtPadThin.setChecked(self.mainConf.fmtPadThin)
self.fmtPadThin.setEnabled(self.mainConf.doReplace)
@@ -1129,6 +1135,7 @@ class GuiPreferencesAutomation(QWidget):
# END Class GuiPreferencesAutomation
class GuiPreferencesQuotes(QWidget):
def __init__(self, theParent):
@@ -1151,7 +1158,7 @@ class GuiPreferencesQuotes(QWidget):
bWidth = int(2.5*self.theTheme.getTextWidth("..."))
self.quoteSym = {}
## Single Quote Style
# Single Quote Style
self.quoteSym["SO"] = QLineEdit()
self.quoteSym["SO"].setMaxLength(1)
self.quoteSym["SO"].setReadOnly(True)
@@ -1184,7 +1191,7 @@ class GuiPreferencesQuotes(QWidget):
theButton=self.btnSingleStyleC
)
## Double Quote Style
# Double Quote Style
self.quoteSym["DO"] = QLineEdit()
self.quoteSym["DO"].setMaxLength(1)
self.quoteSym["DO"].setReadOnly(True)
+1 -1
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter GUI Open Project
==============================
@@ -43,6 +42,7 @@ from nw.constants import nwFiles
logger = logging.getLogger(__name__)
class GuiProjectLoad(QDialog):
NONE_STATE = 0
+4 -1
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter GUI Project Settings
==================================
@@ -40,6 +39,7 @@ from nw.gui.custom import QSwitch, PagedDialog, QConfigLayout
logger = logging.getLogger(__name__)
class GuiProjectSettings(PagedDialog):
def __init__(self, theParent):
@@ -156,6 +156,7 @@ class GuiProjectSettings(PagedDialog):
# END Class GuiProjectSettings
class GuiProjectEditMain(QWidget):
def __init__(self, theParent, theProject):
@@ -239,6 +240,7 @@ class GuiProjectEditMain(QWidget):
# END Class GuiProjectEditMain
class GuiProjectEditStatus(QWidget):
COL_LABEL = 0
@@ -487,6 +489,7 @@ class GuiProjectEditStatus(QWidget):
# END Class GuiProjectEditStatus
class GuiProjectEditReplace(QWidget):
COL_KEY = 0
+1 -1
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter GUI Quotes Dialog
===============================
@@ -38,6 +37,7 @@ from nw.constants import trConst, nwQuotes
logger = logging.getLogger(__name__)
class GuiQuoteSelect(QDialog):
selectedQuote = ""
+1 -1
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter GUI User Wordlist
===============================
@@ -39,6 +38,7 @@ from nw.constants import nwFiles
logger = logging.getLogger(__name__)
class GuiWordList(QDialog):
def __init__(self, theParent):
+9 -1
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter Enums
===================
@@ -26,6 +25,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
from enum import Enum
class nwItemType(Enum):
NO_TYPE = 0
@@ -36,6 +36,7 @@ class nwItemType(Enum):
# END Enum nwItemType
class nwItemClass(Enum):
NO_CLASS = 0
@@ -52,6 +53,7 @@ class nwItemClass(Enum):
# END Enum nwItemClass
class nwItemLayout(Enum):
NO_LAYOUT = 0
@@ -66,6 +68,7 @@ class nwItemLayout(Enum):
# END Enum nwItemLayout
class nwDocAction(Enum):
NO_ACTION = 0
@@ -98,6 +101,7 @@ class nwDocAction(Enum):
# END Enum nwDocAction
class nwDocInsert(Enum):
NO_INSERT = 0
@@ -108,6 +112,7 @@ class nwDocInsert(Enum):
# END Enum nwDocInsert
class nwAlert(Enum):
INFO = 0
@@ -117,6 +122,7 @@ class nwAlert(Enum):
# END Enum nwAlert
class nwState(Enum):
NONE = 0
@@ -125,6 +131,7 @@ class nwState(Enum):
# END Enum nwState
class nwWidget(Enum):
TREE = 1
@@ -134,6 +141,7 @@ class nwWidget(Enum):
# END Enum nwWidget
class nwOutline(Enum):
TITLE = 0
+3 -1
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter Exception Handling
================================
@@ -35,6 +34,7 @@ from PyQt5.QtWidgets import (
logger = logging.getLogger(__name__)
# =============================================================================================== #
# Utility Functions
# =============================================================================================== #
@@ -45,6 +45,7 @@ def logException():
exType, exValue, _ = sys.exc_info()
logger.error("%s: %s" % (exType.__name__, str(exValue).strip("'")))
# =============================================================================================== #
# Error Handler
# =============================================================================================== #
@@ -151,6 +152,7 @@ class NWErrorMessage(QDialog):
# END Class NWErrorMessage
def exceptionHandler(exType, exValue, exTrace):
"""Function to catch unhandled global exceptions.
"""
+20 -1
View File
@@ -1,4 +1,23 @@
# -*- coding: utf-8 -*-
"""
novelWriter GUI Init
======================
This file is a part of novelWriter
Copyright 20182021, Veronica Berglyd Olsen
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
from nw.gui.doceditor import GuiDocEditor
from nw.gui.docviewer import GuiDocViewer, GuiDocViewDetails
+5 -1
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter Custom Widgets and Layouts
========================================
@@ -43,6 +42,7 @@ from nw.constants import nwUnicode
logger = logging.getLogger(__name__)
# =============================================================================================== #
# Config Form Layout
# =============================================================================================== #
@@ -198,6 +198,7 @@ class QConfigLayout(QGridLayout):
# END Class QConfigLayout
class QHelpLabel(QLabel):
def __init__(self, theText, textCol, fontSize=0.9):
@@ -223,6 +224,7 @@ class QHelpLabel(QLabel):
# END Class QHelpLabel
# =============================================================================================== #
# Switch Widget
# =============================================================================================== #
@@ -367,6 +369,7 @@ class QSwitch(QAbstractButton):
# END Class QSwitch
# =============================================================================================== #
# Paged Dialog w/Custom TabWidget
# =============================================================================================== #
@@ -417,6 +420,7 @@ class PagedDialog(QDialog):
# END Class PagedDialog
class VerticalTabBar(QTabBar):
def __init__(self, theParent=None):
+26 -21
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter GUI Document Editor
=================================
@@ -57,6 +56,7 @@ from nw.gui.dochighlight import GuiDocHighlighter
logger = logging.getLogger(__name__)
class GuiDocEditor(QTextEdit):
MOVE_KEYS = (
@@ -84,24 +84,24 @@ class GuiDocEditor(QTextEdit):
self._nwDocument = None
self._nwItem = None
self._docChanged = False # Flag for changed status of document
self._docHandle = None # The handle of the open file
self._docHeaders = [] # Record of headers in the file
self._docChanged = False # Flag for changed status of document
self._docHandle = None # The handle of the open file
self._docHeaders = [] # Record of headers in the file
self._spellCheck = False # Flag for spell checking enabled
self._theDict = None # The current spell check dictionary
self._nonWord = "\"'" # Characters to not include in spell checking
self._spellCheck = False # Flag for spell checking enabled
self._theDict = None # The current spell check dictionary
self._nonWord = "\"'" # Characters to not include in spell checking
# Document Variables
self._charCount = 0 # Character count
self._wordCount = 0 # Word count
self._paraCount = 0 # Paragraph count
self._lastEdit = 0 # Time stamp of last edit
self._lastActive = 0 # Time stamp of last activity
self._lastFind = None # Position of the last found search word
self._bigDoc = False # Flag for very large document size
self._doReplace = False # Switch to temporarily disable auto-replace
self._queuePos = None # Used for delayed change of cursor position
self._charCount = 0 # Character count
self._wordCount = 0 # Word count
self._paraCount = 0 # Paragraph count
self._lastEdit = 0 # Time stamp of last edit
self._lastActive = 0 # Time stamp of last activity
self._lastFind = None # Position of the last found search word
self._bigDoc = False # Flag for very large document size
self._doReplace = False # Switch to temporarily disable auto-replace
self._queuePos = None # Used for delayed change of cursor position
# Typography
self._typDQOpen = '"'
@@ -597,8 +597,8 @@ class GuiDocEditor(QTextEdit):
"""
if self.mainConf.verQtValue >= 50900:
theText = self._qDocument.toRawText()
theText = theText.replace(nwUnicode.U_LSEP, "\n") # Line separators
theText = theText.replace(nwUnicode.U_PSEP, "\n") # Paragraph separators
theText = theText.replace(nwUnicode.U_LSEP, "\n") # Line separators
theText = theText.replace(nwUnicode.U_PSEP, "\n") # Paragraph separators
else:
theText = self.toPlainText()
return theText
@@ -1424,7 +1424,7 @@ class GuiDocEditor(QTextEdit):
theTwo = theText[thePos-2:thePos]
theThree = theText[thePos-3:thePos]
if not theOne: # Makes Neo sad
if not theOne: # Makes Neo sad
return
nDelete = 0
@@ -1931,6 +1931,7 @@ class GuiDocEditor(QTextEdit):
# END Class GuiDocEditor
# =============================================================================================== #
# The Off-GUI Thread Word Counter
# A runnable for the word counter to be run in the thread pool off the main GUI thread.
@@ -1960,7 +1961,8 @@ class BackgroundWordCounter(QRunnable):
self._isRunning = False
return
## END Class BackgroundWordCounter
# END Class BackgroundWordCounter
class BackgroundWordCounterSignals(QObject):
"""The QRunnable cannot emit a signal, so we need a simple QObject
@@ -1970,6 +1972,7 @@ class BackgroundWordCounterSignals(QObject):
# END Class BackgroundWordCounterSignals
# =============================================================================================== #
# The Embedded Document Search/Replace Feature
# Only used by DocEditor, and is at a fixed position in the QTextEdit's viewport
@@ -2233,7 +2236,7 @@ class GuiDocEditSearch(QFrame):
self._alertSearchValid(theRegEx.isValid())
return theRegEx
else: # >= 50300 to < 51300
else: # >= 50300 to < 51300
if self.isCaseSense:
rxOpt = Qt.CaseSensitive
else:
@@ -2345,6 +2348,7 @@ class GuiDocEditSearch(QFrame):
# END Class GuiDocEditSearch
# =============================================================================================== #
# The Embedded Document Header
# Only used by DocEditor, and is at a fixed position in the QTextEdit's viewport
@@ -2567,6 +2571,7 @@ class GuiDocEditHeader(QWidget):
# END Class GuiDocEditHeader
# =============================================================================================== #
# The Embedded Document Footer
# Only used by DocEditor, and is at a fixed position in the QTextEdit's viewport
+8 -8
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter GUI Syntax Highlighter
====================================
@@ -38,6 +37,7 @@ from nw.constants import nwRegEx, nwUnicode
logger = logging.getLogger(__name__)
class GuiDocHighlighter(QSyntaxHighlighter):
BLOCK_NONE = 0
@@ -288,7 +288,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
if self.theHandle is None or not theText:
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]
isValid, theBits, thePos = self.theIndex.scanThis(theText)
@@ -312,27 +312,27 @@ class GuiDocHighlighter(QSyntaxHighlighter):
# so we force a return here
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(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(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(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(4, len(theText), self.hStyles["header4"])
elif theText.startswith("%"): # Comments
elif theText.startswith("%"): # Comments
self.setCurrentBlockState(self.BLOCK_TEXT)
toCheck = theText[1:].lstrip()
synTag = toCheck[:9].lower()
@@ -345,7 +345,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
else:
self.setFormat(0, tLen, self.hStyles["hidden"])
else: # Text Paragraph
else: # Text Paragraph
self.setCurrentBlockState(self.BLOCK_TEXT)
for rX, xFmt in self.rxRules:
rxItt = rX.globalMatch(theText, 0)
+5 -1
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter GUI Document Viewer
=================================
@@ -46,6 +45,7 @@ from nw.constants import nwUnicode
logger = logging.getLogger(__name__)
class GuiDocViewer(QTextBrowser):
def __init__(self, theParent):
@@ -571,6 +571,7 @@ class GuiDocViewer(QTextBrowser):
# END Class GuiDocViewer
class GuiDocViewHistory():
def __init__(self, docViewer):
@@ -702,6 +703,7 @@ class GuiDocViewHistory():
# END Class GuiDocViewHistory
# =============================================================================================== #
# The Embedded Document Header
# Only used by DocViewer, and is at a fixed position in the QTextBrowser's viewport
@@ -908,6 +910,7 @@ class GuiDocViewHeader(QWidget):
# END Class GuiDocViewHeader
# =============================================================================================== #
# The Embedded Document Footer
# Only used by DocViewer, and is at a fixed position in the QTextBrowser's viewport
@@ -1133,6 +1136,7 @@ class GuiDocViewFooter(QWidget):
# END Class GuiDocViewFooter
# =============================================================================================== #
# The Document Back-Reference Panel
# Placed in a separate QSplitter position in the main GUI window
+3 -3
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter GUI Item Details Panel
====================================
@@ -36,6 +35,7 @@ from nw.constants import trConst, nwLabels
logger = logging.getLogger(__name__)
class GuiItemDetails(QWidget):
def __init__(self, theParent):
@@ -235,10 +235,10 @@ class GuiItemDetails(QWidget):
itStatus = nwItem.itemStatus
if nwItem.itemClass == nwItemClass.NOVEL:
itStatus = self.theProject.statusItems.checkEntry(itStatus) # Make sure it's valid
itStatus = self.theProject.statusItems.checkEntry(itStatus) # Make sure it's valid
flagIcon = self.theParent.statusIcons[itStatus]
else:
itStatus = self.theProject.importItems.checkEntry(itStatus) # Make sure it's valid
itStatus = self.theProject.importItems.checkEntry(itStatus) # Make sure it's valid
flagIcon = self.theParent.importIcons[itStatus]
if nwItem.itemType == nwItemType.FILE:
+3 -3
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter GUI Main Menu
===========================
@@ -36,6 +35,7 @@ from nw.constants import trConst, nwKeyWords, nwLabels, nwUnicode
logger = logging.getLogger(__name__)
class GuiMainMenu(QMenuBar):
def __init__(self, theParent):
@@ -241,7 +241,7 @@ class GuiMainMenu(QMenuBar):
self.rootItems[nwItemClass.ARCHIVE] = QAction(self.tr("Outtakes Root"), self.rootMenu)
nCount = 0
for itemClass in self.rootItems.keys():
nCount += 1 # This forces the lambdas to be unique
nCount += 1 # This forces the lambdas to be unique
self.rootItems[itemClass].triggered.connect(
lambda nCount, itemClass=itemClass: self._newTreeItem(nwItemType.ROOT, itemClass)
)
@@ -982,7 +982,7 @@ class GuiMainMenu(QMenuBar):
self.aSpellCheck.setStatusTip(self.tr("Toggle check spelling"))
self.aSpellCheck.setCheckable(True)
self.aSpellCheck.setChecked(self.theProject.spellCheck)
self.aSpellCheck.triggered.connect(self._toggleSpellCheck) # triggered, not toggled!
self.aSpellCheck.triggered.connect(self._toggleSpellCheck) # triggered, not toggled!
self.aSpellCheck.setShortcut("Ctrl+F7")
self.toolsMenu.addAction(self.aSpellCheck)
+1 -1
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter GUI Novel Tree
============================
@@ -37,6 +36,7 @@ from nw.constants import nwKeyWords
logger = logging.getLogger(__name__)
class GuiNovelTree(QTreeWidget):
C_TITLE = 0
+2 -1
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter GUI Project Outline
=================================
@@ -39,6 +38,7 @@ from nw.constants import trConst, nwKeyWords, nwLabels
logger = logging.getLogger(__name__)
class GuiOutline(QTreeWidget):
DEF_WIDTH = {
@@ -467,6 +467,7 @@ class GuiOutline(QTreeWidget):
# END Class GuiOutline
class GuiOutlineHeaderMenu(QMenu):
def __init__(self, theParent):
+1 -1
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter GUI Project Outline Details
=========================================
@@ -37,6 +36,7 @@ from nw.constants import trConst, nwKeyWords, nwLabels
logger = logging.getLogger(__name__)
class GuiOutlineDetails(QScrollArea):
LVL_MAP = {
+3 -1
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter GUI Project Details
=================================
@@ -41,6 +40,7 @@ from nw.gui.custom import PagedDialog, QSwitch
logger = logging.getLogger(__name__)
class GuiProjectDetails(PagedDialog):
def __init__(self, theParent):
@@ -128,6 +128,7 @@ class GuiProjectDetails(PagedDialog):
# END Class GuiProjectDetails
class GuiProjectDetailsMain(QWidget):
def __init__(self, theParent, theProject):
@@ -241,6 +242,7 @@ class GuiProjectDetailsMain(QWidget):
# END Class GuiProjectDetailsMain
class GuiProjectDetailsContents(QWidget):
C_TITLE = 0
+5 -4
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter GUI Project Tree
==============================
@@ -42,6 +41,7 @@ from nw.constants import nwConst, trConst, nwLists, nwLabels
logger = logging.getLogger(__name__)
class GuiProjectTree(QTreeWidget):
C_NAME = 0
@@ -620,10 +620,10 @@ class GuiProjectTree(QTreeWidget):
iStatus = nwItem.itemStatus
if nwItem.itemClass == nwItemClass.NOVEL:
iStatus = self.theProject.statusItems.checkEntry(iStatus) # Make sure it's valid
iStatus = self.theProject.statusItems.checkEntry(iStatus) # Make sure it's valid
flagIcon = self.theParent.statusIcons[iStatus]
else:
iStatus = self.theProject.importItems.checkEntry(iStatus) # Make sure it's valid
iStatus = self.theProject.importItems.checkEntry(iStatus) # Make sure it's valid
flagIcon = self.theParent.importIcons[iStatus]
trItem.setText(self.C_NAME, nwItem.itemName)
@@ -790,7 +790,7 @@ class GuiProjectTree(QTreeWidget):
selItem = self.itemAt(clickPos)
if isinstance(selItem, QTreeWidgetItem):
tHandle = selItem.data(self.C_NAME, Qt.UserRole)
self.setSelectedHandle(tHandle) # Just to be safe
self.setSelectedHandle(tHandle) # Just to be safe
tItem = self.theProject.projTree[tHandle]
if tItem is not None:
if self.ctxMenu.filterActions(tItem):
@@ -1102,6 +1102,7 @@ class GuiProjectTree(QTreeWidget):
# END Class GuiProjectTree
class GuiProjectTreeMenu(QMenu):
def __init__(self, theTree):
+8 -7
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter GUI Main Window Status Bar
========================================
@@ -38,6 +37,7 @@ from nw.enum import nwState
logger = logging.getLogger(__name__)
class GuiMainStatus(QStatusBar):
def __init__(self, theParent):
@@ -62,7 +62,7 @@ class GuiMainStatus(QStatusBar):
xM = self.mainConf.pxInt(8)
## The Spell Checker Language
# The Spell Checker Language
self.langIcon = QLabel("")
self.langText = QLabel(self.tr("None"))
self.langIcon.setPixmap(self.theTheme.getPixmap("status_lang", (iPx, iPx)))
@@ -71,7 +71,7 @@ class GuiMainStatus(QStatusBar):
self.addPermanentWidget(self.langIcon)
self.addPermanentWidget(self.langText)
## The Editor Status
# The Editor Status
self.docIcon = StatusLED(colNone, colTrue, colFalse, iPx, iPx, self)
self.docText = QLabel(self.tr("Editor"))
self.docIcon.setContentsMargins(0, 0, 0, 0)
@@ -79,7 +79,7 @@ class GuiMainStatus(QStatusBar):
self.addPermanentWidget(self.docIcon)
self.addPermanentWidget(self.docText)
## The Project Status
# The Project Status
self.projIcon = StatusLED(colNone, colTrue, colFalse, iPx, iPx, self)
self.projText = QLabel(self.tr("Project"))
self.projIcon.setContentsMargins(0, 0, 0, 0)
@@ -87,7 +87,7 @@ class GuiMainStatus(QStatusBar):
self.addPermanentWidget(self.projIcon)
self.addPermanentWidget(self.projText)
## The Project and Session Stats
# The Project and Session Stats
self.statsIcon = QLabel()
self.statsText = QLabel("")
self.statsIcon.setPixmap(self.theTheme.getPixmap("status_stats", (iPx, iPx)))
@@ -96,8 +96,8 @@ class GuiMainStatus(QStatusBar):
self.addPermanentWidget(self.statsIcon)
self.addPermanentWidget(self.statsText)
## The Session Clock
### Set the mimimum width so the label doesn't rescale every second
# The Session Clock
# Set the mimimum width so the label doesn't rescale every second
self.timePixmap = self.theTheme.getPixmap("status_time", (iPx, iPx))
self.idlePixmap = self.theTheme.getPixmap("status_idle", (iPx, iPx))
@@ -235,6 +235,7 @@ class GuiMainStatus(QStatusBar):
# END Class GuiMainStatus
class StatusLED(QAbstractButton):
def __init__(self, colNone, colGood, colBad, sW, sH, parent=None):
+16 -14
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter Theme and Icons Classes
=====================================
@@ -43,6 +42,7 @@ from nw.enum import nwAlert
logger = logging.getLogger(__name__)
# =============================================================================================== #
# Gui Theme Class
# Handles the look and feel of novelWriter
@@ -65,8 +65,9 @@ class GuiTheme:
self.syntaxList = []
# Loaded Theme Settings
# =====================
## Theme
# Theme
self.themeName = ""
self.themeDescription = ""
self.themeAuthor = ""
@@ -75,7 +76,7 @@ class GuiTheme:
self.themeLicense = ""
self.themeLicenseUrl = ""
## GUI
# GUI
self.statNone = [120, 120, 120]
self.statUnsaved = [200, 15, 39]
self.statSaved = [2, 133, 37]
@@ -83,7 +84,7 @@ class GuiTheme:
# Loaded Syntax Settings
## Main
# Main
self.syntaxName = ""
self.syntaxDescription = ""
self.syntaxAuthor = ""
@@ -92,7 +93,7 @@ class GuiTheme:
self.syntaxLicense = ""
self.syntaxLicenseUrl = ""
## Colours
# Colours
self.colBack = [255, 255, 255]
self.colText = [0, 0, 0]
self.colLink = [0, 0, 0]
@@ -288,7 +289,7 @@ class GuiTheme:
nw.logException()
return False
## Main
# Main
cnfSec = "Main"
if confParser.has_section(cnfSec):
self.themeName = self._parseLine(confParser, cnfSec, "name", "")
@@ -299,7 +300,7 @@ class GuiTheme:
self.themeLicense = self._parseLine(confParser, cnfSec, "license", "N/A")
self.themeLicenseUrl = self._parseLine(confParser, cnfSec, "licenseurl", "")
## Palette
# Palette
cnfSec = "Palette"
if confParser.has_section(cnfSec):
self._setPalette(confParser, cnfSec, "window", QPalette.Window)
@@ -317,7 +318,7 @@ class GuiTheme:
self._setPalette(confParser, cnfSec, "link", QPalette.Link)
self._setPalette(confParser, cnfSec, "linkvisited", QPalette.LinkVisited)
## GUI
# GUI
cnfSec = "GUI"
if confParser.has_section(cnfSec):
self.statNone = self._loadColour(confParser, cnfSec, "statusnone")
@@ -346,7 +347,7 @@ class GuiTheme:
nw.logException()
return False
## Main
# Main
cnfSec = "Main"
if confParser.has_section(cnfSec):
self.syntaxName = self._parseLine(confParser, cnfSec, "name", "")
@@ -357,7 +358,7 @@ class GuiTheme:
self.syntaxLicense = self._parseLine(confParser, cnfSec, "license", "")
self.syntaxLicenseUrl = self._parseLine(confParser, cnfSec, "licenseurl", "")
## Syntax
# Syntax
cnfSec = "Syntax"
if confParser.has_section(cnfSec):
self.colBack = self._loadColour(confParser, cnfSec, "background")
@@ -496,6 +497,7 @@ class GuiTheme:
# End Class GuiTheme
# =============================================================================================== #
# Icons Class
# =============================================================================================== #
@@ -560,7 +562,7 @@ class GuiIcons:
"search_cancel" : (None, None),
"search_preserve" : (None, None),
## General Button Icons
# General Button Icons
"folder-open" : (QStyle.SP_DirOpenIcon, "folder-open"),
"delete" : (QStyle.SP_DialogDiscardButton, "edit-delete"),
"close" : (QStyle.SP_DialogCloseButton, "window-close"),
@@ -583,7 +585,7 @@ class GuiIcons:
"forward" : (None, None),
"settings" : (None, None),
## Switches
# Switches
"sticky-on" : (None, None),
"sticky-off" : (None, None),
"bullet-on" : (None, None),
@@ -651,7 +653,7 @@ class GuiIcons:
nw.logException()
return False
## Main
# Main
cnfSec = "Main"
if confParser.has_section(cnfSec):
self.themeName = self._parseLine(confParser, cnfSec, "name", "")
@@ -662,7 +664,7 @@ class GuiIcons:
self.themeLicense = self._parseLine(confParser, cnfSec, "license", "N/A")
self.themeLicenseUrl = self._parseLine(confParser, cnfSec, "licenseurl", "")
## Palette
# Palette
cnfSec = "Map"
if confParser.has_section(cnfSec):
for iconName, iconFile in confParser.items(cnfSec):
+4 -4
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter GUI Main Window
=============================
@@ -55,6 +54,7 @@ from nw.constants import nwLists
logger = logging.getLogger(__name__)
class GuiMain(QMainWindow):
def __init__(self):
@@ -613,9 +613,9 @@ class GuiMain(QMainWindow):
return False
self.treeView.flushTreeOrder()
nHandle = None # The next handle after tHandle
fHandle = None # The first file handle we encounter
foundIt = False # We've found tHandle, pick the next we see
nHandle = None # The next handle after tHandle
fHandle = None # The first file handle we encounter
foundIt = False # We've found tHandle, pick the next we see
for tItem in self.theProject.projTree:
if tItem is None:
continue
+20 -1
View File
@@ -1,4 +1,23 @@
# -*- coding: utf-8 -*-
"""
novelWriter Tools Init
========================
This file is a part of novelWriter
Copyright 20182021, Veronica Berglyd Olsen
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
from nw.tools.build import GuiBuildNovel
from nw.tools.projwizard import GuiProjectWizard
+18 -17
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter GUI Build Novel Project
=====================================
@@ -52,19 +51,20 @@ from nw.gui.custom import QSwitch
logger = logging.getLogger(__name__)
class GuiBuildNovel(QDialog):
FMT_PDF = 1 # Print to PDF
FMT_PDF = 1 # Print to PDF
FMT_ODT = 2 # Open Document file
FMT_FODT = 3 # Flat Open Document file
FMT_HTM = 4 # HTML5
FMT_NWD = 5 # nW Markdown
FMT_MD = 6 # Standard Markdown
FMT_GH = 7 # GitHub Markdown
FMT_ODT = 2 # Open Document file
FMT_FODT = 3 # Flat Open Document file
FMT_HTM = 4 # HTML5
FMT_NWD = 5 # nW Markdown
FMT_MD = 6 # Standard Markdown
FMT_GH = 7 # GitHub Markdown
FMT_JSON_H = 8 # HTML5 wrapped in JSON
FMT_JSON_M = 9 # nW Markdown wrapped in JSON
FMT_JSON_H = 8 # HTML5 wrapped in JSON
FMT_JSON_M = 9 # nW Markdown wrapped in JSON
def __init__(self, theParent):
QDialog.__init__(self, theParent)
@@ -78,10 +78,10 @@ class GuiBuildNovel(QDialog):
self.theProject = theParent.theProject
self.optState = theParent.theProject.optState
self.htmlText = [] # List of html documents
self.htmlStyle = [] # List of html styles
self.htmlSize = 0 # Size of the html document
self.buildTime = 0 # The timestamp of the last build
self.htmlText = [] # List of html documents
self.htmlStyle = [] # List of html styles
self.htmlSize = 0 # Size of the html document
self.buildTime = 0 # The timestamp of the last build
self.setWindowTitle(self.tr("Build Novel Project"))
self.setMinimumWidth(self.mainConf.pxInt(700))
@@ -215,7 +215,7 @@ class GuiBuildNovel(QDialog):
self.fontForm = QGridLayout(self)
self.fontGroup.setLayout(self.fontForm)
## Font Family
# Font Family
self.textFont = QLineEdit()
self.textFont.setReadOnly(True)
self.textFont.setMinimumWidth(xFmt)
@@ -991,7 +991,7 @@ class GuiBuildNovel(QDialog):
else:
# If the if statements above and here match, it should not
# be possible to reach this else statement.
return False # pragma: no cover
return False # pragma: no cover
# Report to User
# ==============
@@ -1035,7 +1035,7 @@ class GuiBuildNovel(QDialog):
self.textFont.setText(theFont.family())
self.textSize.setValue(theFont.pointSize())
self.raise_() # Move the dialog to front (fixes a bug on macOS)
self.raise_() # Move the dialog to front (fixes a bug on macOS)
return
@@ -1182,6 +1182,7 @@ class GuiBuildNovel(QDialog):
# END Class GuiBuildNovel
class GuiBuildNovelDocView(QTextBrowser):
def __init__(self, theParent, theProject):
+6 -1
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter GUI New Project Wizard
====================================
@@ -48,6 +47,7 @@ PAGE_POP = 2
PAGE_CUSTOM = 3
PAGE_FINAL = 4
class GuiProjectWizard(QWizard):
def __init__(self, theParent):
@@ -86,6 +86,7 @@ class GuiProjectWizard(QWizard):
# END Class GuiProjectWizard
class ProjWizardIntroPage(QWizardPage):
def __init__(self, theWizard):
@@ -155,6 +156,7 @@ class ProjWizardIntroPage(QWizardPage):
# END Class ProjWizardIntroPage
class ProjWizardFolderPage(QWizardPage):
def __init__(self, theWizard):
@@ -227,6 +229,7 @@ class ProjWizardFolderPage(QWizardPage):
# END Class ProjWizardFolderPage
class ProjWizardPopulatePage(QWizardPage):
def __init__(self, theWizard):
@@ -282,6 +285,7 @@ class ProjWizardPopulatePage(QWizardPage):
# END Class ProjWizardPopulatePage
class ProjWizardCustomPage(QWizardPage):
def __init__(self, theWizard):
@@ -400,6 +404,7 @@ class ProjWizardCustomPage(QWizardPage):
# END Class ProjWizardCustomPage
class ProjWizardFinalPage(QWizardPage):
def __init__(self, theWizard):
+2 -2
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter GUI Writing Statistics
====================================
@@ -45,6 +44,7 @@ from nw.gui.custom import QSwitch
logger = logging.getLogger(__name__)
class GuiWritingStats(QDialog):
C_TIME = 0
@@ -570,7 +570,7 @@ class GuiWritingStats(QDialog):
if isFirst:
# Subtract the offset from the first list entry
dwTotal -= self.wordOffset
dwTotal = max(dwTotal, 1) # Don't go zero or negative
dwTotal = max(dwTotal, 1) # Don't go zero or negative
isFirst = False
if groupByDay:
+1 -1
View File
@@ -49,6 +49,6 @@ gui_scripts =
universal = 0
[flake8]
ignore = E203,E221,E226,E228,E241,E251,E261,E266,E302,E305
ignore = E203,E221,E226,E228,E241,E251
max-line-length = 99
exclude = docs/*
+16 -2
View File
@@ -1,5 +1,4 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
novelWriter Main Setup Script
===============================
@@ -35,6 +34,7 @@ OS_LINUX = 1
OS_WIN = 2
OS_DARWIN = 3
# =============================================================================================== #
# Utilities
# =============================================================================================== #
@@ -66,6 +66,7 @@ def extractVersion():
return numVers, hexVers
# =============================================================================================== #
# General
# =============================================================================================== #
@@ -102,6 +103,7 @@ def installPackages(hostOS):
return
##
# Clean Build and Dist Folders (clean)
##
@@ -140,6 +142,7 @@ def cleanInstall():
return
# =============================================================================================== #
# Additional Buiilds
# =============================================================================================== #
@@ -217,6 +220,7 @@ def buildQtDocs():
return
##
# Qt Linguist QM Builder (qtlrelease)
##
@@ -255,6 +259,7 @@ def buildQtI18n():
return
##
# Qt Linguist TS Builder (qtlupdate)
##
@@ -278,6 +283,7 @@ def buildQtI18nTS():
return
##
# Sample Project ZIP File Builder (sample)
##
@@ -318,6 +324,7 @@ def buildSampleZip():
return
# =============================================================================================== #
# Python Packaging
# =============================================================================================== #
@@ -429,6 +436,7 @@ def makeMinimalPackage(targetOS):
return
##
# Make Simple Package (pack-pyz)
##
@@ -607,6 +615,7 @@ def makeSimplePackage(embedPython):
return
# =============================================================================================== #
# General Installers
# =============================================================================================== #
@@ -754,6 +763,7 @@ def xdgInstall():
return
##
# XDG Uninstallation (xdg-uninstall)
##
@@ -823,6 +833,7 @@ def xdgUninstall():
return
##
# WIN Installation (win-install)
##
@@ -947,6 +958,7 @@ def winInstall():
return
##
# WIN Uninstallation (win-uninstall)
##
@@ -1033,6 +1045,7 @@ def winUninstall():
return
# =============================================================================================== #
# Windows Installers
# =============================================================================================== #
@@ -1070,6 +1083,7 @@ def innoSetup():
return
# =============================================================================================== #
# Process Command Line
# =============================================================================================== #
@@ -1280,7 +1294,7 @@ if __name__ == "__main__":
sys.exit(0)
# Run the standard setup
import setuptools # noqa: F401
import setuptools # noqa: F401
setuptools.setup()
# END Main
+15 -3
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter Test Suite Configuration
======================================
@@ -30,9 +29,10 @@ from tools import cleanProject
sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir)))
import nw # noqa: E402
import nw # noqa: E402
from nw.config import Config # noqa: E402
from nw.config import Config # noqa: E402
##
# Core Test Folders
@@ -52,6 +52,7 @@ def tmpDir():
os.mkdir(theDir)
return theDir
@pytest.fixture(scope="session")
def refDir():
"""The folder where all the reference files are stored for verifying
@@ -61,6 +62,7 @@ def refDir():
theDir = os.path.join(testDir, "reference")
return theDir
@pytest.fixture(scope="session")
def filesDir():
"""The folder where additional test files are stored.
@@ -69,6 +71,7 @@ def filesDir():
theDir = os.path.join(testDir, "files")
return theDir
@pytest.fixture(scope="session")
def outDir(tmpDir):
"""An output folder for test results
@@ -78,6 +81,7 @@ def outDir(tmpDir):
os.mkdir(theDir)
return theDir
@pytest.fixture(scope="function")
def fncDir(tmpDir):
"""A temporary folder for a single test function.
@@ -92,6 +96,7 @@ def fncDir(tmpDir):
shutil.rmtree(fncDir)
return
@pytest.fixture(scope="function")
def fncProj(fncDir):
"""A temporary folder for a single test function,
@@ -104,6 +109,7 @@ def fncProj(fncDir):
os.mkdir(prjDir)
return prjDir
##
# novelWriter Objects
##
@@ -121,6 +127,7 @@ def tmpConf(tmpDir):
theConf.guiLang = "en_GB"
return theConf
@pytest.fixture(scope="function")
def fncConf(fncDir):
"""Create a temporary novelWriter configuration object.
@@ -134,6 +141,7 @@ def fncConf(fncDir):
theConf.guiLang = "en_GB"
return theConf
@pytest.fixture(scope="function")
def dummyGUI(monkeypatch, tmpConf):
"""Create a mock instance of novelWriter's main GUI class.
@@ -143,6 +151,7 @@ def dummyGUI(monkeypatch, tmpConf):
theGui.mainConf = tmpConf
return theGui
@pytest.fixture(scope="function")
def nwGUI(qtbot, monkeypatch, fncDir, fncConf):
"""Create an instance of the novelWriter GUI.
@@ -164,6 +173,7 @@ def nwGUI(qtbot, monkeypatch, fncDir, fncConf):
return
##
# Temp Project Folders
##
@@ -188,6 +198,7 @@ def nwMinimal(tmpDir):
return
@pytest.fixture(scope="function")
def nwLipsum(tmpDir):
"""A medium sized novelWriter example project with a lot of Lorem
@@ -209,6 +220,7 @@ def nwLipsum(tmpDir):
return
@pytest.fixture(scope="function")
def nwOldProj(tmpDir):
"""A minimal movelWriter project using the old folder structure used
+5 -1
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter Test Suite Mocked Classes
=======================================
@@ -20,6 +19,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
# =========================================================================== #
# Mock GUI
# =========================================================================== #
@@ -79,6 +79,7 @@ class MockGuiMain():
# END Class MockGuiMain
class MockStatusBar():
def __init__(self):
@@ -92,6 +93,7 @@ class MockStatusBar():
# END Class MockStatusBar
class MockApp:
def __init__(self):
@@ -102,6 +104,7 @@ class MockApp:
# END Class MockApp
# =========================================================================== #
# Error Functions
# Mock functions that will raise errors instead.
@@ -110,5 +113,6 @@ class MockApp:
def causeOSError(*args, **kwargs):
raise OSError("OSError")
def causeException(*args, **kwargs):
raise Exception("Exception")
+17 -1
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter Common Functions Tester
=====================================
@@ -30,6 +29,7 @@ from nw.common import (
isItemLayout, numberToRoman
)
@pytest.mark.base
def testBaseCommon_CheckString():
"""Test the checkString function.
@@ -44,6 +44,7 @@ def testBaseCommon_CheckString():
# END Test testBaseCommon_CheckString
@pytest.mark.base
def testBaseCommon_CheckInt():
"""Test the checkInt function.
@@ -57,6 +58,7 @@ def testBaseCommon_CheckInt():
# END Test testBaseCommon_CheckInt
@pytest.mark.base
def testBaseCommon_CheckBool():
"""Test the checkBool function.
@@ -75,6 +77,7 @@ def testBaseCommon_CheckBool():
# END Test testBaseCommon_CheckBool
@pytest.mark.base
def testBaseCommon_CheckHandle():
"""Test the checkHandle function.
@@ -88,6 +91,7 @@ def testBaseCommon_CheckHandle():
# END Test testBaseCommon_CheckHandle
@pytest.mark.base
def testBaseCommon_IsHandle():
"""Test the isHandle function.
@@ -102,6 +106,7 @@ def testBaseCommon_IsHandle():
# END Test testBaseCommon_IsHandle
@pytest.mark.base
def testBaseCommon_IsTitleTag():
"""Test the isItemClass function.
@@ -119,6 +124,7 @@ def testBaseCommon_IsTitleTag():
# END Test testBaseCommon_IsTitleTag
@pytest.mark.base
def testBaseCommon_IsItemClass():
"""Test the isItemClass function.
@@ -141,6 +147,7 @@ def testBaseCommon_IsItemClass():
# END Test testBaseCommon_IsItemClass
@pytest.mark.base
def testBaseCommon_IsItemType():
"""Test the isItemType function.
@@ -157,6 +164,7 @@ def testBaseCommon_IsItemType():
# END Test testBaseCommon_IsItemType
@pytest.mark.base
def testBaseCommon_IsItemLayout():
"""Test the isItemLayout function.
@@ -177,6 +185,7 @@ def testBaseCommon_IsItemLayout():
# END Test testBaseCommon_IsItemLayout
@pytest.mark.base
def testBaseCommon_HexToInt():
"""Test the hexToInt function.
@@ -190,6 +199,7 @@ def testBaseCommon_HexToInt():
# END Test testBaseCommon_HexToInt
@pytest.mark.base
def testBaseCommon_FormatTimeStamp():
"""Test the formatTimeStamp function.
@@ -200,6 +210,7 @@ def testBaseCommon_FormatTimeStamp():
# END Test testBaseCommon_FormatTimeStamp
@pytest.mark.base
def testBaseCommon_FormatTime():
"""Test the formatTime function.
@@ -222,6 +233,7 @@ def testBaseCommon_FormatTime():
# END Test testBaseCommon_FormatTime
@pytest.mark.base
def testBaseCommon_FormatInt():
"""Test the formatInt function.
@@ -237,6 +249,7 @@ def testBaseCommon_FormatInt():
# END Test testBaseCommon_FormatInt
@pytest.mark.base
def testBaseCommon_TransferCase():
"""Test the transferCase function.
@@ -251,6 +264,7 @@ def testBaseCommon_TransferCase():
# END Test testBaseCommon_TransferCase
@pytest.mark.base
def testBaseCommon_FuzzyTime():
"""Test the fuzzyTime function.
@@ -286,6 +300,7 @@ def testBaseCommon_FuzzyTime():
# END Test testBaseCommon_FuzzyTime
@pytest.mark.base
def testBaseCommon_MakeFileNameSafe():
"""Test the fuzzyTime function.
@@ -297,6 +312,7 @@ def testBaseCommon_MakeFileNameSafe():
# END Test testBaseCommon_MakeFileNameSafe
@pytest.mark.core
def testBaseCommon_RomanNumbers():
"""Test conversion of integers to Roman numbers.
+8 -3
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter Config Class Tester
=================================
@@ -33,6 +32,7 @@ from tools import cmpFiles, writeFile
from nw.config import Config
from nw.constants import nwConst, nwFiles
@pytest.mark.base
def testBaseConfig_Constructor(monkeypatch):
"""Test config contructor.
@@ -79,6 +79,7 @@ def testBaseConfig_Constructor(monkeypatch):
# END Test testBaseConfig_Constructor
@pytest.mark.base
def testBaseConfig_Init(monkeypatch, tmpDir, fncDir, outDir, refDir, filesDir):
"""Test config intialisation.
@@ -95,7 +96,7 @@ def testBaseConfig_Init(monkeypatch, tmpDir, fncDir, outDir, refDir, filesDir):
# Let the config class figure out the path
with monkeypatch.context() as mp:
mp.setattr("PyQt5.QtCore.QStandardPaths.writableLocation", lambda *args: fncDir)
mp.setattr("PyQt5.QtCore.QStandardPaths.writableLocation", lambda *a: fncDir)
tstConf.verQtValue = 50600
tstConf.initConfig()
assert tstConf.confPath == os.path.join(fncDir, tstConf.appHandle)
@@ -132,7 +133,7 @@ def testBaseConfig_Init(monkeypatch, tmpDir, fncDir, outDir, refDir, filesDir):
# Run again and set the paths directly and correctly
# This should create a config file as well
with monkeypatch.context() as mp:
mp.setattr("os.path.expanduser", lambda *args: "")
mp.setattr("os.path.expanduser", lambda *a: "")
tstConf.spellTool = nwConst.SP_INTERNAL
tstConf.initConfig(confPath=tmpDir, dataPath=tmpDir)
assert tstConf.confPath == tmpDir
@@ -205,6 +206,7 @@ def testBaseConfig_Init(monkeypatch, tmpDir, fncDir, outDir, refDir, filesDir):
# END Test testBaseConfig_Init
@pytest.mark.base
def testBaseConfig_RecentCache(monkeypatch, tmpConf, tmpDir, fncDir):
"""Test recent cache file.
@@ -266,6 +268,7 @@ def testBaseConfig_RecentCache(monkeypatch, tmpConf, tmpDir, fncDir):
# END Test testBaseConfig_RecentCache
@pytest.mark.base
def testBaseConfig_SetPath(tmpConf, tmpDir):
"""Test path setters.
@@ -297,6 +300,7 @@ def testBaseConfig_SetPath(tmpConf, tmpDir):
# END Test testBaseConfig_SetPath
@pytest.mark.base
def testBaseConfig_SettersGetters(tmpConf, tmpDir, outDir, refDir):
"""Set various sizes and positions
@@ -470,6 +474,7 @@ def testBaseConfig_SettersGetters(tmpConf, tmpDir, outDir, refDir):
# END Test testBaseConfig_SettersGetters
@pytest.mark.base
def testBaseConfig_Internal(monkeypatch, tmpConf):
"""Check internal functions.
+10 -9
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter Error Handler Tester
==================================
@@ -29,6 +28,7 @@ from mock import causeException
from nw.error import NWErrorMessage, exceptionHandler
@pytest.mark.base
def testBaseError_Dialog(qtbot, monkeypatch, fncDir, tmpDir):
"""Test the error dialog.
@@ -71,6 +71,7 @@ def testBaseError_Dialog(qtbot, monkeypatch, fncDir, tmpDir):
# END Test testBaseError_Dialog
@pytest.mark.base
def testBaseError_Handler(qtbot, monkeypatch, fncDir, tmpDir):
"""Test the error handler. This test doesn'thave any asserts, but it
@@ -85,28 +86,28 @@ def testBaseError_Handler(qtbot, monkeypatch, fncDir, tmpDir):
# Normal shutdown
with monkeypatch.context() as mp:
mp.setattr(NWErrorMessage, "exec_", lambda *args: None)
mp.setattr("PyQt5.QtWidgets.qApp.exit", lambda *args: None)
mp.setattr(NWErrorMessage, "exec_", lambda *a: None)
mp.setattr("PyQt5.QtWidgets.qApp.exit", lambda *a: None)
exceptionHandler(Exception, "Error Message", None)
# Should not crash when no GUI is found
with monkeypatch.context() as mp:
mp.setattr(NWErrorMessage, "exec_", lambda *args: None)
mp.setattr("PyQt5.QtWidgets.qApp.exit", lambda *args: None)
mp.setattr(NWErrorMessage, "exec_", lambda *a: None)
mp.setattr("PyQt5.QtWidgets.qApp.exit", lambda *a: None)
mp.setattr("PyQt5.QtWidgets.qApp.topLevelWidgets", lambda: [])
exceptionHandler(Exception, "Error Message", None)
# Should handle qApp failing
with monkeypatch.context() as mp:
mp.setattr(NWErrorMessage, "exec_", lambda *args: None)
mp.setattr("PyQt5.QtWidgets.qApp.exit", lambda *args: None)
mp.setattr(NWErrorMessage, "exec_", lambda *a: None)
mp.setattr("PyQt5.QtWidgets.qApp.exit", lambda *a: None)
mp.setattr("PyQt5.QtWidgets.qApp.topLevelWidgets", causeException)
exceptionHandler(Exception, "Error Message", None)
# Should handle failing to close main GUI
with monkeypatch.context() as mp:
mp.setattr(NWErrorMessage, "exec_", lambda *args: None)
mp.setattr("PyQt5.QtWidgets.qApp.exit", lambda *args: None)
mp.setattr(NWErrorMessage, "exec_", lambda *a: None)
mp.setattr("PyQt5.QtWidgets.qApp.exit", lambda *a: None)
mp.setattr(nwGUI, "closeMain", causeException)
exceptionHandler(Exception, "Error Message", None)
+7 -5
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter Main Init Tester
==============================
@@ -27,6 +26,7 @@ import sys
from mock import MockGuiMain
@pytest.mark.base
def testBaseInit_Launch(caplog, monkeypatch, tmpDir):
"""Check launching the main GUI.
@@ -64,6 +64,7 @@ def testBaseInit_Launch(caplog, monkeypatch, tmpDir):
# END Test testBaseInit_Launch
@pytest.mark.base
def testBaseInit_Options(monkeypatch, tmpDir):
"""Test command line options for logging level.
@@ -136,6 +137,7 @@ def testBaseInit_Options(monkeypatch, tmpDir):
# END Test testBaseInit_Options
@pytest.mark.base
def testBaseInit_Imports(caplog, monkeypatch, tmpDir):
"""Check import error handling.
@@ -156,10 +158,10 @@ def testBaseInit_Imports(caplog, monkeypatch, tmpDir):
["--testmode", "--config=%s" % tmpDir, "--data=%s" % tmpDir]
)
assert ex.value.code & 4 == 4 # Python version not satisfied
assert ex.value.code & 8 == 8 # Qt version not satisfied
assert ex.value.code & 16 == 16 # PyQt version not satisfied
assert ex.value.code & 32 == 32 # lxml package missing
assert ex.value.code & 4 == 4 # Python version not satisfied
assert ex.value.code & 8 == 8 # Qt version not satisfied
assert ex.value.code & 16 == 16 # PyQt version not satisfied
assert ex.value.code & 32 == 32 # lxml package missing
assert "At least Python" in caplog.messages[0]
assert "At least Qt5" in caplog.messages[1]
+3 -2
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter NWDoc Class Tester
================================
@@ -28,6 +27,7 @@ from mock import causeOSError
from nw.core import NWProject, NWDoc
from nw.enum import nwItemClass, nwItemLayout
@pytest.mark.core
def testCoreDocument_LoadSave(monkeypatch, dummyGUI, nwMinimal):
"""Test loading and saving a document with the NWDoc class.
@@ -120,8 +120,9 @@ def testCoreDocument_LoadSave(monkeypatch, dummyGUI, nwMinimal):
# END Test testCoreDocument_Load
@pytest.mark.core
def testCoreDocument_Methods(monkeypatch, dummyGUI, nwMinimal):
def testCoreDocument_Methods(dummyGUI, nwMinimal):
"""Test other methods of the NWDoc class.
"""
theProject = NWProject(dummyGUI)
+29 -20
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter NWIndex Class Tester
==================================
@@ -33,6 +32,7 @@ from nw.core.project import NWProject
from nw.core.index import NWIndex, countWords
from nw.enum import nwItemClass, nwItemLayout
@pytest.mark.core
def testCoreIndex_LoadSave(monkeypatch, nwLipsum, dummyGUI, outDir, refDir):
"""Test core functionality of scaning, saving, loading and checking
@@ -50,12 +50,12 @@ def testCoreIndex_LoadSave(monkeypatch, nwLipsum, dummyGUI, outDir, refDir):
theIndex = NWIndex(theProject)
notIndexable = {
"b3643d0f92e32": False, # Novel ROOT
"45e6b01ca35c1": False, # Chapter One FOLDER
"6bd935d2490cd": False, # Chapter Two FOLDER
"67a8707f2f249": False, # Character ROOT
"6c6afb1247750": False, # Plot ROOT
"60bdf227455cc": False, # World ROOT
"b3643d0f92e32": False, # Novel ROOT
"45e6b01ca35c1": False, # Chapter One FOLDER
"6bd935d2490cd": False, # Chapter Two FOLDER
"67a8707f2f249": False, # Character ROOT
"6c6afb1247750": False, # Plot ROOT
"60bdf227455cc": False, # World ROOT
}
for tItem in theProject.projTree:
assert theIndex.reIndexHandle(tItem.itemHandle) is notIndexable.get(tItem.itemHandle, True)
@@ -124,6 +124,7 @@ def testCoreIndex_LoadSave(monkeypatch, nwLipsum, dummyGUI, outDir, refDir):
# END Test testCoreIndex_LoadSave
@pytest.mark.core
def testCoreIndex_ScanThis(nwMinimal, dummyGUI):
"""Test the tag scanner function scanThis.
@@ -175,6 +176,7 @@ def testCoreIndex_ScanThis(nwMinimal, dummyGUI):
# END Test testCoreIndex_ScanThis
@pytest.mark.core
def testCoreIndex_CheckThese(nwMinimal, dummyGUI):
"""Test the tag checker function checkThese.
@@ -202,7 +204,7 @@ def testCoreIndex_CheckThese(nwMinimal, dummyGUI):
assert theIndex.scanText(nHandle, (
"# Hello World!\n"
"@pov: Jane\n"
"@invalid: John\n" # Checks for issue #688
"@invalid: John\n" # Checks for issue #688
))
assert theIndex._tagIndex == {"Jane": [2, cHandle, "CHARACTER", "T000001"]}
assert theIndex.getNovelData(nHandle, "T000001")["title"] == "Hello World!"
@@ -236,6 +238,7 @@ def testCoreIndex_CheckThese(nwMinimal, dummyGUI):
# END Test testCoreIndex_CheckThese
@pytest.mark.core
def testCoreIndex_ScanText(nwMinimal, dummyGUI):
"""Check the index text scanner.
@@ -309,29 +312,29 @@ def testCoreIndex_ScanText(nwMinimal, dummyGUI):
"#### Title Four\n\n"
"% synopsis: Synopsis Four.\n\n"
"Paragraph Four.\n\n"
"##### Title Five\n\n" # Not interpreted as a title, the hashes are counted as a word
"##### Title Five\n\n" # Not interpreted as a title, the hashes are counted as a word
"Paragraph Five.\n\n"
))
assert theIndex._refIndex[nHandle].get("T000000", None) is not None # Always there
assert theIndex._refIndex[nHandle].get("T000001", None) is not None # Heading 1
assert theIndex._refIndex[nHandle].get("T000000", None) is not None # Always there
assert theIndex._refIndex[nHandle].get("T000001", None) is not None # Heading 1
assert theIndex._refIndex[nHandle].get("T000002", None) is None
assert theIndex._refIndex[nHandle].get("T000003", None) is None
assert theIndex._refIndex[nHandle].get("T000004", None) is None
assert theIndex._refIndex[nHandle].get("T000005", None) is None
assert theIndex._refIndex[nHandle].get("T000006", None) is None
assert theIndex._refIndex[nHandle].get("T000007", None) is not None # Heading 2
assert theIndex._refIndex[nHandle].get("T000007", None) is not None # Heading 2
assert theIndex._refIndex[nHandle].get("T000008", None) is None
assert theIndex._refIndex[nHandle].get("T000009", None) is None
assert theIndex._refIndex[nHandle].get("T000010", None) is None
assert theIndex._refIndex[nHandle].get("T000011", None) is None
assert theIndex._refIndex[nHandle].get("T000012", None) is None
assert theIndex._refIndex[nHandle].get("T000013", None) is not None # Heading 3
assert theIndex._refIndex[nHandle].get("T000013", None) is not None # Heading 3
assert theIndex._refIndex[nHandle].get("T000014", None) is None
assert theIndex._refIndex[nHandle].get("T000015", None) is None
assert theIndex._refIndex[nHandle].get("T000016", None) is None
assert theIndex._refIndex[nHandle].get("T000017", None) is None
assert theIndex._refIndex[nHandle].get("T000018", None) is None
assert theIndex._refIndex[nHandle].get("T000019", None) is not None # Heading 4
assert theIndex._refIndex[nHandle].get("T000019", None) is not None # Heading 4
assert theIndex._refIndex[nHandle].get("T000020", None) is None
assert theIndex._refIndex[nHandle].get("T000021", None) is None
assert theIndex._refIndex[nHandle].get("T000022", None) is None
@@ -400,9 +403,9 @@ def testCoreIndex_ScanText(nwMinimal, dummyGUI):
assert theIndex.scanText(sHandle, (
"# Title One\n\n"
"@pov: One\n\n" # Valid
"@char: Two\n\n" # Invalid tag
"@:\n\n" # Invalid line
"@pov: One\n\n" # Valid
"@char: Two\n\n" # Invalid tag
"@:\n\n" # Invalid line
"% synopsis: Synopsis One.\n\n"
"Paragraph One.\n\n"
))
@@ -441,6 +444,7 @@ def testCoreIndex_ScanText(nwMinimal, dummyGUI):
# END Test testCoreIndex_ScanText
@pytest.mark.core
def testCoreIndex_ExtractData(nwMinimal, dummyGUI):
"""Check the index data extraction functions.
@@ -499,9 +503,9 @@ def testCoreIndex_ExtractData(nwMinimal, dummyGUI):
# The novel file should have the correct counts
cC, wC, pC = theIndex.getCounts(nHandle)
assert cC == 62 # Characters in text and title only
assert wC == 12 # Words in text and title only
assert pC == 2 # Paragraphs in text only
assert cC == 62 # Characters in text and title only
assert wC == 12 # Words in text and title only
assert pC == 2 # Paragraphs in text only
# getReferences
# =============
@@ -673,6 +677,7 @@ def testCoreIndex_ExtractData(nwMinimal, dummyGUI):
# END Test testCoreIndex_ExtractData
@pytest.mark.core
def testCoreIndex_CheckTagIndex(dummyGUI):
"""Test the tag index checker.
@@ -737,6 +742,7 @@ def testCoreIndex_CheckTagIndex(dummyGUI):
# END Test testCoreIndex_CheckTagIndex
@pytest.mark.core
def testCoreIndex_CheckRefIndex(dummyGUI):
"""Test the reference index checker.
@@ -859,6 +865,7 @@ def testCoreIndex_CheckRefIndex(dummyGUI):
# END Test testCoreIndex_CheckRefIndex
@pytest.mark.core
def testCoreIndex_CheckNovelNoteIndex(dummyGUI):
"""Test the novel and note index checkers.
@@ -1117,6 +1124,7 @@ def testCoreIndex_CheckNovelNoteIndex(dummyGUI):
# END Test testCoreIndex_CheckNovelNoteIndex
@pytest.mark.core
def testCoreIndex_CheckTextCounts(dummyGUI):
"""Test the text counts checker.
@@ -1173,6 +1181,7 @@ def testCoreIndex_CheckTextCounts(dummyGUI):
# END Test testCoreIndex_CheckTextCounts
@pytest.mark.core
def testCoreIndex_CountWords():
"""Test the word counter and the exclusion filers.
+9 -4
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter NWItem Class Tester
=================================
@@ -28,6 +27,7 @@ from nw.core import NWProject
from nw.core.item import NWItem
from nw.enum import nwItemClass, nwItemType, nwItemLayout
@pytest.mark.core
def testCoreItem_Setters(dummyGUI):
"""Test all the simple setters for the NWItem class.
@@ -165,6 +165,7 @@ def testCoreItem_Setters(dummyGUI):
# END Test testCoreItem_Setters
@pytest.mark.core
def testCoreItem_TypeSetter(dummyGUI):
"""Test the setter for all the nwItemType values for the NWItem
@@ -193,6 +194,7 @@ def testCoreItem_TypeSetter(dummyGUI):
# END Test testCoreItem_TypeSetter
@pytest.mark.core
def testCoreItem_ClassSetter(dummyGUI):
"""Test the setter for all the nwItemClass values for the NWItem
@@ -233,6 +235,7 @@ def testCoreItem_ClassSetter(dummyGUI):
# END Test testCoreItem_ClassSetter
@pytest.mark.core
def testCoreItem_LayoutSetter(dummyGUI):
"""Test the setter for all the nwItemLayout values for the NWItem
@@ -269,6 +272,7 @@ def testCoreItem_LayoutSetter(dummyGUI):
# END Test testCoreItem_LayoutSetter
@pytest.mark.core
def testCoreItem_XMLPackUnpack(dummyGUI, caplog):
"""Test packing and unpacking XML objects for the NWItem class.
@@ -368,16 +372,17 @@ def testCoreItem_XMLPackUnpack(dummyGUI, caplog):
assert theItem.itemLayout == nwItemLayout.NO_LAYOUT
# Errors
# ======
## Not an Item
# Not an Item
mockXml = etree.SubElement(nwXML, "stuff")
assert theItem.unpackXML(mockXml) is False
## Item without Handle
# Item without Handle
mockXml = etree.SubElement(nwXML, "item", attrib={"stuff": "nah"})
assert theItem.unpackXML(mockXml) is False
## Item with Invalid SubElement is Accepted w/Error
# Item with Invalid SubElement is Accepted w/Error
mockXml = etree.SubElement(nwXML, "item", attrib={"handle": "0123456789abc"})
xParam = etree.SubElement(mockXml, "invalid")
xParam.text = "stuff"
+3 -2
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter OptionState Class Tester
======================================
@@ -30,6 +29,7 @@ from nw.core import NWProject
from nw.core.options import OptionState
from nw.constants import nwFiles
@pytest.mark.core
def testCoreOptions_LoadSave(monkeypatch, dummyGUI, tmpDir):
"""Test loading and saving from the OptionState class.
@@ -100,8 +100,9 @@ def testCoreOptions_LoadSave(monkeypatch, dummyGUI, tmpDir):
# END Test testCoreOptions_LoadSave
@pytest.mark.core
def testCoreOptions_SetGet(monkeypatch, dummyGUI, tmpDir):
def testCoreOptions_SetGet(dummyGUI):
"""Test setting and getting values from the OptionState class.
"""
theProject = NWProject(dummyGUI)
+53 -37
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter NWProject Class Tester
====================================
@@ -35,8 +34,9 @@ from nw.enum import nwItemClass, nwItemType, nwItemLayout
from nw.common import formatTimeStamp
from nw.constants import nwFiles
@pytest.mark.core
def testCoreProject_NewMinimal(fncDir, outDir, refDir, tmpDir, dummyGUI):
def testCoreProject_NewMinimal(fncDir, outDir, refDir, dummyGUI):
"""Create a new project from a project wizard dictionary. With
default setting, creating a Minimal project.
"""
@@ -83,6 +83,7 @@ def testCoreProject_NewMinimal(fncDir, outDir, refDir, tmpDir, dummyGUI):
# END Test testCoreProject_NewMinimal
@pytest.mark.core
def testCoreProject_NewCustomA(fncDir, outDir, refDir, dummyGUI):
"""Create a new project from a project wizard dictionary.
@@ -124,6 +125,7 @@ def testCoreProject_NewCustomA(fncDir, outDir, refDir, dummyGUI):
# END Test testCoreProject_NewCustomA
@pytest.mark.core
def testCoreProject_NewCustomB(fncDir, outDir, refDir, dummyGUI):
"""Create a new project from a project wizard dictionary.
@@ -165,6 +167,7 @@ def testCoreProject_NewCustomB(fncDir, outDir, refDir, dummyGUI):
# END Test testCoreProject_NewCustomB
@pytest.mark.core
def testCoreProject_NewSampleA(fncDir, tmpConf, dummyGUI, tmpDir):
"""Check that we can create a new project can be created from the
@@ -213,6 +216,7 @@ def testCoreProject_NewSampleA(fncDir, tmpConf, dummyGUI, tmpDir):
# END Test testCoreProject_NewSampleA
@pytest.mark.core
def testCoreProject_NewSampleB(monkeypatch, fncDir, tmpConf, dummyGUI, tmpDir):
"""Check that we can create a new project can be created from the
@@ -250,6 +254,7 @@ def testCoreProject_NewSampleB(monkeypatch, fncDir, tmpConf, dummyGUI, tmpDir):
# END Test testCoreProject_NewSampleB
@pytest.mark.core
def testCoreProject_NewRoot(fncDir, outDir, refDir, dummyGUI):
"""Check that new root folders can be added to the project.
@@ -286,6 +291,7 @@ def testCoreProject_NewRoot(fncDir, outDir, refDir, dummyGUI):
# END Test testCoreProject_NewRoot
@pytest.mark.core
def testCoreProject_NewFile(fncDir, outDir, refDir, dummyGUI):
"""Check that new files can be added to the project.
@@ -315,6 +321,7 @@ def testCoreProject_NewFile(fncDir, outDir, refDir, dummyGUI):
# END Test testCoreProject_NewFile
@pytest.mark.core
def testCoreProject_Open(monkeypatch, nwMinimal, dummyGUI):
"""Test opening a project.
@@ -436,6 +443,7 @@ def testCoreProject_Open(monkeypatch, nwMinimal, dummyGUI):
# END Test testCoreProject_Open
@pytest.mark.core
def testCoreProject_Save(monkeypatch, nwMinimal, dummyGUI, refDir):
"""Test saving a project.
@@ -481,6 +489,7 @@ def testCoreProject_Save(monkeypatch, nwMinimal, dummyGUI, refDir):
# END Test testCoreProject_Save
@pytest.mark.core
def testCoreProject_LockFile(monkeypatch, fncDir, dummyGUI):
"""Test lock file functions for the project folder.
@@ -540,6 +549,7 @@ def testCoreProject_LockFile(monkeypatch, fncDir, dummyGUI):
# END Test testCoreProject_LockFile
@pytest.mark.core
def testCoreProject_Helpers(monkeypatch, fncDir, dummyGUI):
"""Test helper functions for the project folder.
@@ -554,7 +564,7 @@ def testCoreProject_Helpers(monkeypatch, fncDir, dummyGUI):
# Block user's home folder
with monkeypatch.context() as mp:
mp.setattr("os.path.expanduser", lambda *args, **kwargs: fncDir)
mp.setattr("os.path.expanduser", lambda *a, **k: fncDir)
assert theProject.ensureFolderStructure() is False
# Create a file to block meta folder
@@ -583,6 +593,7 @@ def testCoreProject_Helpers(monkeypatch, fncDir, dummyGUI):
# END Test testCoreProject_Helpers
@pytest.mark.core
def testCoreProject_AccessItems(nwMinimal, dummyGUI):
"""Test helper functions for the project folder.
@@ -592,24 +603,24 @@ def testCoreProject_AccessItems(nwMinimal, dummyGUI):
# Move Novel ROOT to after its files
oldOrder = [
"a508bb932959c", # ROOT: Novel
"a35baf2e93843", # FILE: Title Page
"a6d311a93600a", # FOLDER: New Chapter
"f5ab3e30151e1", # FILE: New Chapter
"8c659a11cd429", # FILE: New Scene
"7695ce551d265", # ROOT: Plot
"afb3043c7b2b3", # ROOT: Characters
"9d5247ab588e0", # ROOT: World
"a508bb932959c", # ROOT: Novel
"a35baf2e93843", # FILE: Title Page
"a6d311a93600a", # FOLDER: New Chapter
"f5ab3e30151e1", # FILE: New Chapter
"8c659a11cd429", # FILE: New Scene
"7695ce551d265", # ROOT: Plot
"afb3043c7b2b3", # ROOT: Characters
"9d5247ab588e0", # ROOT: World
]
newOrder = [
"a35baf2e93843", # FILE: Title Page
"f5ab3e30151e1", # FILE: New Chapter
"8c659a11cd429", # FILE: New Scene
"a6d311a93600a", # FOLDER: New Chapter
"a508bb932959c", # ROOT: Novel
"7695ce551d265", # ROOT: Plot
"afb3043c7b2b3", # ROOT: Characters
"9d5247ab588e0", # ROOT: World
"a35baf2e93843", # FILE: Title Page
"f5ab3e30151e1", # FILE: New Chapter
"8c659a11cd429", # FILE: New Scene
"a6d311a93600a", # FOLDER: New Chapter
"a508bb932959c", # ROOT: Novel
"7695ce551d265", # ROOT: Plot
"afb3043c7b2b3", # ROOT: Characters
"9d5247ab588e0", # ROOT: World
]
assert theProject.projTree.handles() == oldOrder
assert theProject.setTreeOrder(newOrder)
@@ -628,20 +639,21 @@ def testCoreProject_AccessItems(nwMinimal, dummyGUI):
retOrder.append(tItem.itemHandle)
assert retOrder == [
"a508bb932959c", # ROOT: Novel
"7695ce551d265", # ROOT: Plot
"afb3043c7b2b3", # ROOT: Characters
"9d5247ab588e0", # ROOT: World
nHandle, # FILE: Test File
"a35baf2e93843", # FILE: Title Page
"a6d311a93600a", # FOLDER: New Chapter
"f5ab3e30151e1", # FILE: New Chapter
"8c659a11cd429", # FILE: New Scene
"a508bb932959c", # ROOT: Novel
"7695ce551d265", # ROOT: Plot
"afb3043c7b2b3", # ROOT: Characters
"9d5247ab588e0", # ROOT: World
nHandle, # FILE: Test File
"a35baf2e93843", # FILE: Title Page
"a6d311a93600a", # FOLDER: New Chapter
"f5ab3e30151e1", # FILE: New Chapter
"8c659a11cd429", # FILE: New Scene
]
assert theProject.projTree[nHandle].itemParent is None
# END Test testCoreProject_AccessItems
@pytest.mark.core
def testCoreProject_Methods(monkeypatch, nwMinimal, dummyGUI, tmpDir):
"""Test other project class methods and functions.
@@ -789,10 +801,10 @@ def testCoreProject_Methods(monkeypatch, nwMinimal, dummyGUI, tmpDir):
theProject.projTree["8c659a11cd429"].setStatus("Finished")
newList = [
("New", 1, 1, 1, "New"),
("Draft", 2, 2, 2, "Note"), # These are swapped
("Note", 3, 3, 3, "Draft"), # These are swapped
("Edited", 4, 4, 4, "Finished"), # Renamed
("Finished", 5, 5, 5, None), # New, with reused name
("Draft", 2, 2, 2, "Note"), # These are swapped
("Note", 3, 3, 3, "Draft"), # These are swapped
("Edited", 4, 4, 4, "Finished"), # Renamed
("Finished", 5, 5, 5, None), # New, with reused name
]
assert theProject.setStatusColours(newList)
assert theProject.statusItems._theLabels == [
@@ -801,10 +813,10 @@ def testCoreProject_Methods(monkeypatch, nwMinimal, dummyGUI, tmpDir):
assert theProject.statusItems._theColours == [
(1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4), (5, 5, 5)
]
assert theProject.projTree["a35baf2e93843"].itemStatus == "Edited" # Renamed
assert theProject.projTree["a6d311a93600a"].itemStatus == "Note" # Swapped
assert theProject.projTree["f5ab3e30151e1"].itemStatus == "Draft" # Swapped
assert theProject.projTree["8c659a11cd429"].itemStatus == "Edited" # Renamed
assert theProject.projTree["a35baf2e93843"].itemStatus == "Edited" # Renamed
assert theProject.projTree["a6d311a93600a"].itemStatus == "Note" # Swapped
assert theProject.projTree["f5ab3e30151e1"].itemStatus == "Draft" # Swapped
assert theProject.projTree["8c659a11cd429"].itemStatus == "Edited" # Renamed
# Change importance
fHandle = theProject.newFile("Jane Doe", nwItemClass.CHARACTER, "afb3043c7b2b3")
@@ -839,7 +851,7 @@ def testCoreProject_Methods(monkeypatch, nwMinimal, dummyGUI, tmpDir):
# Session stats
with monkeypatch.context() as mp:
mp.setattr("os.path.isdir", lambda *args, **kwargs: False)
mp.setattr("os.path.isdir", lambda *a, **k: False)
assert not theProject._appendSessionStats(idleTime=0)
# Block open
@@ -892,6 +904,7 @@ def testCoreProject_Methods(monkeypatch, nwMinimal, dummyGUI, tmpDir):
# END Test testCoreProject_Methods
@pytest.mark.core
def testCoreProject_OrphanedFiles(dummyGUI, nwLipsum):
"""Check that files in the content folder that are not tracked in
@@ -968,6 +981,7 @@ def testCoreProject_OrphanedFiles(dummyGUI, nwLipsum):
# END Test testCoreProject_OrphanedFiles
@pytest.mark.core
def testCoreProject_OldFormat(dummyGUI, nwOldProj):
"""Test that a project folder structure of version 1.0 can be
@@ -1057,6 +1071,7 @@ def testCoreProject_OldFormat(dummyGUI, nwOldProj):
# END Test testCoreProject_OldFormat
@pytest.mark.core
def testCoreProject_LegacyData(monkeypatch, dummyGUI, fncDir):
"""Test the functins that handle legacy data folders and structure
@@ -1162,6 +1177,7 @@ def testCoreProject_LegacyData(monkeypatch, dummyGUI, fncDir):
# END Test testCoreProject_LegacyData
@pytest.mark.core
def testCoreProject_Backup(monkeypatch, dummyGUI, nwMinimal, tmpDir):
"""Test the automated backup feature of the project class. The test
+3 -1
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter Spell Check Classes Tester
========================================
@@ -29,6 +28,7 @@ from tools import readFile, writeFile
from nw.core.spellcheck import NWSpellCheck, NWSpellEnchant, NWSpellSimple
@pytest.mark.core
def testCoreSpell_Super(monkeypatch, tmpDir):
"""Test the spell checker super class
@@ -70,6 +70,7 @@ def testCoreSpell_Super(monkeypatch, tmpDir):
# END Test testCoreSpell_Super
@pytest.mark.core
def testCoreSpell_Enchant(monkeypatch, tmpDir):
"""Test the pyenchant spell checker
@@ -114,6 +115,7 @@ def testCoreSpell_Enchant(monkeypatch, tmpDir):
# END Test testCoreSpell_Enchant
@pytest.mark.core
def testCoreSpell_Simple(monkeypatch, tmpDir):
"""Test the fallback simple spell checker
+2 -1
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter NWStatus Class Tester
===================================
@@ -26,6 +25,7 @@ from lxml import etree
from nw.core.status import NWStatus
@pytest.mark.core
def testCoreStatus_Entries():
"""Test all the simple setters for the NWItem class.
@@ -100,6 +100,7 @@ def testCoreStatus_Entries():
# END Test testCoreStatus_Entries
@pytest.mark.core
def testCoreStatus_XMLPackUnpack():
"""Test all the simple setters for the NWItem class.
+5 -1
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter ToHtml Class Tester
=================================
@@ -27,6 +26,7 @@ from tools import readFile
from nw.core import NWProject, NWIndex, ToHtml
@pytest.mark.core
def testCoreToHtml_Format(dummyGUI):
"""Test all the formatters for the ToHtml class.
@@ -79,6 +79,7 @@ def testCoreToHtml_Format(dummyGUI):
# END Test testCoreToHtml_Format
@pytest.mark.core
def testCoreToHtml_Convert(dummyGUI):
"""Test the converter of the ToHtml class.
@@ -344,6 +345,8 @@ def testCoreToHtml_Convert(dummyGUI):
# END Test testCoreToHtml_Convert
@pytest.mark.core
def testCoreToHtml_Complex(dummyGUI, fncDir):
"""Test the ave method of the ToHtml class.
"""
@@ -416,6 +419,7 @@ def testCoreToHtml_Complex(dummyGUI, fncDir):
# END Test testCoreToHtml_Save
@pytest.mark.core
def testCoreToHtml_Methods(dummyGUI):
"""Test all the other methods of the ToHtml class.
+4 -1
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter Tokenizer Class Tester
====================================
@@ -28,6 +27,7 @@ from tools import readFile
from nw.core import NWProject, NWDoc
from nw.core.tokenizer import Tokenizer
@pytest.mark.core
def testCoreToken_Setters(dummyGUI):
"""Test all the setters for the Tokenizer class.
@@ -111,6 +111,7 @@ def testCoreToken_Setters(dummyGUI):
# END Test testCoreToken_Setters
@pytest.mark.core
def testCoreToken_TextOps(monkeypatch, nwMinimal, dummyGUI):
"""Test handling files and text in the Tokenizer class.
@@ -193,6 +194,7 @@ def testCoreToken_TextOps(monkeypatch, nwMinimal, dummyGUI):
# END Test testCoreToken_TextOps
@pytest.mark.core
def testCoreToken_Tokenize(dummyGUI):
"""Test the tokenization of the Tokenizer class.
@@ -494,6 +496,7 @@ def testCoreToken_Tokenize(dummyGUI):
# END Test testCoreToken_Tokenize
@pytest.mark.core
def testCoreToken_Headers(dummyGUI):
"""Test the header and page parser of the Tokenizer class.
+2 -1
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter ToOdt Class Tester
=================================
@@ -35,6 +34,7 @@ XML_NS = [
' xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0"',
]
def xmlToText(xElem):
"""Get the text content of an XML element.
"""
@@ -43,6 +43,7 @@ def xmlToText(xElem):
rTxt = rTxt.replace(nSpace, "")
return rTxt
@pytest.mark.core
def testCoreToOdt_Convert(dummyGUI):
"""Test the converter of the ToHtml class.
+12 -4
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter NWTree Class Tester
=================================
@@ -30,6 +29,7 @@ from nw.core.project import NWProject, NWItem, NWTree
from nw.enum import nwItemClass, nwItemType, nwItemLayout
from nw.constants import nwFiles
@pytest.fixture(scope="function")
def dummyItems(dummyGUI):
"""Create a list of mock items.
@@ -106,6 +106,7 @@ def dummyItems(dummyGUI):
return theItems
@pytest.mark.core
def testCoreTree_BuildTree(dummyGUI, dummyItems):
"""Test building a project tree from a list of items.
@@ -202,6 +203,7 @@ def testCoreTree_BuildTree(dummyGUI, dummyItems):
# END Test testCoreTree_BuildTree
@pytest.mark.core
def testCoreTree_Methods(dummyGUI, dummyItems):
"""Test bvarious class methods.
@@ -258,6 +260,7 @@ def testCoreTree_Methods(dummyGUI, dummyItems):
# END Test testCoreTree_Methods
@pytest.mark.core
def testCoreTree_UpdateItemLayout(dummyGUI, dummyItems):
"""Test building a project tree from a list of items.
@@ -271,9 +274,9 @@ def testCoreTree_UpdateItemLayout(dummyGUI, dummyItems):
assert len(theTree) == len(dummyItems)
# Check rejected items
assert not theTree.updateItemLayout("0000000000000", "H1") # Non-existent handle
assert not theTree.updateItemLayout("a000000000004", "H2") # Character file
assert not theTree.updateItemLayout("c000000000002", "H0") # Wrong header level
assert not theTree.updateItemLayout("0000000000000", "H1") # Non-existent handle
assert not theTree.updateItemLayout("a000000000004", "H2") # Character file
assert not theTree.updateItemLayout("c000000000002", "H0") # Wrong header level
cHandle = "c000000000002"
@@ -383,6 +386,7 @@ def testCoreTree_UpdateItemLayout(dummyGUI, dummyItems):
# END Test testCoreTree_UpdateItemLayout
@pytest.mark.core
def testCoreTree_MakeHandles(monkeypatch, dummyGUI):
"""Test generating item handles.
@@ -425,6 +429,7 @@ def testCoreTree_MakeHandles(monkeypatch, dummyGUI):
# END Test testCoreTree_MakeHandles
@pytest.mark.core
def testCoreTree_Stats(dummyGUI, dummyItems):
"""Test project stats methods.
@@ -451,6 +456,7 @@ def testCoreTree_Stats(dummyGUI, dummyItems):
# END Test testCoreTree_Stats
@pytest.mark.core
def testCoreTree_Reorder(dummyGUI, dummyItems):
"""Test changing tree order.
@@ -482,6 +488,7 @@ def testCoreTree_Reorder(dummyGUI, dummyItems):
# END Test testCoreTree_Reorder
@pytest.mark.core
def testCoreTree_XMLPackUnpack(dummyGUI, dummyItems):
"""Test packing and unpacking the tree to and from XML.
@@ -537,6 +544,7 @@ def testCoreTree_XMLPackUnpack(dummyGUI, dummyItems):
# END Test testCoreTree_XMLPackUnpack
@pytest.mark.core
def testCoreTree_ToCFile(monkeypatch, dummyGUI, dummyItems, tmpDir):
"""Test writing the ToC.txt file.
+3 -3
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter About Dialog Class Tester
=======================================
@@ -32,12 +31,13 @@ keyDelay = 2
typeDelay = 1
stepDelay = 20
@pytest.mark.gui
def testDlgAbout_Dialog(qtbot, monkeypatch, nwGUI):
"""Test the full about dialogs.
"""
# NW About
monkeypatch.setattr(GuiAbout, "exec_", lambda *args: None)
monkeypatch.setattr(GuiAbout, "exec_", lambda *a: None)
nwGUI.mainMenu.aAboutNW.activate(QAction.Trigger)
qtbot.waitUntil(lambda: getGuiItem("GuiAbout") is not None, timeout=1000)
@@ -61,7 +61,7 @@ def testDlgAbout_Dialog(qtbot, monkeypatch, nwGUI):
assert msgAbout.tabBox.currentWidget() == msgAbout.pageNotes
# Qt About
monkeypatch.setattr(QMessageBox, "aboutQt", lambda *args, **kwargs: None)
monkeypatch.setattr(QMessageBox, "aboutQt", lambda *a, **k: None)
nwGUI.mainMenu.aAboutQt.activate(QAction.Trigger)
# qtbot.stopForInteraction()
+3 -3
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter Other Dialog Classes Tester
=========================================
@@ -31,12 +30,13 @@ keyDelay = 2
typeDelay = 1
stepDelay = 20
@pytest.mark.gui
def testDlgOther_QuoteSelect(qtbot, monkeypatch, nwGUI, nwMinimal):
def testDlgOther_QuoteSelect(monkeypatch, nwGUI):
"""Test the quote symbols dialog.
"""
# Block message box
monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
nwQuot = GuiQuoteSelect(nwGUI)
nwQuot.show()
+5 -5
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter Item Editor Dialog Class Tester
=============================================
@@ -36,8 +35,9 @@ keyDelay = 2
typeDelay = 1
stepDelay = 20
@pytest.mark.gui
def testDlgItemEditor_Dialog(qtbot, monkeypatch, nwGUI, fncDir, fncProj, refDir, outDir):
def testDlgItemEditor_Dialog(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir):
"""Test the full item editor dialog.
"""
projFile = os.path.join(fncProj, "nwProject.nwx")
@@ -45,8 +45,8 @@ def testDlgItemEditor_Dialog(qtbot, monkeypatch, nwGUI, fncDir, fncProj, refDir,
compFile = os.path.join(refDir, "guiItemEditor_Dialog_nwProject.nwx")
# Block message box
monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes)
monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *args: True)
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: True)
# Create new, save, open project
nwGUI.theProject.projTree.setSeed(42)
@@ -54,7 +54,7 @@ def testDlgItemEditor_Dialog(qtbot, monkeypatch, nwGUI, fncDir, fncProj, refDir,
assert nwGUI.openDocument("0e17daca5f3e1")
assert nwGUI.treeView.setSelectedHandle("0e17daca5f3e1", doScroll=True)
monkeypatch.setattr(GuiItemEditor, "exec_", lambda *args: None)
monkeypatch.setattr(GuiItemEditor, "exec_", lambda *a: None)
nwGUI.mainMenu.aEditItem.activate(QAction.Trigger)
qtbot.waitUntil(lambda: getGuiItem("GuiItemEditor") is not None, timeout=1000)
+2 -2
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter Merge and Split Dialog Classes Tester
===================================================
@@ -36,8 +35,9 @@ keyDelay = 2
typeDelay = 1
stepDelay = 20
@pytest.mark.gui
def testDlgMerge_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
def testDlgMerge_Main(qtbot, monkeypatch, nwGUI, fncProj):
"""Test the merge documents tool.
"""
# Block message box
+7 -7
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter Preferences Dialog Class Tester
=============================================
@@ -40,13 +39,14 @@ keyDelay = 2
typeDelay = 1
stepDelay = 20
@pytest.mark.gui
def testDlgPreferences_Main(qtbot, monkeypatch, fncDir, outDir, refDir):
"""Test the load project wizard.
"""
# Block message box
monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "information", lambda *args: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
# Must create a clean config and GUI object as the test-wide
# nw.CONFIG object is created on import an can be tainted by other tests
@@ -69,8 +69,8 @@ def testDlgPreferences_Main(qtbot, monkeypatch, fncDir, outDir, refDir):
assert theConf.confPath == fncDir
theConf.spellTool = nwConst.SP_INTERNAL
monkeypatch.setattr(GuiPreferences, "exec_", lambda *args: None)
monkeypatch.setattr(GuiPreferences, "result", lambda *args: QDialog.Accepted)
monkeypatch.setattr(GuiPreferences, "exec_", lambda *a: None)
monkeypatch.setattr(GuiPreferences, "result", lambda *a: QDialog.Accepted)
nwGUI.mainMenu.aPreferences.activate(QAction.Trigger)
qtbot.waitUntil(lambda: getGuiItem("GuiPreferences") is not None, timeout=1000)
@@ -125,9 +125,9 @@ def testDlgPreferences_Main(qtbot, monkeypatch, fncDir, outDir, refDir):
# qtbot.stopForInteraction()
# Check Browse button
monkeypatch.setattr(QFileDialog, "getExistingDirectory", lambda *args, **kwargs: "")
monkeypatch.setattr(QFileDialog, "getExistingDirectory", lambda *a, **k: "")
assert not tabProjects._backupFolder()
monkeypatch.setattr(QFileDialog, "getExistingDirectory", lambda *args, **kwargs: "some/dir")
monkeypatch.setattr(QFileDialog, "getExistingDirectory", lambda *a, **k: "some/dir")
qtbot.mouseClick(tabProjects.backupGetPath, Qt.LeftButton)
qtbot.wait(keyDelay)
+4 -4
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter Project Load Dialog Class Tester
==============================================
@@ -37,6 +36,7 @@ keyDelay = 2
typeDelay = 1
stepDelay = 20
@pytest.mark.gui
def testDlgLoadProject_Main(qtbot, monkeypatch, nwGUI, nwMinimal):
"""Test the load project wizard.
@@ -48,8 +48,8 @@ def testDlgLoadProject_Main(qtbot, monkeypatch, nwGUI, nwMinimal):
assert nwGUI.closeProject()
qtbot.wait(stepDelay)
monkeypatch.setattr(GuiProjectLoad, "exec_", lambda *args: None)
monkeypatch.setattr(GuiProjectLoad, "result", lambda *args: QDialog.Accepted)
monkeypatch.setattr(GuiProjectLoad, "exec_", lambda *a: None)
monkeypatch.setattr(GuiProjectLoad, "result", lambda *a: QDialog.Accepted)
nwGUI.mainMenu.aOpenProject.activate(QAction.Trigger)
qtbot.waitUntil(lambda: getGuiItem("GuiProjectLoad") is not None, timeout=1000)
@@ -106,7 +106,7 @@ def testDlgLoadProject_Main(qtbot, monkeypatch, nwGUI, nwMinimal):
assert nwLoad.listBox.topLevelItemCount() == recentCount - 1
getFile = os.path.join(nwMinimal, "nwProject.nwx")
monkeypatch.setattr(QFileDialog, "getOpenFileName", lambda *args, **kwargs: (getFile, None))
monkeypatch.setattr(QFileDialog, "getOpenFileName", lambda *a, **k: (getFile, None))
qtbot.mouseClick(nwLoad.browseButton, Qt.LeftButton)
assert nwLoad.openPath == nwMinimal
assert nwLoad.openState == nwLoad.OPEN_STATE
+6 -6
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter Project Settings Dialog Class Tester
==================================================
@@ -38,6 +37,7 @@ keyDelay = 2
typeDelay = 1
stepDelay = 20
@pytest.mark.gui
def testDlgProjSettings_Dialog(qtbot, monkeypatch, nwGUI, fncDir, fncProj, outDir, refDir):
"""Test the full project settings dialog.
@@ -47,8 +47,8 @@ def testDlgProjSettings_Dialog(qtbot, monkeypatch, nwGUI, fncDir, fncProj, outDi
compFile = os.path.join(refDir, "guiProjSettings_Dialog_nwProject.nwx")
# Block message box
monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "critical", lambda *args: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
# Check that we cannot open when there is no project
nwGUI.mainMenu.aProjectSettings.activate(QAction.Trigger)
@@ -64,8 +64,8 @@ def testDlgProjSettings_Dialog(qtbot, monkeypatch, nwGUI, fncDir, fncProj, outDi
nwGUI.theProject.setAutoReplace({"A": "B", "C": "D"})
# Get the dialog object
monkeypatch.setattr(GuiProjectSettings, "exec_", lambda *args: None)
monkeypatch.setattr(GuiProjectSettings, "result", lambda *args: QDialog.Accepted)
monkeypatch.setattr(GuiProjectSettings, "exec_", lambda *a: None)
monkeypatch.setattr(GuiProjectSettings, "result", lambda *a: QDialog.Accepted)
nwGUI.mainMenu.aProjectSettings.activate(QAction.Trigger)
qtbot.waitUntil(lambda: getGuiItem("GuiProjectSettings") is not None, timeout=1000)
@@ -135,7 +135,7 @@ def testDlgProjSettings_Dialog(qtbot, monkeypatch, nwGUI, fncDir, fncProj, outDi
assert projEdit.tabStatus.listBox.topLevelItemCount() == 3
# Add a new item
monkeypatch.setattr(QColorDialog, "getColor", lambda *args: QColor(20, 30, 40))
monkeypatch.setattr(QColorDialog, "getColor", lambda *a: QColor(20, 30, 40))
qtbot.mouseClick(projEdit.tabStatus.addButton, Qt.LeftButton)
projEdit.tabStatus.listBox.topLevelItem(3).setSelected(True)
for n in range(8):
+2 -2
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter Merge and Split Dialog Classes Tester
===================================================
@@ -37,8 +36,9 @@ keyDelay = 2
typeDelay = 1
stepDelay = 20
@pytest.mark.gui
def testDlgSplit_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
def testDlgSplit_Main(qtbot, monkeypatch, nwGUI, fncProj):
"""Test the split document tool.
"""
# Block message box
+10 -8
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter Other Dialog Classes Tester
=========================================
@@ -21,6 +20,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
import os
import pytest
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QDialog, QMessageBox, QAction
@@ -35,14 +35,16 @@ keyDelay = 2
typeDelay = 1
stepDelay = 20
def testDlgWordList_Dialog(qtbot, monkeypatch, nwGUI, nwMinimal, tmpDir):
@pytest.mark.gui
def testDlgWordList_Dialog(qtbot, monkeypatch, nwGUI, nwMinimal):
"""test the word list editor.
"""
monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "critical", lambda *args: QMessageBox.Yes)
monkeypatch.setattr(GuiWordList, "exec_", lambda *args: None)
monkeypatch.setattr(GuiWordList, "result", lambda *args: QDialog.Accepted)
monkeypatch.setattr(GuiWordList, "accept", lambda *args: None)
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(GuiWordList, "exec_", lambda *a: None)
monkeypatch.setattr(GuiWordList, "result", lambda *a: QDialog.Accepted)
monkeypatch.setattr(GuiWordList, "accept", lambda *a: None)
# Open project
nwGUI.openProject(nwMinimal)
@@ -66,7 +68,7 @@ def testDlgWordList_Dialog(qtbot, monkeypatch, nwGUI, nwMinimal, tmpDir):
"word_a\n"
"word_c\n"
"word_g\n"
" \n" # Should be ignored
" \n" # Should be ignored
"word_f\n"
"word_b\n"
))
+14 -13
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter Main GUI Editor Class Tester
==========================================
@@ -20,8 +19,8 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
import pytest
import os
import pytest
from shutil import copyfile
from tools import cmpFiles
@@ -39,17 +38,18 @@ keyDelay = 2
typeDelay = 1
stepDelay = 20
@pytest.mark.gui
def testGuiEditor_Main(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir):
"""Test the document editor.
"""
# Block message box
monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "information", lambda *args: QMessageBox.Yes)
monkeypatch.setattr(GuiItemEditor, "exec_", lambda *args: None)
monkeypatch.setattr(GuiItemEditor, "result", lambda *args: QDialog.Accepted)
monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *args: True)
monkeypatch.setattr(GuiDocEditor, "hasFocus", lambda *args: True)
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(GuiItemEditor, "exec_", lambda *a: None)
monkeypatch.setattr(GuiItemEditor, "result", lambda *a: QDialog.Accepted)
monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: True)
monkeypatch.setattr(GuiDocEditor, "hasFocus", lambda *a: True)
# Create new, save, close project
nwGUI.theProject.projTree.setSeed(42)
@@ -315,7 +315,7 @@ def testGuiEditor_Main(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir):
assert nwGUI.treeView.deleteItem()
assert nwGUI.treeView.setSelectedHandle(newHandle)
assert nwGUI.treeView.deleteItem()
assert nwGUI.theProject.projTree["2fca346db6561"] is not None # Trash
assert nwGUI.theProject.projTree["2fca346db6561"] is not None # Trash
assert nwGUI.saveProject()
# Check the files
@@ -353,13 +353,14 @@ def testGuiEditor_Main(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir):
# END Test testGuiEditor_Main
@pytest.mark.gui
def testGuiEditor_Search(qtbot, monkeypatch, nwGUI, nwLipsum):
"""Test the document editor search functionality.
"""
# Block message box
monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes)
monkeypatch.setattr(GuiDocEditor, "hasFocus", lambda *args: True)
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(GuiDocEditor, "hasFocus", lambda *a: True)
nwGUI.theProject.projTree.setSeed(42)
assert nwGUI.openProject(nwLipsum)
@@ -414,7 +415,7 @@ def testGuiEditor_Search(qtbot, monkeypatch, nwGUI, nwLipsum):
# Set Invalid RegEx
assert nwGUI.docEditor.docSearch.setSearchText(r"\bSus[")
qtbot.mouseClick(nwGUI.docEditor.docSearch.searchButton, Qt.LeftButton, delay=keyDelay)
assert nwGUI.docEditor.getCursorPosition() < 3 # No result
assert nwGUI.docEditor.getCursorPosition() < 3 # No result
# Set Valid RegEx
assert nwGUI.docEditor.docSearch.setSearchText(r"\bSus")
@@ -503,7 +504,7 @@ def testGuiEditor_Search(qtbot, monkeypatch, nwGUI, nwLipsum):
# Next Match
nwGUI.mainMenu.aFindNext.activate(QAction.Trigger)
assert nwGUI.docEditor.docHandle() == "2426c6f0ca922" # Next document
assert nwGUI.docEditor.docHandle() == "2426c6f0ca922" # Next document
nwGUI.mainMenu.aFindNext.activate(QAction.Trigger)
assert abs(nwGUI.docEditor.getCursorPosition() - 620) < 3
nwGUI.mainMenu.aFindNext.activate(QAction.Trigger)
+3 -3
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter Main GUI Viewer Class Tester
==========================================
@@ -32,13 +31,14 @@ keyDelay = 2
typeDelay = 1
stepDelay = 20
@pytest.mark.gui
def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, nwLipsum):
"""Test the document viewer.
"""
# Block message box
monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "information", lambda *args: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
# Open project
nwGUI.theProject.projTree.setSeed(42)
+19 -14
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter Main GUI Main Menu Class Tester
=============================================
@@ -35,6 +34,7 @@ keyDelay = 2
typeDelay = 1
stepDelay = 20
@pytest.mark.gui
def testGuiMenu_EditFormat(qtbot, monkeypatch, nwGUI, nwLipsum):
"""Test the main menu Edit and Format entries.
@@ -113,44 +113,45 @@ def testGuiMenu_EditFormat(qtbot, monkeypatch, nwGUI, nwLipsum):
qtbot.wait(stepDelay)
# Block Formats
# =============
assert nwGUI.docEditor.setCursorPosition(30)
## Header 1
# Header 1
nwGUI.mainMenu.aFmtHead1.activate(QAction.Trigger)
fmtStr = "# Pellentesque nec erat ut nulla posuere commodo."
assert nwGUI.docEditor.getText()[27:76] == fmtStr
qtbot.wait(stepDelay)
## Header 2
# Header 2
nwGUI.mainMenu.aFmtHead2.activate(QAction.Trigger)
fmtStr = "## Pellentesque nec erat ut nulla posuere commodo."
assert nwGUI.docEditor.getText()[27:77] == fmtStr
qtbot.wait(stepDelay)
## Header 3
# Header 3
nwGUI.mainMenu.aFmtHead3.activate(QAction.Trigger)
fmtStr = "### Pellentesque nec erat ut nulla posuere commodo."
assert nwGUI.docEditor.getText()[27:78] == fmtStr
qtbot.wait(stepDelay)
## Header 4
# Header 4
nwGUI.mainMenu.aFmtHead4.activate(QAction.Trigger)
fmtStr = "#### Pellentesque nec erat ut nulla posuere commodo."
assert nwGUI.docEditor.getText()[27:79] == fmtStr
qtbot.wait(stepDelay)
## Clear Format
# Clear Format
nwGUI.mainMenu.aFmtNoFormat.activate(QAction.Trigger)
assert nwGUI.docEditor.getText()[27:74] == cleanText
qtbot.wait(stepDelay)
## Comment On
# Comment On
nwGUI.mainMenu.aFmtComment.activate(QAction.Trigger)
fmtStr = "% Pellentesque nec erat ut nulla posuere commodo."
assert nwGUI.docEditor.getText()[27:76] == fmtStr
qtbot.wait(stepDelay)
## Comment Off
# Comment Off
nwGUI.mainMenu.aFmtComment.activate(QAction.Trigger)
assert nwGUI.docEditor.getText()[27:74] == cleanText
qtbot.wait(stepDelay)
@@ -228,41 +229,43 @@ def testGuiMenu_EditFormat(qtbot, monkeypatch, nwGUI, nwLipsum):
assert nwGUI.docEditor.isEmpty()
# Alignment & Indent
# ==================
cleanText = "A single, short paragraph.\n\n"
nwGUI.docEditor.setText(cleanText)
assert nwGUI.docEditor.setCursorPosition(0)
## Left Align
# Left Align
nwGUI.mainMenu.aFmtAlignLeft.activate(QAction.Trigger)
fmtStr = "A single, short paragraph. <<"
assert nwGUI.docEditor.getText()[:29] == fmtStr
qtbot.wait(stepDelay)
## Right Align
# Right Align
nwGUI.mainMenu.aFmtAlignRight.activate(QAction.Trigger)
fmtStr = ">> A single, short paragraph."
assert nwGUI.docEditor.getText()[:29] == fmtStr
qtbot.wait(stepDelay)
## Centre Align
# Centre Align
nwGUI.mainMenu.aFmtAlignCentre.activate(QAction.Trigger)
fmtStr = ">> A single, short paragraph. <<"
assert nwGUI.docEditor.getText()[:32] == fmtStr
qtbot.wait(stepDelay)
## Left Indent
# Left Indent
nwGUI.mainMenu.aFmtIndentLeft.activate(QAction.Trigger)
fmtStr = "> A single, short paragraph."
assert nwGUI.docEditor.getText()[:28] == fmtStr
qtbot.wait(stepDelay)
## Right Indent
# Right Indent
nwGUI.mainMenu.aFmtIndentRight.activate(QAction.Trigger)
fmtStr = "> A single, short paragraph. <"
assert nwGUI.docEditor.getText()[:30] == fmtStr
qtbot.wait(stepDelay)
## No Format
# No Format
nwGUI.mainMenu.aFmtNoFormat.activate(QAction.Trigger)
assert nwGUI.docEditor.getText()[:30] == cleanText
qtbot.wait(stepDelay)
@@ -368,6 +371,7 @@ def testGuiMenu_EditFormat(qtbot, monkeypatch, nwGUI, nwLipsum):
# END Test testGuiMenu_EditFormat
@pytest.mark.gui
def testGuiMenu_ContextMenus(qtbot, monkeypatch, nwGUI, nwLipsum):
"""Test the context menus.
@@ -452,6 +456,7 @@ def testGuiMenu_ContextMenus(qtbot, monkeypatch, nwGUI, nwLipsum):
# END Test testGuiMenu_ContextMenus
@pytest.mark.gui
def testGuiMenu_Insert(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
"""Test the Insert menu.
+4 -4
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter Main GUI Novel Tree Class Tester
==============================================
@@ -28,13 +27,14 @@ from tools import writeFile
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QMessageBox
@pytest.mark.gui
def testGuiNovelTree_TreeItems(qtbot, caplog, monkeypatch, nwGUI, nwMinimal):
def testGuiNovelTree_TreeItems(qtbot, monkeypatch, nwGUI, nwMinimal):
"""Test navigating the novel tree.
"""
# Block message box
monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "information", lambda *args: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
nwGUI.openProject(nwMinimal)
nwGUI.theProject.projTree.setSeed(42)
+3 -3
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter Main GUI Outline Class Tester
===========================================
@@ -31,13 +30,14 @@ keyDelay = 2
typeDelay = 1
stepDelay = 20
@pytest.mark.gui
def testGuiOutline_Main(qtbot, monkeypatch, nwGUI, nwLipsum):
"""Test the outline view.
"""
# Block message box
monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "information", lambda *args: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
assert nwGUI.openProject(nwLipsum)
nwGUI.mainConf.lastPath = nwLipsum
+5 -5
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter Project Details Dialog Class Tester
=================================================
@@ -32,15 +31,16 @@ keyDelay = 2
typeDelay = 1
stepDelay = 20
@pytest.mark.gui
def testGuiProjDetails_Dialog(qtbot, monkeypatch, nwGUI, nwLipsum):
"""Test the project details dialog.
"""
# Block message box
monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "information", lambda *args: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "warning", lambda *args: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "critical", lambda *args: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
# Create a project to work on
assert nwGUI.openProject(nwLipsum)
+16 -16
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter Main GUI Project Tree Class Tester
================================================
@@ -32,16 +31,17 @@ from nw.guimain import GuiMain
from nw.gui.projtree import GuiProjectTree
from nw.enum import nwItemType, nwItemClass
@pytest.mark.gui
def testGuiProjTree_TreeItems(qtbot, caplog, monkeypatch, nwGUI, nwMinimal):
"""Test adding and removing items from the project tree.
"""
# Block message box
monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "critical", lambda *args: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "warning", lambda *args: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "information", lambda *args: QMessageBox.Yes)
monkeypatch.setattr(GuiMain, "editItem", lambda *args: None)
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(GuiMain, "editItem", lambda *a: None)
nwGUI.theProject.projTree.setSeed(42)
nwTree = nwGUI.treeView
@@ -82,8 +82,8 @@ def testGuiProjTree_TreeItems(qtbot, caplog, monkeypatch, nwGUI, nwMinimal):
]
# Add roots
assert not nwTree.newTreeItem(nwItemType.ROOT, nwItemClass.WORLD) # Duplicate
assert nwTree.newTreeItem(nwItemType.ROOT, nwItemClass.CUSTOM) # Valid
assert not nwTree.newTreeItem(nwItemType.ROOT, nwItemClass.WORLD) # Duplicate
assert nwTree.newTreeItem(nwItemType.ROOT, nwItemClass.CUSTOM) # Valid
# Change max depth and try to add a subfolder that is too deep
monkeypatch.setattr("nw.constants.nwConst.MAX_DEPTH", 2)
@@ -98,12 +98,12 @@ def testGuiProjTree_TreeItems(qtbot, caplog, monkeypatch, nwGUI, nwMinimal):
nwTree.setSelectedHandle("8c659a11cd429")
# Shift focus and try to move item
monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *args: False)
monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: False)
assert not nwTree.moveTreeItem(1)
assert nwTree.getTreeFromHandle("a6d311a93600a") == [
"a6d311a93600a", "f5ab3e30151e1", "8c659a11cd429", "44cb730c42048", "71ee45a3c0db9"
]
monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *args: True)
monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: True)
# Move second item up twice (should give same result)
nwGUI.mainMenu.aMoveUp.activate(QAction.Trigger)
@@ -168,12 +168,12 @@ def testGuiProjTree_TreeItems(qtbot, caplog, monkeypatch, nwGUI, nwMinimal):
# Delete the items we added earlier
nwTree.clearSelection()
assert not nwTree.emptyTrash() # No folder yet
assert not nwTree.emptyTrash() # No folder yet
assert not nwTree.deleteItem(None)
assert not nwTree.deleteItem("1111111111111")
assert nwTree.deleteItem("73475cb40a568") # New File
assert nwTree.deleteItem("71ee45a3c0db9") # New Folder
assert nwTree.deleteItem("811786ad1ae74") # Custom Root
assert nwTree.deleteItem("73475cb40a568") # New File
assert nwTree.deleteItem("71ee45a3c0db9") # New Folder
assert nwTree.deleteItem("811786ad1ae74") # Custom Root
assert "73475cb40a568" in nwGUI.theProject.projTree._treeOrder
assert "71ee45a3c0db9" not in nwGUI.theProject.projTree._treeOrder
assert "811786ad1ae74" not in nwGUI.theProject.projTree._treeOrder
@@ -181,7 +181,7 @@ def testGuiProjTree_TreeItems(qtbot, caplog, monkeypatch, nwGUI, nwMinimal):
# The file is in trash, empty it
assert os.path.isfile(os.path.join(nwMinimal, "content", "73475cb40a568.nwd"))
assert nwTree.emptyTrash()
assert not nwTree.emptyTrash() # Already empty
assert not nwTree.emptyTrash() # Already empty
assert not os.path.isfile(os.path.join(nwMinimal, "content", "73475cb40a568.nwd"))
assert "73475cb40a568" not in nwGUI.theProject.projTree._treeOrder
@@ -228,7 +228,7 @@ def testGuiProjTree_TreeItems(qtbot, caplog, monkeypatch, nwGUI, nwMinimal):
nwTree.clearSelection()
# Add a file with no parent, and fail to find a suitable parent item
monkeypatch.setattr("nw.core.tree.NWTree.findRoot", lambda *args: None)
monkeypatch.setattr("nw.core.tree.NWTree.findRoot", lambda *a: None)
assert not nwTree.newTreeItem(nwItemType.FILE, nwItemClass.NOVEL)
assert not nwTree.newTreeItem(nwItemType.FOLDER, nwItemClass.NOVEL)
+2 -2
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter GUI Theme and Icons Classes Tester
================================================
@@ -30,12 +29,13 @@ keyDelay = 2
typeDelay = 1
stepDelay = 20
@pytest.mark.gui
def testGuiTheme_Main(qtbot, monkeypatch, nwMinimal, tmpDir):
"""Test the theme and icon classes.
"""
# Block message box
monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
nwGUI = nw.main(["--testmode", "--config=%s" % nwMinimal, "--data=%s" % tmpDir, nwMinimal])
qtbot.addWidget(nwGUI)
+6 -6
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter Build Dialog Class Tester
=======================================
@@ -35,14 +34,15 @@ keyDelay = 2
typeDelay = 1
stepDelay = 20
@pytest.mark.gui
def testToolBuild_Main(qtbot, monkeypatch, nwGUI, nwLipsum, refDir, outDir):
"""Test the build tool.
"""
# Block message box
monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "information", lambda *args: QMessageBox.Yes)
monkeypatch.setattr(QFileDialog, "getSaveFileName", lambda a, b, c, **kwargs: (c, None))
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QFileDialog, "getSaveFileName", lambda a, b, c, **k: (c, None))
# Check that we cannot open when there is no project
nwGUI.mainMenu.aBuildProject.activate(QAction.Trigger)
@@ -69,7 +69,7 @@ def testToolBuild_Main(qtbot, monkeypatch, nwGUI, nwLipsum, refDir, outDir):
# Non-existent path
with monkeypatch.context() as mp:
mp.setattr("os.path.expanduser", lambda *args, **kwargs: nwLipsum)
mp.setattr("os.path.expanduser", lambda *a, **k: nwLipsum)
assert nwGUI.mainConf.lastPath != nwLipsum
nwGUI.mainConf.lastPath = "no_such_path"
assert nwBuild._saveDocument(nwBuild.FMT_NWD)
@@ -77,7 +77,7 @@ def testToolBuild_Main(qtbot, monkeypatch, nwGUI, nwLipsum, refDir, outDir):
# No path selected
with monkeypatch.context() as mp:
mp.setattr(QFileDialog, "getSaveFileName", lambda *args, **kwargs: ("", ""))
mp.setattr(QFileDialog, "getSaveFileName", lambda *a, **k: ("", ""))
assert not nwBuild._saveDocument(nwBuild.FMT_NWD)
# Default Settings
+8 -8
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter New Project Wizard Class Tester
=============================================
@@ -39,13 +38,14 @@ keyDelay = 2
typeDelay = 1
stepDelay = 20
@pytest.mark.gui
def testToolProjectWizard_Main(qtbot, monkeypatch, nwGUI, nwMinimal):
"""Test the new project wizard.
"""
# Block message box
monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "critical", lambda *args: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
if sys.platform.startswith("darwin"):
# Disable for macOS because the test segfaults on QWizard.show()
@@ -62,22 +62,22 @@ def testToolProjectWizard_Main(qtbot, monkeypatch, nwGUI, nwMinimal):
# Close project, but call with invalid path
assert nwGUI.closeProject()
with monkeypatch.context() as mp:
mp.setattr(nwGUI, "showNewProjectDialog", lambda *args: None)
mp.setattr(nwGUI, "showNewProjectDialog", lambda *a: None)
assert not nwGUI.newProject()
# Now, with an empty dictionary
mp.setattr(nwGUI, "showNewProjectDialog", lambda *args: {})
mp.setattr(nwGUI, "showNewProjectDialog", lambda *a: {})
assert not nwGUI.newProject()
# Now, with a non-empty folder
mp.setattr(nwGUI, "showNewProjectDialog", lambda *args: {"projPath": nwMinimal})
mp.setattr(nwGUI, "showNewProjectDialog", lambda *a: {"projPath": nwMinimal})
assert not nwGUI.newProject()
##
# Test the Wizard
##
monkeypatch.setattr(GuiProjectWizard, "exec_", lambda *args: None)
monkeypatch.setattr(GuiProjectWizard, "exec_", lambda *a: None)
nwGUI.mainConf.lastPath = " "
nwGUI.closeProject()
@@ -184,7 +184,7 @@ def testToolProjectWizard_Main(qtbot, monkeypatch, nwGUI, nwMinimal):
# Final Page
finalPage = nwWiz.currentPage()
assert isinstance(finalPage, ProjWizardFinalPage)
assert nwWiz.button(QWizard.FinishButton).isEnabled() # But we don't click it
assert nwWiz.button(QWizard.FinishButton).isEnabled() # But we don't click it
# Check Data
projData = nwGUI._assembleProjectWizardData(nwWiz)
+7 -7
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter Writing Stats Dialog Class Tester
===============================================
@@ -37,15 +36,16 @@ keyDelay = 2
typeDelay = 1
stepDelay = 20
@pytest.mark.gui
def testToolWritingStats_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
"""Test the full writing stats tool.
"""
# Block message box
monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "information", lambda *args: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "warning", lambda *args: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "critical", lambda *args: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
# Create a project to work on
assert nwGUI.newProject({"projPath": fncProj})
@@ -116,13 +116,13 @@ def testToolWritingStats_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
sessLog.populateGUI()
# Make the saving fail
monkeypatch.setattr(QFileDialog, "getSaveFileName", lambda *args, **kwargs: ("", ""))
monkeypatch.setattr(QFileDialog, "getSaveFileName", lambda *a, **k: ("", ""))
assert not sessLog._saveData(sessLog.FMT_CSV)
assert not sessLog._saveData(sessLog.FMT_JSON)
assert not sessLog._saveData(None)
# Make the save succeed
monkeypatch.setattr("os.path.expanduser", lambda *args: fncDir)
monkeypatch.setattr("os.path.expanduser", lambda *a: fncDir)
monkeypatch.setattr(QFileDialog, "getSaveFileName", lambda ss, tt, pp, options: (pp, ""))
sessLog.listBox.sortByColumn(sessLog.C_TIME, 0)
+5 -1
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
novelWriter Test Suite Tools
==============================
@@ -25,6 +24,7 @@ import shutil
from PyQt5.QtWidgets import qApp
def cmpFiles(fileOne, fileTwo, ignoreLines=None):
"""Compare two files, but optionally ignore lines given by a list.
"""
@@ -70,6 +70,7 @@ def cmpFiles(fileOne, fileTwo, ignoreLines=None):
return not diffFound
def getGuiItem(theName):
"""Returns a QtWidget based on its objectName.
"""
@@ -78,18 +79,21 @@ def getGuiItem(theName):
return qWidget
return None
def readFile(fileName):
"""Returns the content of a file as a string.
"""
with open(fileName, mode="r", encoding="utf8") as inFile:
return inFile.read()
def writeFile(fileName, fileData):
"""Write the contents of a string to a file.
"""
with open(fileName, mode="w", encoding="utf8") as outFile:
outFile.write(fileData)
def cleanProject(projPath):
"""Delete all generated files in a project.
"""