Merge pull request #453 from vkbo/test_coverage
Even More Test Coverage
This commit is contained in:
@@ -13,7 +13,7 @@ jobs:
|
|||||||
- name: Python Setup
|
- name: Python Setup
|
||||||
uses: actions/setup-python@v1
|
uses: actions/setup-python@v1
|
||||||
with:
|
with:
|
||||||
python-version: 3.7
|
python-version: 3
|
||||||
architecture: x64
|
architecture: x64
|
||||||
- name: Checkout Source
|
- name: Checkout Source
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
name: python 3.6
|
name: Linux (3.6)
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@@ -7,7 +7,7 @@ on:
|
|||||||
branches: [ main, dev ]
|
branches: [ main, dev ]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
pyTest36:
|
testLinux36:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Python Setup
|
- name: Python Setup
|
||||||
@@ -18,16 +18,16 @@ jobs:
|
|||||||
- name: Install Packages
|
- name: Install Packages
|
||||||
run: |
|
run: |
|
||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt install xvfb libenchant-dev qt5-default
|
sudo apt install libenchant-dev qt5-default
|
||||||
- name: Checkout Source
|
- name: Checkout Source
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: |
|
run: |
|
||||||
pip install --upgrade pip
|
pip install --upgrade pip
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
pip install PyVirtualDisplay
|
|
||||||
pip install pytest-timeout
|
pip install pytest-timeout
|
||||||
pip install pytest-xvfb
|
|
||||||
pip install pytest-qt
|
pip install pytest-qt
|
||||||
- name: Run Tests
|
- name: Run Tests
|
||||||
run: xvfb-run pytest -v --timeout=60
|
run: |
|
||||||
|
export QT_QPA_PLATFORM=offscreen
|
||||||
|
pytest -v --timeout=60
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
name: python 3.7
|
name: Linux (3.7)
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@@ -7,7 +7,7 @@ on:
|
|||||||
branches: [ main, dev ]
|
branches: [ main, dev ]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
pyTest37:
|
testLinux37:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Python Setup
|
- name: Python Setup
|
||||||
@@ -18,16 +18,16 @@ jobs:
|
|||||||
- name: Install Packages
|
- name: Install Packages
|
||||||
run: |
|
run: |
|
||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt install xvfb libenchant-dev qt5-default
|
sudo apt install libenchant-dev qt5-default
|
||||||
- name: Checkout Source
|
- name: Checkout Source
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: |
|
run: |
|
||||||
pip install --upgrade pip
|
pip install --upgrade pip
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
pip install PyVirtualDisplay
|
|
||||||
pip install pytest-timeout
|
pip install pytest-timeout
|
||||||
pip install pytest-xvfb
|
|
||||||
pip install pytest-qt
|
pip install pytest-qt
|
||||||
- name: Run Tests
|
- name: Run Tests
|
||||||
run: xvfb-run pytest -v --timeout=60
|
run: |
|
||||||
|
export QT_QPA_PLATFORM=offscreen
|
||||||
|
pytest -v --timeout=60
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
name: python 3.8
|
name: Linux (3.8)
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@@ -7,7 +7,7 @@ on:
|
|||||||
branches: [ main, dev ]
|
branches: [ main, dev ]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
pyTest38Cov:
|
testLinux38:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Python Setup
|
- name: Python Setup
|
||||||
@@ -18,20 +18,20 @@ jobs:
|
|||||||
- name: Install Packages
|
- name: Install Packages
|
||||||
run: |
|
run: |
|
||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt install xvfb libenchant-dev qt5-default
|
sudo apt install libenchant-dev qt5-default
|
||||||
- name: Checkout Source
|
- name: Checkout Source
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: |
|
run: |
|
||||||
pip install --upgrade pip
|
pip install --upgrade pip
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
pip install PyVirtualDisplay
|
|
||||||
pip install pytest-timeout
|
pip install pytest-timeout
|
||||||
pip install pytest-cov
|
pip install pytest-cov
|
||||||
pip install pytest-xvfb
|
|
||||||
pip install pytest-qt
|
pip install pytest-qt
|
||||||
pip install codecov
|
pip install codecov
|
||||||
- name: Run Tests
|
- name: Run Tests
|
||||||
run: xvfb-run pytest -v --cov=nw --timeout=60
|
run: |
|
||||||
|
export QT_QPA_PLATFORM=offscreen
|
||||||
|
pytest -v --cov=nw --timeout=60
|
||||||
- name: Upload to Codecov
|
- name: Upload to Codecov
|
||||||
uses: codecov/codecov-action@v1
|
uses: codecov/codecov-action@v1
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
name: macOS (3.8)
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main, dev ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ main, dev ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
testMac38:
|
||||||
|
runs-on: macos-latest
|
||||||
|
steps:
|
||||||
|
- name: Python Setup
|
||||||
|
uses: actions/setup-python@v1
|
||||||
|
with:
|
||||||
|
python-version: 3.8
|
||||||
|
architecture: x64
|
||||||
|
- name: Install Packages
|
||||||
|
run: |
|
||||||
|
brew install enchant
|
||||||
|
- name: Checkout Source
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- name: Install Dependencies
|
||||||
|
run: |
|
||||||
|
pip install --upgrade pip
|
||||||
|
pip install -r requirements.txt
|
||||||
|
pip install pytest-timeout
|
||||||
|
pip install pytest-cov
|
||||||
|
pip install pytest-qt
|
||||||
|
pip install codecov
|
||||||
|
- name: Run Tests
|
||||||
|
run: |
|
||||||
|
export QT_QPA_PLATFORM=offscreen
|
||||||
|
pytest -v --cov=nw --timeout=60
|
||||||
|
- name: Upload to Codecov
|
||||||
|
uses: codecov/codecov-action@v1
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
name: Windows (3.8)
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main, dev ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ main, dev ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
testWin38:
|
||||||
|
runs-on: windows-latest
|
||||||
|
steps:
|
||||||
|
- name: Python Setup
|
||||||
|
uses: actions/setup-python@v1
|
||||||
|
with:
|
||||||
|
python-version: 3.8
|
||||||
|
architecture: x64
|
||||||
|
- name: Checkout Source
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- name: Install Dependencies
|
||||||
|
run: |
|
||||||
|
pip install --upgrade pip
|
||||||
|
pip install -r requirements.txt
|
||||||
|
pip install pytest-timeout
|
||||||
|
pip install pytest-cov
|
||||||
|
pip install pytest-qt
|
||||||
|
pip install codecov
|
||||||
|
- name: Run Tests
|
||||||
|
run: |
|
||||||
|
pytest -v --cov=nw --timeout=60
|
||||||
|
- name: Upload to Codecov
|
||||||
|
uses: codecov/codecov-action@v1
|
||||||
+6
-2
@@ -8,11 +8,12 @@
|
|||||||
|
|
||||||
# Documentation
|
# Documentation
|
||||||
/docs/build/
|
/docs/build/
|
||||||
novelWriter.qch
|
*.qch
|
||||||
novelWriter.qhc
|
*.qhc
|
||||||
|
|
||||||
# Python Temp
|
# Python Temp
|
||||||
__pycache__
|
__pycache__
|
||||||
|
*.pyc
|
||||||
|
|
||||||
# Sample Project
|
# Sample Project
|
||||||
/nw/assets/sample.zip
|
/nw/assets/sample.zip
|
||||||
@@ -27,6 +28,9 @@ __pycache__
|
|||||||
/tests/temp
|
/tests/temp
|
||||||
/tests/lipsum/cache
|
/tests/lipsum/cache
|
||||||
/tests/lipsum/meta
|
/tests/lipsum/meta
|
||||||
|
/tests/minimal/cache
|
||||||
|
/tests/minimal/meta
|
||||||
|
/tests/oldproj/cache
|
||||||
/.pytest_cache
|
/.pytest_cache
|
||||||
/pytestdebug.log
|
/pytestdebug.log
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ codecov:
|
|||||||
coverage:
|
coverage:
|
||||||
precision: 2
|
precision: 2
|
||||||
round: nearest
|
round: nearest
|
||||||
range: "70...90"
|
range: "70...95"
|
||||||
|
|
||||||
status:
|
status:
|
||||||
project:
|
project:
|
||||||
|
|||||||
+22
-48
@@ -9,7 +9,7 @@
|
|||||||
Created: 2018-09-22 [0.0.1]
|
Created: 2018-09-22 [0.0.1]
|
||||||
|
|
||||||
This file is a part of novelWriter
|
This file is a part of novelWriter
|
||||||
Copyright 2020, Veronica Berglyd Olsen
|
Copyright 2018–2020, Veronica Berglyd Olsen
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -29,8 +29,6 @@ import sys
|
|||||||
import getopt
|
import getopt
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from os import path, remove, rename
|
|
||||||
|
|
||||||
from PyQt5.QtGui import QIcon
|
from PyQt5.QtGui import QIcon
|
||||||
from PyQt5.QtWidgets import QApplication, QErrorMessage
|
from PyQt5.QtWidgets import QApplication, QErrorMessage
|
||||||
|
|
||||||
@@ -97,15 +95,13 @@ def main(sysArgs=None):
|
|||||||
sysArgs = sys.argv[1:]
|
sysArgs = sys.argv[1:]
|
||||||
|
|
||||||
# Valid Input Options
|
# Valid Input Options
|
||||||
shortOpt = "hvq"
|
shortOpt = "hv"
|
||||||
longOpt = [
|
longOpt = [
|
||||||
"help",
|
"help",
|
||||||
"version",
|
"version",
|
||||||
"info",
|
"info",
|
||||||
"debug",
|
"debug",
|
||||||
"verbose",
|
"verbose",
|
||||||
"quiet",
|
|
||||||
"logfile=",
|
|
||||||
"style=",
|
"style=",
|
||||||
"config=",
|
"config=",
|
||||||
"data=",
|
"data=",
|
||||||
@@ -127,8 +123,6 @@ def main(sysArgs=None):
|
|||||||
" --info Print additional runtime information.\n"
|
" --info Print additional runtime information.\n"
|
||||||
" --debug Print debug output. Includes --info.\n"
|
" --debug Print debug output. Includes --info.\n"
|
||||||
" --verbose Increase verbosity of debug output. Includes --debug.\n"
|
" --verbose Increase verbosity of debug output. Includes --debug.\n"
|
||||||
" -q, --quiet Disable output to command line. Does not affect log file.\n"
|
|
||||||
" --logfile= Specify log file.\n"
|
|
||||||
" --style= Sets Qt5 style flag. Defaults to 'Fusion'.\n"
|
" --style= Sets Qt5 style flag. Defaults to 'Fusion'.\n"
|
||||||
" --config= Alternative config file.\n"
|
" --config= Alternative config file.\n"
|
||||||
" --data= Alternative user data path.\n"
|
" --data= Alternative user data path.\n"
|
||||||
@@ -143,9 +137,6 @@ def main(sysArgs=None):
|
|||||||
# Defaults
|
# Defaults
|
||||||
debugLevel = logging.WARN
|
debugLevel = logging.WARN
|
||||||
logFormat = "{levelname:8} {message:}"
|
logFormat = "{levelname:8} {message:}"
|
||||||
logFile = ""
|
|
||||||
toFile = False
|
|
||||||
toStd = True
|
|
||||||
confPath = None
|
confPath = None
|
||||||
dataPath = None
|
dataPath = None
|
||||||
testMode = False
|
testMode = False
|
||||||
@@ -166,22 +157,17 @@ def main(sysArgs=None):
|
|||||||
for inOpt, inArg in inOpts:
|
for inOpt, inArg in inOpts:
|
||||||
if inOpt in ("-h", "--help"):
|
if inOpt in ("-h", "--help"):
|
||||||
print(helpMsg)
|
print(helpMsg)
|
||||||
sys.exit()
|
sys.exit(0)
|
||||||
elif inOpt in ("-v", "--version"):
|
elif inOpt in ("-v", "--version"):
|
||||||
print("novelWriter %s Version %s [%s]" % (
|
print("novelWriter %s Version %s [%s]" % (
|
||||||
__status__, __version__, __date__)
|
__status__, __version__, __date__)
|
||||||
)
|
)
|
||||||
sys.exit()
|
sys.exit(0)
|
||||||
elif inOpt == "--info":
|
elif inOpt == "--info":
|
||||||
debugLevel = logging.INFO
|
debugLevel = logging.INFO
|
||||||
elif inOpt == "--debug":
|
elif inOpt == "--debug":
|
||||||
debugLevel = logging.DEBUG
|
debugLevel = logging.DEBUG
|
||||||
logFormat = "[{asctime:}] {name:>22}:{lineno:<4d} {levelname:8} {message:}"
|
logFormat = "[{asctime:}] {name:>22}:{lineno:<4d} {levelname:8} {message:}"
|
||||||
elif inOpt == "--logfile":
|
|
||||||
logFile = inArg
|
|
||||||
toFile = True
|
|
||||||
elif inOpt in ("-q", "--quiet"):
|
|
||||||
toStd = False
|
|
||||||
elif inOpt == "--verbose":
|
elif inOpt == "--verbose":
|
||||||
debugLevel = VERBOSE
|
debugLevel = VERBOSE
|
||||||
logFormat = "[{asctime:}] {name:>22}:{lineno:<4d} {levelname:8} {message:}"
|
logFormat = "[{asctime:}] {name:>22}:{lineno:<4d} {levelname:8} {message:}"
|
||||||
@@ -201,23 +187,10 @@ def main(sysArgs=None):
|
|||||||
|
|
||||||
# Set Logging
|
# Set Logging
|
||||||
logFmt = logging.Formatter(fmt=logFormat, style="{")
|
logFmt = logging.Formatter(fmt=logFormat, style="{")
|
||||||
|
cHandle = logging.StreamHandler()
|
||||||
if not logFile == "" and toFile:
|
cHandle.setLevel(debugLevel)
|
||||||
if path.isfile(logFile+".bak"):
|
cHandle.setFormatter(logFmt)
|
||||||
remove(logFile+".bak")
|
logger.addHandler(cHandle)
|
||||||
if path.isfile(logFile):
|
|
||||||
rename(logFile, logFile+".bak")
|
|
||||||
|
|
||||||
fHandle = logging.FileHandler(logFile)
|
|
||||||
fHandle.setLevel(debugLevel)
|
|
||||||
fHandle.setFormatter(logFmt)
|
|
||||||
logger.addHandler(fHandle)
|
|
||||||
|
|
||||||
if toStd:
|
|
||||||
cHandle = logging.StreamHandler()
|
|
||||||
cHandle.setLevel(debugLevel)
|
|
||||||
cHandle.setFormatter(logFmt)
|
|
||||||
logger.addHandler(cHandle)
|
|
||||||
|
|
||||||
logger.setLevel(debugLevel)
|
logger.setLevel(debugLevel)
|
||||||
logger.info("Starting novelWriter %s (%s) %s" % (
|
logger.info("Starting novelWriter %s (%s) %s" % (
|
||||||
@@ -250,19 +223,20 @@ def main(sysArgs=None):
|
|||||||
errorData.append("Python module 'lxml' is missing.")
|
errorData.append("Python module 'lxml' is missing.")
|
||||||
|
|
||||||
if errorData:
|
if errorData:
|
||||||
errApp = QApplication([])
|
if not testMode:
|
||||||
errMsg = QErrorMessage()
|
errApp = QApplication([])
|
||||||
errMsg.resize(500, 300)
|
errMsg = QErrorMessage()
|
||||||
errMsg.showMessage((
|
errMsg.resize(500, 300)
|
||||||
"<h3>A critical error has been encountered</h3>"
|
errMsg.showMessage((
|
||||||
"<p>novelWriter cannot start due to the following issues:<p>"
|
"<h3>A critical error has been encountered</h3>"
|
||||||
"<p> - %s</p>"
|
"<p>novelWriter cannot start due to the following issues:<p>"
|
||||||
"<p>Shutting down ...</p>"
|
"<p> - %s</p>"
|
||||||
) % (
|
"<p>Shutting down ...</p>"
|
||||||
"<br> - ".join(errorData)
|
) % (
|
||||||
))
|
"<br> - ".join(errorData)
|
||||||
errApp.exec_()
|
))
|
||||||
sys.exit(1)
|
errApp.exec_()
|
||||||
|
sys.exit(10 + len(errorData))
|
||||||
|
|
||||||
# Finish initialising config
|
# Finish initialising config
|
||||||
CONFIG.initConfig(confPath, dataPath)
|
CONFIG.initConfig(confPath, dataPath)
|
||||||
|
|||||||
+1
-1
@@ -9,7 +9,7 @@
|
|||||||
Created: 2019-05-12 [0.1.0]
|
Created: 2019-05-12 [0.1.0]
|
||||||
|
|
||||||
This file is a part of novelWriter
|
This file is a part of novelWriter
|
||||||
Copyright 2020, Veronica Berglyd Olsen
|
Copyright 2018–2020, Veronica Berglyd Olsen
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
|
|||||||
+60
-62
@@ -9,7 +9,7 @@
|
|||||||
Created: 2018-09-22 [0.0.1]
|
Created: 2018-09-22 [0.0.1]
|
||||||
|
|
||||||
This file is a part of novelWriter
|
This file is a part of novelWriter
|
||||||
Copyright 2020, Veronica Berglyd Olsen
|
Copyright 2018–2020, Veronica Berglyd Olsen
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -29,8 +29,8 @@ import logging
|
|||||||
import configparser
|
import configparser
|
||||||
import json
|
import json
|
||||||
import sys
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
from os import path, mkdir, unlink, rename
|
|
||||||
from time import time
|
from time import time
|
||||||
from shutil import which
|
from shutil import which
|
||||||
|
|
||||||
@@ -54,45 +54,44 @@ class Config:
|
|||||||
# Set Application Variables
|
# Set Application Variables
|
||||||
self.appName = "novelWriter"
|
self.appName = "novelWriter"
|
||||||
self.appHandle = self.appName.lower()
|
self.appHandle = self.appName.lower()
|
||||||
self.cmdOpen = None
|
|
||||||
|
|
||||||
# Debug Settings
|
# Debug Settings
|
||||||
self.showGUI = True
|
self.showGUI = True # Allow blocking the GUI (disabled for testing)
|
||||||
self.debugInfo = False
|
self.debugInfo = False # True if log level is DEBUG or VERBOSE
|
||||||
|
|
||||||
# Config Error Handling
|
# Config Error Handling
|
||||||
self.hasError = False
|
self.hasError = False # True if the config class encountered an error
|
||||||
self.errData = []
|
self.errData = [] # List of error messages
|
||||||
|
|
||||||
# Set Paths
|
# Set Paths
|
||||||
self.confPath = None
|
self.cmdOpen = None # Path from command line for project to be opened on launch
|
||||||
self.confFile = None
|
self.confPath = None # Folder where the config is saved
|
||||||
self.dataPath = None
|
self.confFile = None # The config file name
|
||||||
self.homePath = None
|
self.dataPath = None # Folder where app data is stored
|
||||||
self.lastPath = None
|
self.homePath = None # The user's home folder
|
||||||
self.appPath = None
|
self.lastPath = None # The last user-selected folder (browse dialogs)
|
||||||
self.appRoot = None
|
self.appPath = None # The full path to the novelwriter package folder
|
||||||
self.appIcon = None
|
self.appRoot = None # The full path to the novelwriter root folder
|
||||||
self.assetPath = None
|
self.appIcon = None # The full path to the novelwriter icon file
|
||||||
self.themeRoot = None
|
self.assetPath = None # The full path to the nw/assets folder
|
||||||
self.graphPath = None
|
self.themeRoot = None # The full path to the nw/assets/themes folder
|
||||||
self.dictPath = None
|
self.dictPath = None # The full path to the nw/assets/dict folder
|
||||||
self.iconPath = None
|
self.iconPath = None # The full path to the nw/assets/icons folder
|
||||||
self.helpPath = None
|
self.helpPath = None # The full path to the novelwriter .qhc help file
|
||||||
|
|
||||||
# Runtime Settings and Variables
|
# Runtime Settings and Variables
|
||||||
self.confChanged = False
|
self.confChanged = False # True whenever the config has chenged, false after save
|
||||||
self.hasHelp = False
|
self.hasHelp = False # True if the Qt help files are present in the assets folder
|
||||||
|
|
||||||
## General
|
## General
|
||||||
self.guiTheme = "default"
|
self.guiTheme = "default"
|
||||||
self.guiSyntax = "default_light"
|
self.guiSyntax = "default_light"
|
||||||
self.guiIcons = "typicons_colour_light"
|
self.guiIcons = "typicons_colour_light"
|
||||||
self.guiDark = False
|
self.guiDark = False # Load icons for dark backgrounds, if available
|
||||||
self.guiLang = "en" # Hardcoded for now since the GUI is only in English
|
self.guiLang = "en" # Hardcoded for now since the GUI is only in English
|
||||||
self.guiFont = ""
|
self.guiFont = "" # Defaults to system defualt font
|
||||||
self.guiFontSize = 11
|
self.guiFontSize = 11
|
||||||
self.guiScale = 1.0 # Set automatically by Theme class
|
self.guiScale = 1.0 # Set automatically by Theme class
|
||||||
|
|
||||||
## Sizes
|
## Sizes
|
||||||
self.winGeometry = [1100, 650]
|
self.winGeometry = [1100, 650]
|
||||||
@@ -239,7 +238,7 @@ class Config:
|
|||||||
logger.debug("Initialising Config ...")
|
logger.debug("Initialising Config ...")
|
||||||
if confPath is None:
|
if confPath is None:
|
||||||
confRoot = QStandardPaths.writableLocation(QStandardPaths.ConfigLocation)
|
confRoot = QStandardPaths.writableLocation(QStandardPaths.ConfigLocation)
|
||||||
self.confPath = path.join(path.abspath(confRoot), self.appHandle)
|
self.confPath = os.path.join(os.path.abspath(confRoot), self.appHandle)
|
||||||
else:
|
else:
|
||||||
logger.info("Setting config from alternative path: %s" % confPath)
|
logger.info("Setting config from alternative path: %s" % confPath)
|
||||||
self.confPath = confPath
|
self.confPath = confPath
|
||||||
@@ -249,7 +248,7 @@ class Config:
|
|||||||
dataRoot = QStandardPaths.writableLocation(QStandardPaths.AppDataLocation)
|
dataRoot = QStandardPaths.writableLocation(QStandardPaths.AppDataLocation)
|
||||||
else:
|
else:
|
||||||
dataRoot = QStandardPaths.writableLocation(QStandardPaths.DataLocation)
|
dataRoot = QStandardPaths.writableLocation(QStandardPaths.DataLocation)
|
||||||
self.dataPath = path.join(path.abspath(dataRoot), self.appHandle)
|
self.dataPath = os.path.join(os.path.abspath(dataRoot), self.appHandle)
|
||||||
else:
|
else:
|
||||||
logger.info("Setting data path from alternative path: %s" % dataPath)
|
logger.info("Setting data path from alternative path: %s" % dataPath)
|
||||||
self.dataPath = dataPath
|
self.dataPath = dataPath
|
||||||
@@ -258,25 +257,24 @@ class Config:
|
|||||||
logger.verbose("Data path: %s" % self.dataPath)
|
logger.verbose("Data path: %s" % self.dataPath)
|
||||||
|
|
||||||
self.confFile = self.appHandle+".conf"
|
self.confFile = self.appHandle+".conf"
|
||||||
self.homePath = path.expanduser("~")
|
self.homePath = os.path.expanduser("~")
|
||||||
self.lastPath = self.homePath
|
self.lastPath = self.homePath
|
||||||
self.appPath = getattr(sys, "_MEIPASS", path.abspath(path.dirname(__file__)))
|
self.appPath = getattr(sys, "_MEIPASS", os.path.abspath(os.path.dirname(__file__)))
|
||||||
self.appRoot = path.join(self.appPath, path.pardir)
|
self.appRoot = os.path.join(self.appPath, os.path.pardir)
|
||||||
self.assetPath = path.join(self.appPath, "assets")
|
self.assetPath = os.path.join(self.appPath, "assets")
|
||||||
self.themeRoot = path.join(self.assetPath, "themes")
|
self.themeRoot = os.path.join(self.assetPath, "themes")
|
||||||
self.graphPath = path.join(self.assetPath, "graphics")
|
self.dictPath = os.path.join(self.assetPath, "dict")
|
||||||
self.dictPath = path.join(self.assetPath, "dict")
|
self.iconPath = os.path.join(self.assetPath, "icons")
|
||||||
self.iconPath = path.join(self.assetPath, "icons")
|
self.appIcon = os.path.join(self.iconPath, "novelwriter.svg")
|
||||||
self.appIcon = path.join(self.iconPath, "novelwriter.svg")
|
|
||||||
|
|
||||||
logger.verbose("App path: %s" % self.appPath)
|
logger.verbose("App path: %s" % self.appPath)
|
||||||
logger.verbose("Home path: %s" % self.homePath)
|
logger.verbose("Home path: %s" % self.homePath)
|
||||||
|
|
||||||
# If config folder does not exist, make it.
|
# If config folder does not exist, make it.
|
||||||
# This assumes that the os config folder itself exists.
|
# This assumes that the os config folder itself exists.
|
||||||
if not path.isdir(self.confPath):
|
if not os.path.isdir(self.confPath):
|
||||||
try:
|
try:
|
||||||
mkdir(self.confPath)
|
os.mkdir(self.confPath)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Could not create folder: %s" % self.confPath)
|
logger.error("Could not create folder: %s" % self.confPath)
|
||||||
logger.error(str(e))
|
logger.error(str(e))
|
||||||
@@ -287,7 +285,7 @@ class Config:
|
|||||||
|
|
||||||
# Check if config file exists
|
# Check if config file exists
|
||||||
if self.confPath is not None:
|
if self.confPath is not None:
|
||||||
if path.isfile(path.join(self.confPath, self.confFile)):
|
if os.path.isfile(os.path.join(self.confPath, self.confFile)):
|
||||||
# If it exists, load it
|
# If it exists, load it
|
||||||
self.loadConfig()
|
self.loadConfig()
|
||||||
else:
|
else:
|
||||||
@@ -297,9 +295,9 @@ class Config:
|
|||||||
# If data folder does not exist, make it.
|
# If data folder does not exist, make it.
|
||||||
# This assumes that the os data folder itself exists.
|
# This assumes that the os data folder itself exists.
|
||||||
if self.dataPath is not None:
|
if self.dataPath is not None:
|
||||||
if not path.isdir(self.dataPath):
|
if not os.path.isdir(self.dataPath):
|
||||||
try:
|
try:
|
||||||
mkdir(self.dataPath)
|
os.mkdir(self.dataPath)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Could not create folder: %s" % self.dataPath)
|
logger.error("Could not create folder: %s" % self.dataPath)
|
||||||
logger.error(str(e))
|
logger.error(str(e))
|
||||||
@@ -320,9 +318,9 @@ class Config:
|
|||||||
self.spellLanguage = "en"
|
self.spellLanguage = "en"
|
||||||
|
|
||||||
# Check if local help files exist
|
# Check if local help files exist
|
||||||
self.helpPath = path.join(self.assetPath, "help", "novelWriter.qhc")
|
self.helpPath = os.path.join(self.assetPath, "help", "novelWriter.qhc")
|
||||||
self.hasHelp = path.isfile(self.helpPath)
|
self.hasHelp = os.path.isfile(self.helpPath)
|
||||||
self.hasHelp &= path.isfile(path.join(self.assetPath, "help", "novelWriter.qch"))
|
self.hasHelp &= os.path.isfile(os.path.join(self.assetPath, "help", "novelWriter.qch"))
|
||||||
|
|
||||||
logger.debug("Config initialisation complete")
|
logger.debug("Config initialisation complete")
|
||||||
|
|
||||||
@@ -336,7 +334,7 @@ class Config:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
cnfParse = configparser.ConfigParser()
|
cnfParse = configparser.ConfigParser()
|
||||||
cnfPath = path.join(self.confPath, self.confFile)
|
cnfPath = os.path.join(self.confPath, self.confFile)
|
||||||
try:
|
try:
|
||||||
with open(cnfPath, mode="r", encoding="utf8") as inFile:
|
with open(cnfPath, mode="r", encoding="utf8") as inFile:
|
||||||
cnfParse.read_file(inFile)
|
cnfParse.read_file(inFile)
|
||||||
@@ -631,7 +629,7 @@ class Config:
|
|||||||
cnfParse.set(cnfSec, "lastpath", str(self.lastPath))
|
cnfParse.set(cnfSec, "lastpath", str(self.lastPath))
|
||||||
|
|
||||||
# Write config file
|
# Write config file
|
||||||
cnfPath = path.join(self.confPath, self.confFile)
|
cnfPath = os.path.join(self.confPath, self.confFile)
|
||||||
try:
|
try:
|
||||||
with open(cnfPath, mode="w", encoding="utf8") as outFile:
|
with open(cnfPath, mode="w", encoding="utf8") as outFile:
|
||||||
cnfParse.write(outFile)
|
cnfParse.write(outFile)
|
||||||
@@ -652,10 +650,10 @@ class Config:
|
|||||||
if self.dataPath is None:
|
if self.dataPath is None:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
cacheFile = path.join(self.dataPath, nwFiles.RECENT_FILE)
|
cacheFile = os.path.join(self.dataPath, nwFiles.RECENT_FILE)
|
||||||
self.recentProj = {}
|
self.recentProj = {}
|
||||||
|
|
||||||
if path.isfile(cacheFile):
|
if os.path.isfile(cacheFile):
|
||||||
try:
|
try:
|
||||||
with open(cacheFile, mode="r", encoding="utf8") as inFile:
|
with open(cacheFile, mode="r", encoding="utf8") as inFile:
|
||||||
theJson = inFile.read()
|
theJson = inFile.read()
|
||||||
@@ -692,8 +690,8 @@ class Config:
|
|||||||
if self.dataPath is None:
|
if self.dataPath is None:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
cacheFile = path.join(self.dataPath, nwFiles.RECENT_FILE)
|
cacheFile = os.path.join(self.dataPath, nwFiles.RECENT_FILE)
|
||||||
cacheTemp = path.join(self.dataPath, nwFiles.RECENT_FILE+"~")
|
cacheTemp = os.path.join(self.dataPath, nwFiles.RECENT_FILE+"~")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(cacheTemp, mode="w+", encoding="utf8") as outFile:
|
with open(cacheTemp, mode="w+", encoding="utf8") as outFile:
|
||||||
@@ -704,16 +702,16 @@ class Config:
|
|||||||
self.errData.append(str(e))
|
self.errData.append(str(e))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if path.isfile(cacheFile):
|
if os.path.isfile(cacheFile):
|
||||||
unlink(cacheFile)
|
os.unlink(cacheFile)
|
||||||
rename(cacheTemp, cacheFile)
|
os.rename(cacheTemp, cacheFile)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def updateRecentCache(self, projPath, projTitle, wordCount, saveTime):
|
def updateRecentCache(self, projPath, projTitle, wordCount, saveTime):
|
||||||
"""Add or update recent cache information o9n a given project.
|
"""Add or update recent cache information o9n a given project.
|
||||||
"""
|
"""
|
||||||
self.recentProj[path.abspath(projPath)] = {
|
self.recentProj[os.path.abspath(projPath)] = {
|
||||||
"title" : projTitle,
|
"title" : projTitle,
|
||||||
"time" : int(saveTime),
|
"time" : int(saveTime),
|
||||||
"words" : int(wordCount),
|
"words" : int(wordCount),
|
||||||
@@ -739,27 +737,27 @@ class Config:
|
|||||||
def setConfPath(self, newPath):
|
def setConfPath(self, newPath):
|
||||||
if newPath is None:
|
if newPath is None:
|
||||||
return True
|
return True
|
||||||
if not path.isfile(newPath):
|
if not os.path.isfile(newPath):
|
||||||
logger.error("File not found, using default config path instead")
|
logger.error("File not found, using default config path instead")
|
||||||
return False
|
return False
|
||||||
self.confPath = path.dirname(newPath)
|
self.confPath = os.path.dirname(newPath)
|
||||||
self.confFile = path.basename(newPath)
|
self.confFile = os.path.basename(newPath)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def setDataPath(self, newPath):
|
def setDataPath(self, newPath):
|
||||||
if newPath is None:
|
if newPath is None:
|
||||||
return True
|
return True
|
||||||
if not path.isdir(newPath):
|
if not os.path.isdir(newPath):
|
||||||
logger.error("Path not found, using default data path instead")
|
logger.error("Path not found, using default data path instead")
|
||||||
return False
|
return False
|
||||||
self.dataPath = path.abspath(newPath)
|
self.dataPath = os.path.abspath(newPath)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def setLastPath(self, lastPath):
|
def setLastPath(self, lastPath):
|
||||||
if lastPath is None or lastPath == "":
|
if lastPath is None or lastPath == "":
|
||||||
self.lastPath = ""
|
self.lastPath = ""
|
||||||
else:
|
else:
|
||||||
self.lastPath = path.dirname(lastPath)
|
self.lastPath = os.path.dirname(lastPath)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def setWinSize(self, newWidth, newHeight):
|
def setWinSize(self, newWidth, newHeight):
|
||||||
|
|||||||
@@ -9,20 +9,20 @@ from nw.constants.enum import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"isoLanguage",
|
|
||||||
"isoCountry",
|
"isoCountry",
|
||||||
"nwConst",
|
"isoLanguage",
|
||||||
"nwRegEx",
|
|
||||||
"nwFiles",
|
|
||||||
"nwKeyWords",
|
|
||||||
"nwLabels",
|
|
||||||
"nwQuotes",
|
|
||||||
"nwUnicode",
|
|
||||||
"nwAlert",
|
"nwAlert",
|
||||||
|
"nwConst",
|
||||||
"nwDocAction",
|
"nwDocAction",
|
||||||
|
"nwDocInsert",
|
||||||
|
"nwFiles",
|
||||||
"nwItemClass",
|
"nwItemClass",
|
||||||
"nwItemLayout",
|
"nwItemLayout",
|
||||||
"nwItemType",
|
"nwItemType",
|
||||||
|
"nwKeyWords",
|
||||||
|
"nwLabels",
|
||||||
"nwOutline",
|
"nwOutline",
|
||||||
"nwDocInsert",
|
"nwQuotes",
|
||||||
|
"nwRegEx",
|
||||||
|
"nwUnicode",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
Created: 2019-04-28 [0.0.1]
|
Created: 2019-04-28 [0.0.1]
|
||||||
|
|
||||||
This file is a part of novelWriter
|
This file is a part of novelWriter
|
||||||
Copyright 2020, Veronica Berglyd Olsen
|
Copyright 2018–2020, Veronica Berglyd Olsen
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
Created: 2018-11-02 [0.0.1]
|
Created: 2018-11-02 [0.0.1]
|
||||||
|
|
||||||
This file is a part of novelWriter
|
This file is a part of novelWriter
|
||||||
Copyright 2020, Veronica Berglyd Olsen
|
Copyright 2018–2020, Veronica Berglyd Olsen
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
|
|||||||
+1
-5
@@ -9,7 +9,7 @@
|
|||||||
Created: 2019-11-05 [0.4.0]
|
Created: 2019-11-05 [0.4.0]
|
||||||
|
|
||||||
This file is a part of novelWriter
|
This file is a part of novelWriter
|
||||||
Copyright 2020, Veronica Berglyd Olsen
|
Copyright 2018–2020, Veronica Berglyd Olsen
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -25,10 +25,6 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
class isoLanguage():
|
class isoLanguage():
|
||||||
|
|
||||||
ISO_639_1 = {
|
ISO_639_1 = {
|
||||||
|
|||||||
+3
-3
@@ -8,6 +8,9 @@ from nw.core.tohtml import ToHtml
|
|||||||
from nw.core.tools import countWords, numberToRoman, numberToWord
|
from nw.core.tools import countWords, numberToRoman, numberToWord
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
"countWords",
|
||||||
|
"numberToRoman",
|
||||||
|
"numberToWord",
|
||||||
"NWDoc",
|
"NWDoc",
|
||||||
"NWIndex",
|
"NWIndex",
|
||||||
"NWProject",
|
"NWProject",
|
||||||
@@ -15,7 +18,4 @@ __all__ = [
|
|||||||
"NWSpellEnchant",
|
"NWSpellEnchant",
|
||||||
"NWSpellSimple",
|
"NWSpellSimple",
|
||||||
"ToHtml",
|
"ToHtml",
|
||||||
"countWords",
|
|
||||||
"numberToRoman",
|
|
||||||
"numberToWord",
|
|
||||||
]
|
]
|
||||||
|
|||||||
+13
-14
@@ -9,7 +9,7 @@
|
|||||||
Created: 2018-09-29 [0.0.1]
|
Created: 2018-09-29 [0.0.1]
|
||||||
|
|
||||||
This file is a part of novelWriter
|
This file is a part of novelWriter
|
||||||
Copyright 2020, Veronica Berglyd Olsen
|
Copyright 2018–2020, Veronica Berglyd Olsen
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -26,8 +26,7 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
from os import path, rename, unlink
|
|
||||||
|
|
||||||
from nw.constants import nwAlert
|
from nw.constants import nwAlert
|
||||||
from nw.common import isHandle
|
from nw.common import isHandle
|
||||||
@@ -89,12 +88,12 @@ class NWDoc():
|
|||||||
docFile = self._docHandle+".nwd"
|
docFile = self._docHandle+".nwd"
|
||||||
logger.debug("Opening document %s" % docFile)
|
logger.debug("Opening document %s" % docFile)
|
||||||
|
|
||||||
docPath = path.join(self.theProject.projContent, docFile)
|
docPath = os.path.join(self.theProject.projContent, docFile)
|
||||||
self._fileLoc = docPath
|
self._fileLoc = docPath
|
||||||
|
|
||||||
theText = ""
|
theText = ""
|
||||||
self._docMeta = ""
|
self._docMeta = ""
|
||||||
if path.isfile(docPath):
|
if os.path.isfile(docPath):
|
||||||
try:
|
try:
|
||||||
with open(docPath, mode="r", encoding="utf8") as inFile:
|
with open(docPath, mode="r", encoding="utf8") as inFile:
|
||||||
fstLine = inFile.readline()
|
fstLine = inFile.readline()
|
||||||
@@ -137,8 +136,8 @@ class NWDoc():
|
|||||||
docFile = self._docHandle+".nwd"
|
docFile = self._docHandle+".nwd"
|
||||||
logger.debug("Saving document %s" % docFile)
|
logger.debug("Saving document %s" % docFile)
|
||||||
|
|
||||||
docPath = path.join(self.theProject.projContent, docFile)
|
docPath = os.path.join(self.theProject.projContent, docFile)
|
||||||
docTemp = path.join(self.theProject.projContent, docFile+"~")
|
docTemp = os.path.join(self.theProject.projContent, docFile+"~")
|
||||||
|
|
||||||
if self._theItem is None:
|
if self._theItem is None:
|
||||||
docMeta = ""
|
docMeta = ""
|
||||||
@@ -163,9 +162,9 @@ class NWDoc():
|
|||||||
|
|
||||||
# If we're here, the file was successfully saved, so we can
|
# If we're here, the file was successfully saved, so we can
|
||||||
# replace the temp file with the actual file
|
# replace the temp file with the actual file
|
||||||
if path.isfile(docPath):
|
if os.path.isfile(docPath):
|
||||||
unlink(docPath)
|
os.unlink(docPath)
|
||||||
rename(docTemp, docPath)
|
os.rename(docTemp, docPath)
|
||||||
|
|
||||||
self.theParent.statusBar.setStatus("Saved Document: %s" % self._theItem.itemName)
|
self.theParent.statusBar.setStatus("Saved Document: %s" % self._theItem.itemName)
|
||||||
|
|
||||||
@@ -181,13 +180,13 @@ class NWDoc():
|
|||||||
docFile = tHandle+".nwd"
|
docFile = tHandle+".nwd"
|
||||||
|
|
||||||
chkList = []
|
chkList = []
|
||||||
chkList.append(path.join(self.theProject.projContent, docFile))
|
chkList.append(os.path.join(self.theProject.projContent, docFile))
|
||||||
chkList.append(path.join(self.theProject.projContent, docFile+"~"))
|
chkList.append(os.path.join(self.theProject.projContent, docFile+"~"))
|
||||||
|
|
||||||
for chkFile in chkList:
|
for chkFile in chkList:
|
||||||
if path.isfile(chkFile):
|
if os.path.isfile(chkFile):
|
||||||
try:
|
try:
|
||||||
unlink(chkFile)
|
os.unlink(chkFile)
|
||||||
logger.debug("Deleted: %s" % chkFile)
|
logger.debug("Deleted: %s" % chkFile)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.makeAlert(["Could not delete document file.", str(e)], nwAlert.ERROR)
|
self.makeAlert(["Could not delete document file.", str(e)], nwAlert.ERROR)
|
||||||
|
|||||||
+25
-33
@@ -9,7 +9,7 @@
|
|||||||
Created: 2019-05-27 [0.1.4]
|
Created: 2019-05-27 [0.1.4]
|
||||||
|
|
||||||
This file is a part of novelWriter
|
This file is a part of novelWriter
|
||||||
Copyright 2020, Veronica Berglyd Olsen
|
Copyright 2018–2020, Veronica Berglyd Olsen
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -25,11 +25,11 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import nw
|
||||||
import logging
|
import logging
|
||||||
import json
|
import json
|
||||||
import nw
|
import os
|
||||||
|
|
||||||
from os import path
|
|
||||||
from time import time
|
from time import time
|
||||||
|
|
||||||
from nw.constants import (
|
from nw.constants import (
|
||||||
@@ -153,9 +153,9 @@ class NWIndex():
|
|||||||
"""Load index from last session from the project meta folder.
|
"""Load index from last session from the project meta folder.
|
||||||
"""
|
"""
|
||||||
theData = {}
|
theData = {}
|
||||||
indexFile = path.join(self.theProject.projMeta, nwFiles.INDEX_FILE)
|
indexFile = os.path.join(self.theProject.projMeta, nwFiles.INDEX_FILE)
|
||||||
|
|
||||||
if path.isfile(indexFile):
|
if os.path.isfile(indexFile):
|
||||||
logger.debug("Loading index file")
|
logger.debug("Loading index file")
|
||||||
try:
|
try:
|
||||||
with open(indexFile, mode="r", encoding="utf8") as inFile:
|
with open(indexFile, mode="r", encoding="utf8") as inFile:
|
||||||
@@ -190,7 +190,7 @@ class NWIndex():
|
|||||||
"""Save the current index as a json file in the project meta
|
"""Save the current index as a json file in the project meta
|
||||||
data folder.
|
data folder.
|
||||||
"""
|
"""
|
||||||
indexFile = path.join(self.theProject.projMeta, nwFiles.INDEX_FILE)
|
indexFile = os.path.join(self.theProject.projMeta, nwFiles.INDEX_FILE)
|
||||||
|
|
||||||
logger.debug("Saving index file")
|
logger.debug("Saving index file")
|
||||||
if self.mainConf.debugInfo:
|
if self.mainConf.debugInfo:
|
||||||
@@ -279,8 +279,8 @@ class NWIndex():
|
|||||||
if theItem.itemLayout == nwItemLayout.NO_LAYOUT:
|
if theItem.itemLayout == nwItemLayout.NO_LAYOUT:
|
||||||
logger.error("Not indexing no-layout item %s" % tHandle)
|
logger.error("Not indexing no-layout item %s" % tHandle)
|
||||||
return False
|
return False
|
||||||
if theRoot is None:
|
if theItem.parHandle is None:
|
||||||
logger.error("Not indexing homeless item %s" % tHandle)
|
logger.error("Not indexing orphaned item %s" % tHandle)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Run word counter for the whole text
|
# Run word counter for the whole text
|
||||||
@@ -288,7 +288,7 @@ class NWIndex():
|
|||||||
self.textCounts[tHandle] = [cC, wC, pC]
|
self.textCounts[tHandle] = [cC, wC, pC]
|
||||||
|
|
||||||
# If the file is archived or trashed, we don't index the file itself
|
# If the file is archived or trashed, we don't index the file itself
|
||||||
if theItem.parHandle == self.theProject.projTree.trashRoot():
|
if self.theProject.projTree.isTrashRoot(theItem.parHandle):
|
||||||
logger.error("Not indexing trash item %s" % tHandle)
|
logger.error("Not indexing trash item %s" % tHandle)
|
||||||
return False
|
return False
|
||||||
if theRoot.itemClass == nwItemClass.ARCHIVE:
|
if theRoot.itemClass == nwItemClass.ARCHIVE:
|
||||||
@@ -356,7 +356,7 @@ class NWIndex():
|
|||||||
|
|
||||||
# Count words for remaining text after last heading
|
# Count words for remaining text after last heading
|
||||||
if nTitle > 0:
|
if nTitle > 0:
|
||||||
lastText = "\n".join(theLines[nTitle-1:nLine-1])
|
lastText = "\n".join(theLines[nTitle-1:])
|
||||||
self._indexWordCounts(tHandle, isNovel, lastText, nTitle)
|
self._indexWordCounts(tHandle, isNovel, lastText, nTitle)
|
||||||
|
|
||||||
# Update timestamps for index changes
|
# Update timestamps for index changes
|
||||||
@@ -459,16 +459,12 @@ class NWIndex():
|
|||||||
"""Validate and save the information about a reference to a tag
|
"""Validate and save the information about a reference to a tag
|
||||||
in another file.
|
in another file.
|
||||||
"""
|
"""
|
||||||
isValid, theBits, thePos = self.scanThis(aLine)
|
isValid, theBits, _ = self.scanThis(aLine)
|
||||||
if not isValid or len(theBits) == 0:
|
if not isValid or len(theBits) == 0:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
sTitle = "T%06d" % nTitle
|
sTitle = "T%06d" % nTitle
|
||||||
if sTitle not in self.refIndex[tHandle]:
|
if sTitle in self.refIndex[tHandle] and theBits[0] != nwKeyWords.TAG_KEY:
|
||||||
logger.error("Cannot save tags to file %s, no title %s" % (tHandle, sTitle))
|
|
||||||
return False
|
|
||||||
|
|
||||||
if theBits[0] != nwKeyWords.TAG_KEY:
|
|
||||||
for aVal in theBits[1:]:
|
for aVal in theBits[1:]:
|
||||||
self.refIndex[tHandle][sTitle]["tags"].append([nLine, theBits[0], aVal])
|
self.refIndex[tHandle][sTitle]["tags"].append([nLine, theBits[0], aVal])
|
||||||
|
|
||||||
@@ -574,15 +570,14 @@ class NWIndex():
|
|||||||
"""
|
"""
|
||||||
theStructure = []
|
theStructure = []
|
||||||
for tItem in self.theProject.projTree:
|
for tItem in self.theProject.projTree:
|
||||||
if tItem is None:
|
if tItem is not None:
|
||||||
continue
|
if not tItem.isExported and skipExcluded:
|
||||||
if not tItem.isExported and skipExcluded:
|
continue
|
||||||
continue
|
tHandle = tItem.itemHandle
|
||||||
tHandle = tItem.itemHandle
|
if tHandle not in self.novelIndex:
|
||||||
if tHandle not in self.novelIndex:
|
continue
|
||||||
continue
|
for sTitle in sorted(self.novelIndex[tHandle].keys()):
|
||||||
for sTitle in sorted(self.novelIndex[tHandle].keys()):
|
theStructure.append("%s:%s" % (tHandle, sTitle))
|
||||||
theStructure.append("%s:%s" % (tHandle, sTitle))
|
|
||||||
|
|
||||||
return theStructure
|
return theStructure
|
||||||
|
|
||||||
@@ -624,14 +619,11 @@ class NWIndex():
|
|||||||
if tHandle not in self.refIndex:
|
if tHandle not in self.refIndex:
|
||||||
return theRefs
|
return theRefs
|
||||||
|
|
||||||
try:
|
for refTitle in self.refIndex[tHandle]:
|
||||||
for refTitle in self.refIndex[tHandle]:
|
theTags = self.refIndex[tHandle][refTitle].get("tags", None)
|
||||||
for nLine, tKey, tTag in self.refIndex[tHandle][refTitle]["tags"]:
|
for aTag in theTags:
|
||||||
if sTitle is None or sTitle == refTitle:
|
if len(aTag) == 3 and (sTitle is None or sTitle == refTitle):
|
||||||
theRefs[tKey].append(tTag)
|
theRefs[aTag[1]].append(aTag[2])
|
||||||
except Exception as e:
|
|
||||||
logger.error("Failed to generate reference list")
|
|
||||||
logger.error(str(e))
|
|
||||||
|
|
||||||
return theRefs
|
return theRefs
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -9,7 +9,7 @@
|
|||||||
Created: 2018-10-27 [0.0.1]
|
Created: 2018-10-27 [0.0.1]
|
||||||
|
|
||||||
This file is a part of novelWriter
|
This file is a part of novelWriter
|
||||||
Copyright 2020, Veronica Berglyd Olsen
|
Copyright 2018–2020, Veronica Berglyd Olsen
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
|
|||||||
+5
-6
@@ -10,7 +10,7 @@
|
|||||||
Rewritten: 2020-02-19 [0.4.5]
|
Rewritten: 2020-02-19 [0.4.5]
|
||||||
|
|
||||||
This file is a part of novelWriter
|
This file is a part of novelWriter
|
||||||
Copyright 2020, Veronica Berglyd Olsen
|
Copyright 2018–2020, Veronica Berglyd Olsen
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -28,8 +28,7 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
from os import path
|
|
||||||
|
|
||||||
from nw.constants import nwFiles
|
from nw.constants import nwFiles
|
||||||
|
|
||||||
@@ -100,10 +99,10 @@ class OptionState():
|
|||||||
if self.theProject.projMeta is None:
|
if self.theProject.projMeta is None:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
stateFile = path.join(self.theProject.projMeta, nwFiles.OPTS_FILE)
|
stateFile = os.path.join(self.theProject.projMeta, nwFiles.OPTS_FILE)
|
||||||
theState = {}
|
theState = {}
|
||||||
|
|
||||||
if path.isfile(stateFile):
|
if os.path.isfile(stateFile):
|
||||||
logger.debug("Loading GUI options file")
|
logger.debug("Loading GUI options file")
|
||||||
try:
|
try:
|
||||||
with open(stateFile, mode="r", encoding="utf8") as inFile:
|
with open(stateFile, mode="r", encoding="utf8") as inFile:
|
||||||
@@ -130,7 +129,7 @@ class OptionState():
|
|||||||
if self.theProject.projMeta is None:
|
if self.theProject.projMeta is None:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
stateFile = path.join(self.theProject.projMeta, nwFiles.OPTS_FILE)
|
stateFile = os.path.join(self.theProject.projMeta, nwFiles.OPTS_FILE)
|
||||||
logger.debug("Saving GUI options file")
|
logger.debug("Saving GUI options file")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
+115
-106
@@ -9,7 +9,7 @@
|
|||||||
Created: 2018-09-29 [0.0.1]
|
Created: 2018-09-29 [0.0.1]
|
||||||
|
|
||||||
This file is a part of novelWriter
|
This file is a part of novelWriter
|
||||||
Copyright 2020, Veronica Berglyd Olsen
|
Copyright 2018–2020, Veronica Berglyd Olsen
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -25,10 +25,10 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
|
||||||
import nw
|
import nw
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
from os import path, mkdir, listdir, unlink, rename, rmdir
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
from time import time
|
from time import time
|
||||||
from shutil import make_archive, unpack_archive, copyfile
|
from shutil import make_archive, unpack_archive, copyfile
|
||||||
@@ -41,7 +41,8 @@ from nw.core.document import NWDoc
|
|||||||
from nw.core.status import NWStatus
|
from nw.core.status import NWStatus
|
||||||
from nw.core.options import OptionState
|
from nw.core.options import OptionState
|
||||||
from nw.common import (
|
from nw.common import (
|
||||||
checkString, checkBool, checkInt, isHandle, formatTimeStamp, makeFileNameSafe
|
checkString, checkBool, checkInt, isHandle, formatTimeStamp,
|
||||||
|
makeFileNameSafe
|
||||||
)
|
)
|
||||||
from nw.constants import (
|
from nw.constants import (
|
||||||
nwFiles, nwItemType, nwItemClass, nwItemLayout, nwLabels, nwAlert
|
nwFiles, nwItemType, nwItemClass, nwItemLayout, nwLabels, nwAlert
|
||||||
@@ -350,14 +351,11 @@ class NWProject():
|
|||||||
aDoc.saveDocument("### %s\n\n" % scTitle)
|
aDoc.saveDocument("### %s\n\n" % scTitle)
|
||||||
aDoc.clearDocument()
|
aDoc.clearDocument()
|
||||||
|
|
||||||
else:
|
|
||||||
# Fallback just in case. We shouldn't reach here.
|
|
||||||
self.newRoot("Novel", nwItemClass.NOVEL)
|
|
||||||
|
|
||||||
# Finalise
|
# Finalise
|
||||||
self.projOpened = time()
|
if popCustom or popMinimal:
|
||||||
self.setProjectChanged(True)
|
self.projOpened = time()
|
||||||
self.saveProject(autoSave=True)
|
self.setProjectChanged(True)
|
||||||
|
self.saveProject(autoSave=True)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -367,37 +365,33 @@ class NWProject():
|
|||||||
parse the XML of the file and populate the project variables and
|
parse the XML of the file and populate the project variables and
|
||||||
build the tree of project items.
|
build the tree of project items.
|
||||||
"""
|
"""
|
||||||
if not path.isfile(fileName):
|
if not os.path.isfile(fileName):
|
||||||
fileName = path.join(fileName, nwFiles.PROJ_FILE)
|
fileName = os.path.join(fileName, nwFiles.PROJ_FILE)
|
||||||
if not path.isfile(fileName):
|
if not os.path.isfile(fileName):
|
||||||
self.makeAlert("File not found: %s" % fileName, nwAlert.ERROR)
|
self.makeAlert("File not found: %s" % fileName, nwAlert.ERROR)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self.clearProject()
|
self.clearProject()
|
||||||
self.projPath = path.abspath(path.dirname(fileName))
|
self.projPath = os.path.abspath(os.path.dirname(fileName))
|
||||||
logger.debug("Opening project: %s" % self.projPath)
|
logger.debug("Opening project: %s" % self.projPath)
|
||||||
|
|
||||||
# Standard Folders and Files
|
# Standard Folders and Files
|
||||||
# ==========================
|
# ==========================
|
||||||
|
|
||||||
if not self.ensureFolderStructure():
|
if not self.ensureFolderStructure():
|
||||||
|
self.clearProject()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self.projDict = path.join(self.projMeta, nwFiles.PROJ_DICT)
|
self.projDict = os.path.join(self.projMeta, nwFiles.PROJ_DICT)
|
||||||
|
|
||||||
# Check for Old Legacy Data
|
# Check for Old Legacy Data
|
||||||
# =========================
|
# =========================
|
||||||
|
|
||||||
errList = []
|
legacyList = [] # Cleanup is done later
|
||||||
for projItem in listdir(self.projPath):
|
for projItem in os.listdir(self.projPath):
|
||||||
logger.verbose("Project contains: %s" % projItem)
|
logger.verbose("Project contains: %s" % projItem)
|
||||||
if projItem.startswith("data_"):
|
if projItem.startswith("data_"):
|
||||||
errList = self._legacyDataFolder(projItem, errList)
|
legacyList.append(projItem)
|
||||||
|
|
||||||
if errList:
|
|
||||||
self.makeAlert(errList, nwAlert.ERROR)
|
|
||||||
|
|
||||||
self._deprecatedFiles()
|
|
||||||
|
|
||||||
# Project Lock
|
# Project Lock
|
||||||
# ============
|
# ============
|
||||||
@@ -427,7 +421,7 @@ class NWProject():
|
|||||||
|
|
||||||
# Trying to open backup file instead
|
# Trying to open backup file instead
|
||||||
backFile = fileName[:-3]+"bak"
|
backFile = fileName[:-3]+"bak"
|
||||||
if path.isfile(backFile):
|
if os.path.isfile(backFile):
|
||||||
self.makeAlert("Attempting to open backup project file instead.", nwAlert.INFO)
|
self.makeAlert("Attempting to open backup project file instead.", nwAlert.INFO)
|
||||||
try:
|
try:
|
||||||
nwXML = etree.parse(backFile)
|
nwXML = etree.parse(backFile)
|
||||||
@@ -474,6 +468,7 @@ class NWProject():
|
|||||||
"Project file does not appear to be a novelWriterXML file.",
|
"Project file does not appear to be a novelWriterXML file.",
|
||||||
nwAlert.ERROR
|
nwAlert.ERROR
|
||||||
)
|
)
|
||||||
|
self.clearProject()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Check Project Storage Version
|
# Check Project Storage Version
|
||||||
@@ -488,7 +483,7 @@ class NWProject():
|
|||||||
# parser will lose the autoReplace settings if allowed to
|
# parser will lose the autoReplace settings if allowed to
|
||||||
# read the file. Introduced in version 0.10.
|
# read the file. Introduced in version 0.10.
|
||||||
|
|
||||||
if fileVersion == "1.0":
|
if fileVersion == "1.0" and self.mainConf.showGUI:
|
||||||
msgBox = QMessageBox()
|
msgBox = QMessageBox()
|
||||||
msgRes = msgBox.question(self.theParent, "Old Project Version", (
|
msgRes = msgBox.question(self.theParent, "Old Project Version", (
|
||||||
"The project file and data is created by a novelWriter version "
|
"The project file and data is created by a novelWriter version "
|
||||||
@@ -498,9 +493,10 @@ class NWProject():
|
|||||||
"any more, so make sure you have a recent backup."
|
"any more, so make sure you have a recent backup."
|
||||||
))
|
))
|
||||||
if msgRes != QMessageBox.Yes:
|
if msgRes != QMessageBox.Yes:
|
||||||
|
self.clearProject()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
elif fileVersion != "1.1" and fileVersion != "1.2":
|
elif fileVersion != "1.1" and fileVersion != "1.2" and self.mainConf.showGUI:
|
||||||
self.makeAlert((
|
self.makeAlert((
|
||||||
"Unknown or unsupported novelWriter project file format. "
|
"Unknown or unsupported novelWriter project file format. "
|
||||||
"The project cannot be opened by this version of novelWriter. "
|
"The project cannot be opened by this version of novelWriter. "
|
||||||
@@ -508,6 +504,7 @@ class NWProject():
|
|||||||
).format(
|
).format(
|
||||||
vers = appVersion,
|
vers = appVersion,
|
||||||
), nwAlert.ERROR)
|
), nwAlert.ERROR)
|
||||||
|
self.clearProject()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Check novelWriter Version
|
# Check novelWriter Version
|
||||||
@@ -524,6 +521,7 @@ class NWProject():
|
|||||||
appVersion, nw.__version__
|
appVersion, nw.__version__
|
||||||
))
|
))
|
||||||
if msgRes != QMessageBox.Yes:
|
if msgRes != QMessageBox.Yes:
|
||||||
|
self.clearProject()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Start Parsing the XML
|
# Start Parsing the XML
|
||||||
@@ -605,6 +603,17 @@ class NWProject():
|
|||||||
|
|
||||||
self.optState.loadSettings()
|
self.optState.loadSettings()
|
||||||
|
|
||||||
|
# Sort out old file locations
|
||||||
|
if legacyList:
|
||||||
|
errList = []
|
||||||
|
for projItem in legacyList:
|
||||||
|
errList = self._legacyDataFolder(projItem, errList)
|
||||||
|
if errList:
|
||||||
|
self.makeAlert(errList, nwAlert.ERROR)
|
||||||
|
|
||||||
|
# Clean up old files
|
||||||
|
self._deprecatedFiles()
|
||||||
|
|
||||||
# Update recent projects
|
# Update recent projects
|
||||||
self.mainConf.updateRecentCache(self.projPath, self.projName, self.lastWCount, time())
|
self.mainConf.updateRecentCache(self.projPath, self.projName, self.lastWCount, time())
|
||||||
self.mainConf.saveRecentCache()
|
self.mainConf.saveRecentCache()
|
||||||
@@ -694,9 +703,9 @@ class NWProject():
|
|||||||
self.projTree.packXML(nwXML)
|
self.projTree.packXML(nwXML)
|
||||||
|
|
||||||
# Write the xml tree to file
|
# Write the xml tree to file
|
||||||
tempFile = path.join(self.projPath, self.projFile+"~")
|
tempFile = os.path.join(self.projPath, self.projFile+"~")
|
||||||
saveFile = path.join(self.projPath, self.projFile)
|
saveFile = os.path.join(self.projPath, self.projFile)
|
||||||
backFile = path.join(self.projPath, self.projFile[:-3]+"bak")
|
backFile = os.path.join(self.projPath, self.projFile[:-3]+"bak")
|
||||||
try:
|
try:
|
||||||
with open(tempFile, mode="wb") as outFile:
|
with open(tempFile, mode="wb") as outFile:
|
||||||
outFile.write(etree.tostring(
|
outFile.write(etree.tostring(
|
||||||
@@ -711,11 +720,11 @@ class NWProject():
|
|||||||
|
|
||||||
# If we're here, the file was successfully saved,
|
# If we're here, the file was successfully saved,
|
||||||
# so let's sort out the temps and backups
|
# so let's sort out the temps and backups
|
||||||
if path.isfile(backFile):
|
if os.path.isfile(backFile):
|
||||||
unlink(backFile)
|
os.unlink(backFile)
|
||||||
if path.isfile(saveFile):
|
if os.path.isfile(saveFile):
|
||||||
rename(saveFile, backFile)
|
os.rename(saveFile, backFile)
|
||||||
rename(tempFile, saveFile)
|
os.rename(tempFile, saveFile)
|
||||||
|
|
||||||
# Save project GUI options
|
# Save project GUI options
|
||||||
self.optState.saveSettings()
|
self.optState.saveSettings()
|
||||||
@@ -748,9 +757,9 @@ class NWProject():
|
|||||||
if self.projPath is None or self.projPath == "":
|
if self.projPath is None or self.projPath == "":
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self.projMeta = path.join(self.projPath, "meta")
|
self.projMeta = os.path.join(self.projPath, "meta")
|
||||||
self.projCache = path.join(self.projPath, "cache")
|
self.projCache = os.path.join(self.projPath, "cache")
|
||||||
self.projContent = path.join(self.projPath, "content")
|
self.projContent = os.path.join(self.projPath, "content")
|
||||||
|
|
||||||
if not self._checkFolder(self.projMeta):
|
if not self._checkFolder(self.projMeta):
|
||||||
return False
|
return False
|
||||||
@@ -785,7 +794,7 @@ class NWProject():
|
|||||||
), nwAlert.ERROR)
|
), nwAlert.ERROR)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not path.isdir(self.mainConf.backupPath):
|
if not os.path.isdir(self.mainConf.backupPath):
|
||||||
self.theParent.makeAlert((
|
self.theParent.makeAlert((
|
||||||
"Cannot backup project because the backup path does not exist. "
|
"Cannot backup project because the backup path does not exist. "
|
||||||
"Please set a valid backup location in Tools > Preferences."
|
"Please set a valid backup location in Tools > Preferences."
|
||||||
@@ -793,10 +802,10 @@ class NWProject():
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
cleanName = makeFileNameSafe(self.projName)
|
cleanName = makeFileNameSafe(self.projName)
|
||||||
baseDir = path.abspath(path.join(self.mainConf.backupPath, cleanName))
|
baseDir = os.path.abspath(os.path.join(self.mainConf.backupPath, cleanName))
|
||||||
if not path.isdir(baseDir):
|
if not os.path.isdir(baseDir):
|
||||||
try:
|
try:
|
||||||
mkdir(baseDir)
|
os.mkdir(baseDir)
|
||||||
logger.debug("Created folder %s" % baseDir)
|
logger.debug("Created folder %s" % baseDir)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.theParent.makeAlert(
|
self.theParent.makeAlert(
|
||||||
@@ -805,7 +814,7 @@ class NWProject():
|
|||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if path.commonpath([self.projPath, baseDir]) == self.projPath:
|
if os.path.commonpath([self.projPath, baseDir]) == self.projPath:
|
||||||
self.theParent.makeAlert((
|
self.theParent.makeAlert((
|
||||||
"Cannot backup project because the backup path is within the "
|
"Cannot backup project because the backup path is within the "
|
||||||
"project folder to be backed up. Please choose a different "
|
"project folder to be backed up. Please choose a different "
|
||||||
@@ -814,7 +823,7 @@ class NWProject():
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
archName = "Backup from %s" % formatTimeStamp(time(), fileSafe=True)
|
archName = "Backup from %s" % formatTimeStamp(time(), fileSafe=True)
|
||||||
baseName = path.join(baseDir, archName)
|
baseName = os.path.join(baseDir, archName)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self._clearLockFile()
|
self._clearLockFile()
|
||||||
@@ -822,7 +831,7 @@ class NWProject():
|
|||||||
self._writeLockFile()
|
self._writeLockFile()
|
||||||
if doNotify:
|
if doNotify:
|
||||||
self.theParent.makeAlert(
|
self.theParent.makeAlert(
|
||||||
"Backup archive file written to: %s.zip" % path.join(cleanName, archName),
|
"Backup archive file written to: %s.zip" % os.path.join(cleanName, archName),
|
||||||
nwAlert.INFO
|
nwAlert.INFO
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@@ -849,11 +858,11 @@ class NWProject():
|
|||||||
logger.error("No project path set for the example project")
|
logger.error("No project path set for the example project")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
srcSample = path.abspath(path.join(self.mainConf.appRoot, "sample"))
|
srcSample = os.path.abspath(os.path.join(self.mainConf.appRoot, "sample"))
|
||||||
pkgSample = path.join(self.mainConf.assetPath, "sample.zip")
|
pkgSample = os.path.join(self.mainConf.assetPath, "sample.zip")
|
||||||
|
|
||||||
isSuccess = False
|
isSuccess = False
|
||||||
if path.isfile(pkgSample):
|
if os.path.isfile(pkgSample):
|
||||||
|
|
||||||
self.setProjectPath(projPath, newProject=True)
|
self.setProjectPath(projPath, newProject=True)
|
||||||
try:
|
try:
|
||||||
@@ -864,19 +873,19 @@ class NWProject():
|
|||||||
["Failed to create a new example project.", str(e)], nwAlert.ERROR
|
["Failed to create a new example project.", str(e)], nwAlert.ERROR
|
||||||
)
|
)
|
||||||
|
|
||||||
elif path.isdir(srcSample):
|
elif os.path.isdir(srcSample):
|
||||||
|
|
||||||
self.setProjectPath(projPath, newProject=True)
|
self.setProjectPath(projPath, newProject=True)
|
||||||
try:
|
try:
|
||||||
srcProj = path.join(srcSample, nwFiles.PROJ_FILE)
|
srcProj = os.path.join(srcSample, nwFiles.PROJ_FILE)
|
||||||
dstProj = path.join(projPath, nwFiles.PROJ_FILE)
|
dstProj = os.path.join(projPath, nwFiles.PROJ_FILE)
|
||||||
copyfile(srcProj, dstProj)
|
copyfile(srcProj, dstProj)
|
||||||
|
|
||||||
srcContent = path.join(srcSample, "content")
|
srcContent = os.path.join(srcSample, "content")
|
||||||
dstContent = path.join(projPath, "content")
|
dstContent = os.path.join(projPath, "content")
|
||||||
for srcFile in listdir(srcContent):
|
for srcFile in os.listdir(srcContent):
|
||||||
srcDoc = path.join(srcContent, srcFile)
|
srcDoc = os.path.join(srcContent, srcFile)
|
||||||
dstDoc = path.join(dstContent, srcFile)
|
dstDoc = os.path.join(dstContent, srcFile)
|
||||||
copyfile(srcDoc, dstDoc)
|
copyfile(srcDoc, dstDoc)
|
||||||
|
|
||||||
isSuccess = True
|
isSuccess = True
|
||||||
@@ -911,13 +920,13 @@ class NWProject():
|
|||||||
self.projPath = None
|
self.projPath = None
|
||||||
else:
|
else:
|
||||||
if projPath.startswith("~"):
|
if projPath.startswith("~"):
|
||||||
projPath = path.expanduser(projPath)
|
projPath = os.path.expanduser(projPath)
|
||||||
self.projPath = path.abspath(projPath)
|
self.projPath = os.path.abspath(projPath)
|
||||||
|
|
||||||
if newProject:
|
if newProject:
|
||||||
if not path.isdir(projPath):
|
if not os.path.isdir(projPath):
|
||||||
try:
|
try:
|
||||||
mkdir(projPath)
|
os.mkdir(projPath)
|
||||||
logger.debug("Created folder %s" % projPath)
|
logger.debug("Created folder %s" % projPath)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.theParent.makeAlert((
|
self.theParent.makeAlert((
|
||||||
@@ -925,8 +934,8 @@ class NWProject():
|
|||||||
), nwAlert.ERROR)
|
), nwAlert.ERROR)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if path.isdir(projPath):
|
if os.path.isdir(projPath):
|
||||||
if self.mainConf.showGUI and listdir(self.projPath):
|
if os.listdir(self.projPath):
|
||||||
self.theParent.makeAlert((
|
self.theParent.makeAlert((
|
||||||
"New project folder is not empty. "
|
"New project folder is not empty. "
|
||||||
"Each project requires a dedicated project folder."
|
"Each project requires a dedicated project folder."
|
||||||
@@ -972,11 +981,11 @@ class NWProject():
|
|||||||
|
|
||||||
def setProjBackup(self, doBackup):
|
def setProjBackup(self, doBackup):
|
||||||
"""Set whether projects should be backed up or not. The user
|
"""Set whether projects should be backed up or not. The user
|
||||||
will notified in case dependant settings are missing.
|
will be notified in case required settings are missing.
|
||||||
"""
|
"""
|
||||||
self.doBackup = doBackup
|
self.doBackup = doBackup
|
||||||
if doBackup:
|
if doBackup:
|
||||||
if not path.isdir(self.mainConf.backupPath):
|
if not os.path.isdir(self.mainConf.backupPath):
|
||||||
self.theParent.makeAlert((
|
self.theParent.makeAlert((
|
||||||
"You must set a valid backup path in preferences to use "
|
"You must set a valid backup path in preferences to use "
|
||||||
"the automatic project backup feature."
|
"the automatic project backup feature."
|
||||||
@@ -1175,8 +1184,8 @@ class NWProject():
|
|||||||
if self.projPath is None:
|
if self.projPath is None:
|
||||||
return ["ERROR"]
|
return ["ERROR"]
|
||||||
|
|
||||||
lockFile = path.join(self.projPath, nwFiles.PROJ_LOCK)
|
lockFile = os.path.join(self.projPath, nwFiles.PROJ_LOCK)
|
||||||
if not path.isfile(lockFile):
|
if not os.path.isfile(lockFile):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -1201,7 +1210,7 @@ class NWProject():
|
|||||||
if self.projPath is None:
|
if self.projPath is None:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
lockFile = path.join(self.projPath, nwFiles.PROJ_LOCK)
|
lockFile = os.path.join(self.projPath, nwFiles.PROJ_LOCK)
|
||||||
try:
|
try:
|
||||||
with open(lockFile, mode="w+", encoding="utf8") as outFile:
|
with open(lockFile, mode="w+", encoding="utf8") as outFile:
|
||||||
outFile.write("%s\n" % self.mainConf.hostName)
|
outFile.write("%s\n" % self.mainConf.hostName)
|
||||||
@@ -1222,10 +1231,10 @@ class NWProject():
|
|||||||
if self.projPath is None:
|
if self.projPath is None:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
lockFile = path.join(self.projPath, nwFiles.PROJ_LOCK)
|
lockFile = os.path.join(self.projPath, nwFiles.PROJ_LOCK)
|
||||||
if path.isfile(lockFile):
|
if os.path.isfile(lockFile):
|
||||||
try:
|
try:
|
||||||
unlink(lockFile)
|
os.unlink(lockFile)
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Failed to remove project lockfile")
|
logger.error("Failed to remove project lockfile")
|
||||||
@@ -1237,9 +1246,9 @@ class NWProject():
|
|||||||
def _checkFolder(self, thePath):
|
def _checkFolder(self, thePath):
|
||||||
"""Check if a folder exists, and if it doesn't, create it.
|
"""Check if a folder exists, and if it doesn't, create it.
|
||||||
"""
|
"""
|
||||||
if not path.isdir(thePath):
|
if not os.path.isdir(thePath):
|
||||||
try:
|
try:
|
||||||
mkdir(thePath)
|
os.mkdir(thePath)
|
||||||
logger.debug("Created folder %s" % thePath)
|
logger.debug("Created folder %s" % thePath)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.makeAlert(["Could not create folder.", str(e)], nwAlert.ERROR)
|
self.makeAlert(["Could not create folder.", str(e)], nwAlert.ERROR)
|
||||||
@@ -1280,7 +1289,7 @@ class NWProject():
|
|||||||
# Then check the files in the data folder
|
# Then check the files in the data folder
|
||||||
logger.debug("Checking files in project content folder")
|
logger.debug("Checking files in project content folder")
|
||||||
orphanFiles = []
|
orphanFiles = []
|
||||||
for fileItem in listdir(self.projContent):
|
for fileItem in os.listdir(self.projContent):
|
||||||
if not fileItem.endswith(".nwd"):
|
if not fileItem.endswith(".nwd"):
|
||||||
logger.warning("Skipping file %s" % fileItem)
|
logger.warning("Skipping file %s" % fileItem)
|
||||||
continue
|
continue
|
||||||
@@ -1343,8 +1352,8 @@ class NWProject():
|
|||||||
if not self.ensureFolderStructure():
|
if not self.ensureFolderStructure():
|
||||||
return False
|
return False
|
||||||
|
|
||||||
sessionFile = path.join(self.projMeta, nwFiles.SESS_STATS)
|
sessionFile = os.path.join(self.projMeta, nwFiles.SESS_STATS)
|
||||||
isFile = path.isfile(sessionFile)
|
isFile = os.path.isfile(sessionFile)
|
||||||
|
|
||||||
with open(sessionFile, mode="a+", encoding="utf8") as outFile:
|
with open(sessionFile, mode="a+", encoding="utf8") as outFile:
|
||||||
if not isFile:
|
if not isFile:
|
||||||
@@ -1371,8 +1380,8 @@ class NWProject():
|
|||||||
def _legacyDataFolder(self, theFolder, errList):
|
def _legacyDataFolder(self, theFolder, errList):
|
||||||
"""Clean up legacy data folders.
|
"""Clean up legacy data folders.
|
||||||
"""
|
"""
|
||||||
theData = path.join(self.projPath, theFolder)
|
theData = os.path.join(self.projPath, theFolder)
|
||||||
if not path.isdir(theData):
|
if not os.path.isdir(theData):
|
||||||
errList.append("Not a folder: %s" % theData)
|
errList.append("Not a folder: %s" % theData)
|
||||||
return errList
|
return errList
|
||||||
|
|
||||||
@@ -1380,9 +1389,9 @@ class NWProject():
|
|||||||
|
|
||||||
# Move Documents to Content
|
# Move Documents to Content
|
||||||
# =========================
|
# =========================
|
||||||
for dataItem in listdir(theData):
|
for dataItem in os.listdir(theData):
|
||||||
theFile = path.join(theData, dataItem)
|
theFile = os.path.join(theData, dataItem)
|
||||||
if not path.isfile(theFile):
|
if not os.path.isfile(theFile):
|
||||||
theErr = self._moveUnknownItem(theData, dataItem)
|
theErr = self._moveUnknownItem(theData, dataItem)
|
||||||
if theErr:
|
if theErr:
|
||||||
errList.append(theErr)
|
errList.append(theErr)
|
||||||
@@ -1390,9 +1399,9 @@ class NWProject():
|
|||||||
|
|
||||||
if len(dataItem) == 21 and dataItem.endswith("_main.nwd"):
|
if len(dataItem) == 21 and dataItem.endswith("_main.nwd"):
|
||||||
tHandle = theFolder[-1]+dataItem[:12]
|
tHandle = theFolder[-1]+dataItem[:12]
|
||||||
newPath = path.join(self.projContent, tHandle+".nwd")
|
newPath = os.path.join(self.projContent, tHandle+".nwd")
|
||||||
try:
|
try:
|
||||||
rename(theFile, newPath)
|
os.rename(theFile, newPath)
|
||||||
logger.info("Moved file: %s" % theFile)
|
logger.info("Moved file: %s" % theFile)
|
||||||
logger.info("New location: %s" % newPath)
|
logger.info("New location: %s" % newPath)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -1401,7 +1410,7 @@ class NWProject():
|
|||||||
|
|
||||||
elif len(dataItem) == 21 and dataItem.endswith("_main.bak"):
|
elif len(dataItem) == 21 and dataItem.endswith("_main.bak"):
|
||||||
try:
|
try:
|
||||||
unlink(theFile)
|
os.unlink(theFile)
|
||||||
logger.info("Deleted file: %s" % theFile)
|
logger.info("Deleted file: %s" % theFile)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(str(e))
|
logger.error(str(e))
|
||||||
@@ -1415,7 +1424,7 @@ class NWProject():
|
|||||||
# Remove Data Folder
|
# Remove Data Folder
|
||||||
# ==================
|
# ==================
|
||||||
try:
|
try:
|
||||||
rmdir(theData)
|
os.rmdir(theData)
|
||||||
logger.info("Removed folder: %s" % theFolder)
|
logger.info("Removed folder: %s" % theFolder)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(str(e))
|
logger.error(str(e))
|
||||||
@@ -1427,15 +1436,15 @@ class NWProject():
|
|||||||
"""Move an item that doesn't belong in the project folder to
|
"""Move an item that doesn't belong in the project folder to
|
||||||
a junk folder.
|
a junk folder.
|
||||||
"""
|
"""
|
||||||
theJunk = path.join(self.projPath, "junk")
|
theJunk = os.path.join(self.projPath, "junk")
|
||||||
if not self._checkFolder(theJunk):
|
if not self._checkFolder(theJunk):
|
||||||
return "Could not make folder: %s" % theJunk
|
return "Could not make folder: %s" % theJunk
|
||||||
|
|
||||||
theSrc = path.join(theDir, theItem)
|
theSrc = os.path.join(theDir, theItem)
|
||||||
theDst = path.join(theJunk, theItem)
|
theDst = os.path.join(theJunk, theItem)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
rename(theSrc, theDst)
|
os.rename(theSrc, theDst)
|
||||||
logger.info("Moved to junk: %s" % theSrc)
|
logger.info("Moved to junk: %s" % theSrc)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(str(e))
|
logger.error(str(e))
|
||||||
@@ -1447,29 +1456,29 @@ class NWProject():
|
|||||||
"""Delete files that are no longer used by novelWriter.
|
"""Delete files that are no longer used by novelWriter.
|
||||||
"""
|
"""
|
||||||
rmList = [
|
rmList = [
|
||||||
path.join(self.projCache, "nwProject.nwx.0"),
|
os.path.join(self.projCache, "nwProject.nwx.0"),
|
||||||
path.join(self.projCache, "nwProject.nwx.1"),
|
os.path.join(self.projCache, "nwProject.nwx.1"),
|
||||||
path.join(self.projCache, "nwProject.nwx.2"),
|
os.path.join(self.projCache, "nwProject.nwx.2"),
|
||||||
path.join(self.projCache, "nwProject.nwx.3"),
|
os.path.join(self.projCache, "nwProject.nwx.3"),
|
||||||
path.join(self.projCache, "nwProject.nwx.4"),
|
os.path.join(self.projCache, "nwProject.nwx.4"),
|
||||||
path.join(self.projCache, "nwProject.nwx.5"),
|
os.path.join(self.projCache, "nwProject.nwx.5"),
|
||||||
path.join(self.projCache, "nwProject.nwx.6"),
|
os.path.join(self.projCache, "nwProject.nwx.6"),
|
||||||
path.join(self.projCache, "nwProject.nwx.7"),
|
os.path.join(self.projCache, "nwProject.nwx.7"),
|
||||||
path.join(self.projCache, "nwProject.nwx.8"),
|
os.path.join(self.projCache, "nwProject.nwx.8"),
|
||||||
path.join(self.projCache, "nwProject.nwx.9"),
|
os.path.join(self.projCache, "nwProject.nwx.9"),
|
||||||
path.join(self.projMeta, "mainOptions.json"),
|
os.path.join(self.projMeta, "mainOptions.json"),
|
||||||
path.join(self.projMeta, "exportOptions.json"),
|
os.path.join(self.projMeta, "exportOptions.json"),
|
||||||
path.join(self.projMeta, "outlineOptions.json"),
|
os.path.join(self.projMeta, "outlineOptions.json"),
|
||||||
path.join(self.projMeta, "timelineOptions.json"),
|
os.path.join(self.projMeta, "timelineOptions.json"),
|
||||||
path.join(self.projMeta, "docMergeOptions.json"),
|
os.path.join(self.projMeta, "docMergeOptions.json"),
|
||||||
path.join(self.projMeta, "sessionLogOptions.json"),
|
os.path.join(self.projMeta, "sessionLogOptions.json"),
|
||||||
]
|
]
|
||||||
|
|
||||||
for rmFile in rmList:
|
for rmFile in rmList:
|
||||||
if path.isfile(rmFile):
|
if os.path.isfile(rmFile):
|
||||||
logger.info("Deleting: %s" % rmFile)
|
logger.info("Deleting: %s" % rmFile)
|
||||||
try:
|
try:
|
||||||
unlink(rmFile)
|
os.unlink(rmFile)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(str(e))
|
logger.error(str(e))
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
Created: 2019-06-11 [0.1.5]
|
Created: 2019-06-11 [0.1.5]
|
||||||
|
|
||||||
This file is a part of novelWriter
|
This file is a part of novelWriter
|
||||||
Copyright 2020, Veronica Berglyd Olsen
|
Copyright 2018–2020, Veronica Berglyd Olsen
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -25,10 +25,10 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
|
||||||
import nw
|
import nw
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
from os import path, listdir
|
|
||||||
from difflib import get_close_matches
|
from difflib import get_close_matches
|
||||||
|
|
||||||
from nw.constants import isoLanguage
|
from nw.constants import isoLanguage
|
||||||
@@ -108,7 +108,7 @@ class NWSpellCheck():
|
|||||||
self.PROJW = []
|
self.PROJW = []
|
||||||
if projectDict is not None:
|
if projectDict is not None:
|
||||||
self.projectDict = projectDict
|
self.projectDict = projectDict
|
||||||
if not path.isfile(projectDict):
|
if not os.path.isfile(projectDict):
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
logger.debug("Loading project word list")
|
logger.debug("Loading project word list")
|
||||||
@@ -228,7 +228,7 @@ class NWSpellSimple(NWSpellCheck):
|
|||||||
"""Load a dictionary as a list from the app assets folder.
|
"""Load a dictionary as a list from the app assets folder.
|
||||||
"""
|
"""
|
||||||
self.WORDS = []
|
self.WORDS = []
|
||||||
dictFile = path.join(self.mainConf.dictPath, theLang+".dict")
|
dictFile = os.path.join(self.mainConf.dictPath, theLang+".dict")
|
||||||
try:
|
try:
|
||||||
with open(dictFile, mode="r", encoding="utf-8") as wordsFile:
|
with open(dictFile, mode="r", encoding="utf-8") as wordsFile:
|
||||||
for theLine in wordsFile:
|
for theLine in wordsFile:
|
||||||
@@ -297,9 +297,9 @@ class NWSpellSimple(NWSpellCheck):
|
|||||||
"""Lists the dictionary files in the app assets folder.
|
"""Lists the dictionary files in the app assets folder.
|
||||||
"""
|
"""
|
||||||
retList = []
|
retList = []
|
||||||
for dictFile in listdir(self.mainConf.dictPath):
|
for dictFile in os.listdir(self.mainConf.dictPath):
|
||||||
|
|
||||||
theBits = path.splitext(dictFile)
|
theBits = os.path.splitext(dictFile)
|
||||||
if len(theBits) != 2:
|
if len(theBits) != 2:
|
||||||
continue
|
continue
|
||||||
if theBits[1] != ".dict":
|
if theBits[1] != ".dict":
|
||||||
|
|||||||
+1
-1
@@ -9,7 +9,7 @@
|
|||||||
Created: 2019-05-19 [0.1.3]
|
Created: 2019-05-19 [0.1.3]
|
||||||
|
|
||||||
This file is a part of novelWriter
|
This file is a part of novelWriter
|
||||||
Copyright 2020, Veronica Berglyd Olsen
|
Copyright 2018–2020, Veronica Berglyd Olsen
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
|
|||||||
+1
-1
@@ -9,7 +9,7 @@
|
|||||||
Created: 2019-05-07 [0.0.1]
|
Created: 2019-05-07 [0.0.1]
|
||||||
|
|
||||||
This file is a part of novelWriter
|
This file is a part of novelWriter
|
||||||
Copyright 2020, Veronica Berglyd Olsen
|
Copyright 2018–2020, Veronica Berglyd Olsen
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
|
|||||||
+1
-27
@@ -9,7 +9,7 @@
|
|||||||
Created: 2019-05-05 [0.0.1]
|
Created: 2019-05-05 [0.0.1]
|
||||||
|
|
||||||
This file is a part of novelWriter
|
This file is a part of novelWriter
|
||||||
Copyright 2020, Veronica Berglyd Olsen
|
Copyright 2018–2020, Veronica Berglyd Olsen
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -122,32 +122,6 @@ class Tokenizer():
|
|||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def clearData(self):
|
|
||||||
"""Clear the data arrays and variables, but not settings, so the class
|
|
||||||
can be reused for multiple documents.
|
|
||||||
"""
|
|
||||||
self.theText = None
|
|
||||||
self.theHandle = None
|
|
||||||
self.theItem = None
|
|
||||||
self.theTokens = None
|
|
||||||
self.theResult = None
|
|
||||||
self.theMarkdown = None
|
|
||||||
self.numChapter = 0
|
|
||||||
self.firstScene = False
|
|
||||||
|
|
||||||
self.isNone = False
|
|
||||||
self.isTitle = False
|
|
||||||
self.isBook = False
|
|
||||||
self.isPage = False
|
|
||||||
self.isPart = False
|
|
||||||
self.isUnNum = False
|
|
||||||
self.isChap = False
|
|
||||||
self.isScene = False
|
|
||||||
self.isNote = False
|
|
||||||
self.isNovel = False
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Setters
|
# Setters
|
||||||
##
|
##
|
||||||
|
|||||||
+1
-2
@@ -11,7 +11,7 @@
|
|||||||
Merged: 2020-05-08 [0.4.5] All of the above into this file
|
Merged: 2020-05-08 [0.4.5] All of the above into this file
|
||||||
|
|
||||||
This file is a part of novelWriter
|
This file is a part of novelWriter
|
||||||
Copyright 2020, Veronica Berglyd Olsen
|
Copyright 2018–2020, Veronica Berglyd Olsen
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -121,7 +121,6 @@ def numberToWord(numVal, theLanguage):
|
|||||||
numWord = _numberToWordEN(numVal)
|
numWord = _numberToWordEN(numVal)
|
||||||
else:
|
else:
|
||||||
numWord = _numberToWordEN(numVal)
|
numWord = _numberToWordEN(numVal)
|
||||||
# print("%4d : %s" % (numVal, numWord))
|
|
||||||
return numWord
|
return numWord
|
||||||
|
|
||||||
def _numberToWordEN(numVal):
|
def _numberToWordEN(numVal):
|
||||||
|
|||||||
+14
-7
@@ -9,7 +9,7 @@
|
|||||||
Created: 2020-05-07 [0.4.5]
|
Created: 2020-05-07 [0.4.5]
|
||||||
|
|
||||||
This file is a part of novelWriter
|
This file is a part of novelWriter
|
||||||
Copyright 2020, Veronica Berglyd Olsen
|
Copyright 2018–2020, Veronica Berglyd Olsen
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -27,8 +27,8 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
from os import path
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
from hashlib import sha256
|
from hashlib import sha256
|
||||||
from time import time
|
from time import time
|
||||||
@@ -144,8 +144,8 @@ class NWTree():
|
|||||||
the project directory. These files are there to assist the user
|
the project directory. These files are there to assist the user
|
||||||
if they wish to browse the stored files.
|
if they wish to browse the stored files.
|
||||||
"""
|
"""
|
||||||
tocText = path.join(self.theProject.projPath, nwFiles.TOC_TXT)
|
tocText = os.path.join(self.theProject.projPath, nwFiles.TOC_TXT)
|
||||||
tocJson = path.join(self.theProject.projPath, nwFiles.TOC_JSON)
|
tocJson = os.path.join(self.theProject.projPath, nwFiles.TOC_JSON)
|
||||||
|
|
||||||
jsonData = []
|
jsonData = []
|
||||||
try:
|
try:
|
||||||
@@ -162,14 +162,14 @@ class NWTree():
|
|||||||
if tItem is None:
|
if tItem is None:
|
||||||
continue
|
continue
|
||||||
tFile = tHandle+".nwd"
|
tFile = tHandle+".nwd"
|
||||||
if path.isfile(path.join(self.theProject.projContent, tFile)):
|
if os.path.isfile(os.path.join(self.theProject.projContent, tFile)):
|
||||||
outFile.write(" %-25s %-9s %s\n" % (
|
outFile.write(" %-25s %-9s %s\n" % (
|
||||||
path.join("content", tFile),
|
os.path.join("content", tFile),
|
||||||
tItem.itemClass.name,
|
tItem.itemClass.name,
|
||||||
tItem.itemName,
|
tItem.itemName,
|
||||||
))
|
))
|
||||||
jsonData.append([
|
jsonData.append([
|
||||||
path.join("content", tFile),
|
os.path.join("content", tFile),
|
||||||
tItem.itemClass.name,
|
tItem.itemClass.name,
|
||||||
tItem.itemName,
|
tItem.itemName,
|
||||||
])
|
])
|
||||||
@@ -213,6 +213,13 @@ class NWTree():
|
|||||||
return self._trashRoot
|
return self._trashRoot
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def isTrashRoot(self, tHandle):
|
||||||
|
"""Check if a handle is the trash folder.
|
||||||
|
"""
|
||||||
|
if self._trashRoot is None:
|
||||||
|
return False
|
||||||
|
return tHandle == self._trashRoot
|
||||||
|
|
||||||
def archiveRoot(self):
|
def archiveRoot(self):
|
||||||
"""Returns the handle of the archive folder, or None if there
|
"""Returns the handle of the archive folder, or None if there
|
||||||
isn't one.
|
isn't one.
|
||||||
|
|||||||
+1
-1
@@ -9,7 +9,7 @@
|
|||||||
Created: 2020-08-02 [0.10.2]
|
Created: 2020-08-02 [0.10.2]
|
||||||
|
|
||||||
This file is a part of novelWriter
|
This file is a part of novelWriter
|
||||||
Copyright 2020, Veronica Berglyd Olsen
|
Copyright 2018–2020, Veronica Berglyd Olsen
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
|
|||||||
+5
-6
@@ -16,9 +16,9 @@ from nw.gui.projload import GuiProjectLoad
|
|||||||
from nw.gui.projsettings import GuiProjectSettings
|
from nw.gui.projsettings import GuiProjectSettings
|
||||||
from nw.gui.projtree import GuiProjectTree
|
from nw.gui.projtree import GuiProjectTree
|
||||||
from nw.gui.projwizard import GuiProjectWizard
|
from nw.gui.projwizard import GuiProjectWizard
|
||||||
from nw.gui.writingstats import GuiWritingStats
|
|
||||||
from nw.gui.statusbar import GuiMainStatus
|
from nw.gui.statusbar import GuiMainStatus
|
||||||
from nw.gui.theme import GuiIcons, GuiTheme
|
from nw.gui.theme import GuiTheme
|
||||||
|
from nw.gui.writingstats import GuiWritingStats
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"GuiAbout",
|
"GuiAbout",
|
||||||
@@ -26,11 +26,12 @@ __all__ = [
|
|||||||
"GuiDocEditor",
|
"GuiDocEditor",
|
||||||
"GuiDocMerge",
|
"GuiDocMerge",
|
||||||
"GuiDocSplit",
|
"GuiDocSplit",
|
||||||
"GuiDocViewer",
|
|
||||||
"GuiDocViewDetails",
|
"GuiDocViewDetails",
|
||||||
|
"GuiDocViewer",
|
||||||
"GuiItemDetails",
|
"GuiItemDetails",
|
||||||
"GuiItemEditor",
|
"GuiItemEditor",
|
||||||
"GuiMainMenu",
|
"GuiMainMenu",
|
||||||
|
"GuiMainStatus",
|
||||||
"GuiOutline",
|
"GuiOutline",
|
||||||
"GuiOutlineDetails",
|
"GuiOutlineDetails",
|
||||||
"GuiPreferences",
|
"GuiPreferences",
|
||||||
@@ -38,8 +39,6 @@ __all__ = [
|
|||||||
"GuiProjectSettings",
|
"GuiProjectSettings",
|
||||||
"GuiProjectTree",
|
"GuiProjectTree",
|
||||||
"GuiProjectWizard",
|
"GuiProjectWizard",
|
||||||
"GuiWritingStats",
|
|
||||||
"GuiMainStatus",
|
|
||||||
"GuiIcons",
|
|
||||||
"GuiTheme",
|
"GuiTheme",
|
||||||
|
"GuiWritingStats",
|
||||||
]
|
]
|
||||||
|
|||||||
+6
-5
@@ -9,7 +9,7 @@
|
|||||||
Created: 2020-05-21 [0.5.2]
|
Created: 2020-05-21 [0.5.2]
|
||||||
|
|
||||||
This file is a part of novelWriter
|
This file is a part of novelWriter
|
||||||
Copyright 2020, Veronica Berglyd Olsen
|
Copyright 2018–2020, Veronica Berglyd Olsen
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -25,10 +25,10 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
|
||||||
import nw
|
import nw
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
from os import path
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt
|
from PyQt5.QtCore import Qt
|
||||||
@@ -45,6 +45,7 @@ class GuiAbout(QDialog):
|
|||||||
QDialog.__init__(self, theParent)
|
QDialog.__init__(self, theParent)
|
||||||
|
|
||||||
logger.debug("Initialising GuiAbout ...")
|
logger.debug("Initialising GuiAbout ...")
|
||||||
|
self.setObjectName("GuiAbout")
|
||||||
|
|
||||||
self.mainConf = nw.CONFIG
|
self.mainConf = nw.CONFIG
|
||||||
self.theParent = theParent
|
self.theParent = theParent
|
||||||
@@ -196,8 +197,8 @@ class GuiAbout(QDialog):
|
|||||||
"""Load the content for the License page.
|
"""Load the content for the License page.
|
||||||
"""
|
"""
|
||||||
docName = "gplv3_%s.htm" % self.mainConf.guiLang
|
docName = "gplv3_%s.htm" % self.mainConf.guiLang
|
||||||
docPath = path.join(self.mainConf.assetPath, "text", docName)
|
docPath = os.path.join(self.mainConf.assetPath, "text", docName)
|
||||||
if path.isfile(docPath):
|
if os.path.isfile(docPath):
|
||||||
with open(docPath, mode="r", encoding="utf8") as inFile:
|
with open(docPath, mode="r", encoding="utf8") as inFile:
|
||||||
helpText = inFile.read()
|
helpText = inFile.read()
|
||||||
self.pageLicense.setHtml(helpText)
|
self.pageLicense.setHtml(helpText)
|
||||||
|
|||||||
+11
-10
@@ -9,7 +9,7 @@
|
|||||||
Created: 2020-05-09 [0.5]
|
Created: 2020-05-09 [0.5]
|
||||||
|
|
||||||
This file is a part of novelWriter
|
This file is a part of novelWriter
|
||||||
Copyright 2020, Veronica Berglyd Olsen
|
Copyright 2018–2020, Veronica Berglyd Olsen
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -25,11 +25,11 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import nw
|
||||||
import logging
|
import logging
|
||||||
import json
|
import json
|
||||||
import nw
|
import os
|
||||||
|
|
||||||
from os import path
|
|
||||||
from time import time
|
from time import time
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
@@ -68,6 +68,7 @@ class GuiBuildNovel(QDialog):
|
|||||||
QDialog.__init__(self, theParent)
|
QDialog.__init__(self, theParent)
|
||||||
|
|
||||||
logger.debug("Initialising GuiBuildNovel ...")
|
logger.debug("Initialising GuiBuildNovel ...")
|
||||||
|
self.setObjectName("GuiBuildNovel")
|
||||||
|
|
||||||
self.mainConf = nw.CONFIG
|
self.mainConf = nw.CONFIG
|
||||||
self.theProject = theProject
|
self.theProject = theProject
|
||||||
@@ -467,7 +468,7 @@ class GuiBuildNovel(QDialog):
|
|||||||
self.buildProgress.setMaximum(len(self.theProject.projTree))
|
self.buildProgress.setMaximum(len(self.theProject.projTree))
|
||||||
self.buildProgress.setValue(0)
|
self.buildProgress.setValue(0)
|
||||||
|
|
||||||
tStart = time()
|
tStart = int(time())
|
||||||
|
|
||||||
self.htmlText = []
|
self.htmlText = []
|
||||||
self.htmlStyle = []
|
self.htmlStyle = []
|
||||||
@@ -510,7 +511,7 @@ class GuiBuildNovel(QDialog):
|
|||||||
# Update progress bar, also for skipped items
|
# Update progress bar, also for skipped items
|
||||||
self.buildProgress.setValue(nItt+1)
|
self.buildProgress.setValue(nItt+1)
|
||||||
|
|
||||||
tEnd = time()
|
tEnd = int(time())
|
||||||
logger.debug("Built project in %.3f ms" % (1000*(tEnd-tStart)))
|
logger.debug("Built project in %.3f ms" % (1000*(tEnd-tStart)))
|
||||||
self.htmlStyle = makeHtml.getStyleSheet()
|
self.htmlStyle = makeHtml.getStyleSheet()
|
||||||
self.buildTime = tEnd
|
self.buildTime = tEnd
|
||||||
@@ -629,8 +630,8 @@ class GuiBuildNovel(QDialog):
|
|||||||
cleanName = makeFileNameSafe(self.theProject.projName)
|
cleanName = makeFileNameSafe(self.theProject.projName)
|
||||||
fileName = "%s.%s" % (cleanName, fileExt)
|
fileName = "%s.%s" % (cleanName, fileExt)
|
||||||
saveDir = self.mainConf.lastPath
|
saveDir = self.mainConf.lastPath
|
||||||
savePath = path.join(saveDir, fileName)
|
savePath = os.path.join(saveDir, fileName)
|
||||||
if not path.isdir(saveDir):
|
if not os.path.isdir(saveDir):
|
||||||
saveDir = self.mainConf.homePath
|
saveDir = self.mainConf.homePath
|
||||||
|
|
||||||
if self.mainConf.showGUI:
|
if self.mainConf.showGUI:
|
||||||
@@ -791,9 +792,9 @@ class GuiBuildNovel(QDialog):
|
|||||||
def _loadCache(self):
|
def _loadCache(self):
|
||||||
"""Save the current data to cache.
|
"""Save the current data to cache.
|
||||||
"""
|
"""
|
||||||
buildCache = path.join(self.theProject.projCache, nwFiles.BUILD_CACHE)
|
buildCache = os.path.join(self.theProject.projCache, nwFiles.BUILD_CACHE)
|
||||||
dataCount = 0
|
dataCount = 0
|
||||||
if path.isfile(buildCache):
|
if os.path.isfile(buildCache):
|
||||||
|
|
||||||
logger.debug("Loading build cache")
|
logger.debug("Loading build cache")
|
||||||
try:
|
try:
|
||||||
@@ -822,7 +823,7 @@ class GuiBuildNovel(QDialog):
|
|||||||
def _saveCache(self):
|
def _saveCache(self):
|
||||||
"""Save the current data to cache.
|
"""Save the current data to cache.
|
||||||
"""
|
"""
|
||||||
buildCache = path.join(self.theProject.projCache, nwFiles.BUILD_CACHE)
|
buildCache = os.path.join(self.theProject.projCache, nwFiles.BUILD_CACHE)
|
||||||
|
|
||||||
if self.mainConf.debugInfo:
|
if self.mainConf.debugInfo:
|
||||||
nIndent = 2
|
nIndent = 2
|
||||||
|
|||||||
+4
-2
@@ -11,7 +11,7 @@
|
|||||||
Created: 2020-05-17 [0.5.1] PagedDialog
|
Created: 2020-05-17 [0.5.1] PagedDialog
|
||||||
|
|
||||||
This file is a part of novelWriter
|
This file is a part of novelWriter
|
||||||
Copyright 2020, Veronica Berglyd Olsen
|
Copyright 2018–2020, Veronica Berglyd Olsen
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -27,8 +27,8 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
|
||||||
import nw
|
import nw
|
||||||
|
import logging
|
||||||
|
|
||||||
from PyQt5.QtGui import QColor, QPalette, QPainter, QFontMetrics
|
from PyQt5.QtGui import QColor, QPalette, QPainter, QFontMetrics
|
||||||
from PyQt5.QtCore import (
|
from PyQt5.QtCore import (
|
||||||
@@ -443,6 +443,8 @@ class VerticalTabBar(QTabBar):
|
|||||||
|
|
||||||
class QuotesDialog(QDialog):
|
class QuotesDialog(QDialog):
|
||||||
|
|
||||||
|
selectedQuote = ""
|
||||||
|
|
||||||
def __init__(self, theParent=None, currentQuote="\""):
|
def __init__(self, theParent=None, currentQuote="\""):
|
||||||
QDialog.__init__(self, parent=theParent)
|
QDialog.__init__(self, parent=theParent)
|
||||||
|
|
||||||
|
|||||||
+12
-14
@@ -14,7 +14,7 @@
|
|||||||
Created: 2020-06-27 [0.10.0] GuiDocEditFooter
|
Created: 2020-06-27 [0.10.0] GuiDocEditFooter
|
||||||
|
|
||||||
This file is a part of novelWriter
|
This file is a part of novelWriter
|
||||||
Copyright 2020, Veronica Berglyd Olsen
|
Copyright 2018–2020, Veronica Berglyd Olsen
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -30,8 +30,8 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
|
||||||
import nw
|
import nw
|
||||||
|
import logging
|
||||||
|
|
||||||
from time import time
|
from time import time
|
||||||
|
|
||||||
@@ -423,10 +423,10 @@ class GuiDocEditor(QTextEdit):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def getCursorPosition(self):
|
def getCursorPosition(self):
|
||||||
"""Find the cursor position in the document.
|
"""Find the cursor position in the document. If the editor has a
|
||||||
|
selection, return the position of the end of the selection.
|
||||||
"""
|
"""
|
||||||
theCursor = self.textCursor()
|
return self.textCursor().selectionEnd()
|
||||||
return theCursor.position()
|
|
||||||
|
|
||||||
def setCursorLine(self, theLine):
|
def setCursorLine(self, theLine):
|
||||||
"""Move the cursor to a given line in the document.
|
"""Move the cursor to a given line in the document.
|
||||||
@@ -608,10 +608,8 @@ class GuiDocEditor(QTextEdit):
|
|||||||
"""
|
"""
|
||||||
if isinstance(theInsert, str):
|
if isinstance(theInsert, str):
|
||||||
theText = theInsert
|
theText = theInsert
|
||||||
elif theInsert in nwDocInsert:
|
elif isinstance(theInsert, nwDocInsert):
|
||||||
if theInsert == nwDocInsert.NO_INSERT:
|
if theInsert == nwDocInsert.HARD_BREAK:
|
||||||
theText = ""
|
|
||||||
elif theInsert == nwDocInsert.HARD_BREAK:
|
|
||||||
theText = " \n"
|
theText = " \n"
|
||||||
elif theInsert == nwDocInsert.NB_SPACE:
|
elif theInsert == nwDocInsert.NB_SPACE:
|
||||||
theText = nwUnicode.U_NBSP
|
theText = nwUnicode.U_NBSP
|
||||||
@@ -1177,17 +1175,17 @@ class GuiDocEditor(QTextEdit):
|
|||||||
theBlock = theCursor.block()
|
theBlock = theCursor.block()
|
||||||
if not theBlock.isValid():
|
if not theBlock.isValid():
|
||||||
logger.debug("Invalid block selected for action %s" % str(docAction))
|
logger.debug("Invalid block selected for action %s" % str(docAction))
|
||||||
return
|
return False
|
||||||
|
|
||||||
theText = theBlock.text()
|
theText = theBlock.text()
|
||||||
if len(theText.strip()) == 0:
|
if len(theText.strip()) == 0:
|
||||||
logger.debug("Empty block selected for action %s" % str(docAction))
|
logger.debug("Empty block selected for action %s" % str(docAction))
|
||||||
return
|
return False
|
||||||
|
|
||||||
# Remove existing format first, if any
|
# Remove existing format first, if any
|
||||||
if theText.startswith("@"):
|
if theText.startswith("@"):
|
||||||
logger.error("Cannot apply block format to keyword/value line")
|
logger.error("Cannot apply block format to keyword/value line")
|
||||||
return
|
return False
|
||||||
elif theText.startswith("% "):
|
elif theText.startswith("% "):
|
||||||
newText = theText[2:]
|
newText = theText[2:]
|
||||||
cOffset = 2
|
cOffset = 2
|
||||||
@@ -1233,7 +1231,7 @@ class GuiDocEditor(QTextEdit):
|
|||||||
logger.error(
|
logger.error(
|
||||||
"Unknown or unsupported block format requested: %s" % str(docAction)
|
"Unknown or unsupported block format requested: %s" % str(docAction)
|
||||||
)
|
)
|
||||||
return
|
return False
|
||||||
|
|
||||||
# Replace the block text
|
# Replace the block text
|
||||||
theCursor.beginEditBlock()
|
theCursor.beginEditBlock()
|
||||||
@@ -1250,7 +1248,7 @@ class GuiDocEditor(QTextEdit):
|
|||||||
theCursor.endEditBlock()
|
theCursor.endEditBlock()
|
||||||
self.setTextCursor(theCursor)
|
self.setTextCursor(theCursor)
|
||||||
|
|
||||||
return
|
return True
|
||||||
|
|
||||||
def _makeSelection(self, selMode):
|
def _makeSelection(self, selMode):
|
||||||
"""Wrapper function to select text based on a selection mode.
|
"""Wrapper function to select text based on a selection mode.
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
Created: 2019-04-06 [0.0.1]
|
Created: 2019-04-06 [0.0.1]
|
||||||
|
|
||||||
This file is a part of novelWriter
|
This file is a part of novelWriter
|
||||||
Copyright 2020, Veronica Berglyd Olsen
|
Copyright 2018–2020, Veronica Berglyd Olsen
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -25,8 +25,8 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
|
||||||
import nw
|
import nw
|
||||||
|
import logging
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt, QRegularExpression
|
from PyQt5.QtCore import Qt, QRegularExpression
|
||||||
from PyQt5.QtGui import (
|
from PyQt5.QtGui import (
|
||||||
|
|||||||
+5
-5
@@ -9,7 +9,7 @@
|
|||||||
Created: 2020-01-23 [0.4.3]
|
Created: 2020-01-23 [0.4.3]
|
||||||
|
|
||||||
This file is a part of novelWriter
|
This file is a part of novelWriter
|
||||||
Copyright 2020, Veronica Berglyd Olsen
|
Copyright 2018–2020, Veronica Berglyd Olsen
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -25,8 +25,8 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
|
||||||
import nw
|
import nw
|
||||||
|
import logging
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt
|
from PyQt5.QtCore import Qt
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
@@ -46,6 +46,7 @@ class GuiDocMerge(QDialog):
|
|||||||
QDialog.__init__(self, theParent)
|
QDialog.__init__(self, theParent)
|
||||||
|
|
||||||
logger.debug("Initialising GuiDocMerge ...")
|
logger.debug("Initialising GuiDocMerge ...")
|
||||||
|
self.setObjectName("GuiDocMerge")
|
||||||
|
|
||||||
self.mainConf = nw.CONFIG
|
self.mainConf = nw.CONFIG
|
||||||
self.theParent = theParent
|
self.theParent = theParent
|
||||||
@@ -132,17 +133,16 @@ class GuiDocMerge(QDialog):
|
|||||||
|
|
||||||
theDoc.openDocument(nHandle, False)
|
theDoc.openDocument(nHandle, False)
|
||||||
theDoc.saveDocument(theText)
|
theDoc.saveDocument(theText)
|
||||||
self.theParent.treeView.revealTreeItem(nHandle)
|
self.theParent.treeView.revealNewTreeItem(nHandle)
|
||||||
self.theParent.openDocument(nHandle, doScroll=True)
|
self.theParent.openDocument(nHandle, doScroll=True)
|
||||||
|
|
||||||
self.close()
|
self._doClose()
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def _doClose(self):
|
def _doClose(self):
|
||||||
"""Close the dialog window without doing anything.
|
"""Close the dialog window without doing anything.
|
||||||
"""
|
"""
|
||||||
logger.verbose("GuiDocMerge close button clicked")
|
|
||||||
self.close()
|
self.close()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
+20
-19
@@ -9,7 +9,7 @@
|
|||||||
Created: 2020-02-01 [0.4.3]
|
Created: 2020-02-01 [0.4.3]
|
||||||
|
|
||||||
This file is a part of novelWriter
|
This file is a part of novelWriter
|
||||||
Copyright 2020, Veronica Berglyd Olsen
|
Copyright 2018–2020, Veronica Berglyd Olsen
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -25,8 +25,8 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
|
||||||
import nw
|
import nw
|
||||||
|
import logging
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt
|
from PyQt5.QtCore import Qt
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
@@ -34,9 +34,11 @@ from PyQt5.QtWidgets import (
|
|||||||
QListWidgetItem, QDialogButtonBox, QLabel, QMessageBox
|
QListWidgetItem, QDialogButtonBox, QLabel, QMessageBox
|
||||||
)
|
)
|
||||||
|
|
||||||
from nw.constants import nwAlert, nwItemType, nwItemClass, nwItemLayout, nwConst
|
|
||||||
from nw.gui.custom import QHelpLabel
|
|
||||||
from nw.core import NWDoc
|
from nw.core import NWDoc
|
||||||
|
from nw.gui.custom import QHelpLabel
|
||||||
|
from nw.constants import (
|
||||||
|
nwAlert, nwItemType, nwItemClass, nwItemLayout, nwConst
|
||||||
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -46,6 +48,7 @@ class GuiDocSplit(QDialog):
|
|||||||
QDialog.__init__(self, theParent)
|
QDialog.__init__(self, theParent)
|
||||||
|
|
||||||
logger.debug("Initialising GuiDocSplit ...")
|
logger.debug("Initialising GuiDocSplit ...")
|
||||||
|
self.setObjectName("GuiDocSplit")
|
||||||
|
|
||||||
self.mainConf = nw.CONFIG
|
self.mainConf = nw.CONFIG
|
||||||
self.theParent = theParent
|
self.theParent = theParent
|
||||||
@@ -160,23 +163,22 @@ class GuiDocSplit(QDialog):
|
|||||||
), nwAlert.ERROR)
|
), nwAlert.ERROR)
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.mainConf.showGUI:
|
msgBox = QMessageBox()
|
||||||
msgBox = QMessageBox()
|
msgRes = msgBox.question(
|
||||||
msgRes = msgBox.question(
|
self, "Split Document", (
|
||||||
self, "Split Document", (
|
"The document will be split into %d file(s) in a new folder. "
|
||||||
"The document will be split into %d file(s) in a new folder. "
|
"The original document will remain intact.<br><br>"
|
||||||
"The original document will remain intact.<br><br>"
|
"Continue with the splitting process?"
|
||||||
"Continue with the splitting process?"
|
) % nFiles
|
||||||
) % nFiles
|
)
|
||||||
)
|
if msgRes != QMessageBox.Yes:
|
||||||
if msgRes != QMessageBox.Yes:
|
return
|
||||||
return
|
|
||||||
|
|
||||||
# Create the folder
|
# Create the folder
|
||||||
fHandle = self.theProject.newFolder(
|
fHandle = self.theProject.newFolder(
|
||||||
srcItem.itemName, srcItem.itemClass, srcItem.parHandle
|
srcItem.itemName, srcItem.itemClass, srcItem.parHandle
|
||||||
)
|
)
|
||||||
self.theParent.treeView.revealTreeItem(fHandle)
|
self.theParent.treeView.revealNewTreeItem(fHandle)
|
||||||
logger.verbose("Creating folder %s" % fHandle)
|
logger.verbose("Creating folder %s" % fHandle)
|
||||||
|
|
||||||
# Loop through, and create the files
|
# Loop through, and create the files
|
||||||
@@ -211,16 +213,15 @@ class GuiDocSplit(QDialog):
|
|||||||
theDoc.openDocument(nHandle, False)
|
theDoc.openDocument(nHandle, False)
|
||||||
theDoc.saveDocument(theText)
|
theDoc.saveDocument(theText)
|
||||||
theDoc.clearDocument()
|
theDoc.clearDocument()
|
||||||
self.theParent.treeView.revealTreeItem(nHandle)
|
self.theParent.treeView.revealNewTreeItem(nHandle)
|
||||||
|
|
||||||
self.close()
|
self._doClose()
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def _doClose(self):
|
def _doClose(self):
|
||||||
"""Close the dialog window without doing anything.
|
"""Close the dialog window without doing anything.
|
||||||
"""
|
"""
|
||||||
logger.verbose("GuiDocSplit close button clicked")
|
|
||||||
self.optState.saveSettings()
|
self.optState.saveSettings()
|
||||||
self.close()
|
self.close()
|
||||||
return
|
return
|
||||||
|
|||||||
+2
-2
@@ -13,7 +13,7 @@
|
|||||||
Created: 2020-09-08 [1.0b1] GuiDocViewHistory
|
Created: 2020-09-08 [1.0b1] GuiDocViewHistory
|
||||||
|
|
||||||
This file is a part of novelWriter
|
This file is a part of novelWriter
|
||||||
Copyright 2020, Veronica Berglyd Olsen
|
Copyright 2018–2020, Veronica Berglyd Olsen
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -29,8 +29,8 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
|
||||||
import nw
|
import nw
|
||||||
|
import logging
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt, QUrl, QSize, pyqtSlot
|
from PyQt5.QtCore import Qt, QUrl, QSize, pyqtSlot
|
||||||
from PyQt5.QtGui import (
|
from PyQt5.QtGui import (
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
Created: 2019-04-24 [0.0.1]
|
Created: 2019-04-24 [0.0.1]
|
||||||
|
|
||||||
This file is a part of novelWriter
|
This file is a part of novelWriter
|
||||||
Copyright 2020, Veronica Berglyd Olsen
|
Copyright 2018–2020, Veronica Berglyd Olsen
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -25,8 +25,8 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
|
||||||
import nw
|
import nw
|
||||||
|
import logging
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt
|
from PyQt5.QtCore import Qt
|
||||||
from PyQt5.QtGui import QFont, QPixmap
|
from PyQt5.QtGui import QFont, QPixmap
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
Created: 2019-04-27 [0.0.1]
|
Created: 2019-04-27 [0.0.1]
|
||||||
|
|
||||||
This file is a part of novelWriter
|
This file is a part of novelWriter
|
||||||
Copyright 2020, Veronica Berglyd Olsen
|
Copyright 2018–2020, Veronica Berglyd Olsen
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -25,8 +25,8 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
|
||||||
import nw
|
import nw
|
||||||
|
import logging
|
||||||
|
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
QDialog, QVBoxLayout, QGridLayout, QLineEdit, QComboBox, QLabel,
|
QDialog, QVBoxLayout, QGridLayout, QLineEdit, QComboBox, QLabel,
|
||||||
@@ -44,6 +44,7 @@ class GuiItemEditor(QDialog):
|
|||||||
QDialog.__init__(self, theParent)
|
QDialog.__init__(self, theParent)
|
||||||
|
|
||||||
logger.debug("Initialising GuiItemEditor ...")
|
logger.debug("Initialising GuiItemEditor ...")
|
||||||
|
self.setObjectName("GuiItemEditor")
|
||||||
|
|
||||||
self.mainConf = nw.CONFIG
|
self.mainConf = nw.CONFIG
|
||||||
self.theProject = theProject
|
self.theProject = theProject
|
||||||
|
|||||||
+6
-6
@@ -9,7 +9,7 @@
|
|||||||
Created: 2019-04-27 [0.0.1] (Split from winmain)
|
Created: 2019-04-27 [0.0.1] (Split from winmain)
|
||||||
|
|
||||||
This file is a part of novelWriter
|
This file is a part of novelWriter
|
||||||
Copyright 2020, Veronica Berglyd Olsen
|
Copyright 2018–2020, Veronica Berglyd Olsen
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -25,8 +25,8 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
|
||||||
import nw
|
import nw
|
||||||
|
import logging
|
||||||
|
|
||||||
from PyQt5.QtCore import QUrl, QProcess
|
from PyQt5.QtCore import QUrl, QProcess
|
||||||
from PyQt5.QtGui import QDesktopServices
|
from PyQt5.QtGui import QDesktopServices
|
||||||
@@ -192,7 +192,7 @@ class GuiMainMenu(QMenuBar):
|
|||||||
self.aOpenProject = QAction("Open Project", self)
|
self.aOpenProject = QAction("Open Project", self)
|
||||||
self.aOpenProject.setStatusTip("Open project")
|
self.aOpenProject.setStatusTip("Open project")
|
||||||
self.aOpenProject.setShortcut("Ctrl+Shift+O")
|
self.aOpenProject.setShortcut("Ctrl+Shift+O")
|
||||||
self.aOpenProject.triggered.connect(lambda: self.theParent.manageProjects())
|
self.aOpenProject.triggered.connect(lambda: self.theParent.showProjectLoadDialog())
|
||||||
self.projMenu.addAction(self.aOpenProject)
|
self.projMenu.addAction(self.aOpenProject)
|
||||||
|
|
||||||
# Project > Save Project
|
# Project > Save Project
|
||||||
@@ -213,7 +213,7 @@ class GuiMainMenu(QMenuBar):
|
|||||||
self.aProjectSettings = QAction("Project Settings", self)
|
self.aProjectSettings = QAction("Project Settings", self)
|
||||||
self.aProjectSettings.setStatusTip("Project settings")
|
self.aProjectSettings.setStatusTip("Project settings")
|
||||||
self.aProjectSettings.setShortcut("Ctrl+Shift+,")
|
self.aProjectSettings.setShortcut("Ctrl+Shift+,")
|
||||||
self.aProjectSettings.triggered.connect(lambda: self.theParent.editProjectDialog())
|
self.aProjectSettings.triggered.connect(lambda: self.theParent.showProjectSettingsDialog())
|
||||||
self.projMenu.addAction(self.aProjectSettings)
|
self.projMenu.addAction(self.aProjectSettings)
|
||||||
|
|
||||||
# Project > Separator
|
# Project > Separator
|
||||||
@@ -829,7 +829,7 @@ class GuiMainMenu(QMenuBar):
|
|||||||
self.aBuildProject = QAction("Build Novel Project", self)
|
self.aBuildProject = QAction("Build Novel Project", self)
|
||||||
self.aBuildProject.setStatusTip("Launch the Build novel project tool")
|
self.aBuildProject.setStatusTip("Launch the Build novel project tool")
|
||||||
self.aBuildProject.setShortcut("F5")
|
self.aBuildProject.setShortcut("F5")
|
||||||
self.aBuildProject.triggered.connect(lambda: self.theParent.buildProjectDialog())
|
self.aBuildProject.triggered.connect(lambda: self.theParent.showBuildProjectDialog())
|
||||||
self.toolsMenu.addAction(self.aBuildProject)
|
self.toolsMenu.addAction(self.aBuildProject)
|
||||||
|
|
||||||
# Tools > Writing Stats
|
# Tools > Writing Stats
|
||||||
@@ -843,7 +843,7 @@ class GuiMainMenu(QMenuBar):
|
|||||||
self.aPreferences = QAction("Preferences", self)
|
self.aPreferences = QAction("Preferences", self)
|
||||||
self.aPreferences.setStatusTip("Preferences")
|
self.aPreferences.setStatusTip("Preferences")
|
||||||
self.aPreferences.setShortcut("Ctrl+,")
|
self.aPreferences.setShortcut("Ctrl+,")
|
||||||
self.aPreferences.triggered.connect(lambda: self.theParent.editConfigDialog())
|
self.aPreferences.triggered.connect(lambda: self.theParent.showPreferencesDialog())
|
||||||
self.toolsMenu.addAction(self.aPreferences)
|
self.toolsMenu.addAction(self.aPreferences)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|||||||
+2
-2
@@ -9,7 +9,7 @@
|
|||||||
Created: 2019-11-16 [0.4.1]
|
Created: 2019-11-16 [0.4.1]
|
||||||
|
|
||||||
This file is a part of novelWriter
|
This file is a part of novelWriter
|
||||||
Copyright 2020, Veronica Berglyd Olsen
|
Copyright 2018–2020, Veronica Berglyd Olsen
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -25,8 +25,8 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
|
||||||
import nw
|
import nw
|
||||||
|
import logging
|
||||||
|
|
||||||
from time import time
|
from time import time
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
Created: 2020-06-02 [0.7.0]
|
Created: 2020-06-02 [0.7.0]
|
||||||
|
|
||||||
This file is a part of novelWriter
|
This file is a part of novelWriter
|
||||||
Copyright 2020, Veronica Berglyd Olsen
|
Copyright 2018–2020, Veronica Berglyd Olsen
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -25,8 +25,8 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
|
||||||
import nw
|
import nw
|
||||||
|
import logging
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt
|
from PyQt5.QtCore import Qt
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
Created: 2019-06-10 [0.1.5]
|
Created: 2019-06-10 [0.1.5]
|
||||||
|
|
||||||
This file is a part of novelWriter
|
This file is a part of novelWriter
|
||||||
Copyright 2020, Veronica Berglyd Olsen
|
Copyright 2018–2020, Veronica Berglyd Olsen
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -25,10 +25,9 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
|
||||||
import nw
|
import nw
|
||||||
|
import logging
|
||||||
from os import path
|
import os
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt
|
from PyQt5.QtCore import Qt
|
||||||
from PyQt5.QtGui import QFont
|
from PyQt5.QtGui import QFont
|
||||||
@@ -48,6 +47,7 @@ class GuiPreferences(PagedDialog):
|
|||||||
PagedDialog.__init__(self, theParent)
|
PagedDialog.__init__(self, theParent)
|
||||||
|
|
||||||
logger.debug("Initialising GuiPreferences ...")
|
logger.debug("Initialising GuiPreferences ...")
|
||||||
|
self.setObjectName("GuiPreferences")
|
||||||
|
|
||||||
self.mainConf = nw.CONFIG
|
self.mainConf = nw.CONFIG
|
||||||
self.theParent = theParent
|
self.theParent = theParent
|
||||||
@@ -103,7 +103,7 @@ class GuiPreferences(PagedDialog):
|
|||||||
validEntries &= retA
|
validEntries &= retA
|
||||||
needsRestart |= retB
|
needsRestart |= retB
|
||||||
|
|
||||||
if needsRestart and self.mainConf.showGUI:
|
if needsRestart:
|
||||||
msgBox = QMessageBox()
|
msgBox = QMessageBox()
|
||||||
msgBox.information(
|
msgBox.information(
|
||||||
self, "Preferences",
|
self, "Preferences",
|
||||||
@@ -119,7 +119,7 @@ class GuiPreferences(PagedDialog):
|
|||||||
"""Close the preferences without saving the changes.
|
"""Close the preferences without saving the changes.
|
||||||
"""
|
"""
|
||||||
logger.verbose("ConfigEditor close button clicked")
|
logger.verbose("ConfigEditor close button clicked")
|
||||||
self.close()
|
self.reject()
|
||||||
return
|
return
|
||||||
|
|
||||||
# END Class GuiPreferences
|
# END Class GuiPreferences
|
||||||
@@ -334,7 +334,7 @@ class GuiConfigEditGeneralTab(QWidget):
|
|||||||
"""Open a dialog to select the backup folder.
|
"""Open a dialog to select the backup folder.
|
||||||
"""
|
"""
|
||||||
currDir = self.backupPath
|
currDir = self.backupPath
|
||||||
if not path.isdir(currDir):
|
if not os.path.isdir(currDir):
|
||||||
currDir = ""
|
currDir = ""
|
||||||
|
|
||||||
dlgOpt = QFileDialog.Options()
|
dlgOpt = QFileDialog.Options()
|
||||||
|
|||||||
+23
-29
@@ -9,7 +9,7 @@
|
|||||||
Created: 2020-02-26 [0.4.5]
|
Created: 2020-02-26 [0.4.5]
|
||||||
|
|
||||||
This file is a part of novelWriter
|
This file is a part of novelWriter
|
||||||
Copyright 2020, Veronica Berglyd Olsen
|
Copyright 2018–2020, Veronica Berglyd Olsen
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -25,10 +25,10 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
|
||||||
import nw
|
import nw
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
from os import path
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt, QSize
|
from PyQt5.QtCore import Qt, QSize
|
||||||
@@ -58,6 +58,7 @@ class GuiProjectLoad(QDialog):
|
|||||||
QDialog.__init__(self, theParent)
|
QDialog.__init__(self, theParent)
|
||||||
|
|
||||||
logger.debug("Initialising GuiProjectLoad ...")
|
logger.debug("Initialising GuiProjectLoad ...")
|
||||||
|
self.setObjectName("GuiProjectLoad")
|
||||||
|
|
||||||
self.mainConf = nw.CONFIG
|
self.mainConf = nw.CONFIG
|
||||||
self.theParent = theParent
|
self.theParent = theParent
|
||||||
@@ -177,20 +178,19 @@ class GuiProjectLoad(QDialog):
|
|||||||
"""Browse for a folder path.
|
"""Browse for a folder path.
|
||||||
"""
|
"""
|
||||||
logger.verbose("GuiProjectLoad browse button clicked")
|
logger.verbose("GuiProjectLoad browse button clicked")
|
||||||
if self.mainConf.showGUI:
|
dlgOpt = QFileDialog.Options()
|
||||||
dlgOpt = QFileDialog.Options()
|
dlgOpt |= QFileDialog.DontUseNativeDialog
|
||||||
dlgOpt |= QFileDialog.DontUseNativeDialog
|
projFile, _ = QFileDialog.getOpenFileName(
|
||||||
projFile, _ = QFileDialog.getOpenFileName(
|
self, "Open novelWriter Project", "",
|
||||||
self, "Open novelWriter Project", "",
|
"novelWriter Project File (%s);;All Files (*)" % nwFiles.PROJ_FILE,
|
||||||
"novelWriter Project File (%s);;All Files (*)" % nwFiles.PROJ_FILE,
|
options=dlgOpt
|
||||||
options=dlgOpt
|
)
|
||||||
)
|
if projFile:
|
||||||
if projFile:
|
thePath = os.path.abspath(os.path.dirname(projFile))
|
||||||
thePath = path.abspath(path.dirname(projFile))
|
self.selPath.setText(thePath)
|
||||||
self.selPath.setText(thePath)
|
self.openPath = thePath
|
||||||
self.openPath = thePath
|
self.openState = self.OPEN_STATE
|
||||||
self.openState = self.OPEN_STATE
|
self.accept()
|
||||||
self.accept()
|
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -217,18 +217,12 @@ class GuiProjectLoad(QDialog):
|
|||||||
"""
|
"""
|
||||||
selList = self.listBox.selectedItems()
|
selList = self.listBox.selectedItems()
|
||||||
if selList:
|
if selList:
|
||||||
doRemove = False
|
msgBox = QMessageBox()
|
||||||
if self.mainConf.showGUI:
|
msgRes = msgBox.question(
|
||||||
msgBox = QMessageBox()
|
self, "Remove Entry",
|
||||||
msgRes = msgBox.question(
|
"Remove the selected entry from the recent projects list?"
|
||||||
self, "Remove Entry",
|
)
|
||||||
"Remove the selected entry from the recent projects list?"
|
if msgRes == QMessageBox.Yes:
|
||||||
)
|
|
||||||
doRemove = (msgRes == QMessageBox.Yes)
|
|
||||||
else:
|
|
||||||
doRemove = True
|
|
||||||
|
|
||||||
if doRemove:
|
|
||||||
self.mainConf.removeFromRecentCache(
|
self.mainConf.removeFromRecentCache(
|
||||||
selList[0].data(self.C_NAME, Qt.UserRole)
|
selList[0].data(self.C_NAME, Qt.UserRole)
|
||||||
)
|
)
|
||||||
|
|||||||
+16
-5
@@ -9,7 +9,7 @@
|
|||||||
Created: 2018-09-29 [0.0.1]
|
Created: 2018-09-29 [0.0.1]
|
||||||
|
|
||||||
This file is a part of novelWriter
|
This file is a part of novelWriter
|
||||||
Copyright 2020, Veronica Berglyd Olsen
|
Copyright 2018–2020, Veronica Berglyd Olsen
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -25,8 +25,8 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
|
||||||
import nw
|
import nw
|
||||||
|
import logging
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt
|
from PyQt5.QtCore import Qt
|
||||||
from PyQt5.QtGui import QIcon, QPixmap, QColor, QBrush
|
from PyQt5.QtGui import QIcon, QPixmap, QColor, QBrush
|
||||||
@@ -47,6 +47,7 @@ class GuiProjectSettings(PagedDialog):
|
|||||||
PagedDialog.__init__(self, theParent)
|
PagedDialog.__init__(self, theParent)
|
||||||
|
|
||||||
logger.debug("Initialising GuiProjectSettings ...")
|
logger.debug("Initialising GuiProjectSettings ...")
|
||||||
|
self.setObjectName("GuiProjectSettings")
|
||||||
|
|
||||||
self.mainConf = nw.CONFIG
|
self.mainConf = nw.CONFIG
|
||||||
self.theParent = theParent
|
self.theParent = theParent
|
||||||
@@ -123,13 +124,25 @@ class GuiProjectSettings(PagedDialog):
|
|||||||
newList = self.tabReplace.getNewList()
|
newList = self.tabReplace.getNewList()
|
||||||
self.theProject.setAutoReplace(newList)
|
self.theProject.setAutoReplace(newList)
|
||||||
|
|
||||||
self._doClose()
|
self._saveGuiSettings()
|
||||||
|
self.accept()
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def _doClose(self):
|
def _doClose(self):
|
||||||
"""Save settings and close the dialog.
|
"""Save settings and close the dialog.
|
||||||
"""
|
"""
|
||||||
|
self._saveGuiSettings()
|
||||||
|
self.reject()
|
||||||
|
return
|
||||||
|
|
||||||
|
##
|
||||||
|
# Internal Functions
|
||||||
|
##
|
||||||
|
|
||||||
|
def _saveGuiSettings(self):
|
||||||
|
"""Save GUI settings.
|
||||||
|
"""
|
||||||
winWidth = self.mainConf.rpxInt(self.width())
|
winWidth = self.mainConf.rpxInt(self.width())
|
||||||
winHeight = self.mainConf.rpxInt(self.height())
|
winHeight = self.mainConf.rpxInt(self.height())
|
||||||
replaceColW = self.mainConf.rpxInt(self.tabReplace.listBox.columnWidth(0))
|
replaceColW = self.mainConf.rpxInt(self.tabReplace.listBox.columnWidth(0))
|
||||||
@@ -138,8 +151,6 @@ class GuiProjectSettings(PagedDialog):
|
|||||||
self.optState.setValue("GuiProjectSettings", "winHeight", winHeight)
|
self.optState.setValue("GuiProjectSettings", "winHeight", winHeight)
|
||||||
self.optState.setValue("GuiProjectSettings", "replaceColW", replaceColW)
|
self.optState.setValue("GuiProjectSettings", "replaceColW", replaceColW)
|
||||||
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# END Class GuiProjectSettings
|
# END Class GuiProjectSettings
|
||||||
|
|||||||
+18
-12
@@ -10,7 +10,7 @@
|
|||||||
Created: 2020-06-04 [0.7.0] GuiProjectTreeMenu
|
Created: 2020-06-04 [0.7.0] GuiProjectTreeMenu
|
||||||
|
|
||||||
This file is a part of novelWriter
|
This file is a part of novelWriter
|
||||||
Copyright 2020, Veronica Berglyd Olsen
|
Copyright 2018–2020, Veronica Berglyd Olsen
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -26,8 +26,8 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
|
||||||
import nw
|
import nw
|
||||||
|
import logging
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt, QSize
|
from PyQt5.QtCore import Qt, QSize
|
||||||
from PyQt5.QtGui import QIcon
|
from PyQt5.QtGui import QIcon
|
||||||
@@ -178,6 +178,9 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
|
|
||||||
if itemType == nwItemType.ROOT:
|
if itemType == nwItemType.ROOT:
|
||||||
tHandle = self.theProject.newRoot(nwLabels.CLASS_NAME[itemClass], itemClass)
|
tHandle = self.theProject.newRoot(nwLabels.CLASS_NAME[itemClass], itemClass)
|
||||||
|
if tHandle is None:
|
||||||
|
logger.error("No root item added")
|
||||||
|
return False
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# If no parent has been selected, make the new file under
|
# If no parent has been selected, make the new file under
|
||||||
@@ -206,7 +209,7 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if pHandle == self.theProject.projTree.trashRoot():
|
if self.theProject.projTree.isTrashRoot(pHandle):
|
||||||
self.makeAlert(
|
self.makeAlert(
|
||||||
"Cannot add new files or folders to the %s folder." % (
|
"Cannot add new files or folders to the %s folder." % (
|
||||||
nwLabels.CLASS_NAME[nwItemClass.TRASH]
|
nwLabels.CLASS_NAME[nwItemClass.TRASH]
|
||||||
@@ -236,12 +239,13 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# Add the new item to the tree
|
# Add the new item to the tree
|
||||||
self.revealTreeItem(tHandle, nHandle)
|
if tHandle is not None:
|
||||||
self.theParent.editItem(tHandle)
|
self.revealNewTreeItem(tHandle, nHandle)
|
||||||
|
self.theParent.editItem(tHandle)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def revealTreeItem(self, tHandle, nHandle=None):
|
def revealNewTreeItem(self, tHandle, nHandle=None):
|
||||||
"""Reveal a newly added project item in the project tree.
|
"""Reveal a newly added project item in the project tree.
|
||||||
"""
|
"""
|
||||||
nwItem = self.theProject.projTree[tHandle]
|
nwItem = self.theProject.projTree[tHandle]
|
||||||
@@ -257,7 +261,8 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
"""Move an item up or down in the tree, but only if the treeView
|
"""Move an item up or down in the tree, but only if the treeView
|
||||||
has focus. This also applies when the menu is used.
|
has focus. This also applies when the menu is used.
|
||||||
"""
|
"""
|
||||||
if qApp.focusWidget() == self and self.theParent.hasProject:
|
hasFocus = qApp.focusWidget() == self or not self.mainConf.showGUI
|
||||||
|
if hasFocus and self.theParent.hasProject:
|
||||||
|
|
||||||
tHandle = self.getSelectedHandle()
|
tHandle = self.getSelectedHandle()
|
||||||
tItem = self._getTreeItem(tHandle)
|
tItem = self._getTreeItem(tHandle)
|
||||||
@@ -406,11 +411,11 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
pHandle = nwItemS.parHandle
|
pHandle = nwItemS.parHandle
|
||||||
if pHandle is not None and pHandle == self.theProject.projTree.trashRoot():
|
if self.theProject.projTree.isTrashRoot(pHandle):
|
||||||
# If the file is in the trash folder already, as the
|
# If the file is in the trash folder already, as the
|
||||||
# user if they want to permanently delete the file.
|
# user if they want to permanently delete the file.
|
||||||
doPermanent = False
|
doPermanent = False
|
||||||
if self.mainConf.showGUI and not alreadyAsked:
|
if not alreadyAsked:
|
||||||
msgBox = QMessageBox()
|
msgBox = QMessageBox()
|
||||||
msgRes = msgBox.question(
|
msgRes = msgBox.question(
|
||||||
self, "Delete File", "Permanently delete file '%s'?" % nwItemS.itemName
|
self, "Delete File", "Permanently delete file '%s'?" % nwItemS.itemName
|
||||||
@@ -439,7 +444,7 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
# The file is not already in the trash folder, so we
|
# The file is not already in the trash folder, so we
|
||||||
# move it there.
|
# move it there.
|
||||||
doTrash = False
|
doTrash = False
|
||||||
if self.mainConf.showGUI and askForTrash:
|
if askForTrash:
|
||||||
msgBox = QMessageBox()
|
msgBox = QMessageBox()
|
||||||
msgRes = msgBox.question(
|
msgRes = msgBox.question(
|
||||||
self, "Delete File", "Move file '%s' to Trash?" % nwItemS.itemName
|
self, "Delete File", "Move file '%s' to Trash?" % nwItemS.itemName
|
||||||
@@ -754,6 +759,7 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
self.theIndex.reIndexHandle(sHandle)
|
self.theIndex.reIndexHandle(sHandle)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
theEvent.ignore()
|
||||||
logger.debug("Drag'n'drop of item %s not accepted" % sHandle)
|
logger.debug("Drag'n'drop of item %s not accepted" % sHandle)
|
||||||
self.makeAlert("The item cannot be moved to that location.", nwAlert.ERROR)
|
self.makeAlert("The item cannot be moved to that location.", nwAlert.ERROR)
|
||||||
|
|
||||||
@@ -920,14 +926,14 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
nwItemS.setClass(nwItemD.itemClass)
|
nwItemS.setClass(nwItemD.itemClass)
|
||||||
if trItemP is None:
|
if trItemP is None:
|
||||||
logger.error("Failed to find new parent item of %s" % tHandle)
|
logger.error("Failed to find new parent item of %s" % tHandle)
|
||||||
return
|
return False
|
||||||
|
|
||||||
pHandle = trItemP.data(self.C_NAME, Qt.UserRole)
|
pHandle = trItemP.data(self.C_NAME, Qt.UserRole)
|
||||||
nwItemS.setParent(pHandle)
|
nwItemS.setParent(pHandle)
|
||||||
self.setTreeItemValues(tHandle)
|
self.setTreeItemValues(tHandle)
|
||||||
self._setTreeChanged(True)
|
self._setTreeChanged(True)
|
||||||
|
|
||||||
return
|
return True
|
||||||
|
|
||||||
def _setTreeChanged(self, theState):
|
def _setTreeChanged(self, theState):
|
||||||
"""Set the tree change flag, and propagate to the project.
|
"""Set the tree change flag, and propagate to the project.
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
Created: 2020-07-11 [0.10.1]
|
Created: 2020-07-11 [0.10.1]
|
||||||
|
|
||||||
This file is a part of novelWriter
|
This file is a part of novelWriter
|
||||||
Copyright 2020, Veronica Berglyd Olsen
|
Copyright 2018–2020, Veronica Berglyd Olsen
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -25,10 +25,9 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
|
||||||
import nw
|
import nw
|
||||||
|
import logging
|
||||||
from os import path
|
import os
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt
|
from PyQt5.QtCore import Qt
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
@@ -55,6 +54,7 @@ class GuiProjectWizard(QWizard):
|
|||||||
QWizard.__init__(self, theParent)
|
QWizard.__init__(self, theParent)
|
||||||
|
|
||||||
logger.debug("Initialising GuiProjectWizard ...")
|
logger.debug("Initialising GuiProjectWizard ...")
|
||||||
|
self.setObjectName("GuiProjectWizard")
|
||||||
|
|
||||||
self.mainConf = nw.CONFIG
|
self.mainConf = nw.CONFIG
|
||||||
self.theParent = theParent
|
self.theParent = theParent
|
||||||
@@ -207,7 +207,7 @@ class ProjWizardFolderPage(QWizardPage):
|
|||||||
"""Select a project folder.
|
"""Select a project folder.
|
||||||
"""
|
"""
|
||||||
lastPath = self.mainConf.lastPath
|
lastPath = self.mainConf.lastPath
|
||||||
if not path.isdir(lastPath):
|
if not os.path.isdir(lastPath):
|
||||||
lastPath = ""
|
lastPath = ""
|
||||||
|
|
||||||
dlgOpt = QFileDialog.Options()
|
dlgOpt = QFileDialog.Options()
|
||||||
@@ -219,7 +219,7 @@ class ProjWizardFolderPage(QWizardPage):
|
|||||||
if projDir:
|
if projDir:
|
||||||
projName = self.field("projName")
|
projName = self.field("projName")
|
||||||
if projName is not None:
|
if projName is not None:
|
||||||
fullDir = path.join(path.abspath(projDir), makeFileNameSafe(projName))
|
fullDir = os.path.join(os.path.abspath(projDir), makeFileNameSafe(projName))
|
||||||
self.projPath.setText(fullDir)
|
self.projPath.setText(fullDir)
|
||||||
else:
|
else:
|
||||||
self.projPath.setText("")
|
self.projPath.setText("")
|
||||||
|
|||||||
+2
-2
@@ -9,7 +9,7 @@
|
|||||||
Created: 2019-04-20 [0.0.1]
|
Created: 2019-04-20 [0.0.1]
|
||||||
|
|
||||||
This file is a part of novelWriter
|
This file is a part of novelWriter
|
||||||
Copyright 2020, Veronica Berglyd Olsen
|
Copyright 2018–2020, Veronica Berglyd Olsen
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -25,8 +25,8 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
|
||||||
import nw
|
import nw
|
||||||
|
import logging
|
||||||
|
|
||||||
from time import time
|
from time import time
|
||||||
|
|
||||||
|
|||||||
+43
-42
@@ -10,7 +10,7 @@
|
|||||||
Created: 2019-11-08 [0.4.0] GuiIcons
|
Created: 2019-11-08 [0.4.0] GuiIcons
|
||||||
|
|
||||||
This file is a part of novelWriter
|
This file is a part of novelWriter
|
||||||
Copyright 2020, Veronica Berglyd Olsen
|
Copyright 2018–2020, Veronica Berglyd Olsen
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -26,15 +26,14 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import nw
|
||||||
import logging
|
import logging
|
||||||
import configparser
|
import configparser
|
||||||
import nw
|
import os
|
||||||
|
|
||||||
from os import path, listdir
|
|
||||||
from math import ceil
|
from math import ceil
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt
|
from PyQt5.QtCore import Qt
|
||||||
from PyQt5.QtSvg import QSvgWidget
|
|
||||||
from PyQt5.QtWidgets import QStyle, qApp
|
from PyQt5.QtWidgets import QStyle, qApp
|
||||||
from PyQt5.QtGui import (
|
from PyQt5.QtGui import (
|
||||||
QPalette, QColor, QIcon, QFont, QFontMetrics, QFontDatabase, QPixmap
|
QPalette, QColor, QIcon, QFont, QFontMetrics, QFontDatabase, QPixmap
|
||||||
@@ -183,21 +182,21 @@ class GuiTheme:
|
|||||||
"""Add the fonts in the assets fonts folder to the app.
|
"""Add the fonts in the assets fonts folder to the app.
|
||||||
"""
|
"""
|
||||||
ttfList = []
|
ttfList = []
|
||||||
fontAssets = path.join(self.mainConf.assetPath, self.fontPath)
|
fontAssets = os.path.join(self.mainConf.assetPath, self.fontPath)
|
||||||
for fontFam in listdir(fontAssets):
|
for fontFam in os.listdir(fontAssets):
|
||||||
fontDir = path.join(fontAssets, fontFam)
|
fontDir = os.path.join(fontAssets, fontFam)
|
||||||
if path.isdir(fontDir):
|
if os.path.isdir(fontDir):
|
||||||
if fontFam not in self.guiFontDB.families():
|
if fontFam not in self.guiFontDB.families():
|
||||||
for fontFile in listdir(fontDir):
|
for fontFile in os.listdir(fontDir):
|
||||||
ttfFile = path.join(fontDir, fontFile)
|
ttfFile = os.path.join(fontDir, fontFile)
|
||||||
if path.isfile(ttfFile) and fontFile.endswith(".ttf"):
|
if os.path.isfile(ttfFile) and fontFile.endswith(".ttf"):
|
||||||
ttfList.append(ttfFile)
|
ttfList.append(ttfFile)
|
||||||
|
|
||||||
for ttfFile in ttfList:
|
for ttfFile in ttfList:
|
||||||
logger.verbose("Font asset: %s" % path.relpath(ttfFile))
|
logger.verbose("Font asset: %s" % os.path.relpath(ttfFile))
|
||||||
fontID = self.guiFontDB.addApplicationFont(ttfFile)
|
fontID = self.guiFontDB.addApplicationFont(ttfFile)
|
||||||
if fontID < 0:
|
if fontID < 0:
|
||||||
logger.error("Failed to add font: %s" % path.relpath(ttfFile))
|
logger.error("Failed to add font: %s" % os.path.relpath(ttfFile))
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -228,10 +227,10 @@ class GuiTheme:
|
|||||||
self.guiTheme = self.mainConf.guiTheme
|
self.guiTheme = self.mainConf.guiTheme
|
||||||
self.guiSyntax = self.mainConf.guiSyntax
|
self.guiSyntax = self.mainConf.guiSyntax
|
||||||
self.themeRoot = self.mainConf.themeRoot
|
self.themeRoot = self.mainConf.themeRoot
|
||||||
self.themePath = path.join(self.mainConf.themeRoot, self.guiPath, self.guiTheme)
|
self.themePath = os.path.join(self.mainConf.themeRoot, self.guiPath, self.guiTheme)
|
||||||
self.syntaxFile = path.join(self.themeRoot, self.syntaxPath, self.guiSyntax+".conf")
|
self.syntaxFile = os.path.join(self.themeRoot, self.syntaxPath, self.guiSyntax+".conf")
|
||||||
self.confFile = path.join(self.themePath, self.confName)
|
self.confFile = os.path.join(self.themePath, self.confName)
|
||||||
self.cssFile = path.join(self.themePath, self.cssName)
|
self.cssFile = os.path.join(self.themePath, self.cssName)
|
||||||
|
|
||||||
self.loadTheme()
|
self.loadTheme()
|
||||||
self.loadSyntax()
|
self.loadSyntax()
|
||||||
@@ -260,7 +259,7 @@ class GuiTheme:
|
|||||||
# CSS File
|
# CSS File
|
||||||
cssData = ""
|
cssData = ""
|
||||||
try:
|
try:
|
||||||
if path.isfile(self.cssFile):
|
if os.path.isfile(self.cssFile):
|
||||||
with open(self.cssFile, mode="r", encoding="utf8") as inFile:
|
with open(self.cssFile, mode="r", encoding="utf8") as inFile:
|
||||||
cssData = inFile.read()
|
cssData = inFile.read()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -376,8 +375,10 @@ class GuiTheme:
|
|||||||
return self.themeList
|
return self.themeList
|
||||||
|
|
||||||
confParser = configparser.ConfigParser()
|
confParser = configparser.ConfigParser()
|
||||||
for themeDir in listdir(path.join(self.mainConf.themeRoot, self.guiPath)):
|
for themeDir in os.listdir(os.path.join(self.mainConf.themeRoot, self.guiPath)):
|
||||||
themeConf = path.join(self.mainConf.themeRoot, self.guiPath, themeDir, self.confName)
|
themeConf = os.path.join(
|
||||||
|
self.mainConf.themeRoot, self.guiPath, themeDir, self.confName
|
||||||
|
)
|
||||||
logger.verbose("Checking theme config for '%s'" % themeDir)
|
logger.verbose("Checking theme config for '%s'" % themeDir)
|
||||||
try:
|
try:
|
||||||
with open(themeConf, mode="r", encoding="utf8") as inFile:
|
with open(themeConf, mode="r", encoding="utf8") as inFile:
|
||||||
@@ -406,10 +407,10 @@ class GuiTheme:
|
|||||||
return self.syntaxList
|
return self.syntaxList
|
||||||
|
|
||||||
confParser = configparser.ConfigParser()
|
confParser = configparser.ConfigParser()
|
||||||
syntaxDir = path.join(self.mainConf.themeRoot, self.syntaxPath)
|
syntaxDir = os.path.join(self.mainConf.themeRoot, self.syntaxPath)
|
||||||
for syntaxFile in listdir(syntaxDir):
|
for syntaxFile in os.listdir(syntaxDir):
|
||||||
syntaxPath = path.join(syntaxDir, syntaxFile)
|
syntaxPath = os.path.join(syntaxDir, syntaxFile)
|
||||||
if not path.isfile(syntaxPath):
|
if not os.path.isfile(syntaxPath):
|
||||||
continue
|
continue
|
||||||
logger.verbose("Checking theme syntax for '%s'" % syntaxFile)
|
logger.verbose("Checking theme syntax for '%s'" % syntaxFile)
|
||||||
try:
|
try:
|
||||||
@@ -613,11 +614,11 @@ class GuiIcons:
|
|||||||
logger.debug("Loading icon theme files")
|
logger.debug("Loading icon theme files")
|
||||||
|
|
||||||
self.themeMap = {}
|
self.themeMap = {}
|
||||||
checkPath = path.join(self.mainConf.iconPath, self.mainConf.guiIcons)
|
checkPath = os.path.join(self.mainConf.iconPath, self.mainConf.guiIcons)
|
||||||
if path.isdir(checkPath):
|
if os.path.isdir(checkPath):
|
||||||
logger.debug("Loading icon theme '%s'" % self.mainConf.guiIcons)
|
logger.debug("Loading icon theme '%s'" % self.mainConf.guiIcons)
|
||||||
self.iconPath = checkPath
|
self.iconPath = checkPath
|
||||||
self.confFile = path.join(checkPath, self.confName)
|
self.confFile = os.path.join(checkPath, self.confName)
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -649,8 +650,8 @@ class GuiIcons:
|
|||||||
if iconName not in self.ICON_MAP:
|
if iconName not in self.ICON_MAP:
|
||||||
logger.error("Unknown icon name '%s' in config file" % iconName)
|
logger.error("Unknown icon name '%s' in config file" % iconName)
|
||||||
else:
|
else:
|
||||||
iconPath = path.join(self.iconPath, iconFile)
|
iconPath = os.path.join(self.iconPath, iconFile)
|
||||||
if path.isfile(iconPath):
|
if os.path.isfile(iconPath):
|
||||||
self.themeMap[iconName] = iconPath
|
self.themeMap[iconName] = iconPath
|
||||||
logger.verbose("Icon slot '%s' using file '%s'" % (iconName, iconFile))
|
logger.verbose("Icon slot '%s' using file '%s'" % (iconName, iconFile))
|
||||||
else:
|
else:
|
||||||
@@ -670,12 +671,12 @@ class GuiIcons:
|
|||||||
"""
|
"""
|
||||||
if decoKey not in self.DECO_MAP:
|
if decoKey not in self.DECO_MAP:
|
||||||
logger.error("Decoration with name '%s' does not exist" % decoKey)
|
logger.error("Decoration with name '%s' does not exist" % decoKey)
|
||||||
return QSvgWidget()
|
return QPixmap()
|
||||||
|
|
||||||
imgPath = path.join(
|
imgPath = os.path.join(
|
||||||
self.mainConf.assetPath, "images", self.DECO_MAP[decoKey]
|
self.mainConf.assetPath, "images", self.DECO_MAP[decoKey]
|
||||||
)
|
)
|
||||||
if not path.isfile(imgPath):
|
if not os.path.isfile(imgPath):
|
||||||
logger.error("Decoration file '%s' not in assets folder" % self.DECO_MAP[decoKey])
|
logger.error("Decoration file '%s' not in assets folder" % self.DECO_MAP[decoKey])
|
||||||
return QPixmap()
|
return QPixmap()
|
||||||
|
|
||||||
@@ -715,11 +716,11 @@ class GuiIcons:
|
|||||||
return self.themeList
|
return self.themeList
|
||||||
|
|
||||||
confParser = configparser.ConfigParser()
|
confParser = configparser.ConfigParser()
|
||||||
for themeDir in listdir(self.mainConf.iconPath):
|
for themeDir in os.listdir(self.mainConf.iconPath):
|
||||||
themePath = path.join(self.mainConf.iconPath, themeDir)
|
themePath = os.path.join(self.mainConf.iconPath, themeDir)
|
||||||
if not path.isdir(themePath) or themeDir == self.fbackName:
|
if not os.path.isdir(themePath) or themeDir == self.fbackName:
|
||||||
continue
|
continue
|
||||||
themeConf = path.join(themePath, self.confName)
|
themeConf = os.path.join(themePath, self.confName)
|
||||||
logger.verbose("Checking icon theme config for '%s'" % themeDir)
|
logger.verbose("Checking icon theme config for '%s'" % themeDir)
|
||||||
try:
|
try:
|
||||||
with open(themeConf, mode="r", encoding="utf8") as inFile:
|
with open(themeConf, mode="r", encoding="utf8") as inFile:
|
||||||
@@ -757,12 +758,12 @@ class GuiIcons:
|
|||||||
|
|
||||||
# If we just want the app icon, return it right away
|
# If we just want the app icon, return it right away
|
||||||
if iconKey == "novelwriter":
|
if iconKey == "novelwriter":
|
||||||
return QIcon(path.join(self.mainConf.iconPath, "novelwriter.svg"))
|
return QIcon(os.path.join(self.mainConf.iconPath, "novelwriter.svg"))
|
||||||
|
|
||||||
# Otherwise, we start looking for it
|
# Otherwise, we start looking for it
|
||||||
# First in the theme folder
|
# First in the theme folder
|
||||||
if iconKey in self.themeMap:
|
if iconKey in self.themeMap:
|
||||||
logger.verbose("Loading: %s" % path.relpath(self.themeMap[iconKey]))
|
logger.verbose("Loading: %s" % os.path.relpath(self.themeMap[iconKey]))
|
||||||
return QIcon(self.themeMap[iconKey])
|
return QIcon(self.themeMap[iconKey])
|
||||||
|
|
||||||
# Next, we try to load the Qt style icons
|
# Next, we try to load the Qt style icons
|
||||||
@@ -778,14 +779,14 @@ class GuiIcons:
|
|||||||
|
|
||||||
# Finally. we check if we have a fallback icon
|
# Finally. we check if we have a fallback icon
|
||||||
if self.mainConf.guiDark:
|
if self.mainConf.guiDark:
|
||||||
fbackIcon = path.join(
|
fbackIcon = os.path.join(
|
||||||
self.mainConf.iconPath, self.fbackName, "%s-dark.svg" % iconKey
|
self.mainConf.iconPath, self.fbackName, "%s-dark.svg" % iconKey
|
||||||
)
|
)
|
||||||
if path.isfile(fbackIcon):
|
if os.path.isfile(fbackIcon):
|
||||||
logger.verbose("Loading icon '%s' from fallback theme (dark mode)" % iconKey)
|
logger.verbose("Loading icon '%s' from fallback theme (dark mode)" % iconKey)
|
||||||
return QIcon(fbackIcon)
|
return QIcon(fbackIcon)
|
||||||
fbackIcon = path.join(self.mainConf.iconPath, self.fbackName, "%s.svg" % iconKey)
|
fbackIcon = os.path.join(self.mainConf.iconPath, self.fbackName, "%s.svg" % iconKey)
|
||||||
if path.isfile(fbackIcon):
|
if os.path.isfile(fbackIcon):
|
||||||
logger.verbose("Loading icon '%s' from fallback theme (light mode)" % iconKey)
|
logger.verbose("Loading icon '%s' from fallback theme (light mode)" % iconKey)
|
||||||
return QIcon(fbackIcon)
|
return QIcon(fbackIcon)
|
||||||
|
|
||||||
|
|||||||
+28
-29
@@ -9,7 +9,7 @@
|
|||||||
Created: 2019-10-20 [0.3]
|
Created: 2019-10-20 [0.3]
|
||||||
|
|
||||||
This file is a part of novelWriter
|
This file is a part of novelWriter
|
||||||
Copyright 2020, Veronica Berglyd Olsen
|
Copyright 2018–2020, Veronica Berglyd Olsen
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -25,11 +25,11 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import nw
|
||||||
import logging
|
import logging
|
||||||
import json
|
import json
|
||||||
import nw
|
import os
|
||||||
|
|
||||||
from os import path
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt
|
from PyQt5.QtCore import Qt
|
||||||
@@ -58,6 +58,7 @@ class GuiWritingStats(QDialog):
|
|||||||
QDialog.__init__(self, theParent)
|
QDialog.__init__(self, theParent)
|
||||||
|
|
||||||
logger.debug("Initialising GuiWritingStats ...")
|
logger.debug("Initialising GuiWritingStats ...")
|
||||||
|
self.setObjectName("GuiWritingStats")
|
||||||
|
|
||||||
self.mainConf = nw.CONFIG
|
self.mainConf = nw.CONFIG
|
||||||
self.theParent = theParent
|
self.theParent = theParent
|
||||||
@@ -321,20 +322,19 @@ class GuiWritingStats(QDialog):
|
|||||||
if fileExt:
|
if fileExt:
|
||||||
fileName = "sessionStats.%s" % fileExt
|
fileName = "sessionStats.%s" % fileExt
|
||||||
saveDir = self.mainConf.lastPath
|
saveDir = self.mainConf.lastPath
|
||||||
savePath = path.join(saveDir, fileName)
|
savePath = os.path.join(saveDir, fileName)
|
||||||
if not path.isdir(saveDir):
|
if not os.path.isdir(saveDir):
|
||||||
saveDir = self.mainConf.homePath
|
saveDir = self.mainConf.homePath
|
||||||
|
|
||||||
if self.mainConf.showGUI:
|
dlgOpt = QFileDialog.Options()
|
||||||
dlgOpt = QFileDialog.Options()
|
dlgOpt |= QFileDialog.DontUseNativeDialog
|
||||||
dlgOpt |= QFileDialog.DontUseNativeDialog
|
saveTo = QFileDialog.getSaveFileName(
|
||||||
saveTo = QFileDialog.getSaveFileName(
|
self, "Save Document As", savePath, options=dlgOpt
|
||||||
self, "Save Document As", savePath, options=dlgOpt
|
)
|
||||||
)
|
if saveTo:
|
||||||
if saveTo[0]:
|
savePath = saveTo[0]
|
||||||
savePath = saveTo[0]
|
else:
|
||||||
else:
|
return False
|
||||||
return False
|
|
||||||
|
|
||||||
self.mainConf.setLastPath(savePath)
|
self.mainConf.setLastPath(savePath)
|
||||||
|
|
||||||
@@ -379,19 +379,18 @@ class GuiWritingStats(QDialog):
|
|||||||
errMsg = str(e)
|
errMsg = str(e)
|
||||||
|
|
||||||
# Report to user
|
# Report to user
|
||||||
if self.mainConf.showGUI:
|
if wSuccess:
|
||||||
if wSuccess:
|
self.theParent.makeAlert(
|
||||||
self.theParent.makeAlert(
|
"%s file successfully written to:<br> %s" % (
|
||||||
"%s file successfully written to:<br> %s" % (
|
textFmt, savePath
|
||||||
textFmt, savePath
|
), nwAlert.INFO
|
||||||
), nwAlert.INFO
|
)
|
||||||
)
|
else:
|
||||||
else:
|
self.theParent.makeAlert(
|
||||||
self.theParent.makeAlert(
|
"Failed to write %s file. %s" % (
|
||||||
"Failed to write %s file. %s" % (
|
textFmt, errMsg
|
||||||
textFmt, errMsg
|
), nwAlert.ERROR
|
||||||
), nwAlert.ERROR
|
)
|
||||||
)
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -411,7 +410,7 @@ class GuiWritingStats(QDialog):
|
|||||||
ttTime = 0
|
ttTime = 0
|
||||||
|
|
||||||
try:
|
try:
|
||||||
logFile = path.join(self.theProject.projMeta, nwFiles.SESS_STATS)
|
logFile = os.path.join(self.theProject.projMeta, nwFiles.SESS_STATS)
|
||||||
with open(logFile, mode="r", encoding="utf8") as inFile:
|
with open(logFile, mode="r", encoding="utf8") as inFile:
|
||||||
for inLine in inFile:
|
for inLine in inFile:
|
||||||
if inLine.startswith("#"):
|
if inLine.startswith("#"):
|
||||||
|
|||||||
+134
-130
@@ -9,7 +9,7 @@
|
|||||||
Created: 2018-09-22 [0.0.1]
|
Created: 2018-09-22 [0.0.1]
|
||||||
|
|
||||||
This file is a part of novelWriter
|
This file is a part of novelWriter
|
||||||
Copyright 2020, Veronica Berglyd Olsen
|
Copyright 2018–2020, Veronica Berglyd Olsen
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -25,10 +25,10 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
|
||||||
import nw
|
import nw
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
from os import path
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from time import time
|
from time import time
|
||||||
|
|
||||||
@@ -40,11 +40,11 @@ from PyQt5.QtWidgets import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from nw.gui import (
|
from nw.gui import (
|
||||||
GuiBuildNovel, GuiDocEditor, GuiDocMerge, GuiDocSplit, GuiDocViewDetails,
|
GuiAbout, GuiBuildNovel, GuiDocEditor, GuiDocMerge, GuiDocSplit,
|
||||||
GuiDocViewer, GuiItemDetails, GuiItemEditor, GuiMainMenu, GuiMainStatus,
|
GuiDocViewDetails, GuiDocViewer, GuiItemDetails, GuiItemEditor,
|
||||||
GuiOutline, GuiOutlineDetails, GuiPreferences, GuiProjectLoad, GuiTheme,
|
GuiMainMenu, GuiMainStatus, GuiOutline, GuiOutlineDetails, GuiPreferences,
|
||||||
GuiProjectSettings, GuiProjectTree, GuiWritingStats, GuiProjectWizard,
|
GuiProjectLoad, GuiProjectSettings, GuiProjectTree, GuiProjectWizard,
|
||||||
GuiAbout
|
GuiTheme, GuiWritingStats
|
||||||
)
|
)
|
||||||
from nw.core import NWProject, NWDoc, NWIndex
|
from nw.core import NWProject, NWDoc, NWIndex
|
||||||
from nw.constants import nwItemType, nwItemClass, nwAlert
|
from nw.constants import nwItemType, nwItemClass, nwAlert
|
||||||
@@ -195,8 +195,8 @@ class GuiMain(QMainWindow):
|
|||||||
self.setStatus = self.statusBar.setStatus
|
self.setStatus = self.statusBar.setStatus
|
||||||
self.setProjectStatus = self.statusBar.setProjectStatus
|
self.setProjectStatus = self.statusBar.setProjectStatus
|
||||||
|
|
||||||
if self.mainConf.showGUI:
|
# Force a show of the GUI
|
||||||
self.show()
|
self.show()
|
||||||
|
|
||||||
# Check that config loaded fine
|
# Check that config loaded fine
|
||||||
self.reportConfErr()
|
self.reportConfErr()
|
||||||
@@ -218,7 +218,8 @@ class GuiMain(QMainWindow):
|
|||||||
logger.debug("Opening project from additional command line option")
|
logger.debug("Opening project from additional command line option")
|
||||||
self.openProject(self.mainConf.cmdOpen)
|
self.openProject(self.mainConf.cmdOpen)
|
||||||
else:
|
else:
|
||||||
self.manageProjects()
|
if self.mainConf.showGUI:
|
||||||
|
self.showProjectLoadDialog()
|
||||||
|
|
||||||
logger.debug("novelWriter is ready ...")
|
logger.debug("novelWriter is ready ...")
|
||||||
self.statusBar.setStatus("novelWriter is ready ...")
|
self.statusBar.setStatus("novelWriter is ready ...")
|
||||||
@@ -245,38 +246,19 @@ class GuiMain(QMainWindow):
|
|||||||
# Project Actions
|
# Project Actions
|
||||||
##
|
##
|
||||||
|
|
||||||
def manageProjects(self):
|
def newProject(self, projData=None):
|
||||||
"""Opens the projects dialog for selecting either existing
|
|
||||||
projects from a cache of recently opened projects, or provide a
|
|
||||||
browse button for projects not yet cached.
|
|
||||||
"""
|
|
||||||
if not self.mainConf.showGUI:
|
|
||||||
return False
|
|
||||||
|
|
||||||
dlgProj = GuiProjectLoad(self)
|
|
||||||
dlgProj.exec_()
|
|
||||||
if dlgProj.result() == QDialog.Accepted:
|
|
||||||
if dlgProj.openState == GuiProjectLoad.OPEN_STATE:
|
|
||||||
self.openProject(dlgProj.openPath)
|
|
||||||
elif dlgProj.openState == GuiProjectLoad.NEW_STATE:
|
|
||||||
self.newProject()
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def newProject(self, projData=None, forceNew=False):
|
|
||||||
"""Create new project with a few default files and folders.
|
"""Create new project with a few default files and folders.
|
||||||
The variable forceNew is used for testing.
|
The variable forceNew is used for testing.
|
||||||
"""
|
"""
|
||||||
if self.hasProject:
|
if self.hasProject:
|
||||||
msgBox = QMessageBox()
|
self.makeAlert(
|
||||||
msgBox.warning(
|
"Please close the current project before making a new one.",
|
||||||
self, "New Project",
|
nwAlert.ERROR
|
||||||
"Please close the current project before making a new one."
|
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if projData is None and self.mainConf.showGUI:
|
if projData is None:
|
||||||
projData = self.newProjectDialog()
|
projData = self.showNewProjectDialog()
|
||||||
|
|
||||||
if projData is None:
|
if projData is None:
|
||||||
return False
|
return False
|
||||||
@@ -286,11 +268,10 @@ class GuiMain(QMainWindow):
|
|||||||
logger.error("No projData or projPath set")
|
logger.error("No projData or projPath set")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if path.isfile(path.join(projPath, self.theProject.projFile)) and not forceNew:
|
if os.path.isfile(os.path.join(projPath, self.theProject.projFile)):
|
||||||
msgBox = QMessageBox()
|
self.makeAlert(
|
||||||
msgBox.critical(
|
"A project already exists in that location. Please choose another folder.",
|
||||||
self, "New Project",
|
nwAlert.ERROR
|
||||||
"A project already exists in that location. Please choose another folder."
|
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -316,7 +297,7 @@ class GuiMain(QMainWindow):
|
|||||||
# There is no project loaded, everything OK
|
# There is no project loaded, everything OK
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if self.mainConf.showGUI and not isYes:
|
if not isYes:
|
||||||
msgBox = QMessageBox()
|
msgBox = QMessageBox()
|
||||||
msgRes = msgBox.question(
|
msgRes = msgBox.question(
|
||||||
self, "Close Project", "Save changes and close current project?"
|
self, "Close Project", "Save changes and close current project?"
|
||||||
@@ -332,7 +313,7 @@ class GuiMain(QMainWindow):
|
|||||||
doBackup = False
|
doBackup = False
|
||||||
if self.theProject.doBackup and self.mainConf.backupOnClose:
|
if self.theProject.doBackup and self.mainConf.backupOnClose:
|
||||||
doBackup = True
|
doBackup = True
|
||||||
if self.mainConf.showGUI and self.mainConf.askBeforeBackup:
|
if self.mainConf.askBeforeBackup:
|
||||||
msgBox = QMessageBox()
|
msgBox = QMessageBox()
|
||||||
msgRes = msgBox.question(
|
msgRes = msgBox.question(
|
||||||
self, "Backup Project", "Backup current project?"
|
self, "Backup Project", "Backup current project?"
|
||||||
@@ -379,39 +360,38 @@ class GuiMain(QMainWindow):
|
|||||||
# reason handled by the project class.
|
# reason handled by the project class.
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if self.mainConf.showGUI:
|
try:
|
||||||
try:
|
lockDetails = (
|
||||||
lockDetails = (
|
"<br><br>The project was locked by the computer "
|
||||||
"<br><br>The project was locked by the computer "
|
"'%s' (%s %s), last active on %s"
|
||||||
"'%s' (%s %s), last active on %s"
|
) % (
|
||||||
) % (
|
self.theProject.lockedBy[0],
|
||||||
self.theProject.lockedBy[0],
|
self.theProject.lockedBy[1],
|
||||||
self.theProject.lockedBy[1],
|
self.theProject.lockedBy[2],
|
||||||
self.theProject.lockedBy[2],
|
datetime.fromtimestamp(
|
||||||
datetime.fromtimestamp(
|
int(self.theProject.lockedBy[3])
|
||||||
int(self.theProject.lockedBy[3])
|
).strftime("%x %X")
|
||||||
).strftime("%x %X")
|
|
||||||
)
|
|
||||||
except Exception:
|
|
||||||
lockDetails = ""
|
|
||||||
|
|
||||||
msgBox = QMessageBox()
|
|
||||||
msgRes = msgBox.warning(
|
|
||||||
self, "Project Locked", (
|
|
||||||
"The project is already open by another instance of novelWriter, and "
|
|
||||||
"is therefore locked. Override lock and continue anyway?<br><br>"
|
|
||||||
"Note: If the program or the computer previously crashed, the lock "
|
|
||||||
"can safely be overridden. If, however, another instance of "
|
|
||||||
"novelWriter has the project open, overriding the lock may corrupt "
|
|
||||||
"the project, and is not recommended.%s"
|
|
||||||
) % lockDetails,
|
|
||||||
QMessageBox.Yes | QMessageBox.No, QMessageBox.No
|
|
||||||
)
|
)
|
||||||
if msgRes == QMessageBox.Yes:
|
except Exception:
|
||||||
if not self.theProject.openProject(projFile, overrideLock=True):
|
lockDetails = ""
|
||||||
return False
|
|
||||||
else:
|
msgBox = QMessageBox()
|
||||||
|
msgRes = msgBox.warning(
|
||||||
|
self, "Project Locked", (
|
||||||
|
"The project is already open by another instance of novelWriter, and "
|
||||||
|
"is therefore locked. Override lock and continue anyway?<br><br>"
|
||||||
|
"Note: If the program or the computer previously crashed, the lock "
|
||||||
|
"can safely be overridden. If, however, another instance of "
|
||||||
|
"novelWriter has the project open, overriding the lock may corrupt "
|
||||||
|
"the project, and is not recommended.%s"
|
||||||
|
) % lockDetails,
|
||||||
|
QMessageBox.Yes | QMessageBox.No, QMessageBox.No
|
||||||
|
)
|
||||||
|
if msgRes == QMessageBox.Yes:
|
||||||
|
if not self.theProject.openProject(projFile, overrideLock=True):
|
||||||
return False
|
return False
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
# Project is loaded
|
# Project is loaded
|
||||||
self.hasProject = True
|
self.hasProject = True
|
||||||
@@ -449,7 +429,7 @@ class GuiMain(QMainWindow):
|
|||||||
|
|
||||||
# If the project is new, it may not have a path, so we need one
|
# If the project is new, it may not have a path, so we need one
|
||||||
if self.theProject.projPath is None:
|
if self.theProject.projPath is None:
|
||||||
projPath = self.saveProjectDialog()
|
projPath = self.selectProjectPath()
|
||||||
self.theProject.setProjectPath(projPath)
|
self.theProject.setProjectPath(projPath)
|
||||||
if self.theProject.projPath is None:
|
if self.theProject.projPath is None:
|
||||||
return False
|
return False
|
||||||
@@ -611,15 +591,12 @@ class GuiMain(QMainWindow):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
if not self.docEditor.isEmpty():
|
if not self.docEditor.isEmpty():
|
||||||
if self.mainConf.showGUI:
|
msgBox = QMessageBox()
|
||||||
msgBox = QMessageBox()
|
msgRes = msgBox.question(self, "Import Document", (
|
||||||
msgRes = msgBox.question(self, "Import Document", (
|
"Importing the file will overwrite the current content of the document. "
|
||||||
"Importing the file will overwrite the current content of the document. "
|
"Do you want to proceed?"
|
||||||
"Do you want to proceed?"
|
))
|
||||||
))
|
if msgRes != QMessageBox.Yes:
|
||||||
if msgRes != QMessageBox.Yes:
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self.docEditor.replaceText(theText)
|
self.docEditor.replaceText(theText)
|
||||||
@@ -629,18 +606,16 @@ class GuiMain(QMainWindow):
|
|||||||
def mergeDocuments(self):
|
def mergeDocuments(self):
|
||||||
"""Merge multiple documents to one single new document.
|
"""Merge multiple documents to one single new document.
|
||||||
"""
|
"""
|
||||||
if self.mainConf.showGUI:
|
dlgMerge = GuiDocMerge(self, self.theProject)
|
||||||
dlgMerge = GuiDocMerge(self, self.theProject)
|
dlgMerge.exec_()
|
||||||
dlgMerge.exec_()
|
return
|
||||||
return True
|
|
||||||
|
|
||||||
def splitDocument(self):
|
def splitDocument(self):
|
||||||
"""Split a single document into multiple documents.
|
"""Split a single document into multiple documents.
|
||||||
"""
|
"""
|
||||||
if self.mainConf.showGUI:
|
dlgSplit = GuiDocSplit(self, self.theProject)
|
||||||
dlgSplit = GuiDocSplit(self, self.theProject)
|
dlgSplit.exec_()
|
||||||
dlgSplit.exec_()
|
return
|
||||||
return True
|
|
||||||
|
|
||||||
def passDocumentAction(self, theAction):
|
def passDocumentAction(self, theAction):
|
||||||
"""Pass on document action theAction to the document viewer if
|
"""Pass on document action theAction to the document viewer if
|
||||||
@@ -684,13 +659,13 @@ class GuiMain(QMainWindow):
|
|||||||
return
|
return
|
||||||
|
|
||||||
logger.verbose("Requesting change to item %s" % tHandle)
|
logger.verbose("Requesting change to item %s" % tHandle)
|
||||||
if self.mainConf.showGUI:
|
dlgProj = GuiItemEditor(self, self.theProject, tHandle)
|
||||||
dlgProj = GuiItemEditor(self, self.theProject, tHandle)
|
dlgProj.exec_()
|
||||||
if dlgProj.exec_():
|
if dlgProj.result() == QDialog.Accepted:
|
||||||
self.treeView.setTreeItemValues(tHandle)
|
self.treeView.setTreeItemValues(tHandle)
|
||||||
self.treeMeta.updateViewBox(tHandle)
|
self.treeMeta.updateViewBox(tHandle)
|
||||||
self.docEditor.updateDocInfo(tHandle)
|
self.docEditor.updateDocInfo(tHandle)
|
||||||
self.docViewer.updateDocInfo(tHandle)
|
self.docViewer.updateDocInfo(tHandle)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -745,7 +720,7 @@ class GuiMain(QMainWindow):
|
|||||||
|
|
||||||
qApp.restoreOverrideCursor()
|
qApp.restoreOverrideCursor()
|
||||||
|
|
||||||
if self.mainConf.showGUI and not beQuiet:
|
if not beQuiet:
|
||||||
self.makeAlert("The project index has been successfully rebuilt.", nwAlert.INFO)
|
self.makeAlert("The project index has been successfully rebuilt.", nwAlert.INFO)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
@@ -762,7 +737,7 @@ class GuiMain(QMainWindow):
|
|||||||
# Main Dialogs
|
# Main Dialogs
|
||||||
##
|
##
|
||||||
|
|
||||||
def saveProjectDialog(self):
|
def selectProjectPath(self):
|
||||||
"""Select where to save project.
|
"""Select where to save project.
|
||||||
"""
|
"""
|
||||||
dlgOpt = QFileDialog.Options()
|
dlgOpt = QFileDialog.Options()
|
||||||
@@ -775,7 +750,22 @@ class GuiMain(QMainWindow):
|
|||||||
return projPath
|
return projPath
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def newProjectDialog(self):
|
def showProjectLoadDialog(self):
|
||||||
|
"""Opens the projects dialog for selecting either existing
|
||||||
|
projects from a cache of recently opened projects, or provide a
|
||||||
|
browse button for projects not yet cached.
|
||||||
|
"""
|
||||||
|
dlgProj = GuiProjectLoad(self)
|
||||||
|
dlgProj.exec_()
|
||||||
|
if dlgProj.result() == QDialog.Accepted:
|
||||||
|
if dlgProj.openState == GuiProjectLoad.OPEN_STATE:
|
||||||
|
self.openProject(dlgProj.openPath)
|
||||||
|
elif dlgProj.openState == GuiProjectLoad.NEW_STATE:
|
||||||
|
self.newProject()
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def showNewProjectDialog(self):
|
||||||
"""Open the wizard and assemble the project options dict.
|
"""Open the wizard and assemble the project options dict.
|
||||||
"""
|
"""
|
||||||
newProj = GuiProjectWizard(self)
|
newProj = GuiProjectWizard(self)
|
||||||
@@ -786,62 +776,76 @@ class GuiMain(QMainWindow):
|
|||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def editConfigDialog(self):
|
def showPreferencesDialog(self):
|
||||||
"""Open the preferences dialog.
|
"""Open the preferences dialog.
|
||||||
"""
|
"""
|
||||||
dlgConf = GuiPreferences(self, self.theProject)
|
dlgConf = GuiPreferences(self, self.theProject)
|
||||||
if dlgConf.exec_() == QDialog.Accepted:
|
dlgConf.exec_()
|
||||||
|
|
||||||
|
if dlgConf.result() == QDialog.Accepted:
|
||||||
logger.debug("Applying new preferences")
|
logger.debug("Applying new preferences")
|
||||||
self.initMain()
|
self.initMain()
|
||||||
self.theTheme.updateTheme()
|
self.theTheme.updateTheme()
|
||||||
self.saveDocument()
|
self.saveDocument()
|
||||||
self.docEditor.initEditor()
|
self.docEditor.initEditor()
|
||||||
self.docViewer.initViewer()
|
self.docViewer.initViewer()
|
||||||
return True
|
|
||||||
|
|
||||||
def editProjectDialog(self):
|
return
|
||||||
|
|
||||||
|
def showProjectSettingsDialog(self):
|
||||||
"""Open the project settings dialog.
|
"""Open the project settings dialog.
|
||||||
"""
|
"""
|
||||||
if self.hasProject:
|
if not self.hasProject:
|
||||||
dlgProj = GuiProjectSettings(self, self.theProject)
|
logger.error("No project open")
|
||||||
dlgProj.exec_()
|
return
|
||||||
|
|
||||||
|
dlgProj = GuiProjectSettings(self, self.theProject)
|
||||||
|
dlgProj.exec_()
|
||||||
|
|
||||||
|
if dlgProj.result() == QDialog.Accepted:
|
||||||
|
logger.debug("Applying new project settings")
|
||||||
self.docEditor.setDictionaries()
|
self.docEditor.setDictionaries()
|
||||||
self._setWindowTitle(self.theProject.projName)
|
self._setWindowTitle(self.theProject.projName)
|
||||||
return True
|
|
||||||
|
|
||||||
def buildProjectDialog(self):
|
return
|
||||||
|
|
||||||
|
def showBuildProjectDialog(self):
|
||||||
"""Open the build project dialog.
|
"""Open the build project dialog.
|
||||||
"""
|
"""
|
||||||
if self.hasProject:
|
if not self.hasProject:
|
||||||
dlgBuild = GuiBuildNovel(self, self.theProject)
|
logger.error("No project open")
|
||||||
dlgBuild.setModal(False)
|
return
|
||||||
dlgBuild.show()
|
|
||||||
return True
|
dlgBuild = GuiBuildNovel(self, self.theProject)
|
||||||
|
dlgBuild.setModal(False)
|
||||||
|
dlgBuild.show()
|
||||||
|
return
|
||||||
|
|
||||||
def showWritingStatsDialog(self):
|
def showWritingStatsDialog(self):
|
||||||
"""Open the session log dialog.
|
"""Open the session log dialog.
|
||||||
"""
|
"""
|
||||||
if self.hasProject:
|
if not self.hasProject:
|
||||||
dlgStats = GuiWritingStats(self, self.theProject)
|
logger.error("No project open")
|
||||||
dlgStats.setModal(False)
|
return
|
||||||
dlgStats.show()
|
|
||||||
return True
|
dlgStats = GuiWritingStats(self, self.theProject)
|
||||||
|
dlgStats.setModal(False)
|
||||||
|
dlgStats.show()
|
||||||
|
return
|
||||||
|
|
||||||
def showAboutNWDialog(self):
|
def showAboutNWDialog(self):
|
||||||
"""Show the about dialog for novelWriter.
|
"""Show the about dialog for novelWriter.
|
||||||
"""
|
"""
|
||||||
if self.mainConf.showGUI:
|
dlgAbout = GuiAbout(self)
|
||||||
dlgAbout = GuiAbout(self)
|
dlgAbout.exec_()
|
||||||
dlgAbout.exec_()
|
return
|
||||||
return True
|
|
||||||
|
|
||||||
def showAboutQtDialog(self):
|
def showAboutQtDialog(self):
|
||||||
"""Show the about dialog for Qt.
|
"""Show the about dialog for Qt.
|
||||||
"""
|
"""
|
||||||
if self.mainConf.showGUI:
|
msgBox = QMessageBox()
|
||||||
msgBox = QMessageBox()
|
msgBox.aboutQt(self, "About Qt")
|
||||||
msgBox.aboutQt(self, "About Qt")
|
return
|
||||||
return True
|
|
||||||
|
|
||||||
def makeAlert(self, theMessage, theLevel=nwAlert.INFO):
|
def makeAlert(self, theMessage, theLevel=nwAlert.INFO):
|
||||||
"""Alert both the user and the logger at the same time. Message
|
"""Alert both the user and the logger at the same time. Message
|
||||||
@@ -901,7 +905,7 @@ class GuiMain(QMainWindow):
|
|||||||
def closeMain(self):
|
def closeMain(self):
|
||||||
"""Save everything, and close novelWriter.
|
"""Save everything, and close novelWriter.
|
||||||
"""
|
"""
|
||||||
if self.mainConf.showGUI and self.hasProject:
|
if self.hasProject:
|
||||||
msgBox = QMessageBox()
|
msgBox = QMessageBox()
|
||||||
msgRes = msgBox.question(
|
msgRes = msgBox.question(
|
||||||
self, "Exit", "Do you want to save changes and exit?"
|
self, "Exit", "Do you want to save changes and exit?"
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import shutil
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import setuptools
|
import setuptools
|
||||||
|
|
||||||
@@ -42,7 +41,7 @@ if buildDocs:
|
|||||||
|
|
||||||
buildFail = False
|
buildFail = False
|
||||||
try:
|
try:
|
||||||
subprocess.call(["make","-C", "docs", "qthelp"])
|
subprocess.call(["make", "-C", "docs", "qthelp"])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Failed with error:")
|
print("Failed with error:")
|
||||||
print(str(e))
|
print(str(e))
|
||||||
@@ -98,7 +97,7 @@ if buildSample:
|
|||||||
from zipfile import ZipFile
|
from zipfile import ZipFile
|
||||||
|
|
||||||
with ZipFile(dstSample, "w") as zipObj:
|
with ZipFile(dstSample, "w") as zipObj:
|
||||||
zipObj.write(os.path.join("sample", "nwProject.nwx"), "nwProject.nwx")
|
zipObj.write(os.path.join(srcSample, "nwProject.nwx"), "nwProject.nwx")
|
||||||
for docFile in os.listdir(os.path.join(srcSample, "content")):
|
for docFile in os.listdir(os.path.join(srcSample, "content")):
|
||||||
srcDoc = os.path.join(srcSample, "content", docFile)
|
srcDoc = os.path.join(srcSample, "content", docFile)
|
||||||
zipObj.write(srcDoc, "content/"+docFile)
|
zipObj.write(srcDoc, "content/"+docFile)
|
||||||
|
|||||||
+128
-46
@@ -5,116 +5,198 @@
|
|||||||
import sys
|
import sys
|
||||||
import pytest
|
import pytest
|
||||||
import shutil
|
import shutil
|
||||||
|
import os
|
||||||
|
|
||||||
from os import path, mkdir
|
|
||||||
from nwdummy import DummyMain
|
from nwdummy import DummyMain
|
||||||
|
|
||||||
sys.path.insert(1, path.abspath(path.join(path.dirname(__file__), path.pardir)))
|
from PyQt5.QtWidgets import QMessageBox
|
||||||
|
|
||||||
|
sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir)))
|
||||||
|
|
||||||
from nw.config import Config # noqa: E402
|
from nw.config import Config # noqa: E402
|
||||||
|
|
||||||
|
##
|
||||||
|
# Core Test Folders
|
||||||
|
##
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
def nwTemp():
|
def nwTemp():
|
||||||
testDir = path.dirname(__file__)
|
"""A temporary folder for the test session. This folder is
|
||||||
tempDir = path.join(testDir, "temp")
|
presistent after the test so that the status of generated files can
|
||||||
if path.isdir(tempDir):
|
be checked. The folder is instead cleared before a new test session.
|
||||||
|
"""
|
||||||
|
testDir = os.path.dirname(__file__)
|
||||||
|
tempDir = os.path.join(testDir, "temp")
|
||||||
|
if os.path.isdir(tempDir):
|
||||||
shutil.rmtree(tempDir)
|
shutil.rmtree(tempDir)
|
||||||
if not path.isdir(tempDir):
|
if not os.path.isdir(tempDir):
|
||||||
mkdir(tempDir)
|
os.mkdir(tempDir)
|
||||||
return tempDir
|
return tempDir
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
def nwRef():
|
def nwRef():
|
||||||
testDir = path.dirname(__file__)
|
"""The folder where all the reference files are stored for verifying
|
||||||
refDir = path.join(testDir, "reference")
|
the results of tests.
|
||||||
|
"""
|
||||||
|
testDir = os.path.dirname(__file__)
|
||||||
|
refDir = os.path.join(testDir, "reference")
|
||||||
return refDir
|
return refDir
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
##
|
||||||
def nwConf(nwRef, nwTemp):
|
# novelWriter Objects
|
||||||
theConf = Config()
|
##
|
||||||
theConf.initConfig(nwRef, nwTemp)
|
|
||||||
return theConf
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
def tmpConf(nwTemp):
|
def tmpConf(nwTemp):
|
||||||
|
"""Create a temporary novelWriter configuration object.
|
||||||
|
"""
|
||||||
theConf = Config()
|
theConf = Config()
|
||||||
theConf.initConfig(nwTemp, nwTemp)
|
theConf.initConfig(nwTemp, nwTemp)
|
||||||
theConf.setLastPath("")
|
theConf.setLastPath("")
|
||||||
return theConf
|
return theConf
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def nwConf(nwRef, nwTemp):
|
||||||
|
"""Temporary novelWriter configuration used for the dummy instance
|
||||||
|
of novelWriter's main GUI.
|
||||||
|
"""
|
||||||
|
theConf = Config()
|
||||||
|
theConf.initConfig(nwRef, nwTemp)
|
||||||
|
return theConf
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
def nwDummy(nwRef, nwTemp, nwConf):
|
def nwDummy(nwRef, nwTemp, nwConf):
|
||||||
|
"""Create a dummy instance of novelWriter's main GUI class.
|
||||||
|
"""
|
||||||
theDummy = DummyMain()
|
theDummy = DummyMain()
|
||||||
theDummy.mainConf = nwConf
|
theDummy.mainConf = nwConf
|
||||||
return theDummy
|
return theDummy
|
||||||
|
|
||||||
|
##
|
||||||
|
# Temporary Test Folders
|
||||||
|
##
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
def nwTempProj(nwTemp):
|
def nwTempProj(nwTemp):
|
||||||
projDir = path.join(nwTemp, "proj")
|
"""A temporary folder for project tests.
|
||||||
if not path.isdir(projDir):
|
"""
|
||||||
mkdir(projDir)
|
projDir = os.path.join(nwTemp, "proj")
|
||||||
|
if not os.path.isdir(projDir):
|
||||||
|
os.mkdir(projDir)
|
||||||
return projDir
|
return projDir
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
def nwTempGUI(nwTemp):
|
def nwTempGUI(nwTemp):
|
||||||
guiDir = path.join(nwTemp, "gui")
|
"""A temporary folder for GUI tests.
|
||||||
if not path.isdir(guiDir):
|
"""
|
||||||
mkdir(guiDir)
|
guiDir = os.path.join(nwTemp, "gui")
|
||||||
|
if not os.path.isdir(guiDir):
|
||||||
|
os.mkdir(guiDir)
|
||||||
return guiDir
|
return guiDir
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
def nwTempBuild(nwTemp):
|
def nwTempBuild(nwTemp):
|
||||||
buildDir = path.join(nwTemp, "build")
|
"""A temporary folder for build tests.
|
||||||
if not path.isdir(buildDir):
|
"""
|
||||||
mkdir(buildDir)
|
buildDir = os.path.join(nwTemp, "build")
|
||||||
|
if not os.path.isdir(buildDir):
|
||||||
|
os.mkdir(buildDir)
|
||||||
return buildDir
|
return buildDir
|
||||||
|
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
def nwFuncTemp(nwTemp):
|
def nwFuncTemp(nwTemp):
|
||||||
funcDir = path.join(nwTemp, "ftemp")
|
"""A temporary folder for a single test function.
|
||||||
if path.isdir(funcDir):
|
"""
|
||||||
|
funcDir = os.path.join(nwTemp, "ftemp")
|
||||||
|
if os.path.isdir(funcDir):
|
||||||
shutil.rmtree(funcDir)
|
shutil.rmtree(funcDir)
|
||||||
if not path.isdir(funcDir):
|
if not os.path.isdir(funcDir):
|
||||||
mkdir(funcDir)
|
os.mkdir(funcDir)
|
||||||
yield funcDir
|
yield funcDir
|
||||||
if path.isdir(funcDir):
|
if os.path.isdir(funcDir):
|
||||||
shutil.rmtree(funcDir)
|
shutil.rmtree(funcDir)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
##
|
||||||
|
# Temp Folders for Projects
|
||||||
|
##
|
||||||
|
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
def nwMinimal(nwTemp):
|
def nwMinimal(nwTemp):
|
||||||
testDir = path.dirname(__file__)
|
"""A minimal novelWriter example project.
|
||||||
minimalStore = path.join(testDir, "minimal")
|
"""
|
||||||
minimalDir = path.join(nwTemp, "minimal")
|
testDir = os.path.dirname(__file__)
|
||||||
if path.isdir(minimalDir):
|
minimalStore = os.path.join(testDir, "minimal")
|
||||||
|
minimalDir = os.path.join(nwTemp, "minimal")
|
||||||
|
if os.path.isdir(minimalDir):
|
||||||
shutil.rmtree(minimalDir)
|
shutil.rmtree(minimalDir)
|
||||||
shutil.copytree(minimalStore, minimalDir)
|
shutil.copytree(minimalStore, minimalDir)
|
||||||
cacheDir = path.join(minimalDir, "cache")
|
cacheDir = os.path.join(minimalDir, "cache")
|
||||||
if path.isdir(cacheDir):
|
if os.path.isdir(cacheDir):
|
||||||
shutil.rmtree(cacheDir)
|
shutil.rmtree(cacheDir)
|
||||||
metaDir = path.join(minimalDir, "meta")
|
metaDir = os.path.join(minimalDir, "meta")
|
||||||
if path.isdir(metaDir):
|
if os.path.isdir(metaDir):
|
||||||
shutil.rmtree(metaDir)
|
shutil.rmtree(metaDir)
|
||||||
yield minimalDir
|
yield minimalDir
|
||||||
if path.isdir(minimalDir):
|
if os.path.isdir(minimalDir):
|
||||||
shutil.rmtree(minimalDir)
|
shutil.rmtree(minimalDir)
|
||||||
return
|
return
|
||||||
|
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
def nwLipsum(nwTemp):
|
def nwLipsum(nwTemp):
|
||||||
testDir = path.dirname(__file__)
|
"""A medium sized novelWriter example project with a lot of Lorem
|
||||||
lipsumStore = path.join(testDir, "lipsum")
|
Ipsum dummy text.
|
||||||
lipsumDir = path.join(nwTemp, "lipsum")
|
"""
|
||||||
if path.isdir(lipsumDir):
|
testDir = os.path.dirname(__file__)
|
||||||
|
lipsumStore = os.path.join(testDir, "lipsum")
|
||||||
|
lipsumDir = os.path.join(nwTemp, "lipsum")
|
||||||
|
if os.path.isdir(lipsumDir):
|
||||||
shutil.rmtree(lipsumDir)
|
shutil.rmtree(lipsumDir)
|
||||||
shutil.copytree(lipsumStore, lipsumDir)
|
shutil.copytree(lipsumStore, lipsumDir)
|
||||||
cacheDir = path.join(lipsumDir, "cache")
|
cacheDir = os.path.join(lipsumDir, "cache")
|
||||||
if path.isdir(cacheDir):
|
if os.path.isdir(cacheDir):
|
||||||
shutil.rmtree(cacheDir)
|
shutil.rmtree(cacheDir)
|
||||||
metaDir = path.join(lipsumDir, "meta")
|
metaDir = os.path.join(lipsumDir, "meta")
|
||||||
if path.isdir(metaDir):
|
if os.path.isdir(metaDir):
|
||||||
shutil.rmtree(metaDir)
|
shutil.rmtree(metaDir)
|
||||||
yield lipsumDir
|
yield lipsumDir
|
||||||
if path.isdir(lipsumDir):
|
if os.path.isdir(lipsumDir):
|
||||||
shutil.rmtree(lipsumDir)
|
shutil.rmtree(lipsumDir)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@pytest.fixture(scope="function")
|
||||||
|
def nwOldProj(nwTemp):
|
||||||
|
"""A minimal movelWriter project using the old folder structure.
|
||||||
|
"""
|
||||||
|
testDir = os.path.dirname(__file__)
|
||||||
|
oldProjStore = os.path.join(testDir, "oldproj")
|
||||||
|
oldProjDir = os.path.join(nwTemp, "oldproj")
|
||||||
|
if os.path.isdir(oldProjDir):
|
||||||
|
shutil.rmtree(oldProjDir)
|
||||||
|
shutil.copytree(oldProjStore, oldProjDir)
|
||||||
|
yield oldProjDir
|
||||||
|
if os.path.isdir(oldProjDir):
|
||||||
|
shutil.rmtree(oldProjDir)
|
||||||
|
return
|
||||||
|
|
||||||
|
##
|
||||||
|
# Monkey Patch Dialogs
|
||||||
|
##
|
||||||
|
|
||||||
|
@pytest.fixture(scope="function")
|
||||||
|
def yesToAll(monkeypatch):
|
||||||
|
"""Make the message boxes/questions always say yes.
|
||||||
|
"""
|
||||||
|
monkeypatch.setattr(
|
||||||
|
QMessageBox, "question", lambda *args, **kwargs: QMessageBox.Yes
|
||||||
|
)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
QMessageBox, "information", lambda *args, **kwargs: QMessageBox.Yes
|
||||||
|
)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
QMessageBox, "warning", lambda *args, **kwargs: QMessageBox.Yes
|
||||||
|
)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
QMessageBox, "critical", lambda *args, **kwargs: QMessageBox.Yes
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
{}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
# Start Time End Time Novel Notes
|
|
||||||
2020-08-28 11:36:36 2020-08-28 11:36:48 10 0
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
{
|
|
||||||
"tagIndex": {},
|
|
||||||
"refIndex": {
|
|
||||||
"a35baf2e93843": {
|
|
||||||
"T000000": {
|
|
||||||
"tags": [],
|
|
||||||
"updated": 1598607396
|
|
||||||
},
|
|
||||||
"T000001": {
|
|
||||||
"tags": [],
|
|
||||||
"updated": 1598607396
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"f5ab3e30151e1": {
|
|
||||||
"T000000": {
|
|
||||||
"tags": [],
|
|
||||||
"updated": 1598607396
|
|
||||||
},
|
|
||||||
"T000001": {
|
|
||||||
"tags": [],
|
|
||||||
"updated": 1598607396
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"8c659a11cd429": {
|
|
||||||
"T000000": {
|
|
||||||
"tags": [],
|
|
||||||
"updated": 1598607396
|
|
||||||
},
|
|
||||||
"T000001": {
|
|
||||||
"tags": [],
|
|
||||||
"updated": 1598607396
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"novelIndex": {
|
|
||||||
"a35baf2e93843": {
|
|
||||||
"T000001": {
|
|
||||||
"level": "H1",
|
|
||||||
"title": "Minimal",
|
|
||||||
"layout": "TITLE",
|
|
||||||
"synopsis": "",
|
|
||||||
"cCount": 7,
|
|
||||||
"wCount": 1,
|
|
||||||
"pCount": 0,
|
|
||||||
"updated": 1598607396
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"f5ab3e30151e1": {
|
|
||||||
"T000001": {
|
|
||||||
"level": "H2",
|
|
||||||
"title": "New Chapter",
|
|
||||||
"layout": "CHAPTER",
|
|
||||||
"synopsis": "",
|
|
||||||
"cCount": 11,
|
|
||||||
"wCount": 2,
|
|
||||||
"pCount": 0,
|
|
||||||
"updated": 1598607396
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"8c659a11cd429": {
|
|
||||||
"T000001": {
|
|
||||||
"level": "H3",
|
|
||||||
"title": "New Scene",
|
|
||||||
"layout": "SCENE",
|
|
||||||
"synopsis": "",
|
|
||||||
"cCount": 9,
|
|
||||||
"wCount": 2,
|
|
||||||
"pCount": 0,
|
|
||||||
"updated": 1598607396
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"noteIndex": {},
|
|
||||||
"textCounts": {
|
|
||||||
"a35baf2e93843": [
|
|
||||||
28,
|
|
||||||
6,
|
|
||||||
1
|
|
||||||
],
|
|
||||||
"f5ab3e30151e1": [
|
|
||||||
11,
|
|
||||||
2,
|
|
||||||
0
|
|
||||||
],
|
|
||||||
"8c659a11cd429": [
|
|
||||||
9,
|
|
||||||
2,
|
|
||||||
0
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+9
-7
@@ -2,17 +2,15 @@
|
|||||||
"""novelWriter Test Tools
|
"""novelWriter Test Tools
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from os import path, mkdir
|
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
|
|
||||||
from PyQt5.QtWidgets import qApp
|
from PyQt5.QtWidgets import qApp
|
||||||
|
|
||||||
def ensureDir(theDir):
|
def cmpFiles(fileOne, fileTwo, ignoreLines=None):
|
||||||
if not path.isdir(theDir):
|
"""Compare two files, but optionally ignore lines given by a list.
|
||||||
mkdir(theDir)
|
"""
|
||||||
return
|
if ignoreLines is None:
|
||||||
|
ignoreLines = []
|
||||||
def cmpFiles(fileOne, fileTwo, ignoreLines=[]):
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
foOne = open(fileOne, mode="r", encoding="utf8")
|
foOne = open(fileOne, mode="r", encoding="utf8")
|
||||||
@@ -54,6 +52,8 @@ def cmpFiles(fileOne, fileTwo, ignoreLines=[]):
|
|||||||
return not diffFound
|
return not diffFound
|
||||||
|
|
||||||
def cmpList(listOne, listTwo):
|
def cmpList(listOne, listTwo):
|
||||||
|
"""Compare two iterable objects.
|
||||||
|
"""
|
||||||
flatOne = list(chain.from_iterable([listOne]))
|
flatOne = list(chain.from_iterable([listOne]))
|
||||||
flatTwo = list(chain.from_iterable([listTwo]))
|
flatTwo = list(chain.from_iterable([listTwo]))
|
||||||
if len(flatOne) != len(flatTwo):
|
if len(flatOne) != len(flatTwo):
|
||||||
@@ -64,6 +64,8 @@ def cmpList(listOne, listTwo):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def getGuiItem(theName):
|
def getGuiItem(theName):
|
||||||
|
"""Returns a QtWidget based on its objectName.
|
||||||
|
"""
|
||||||
for qWidget in qApp.topLevelWidgets():
|
for qWidget in qApp.topLevelWidgets():
|
||||||
if qWidget.objectName() == theName:
|
if qWidget.objectName() == theName:
|
||||||
return qWidget
|
return qWidget
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
### Scene Four
|
||||||
|
|
||||||
|
Scene Four
|
||||||
|
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# Antagonist
|
||||||
|
|
||||||
|
Antagonist
|
||||||
|
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
### Scene Two
|
||||||
|
|
||||||
|
Scene Two
|
||||||
|
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# Protagonist
|
||||||
|
|
||||||
|
Protagonist
|
||||||
|
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
### Scene Three
|
||||||
|
|
||||||
|
Scene Three
|
||||||
|
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
### Scene Five
|
||||||
|
|
||||||
|
Scene Five
|
||||||
|
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
### Scene One
|
||||||
|
|
||||||
|
Scene One
|
||||||
|
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
Start: 2020-09-26 16:13:00 End: 2020-09-26 16:15:54 Words: 24
|
||||||
|
Start: 2020-09-26 16:16:28 End: 2020-09-26 16:16:40 Words: -1
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
{
|
||||||
|
"tagIndex": {},
|
||||||
|
"refIndex": {
|
||||||
|
"f528d831f5b24": [],
|
||||||
|
"88124a4292d8b": [],
|
||||||
|
"91239bf2f8b69": [],
|
||||||
|
"19752e7f9d8af": [],
|
||||||
|
"a764d5acf5a21": [],
|
||||||
|
"9058ae29f0dfd": [],
|
||||||
|
"7ff63b8afc4cd": []
|
||||||
|
},
|
||||||
|
"novelIndex": {
|
||||||
|
"f528d831f5b24": [
|
||||||
|
[
|
||||||
|
1,
|
||||||
|
3,
|
||||||
|
"Scene One",
|
||||||
|
"SCENE"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"88124a4292d8b": [
|
||||||
|
[
|
||||||
|
1,
|
||||||
|
3,
|
||||||
|
"Scene Two",
|
||||||
|
"SCENE"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"91239bf2f8b69": [
|
||||||
|
[
|
||||||
|
1,
|
||||||
|
3,
|
||||||
|
"Scene Three",
|
||||||
|
"SCENE"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"19752e7f9d8af": [
|
||||||
|
[
|
||||||
|
1,
|
||||||
|
3,
|
||||||
|
"Scene Four",
|
||||||
|
"SCENE"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"a764d5acf5a21": [
|
||||||
|
[
|
||||||
|
1,
|
||||||
|
3,
|
||||||
|
"Scene Five",
|
||||||
|
"SCENE"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"noteIndex": {
|
||||||
|
"9058ae29f0dfd": [
|
||||||
|
[
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
"Protagonist",
|
||||||
|
"NOTE"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"7ff63b8afc4cd": [
|
||||||
|
[
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
"Antagonist",
|
||||||
|
"NOTE"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
|
<novelWriterXML appVersion="0.4.5" fileVersion="1.0" timeStamp="2020-09-26 16:16:39">
|
||||||
|
<project>
|
||||||
|
<name></name>
|
||||||
|
<title></title>
|
||||||
|
<backup>True</backup>
|
||||||
|
</project>
|
||||||
|
<settings>
|
||||||
|
<spellCheck>False</spellCheck>
|
||||||
|
<lastEdited>a764d5acf5a21</lastEdited>
|
||||||
|
<lastViewed>None</lastViewed>
|
||||||
|
<lastWordCount>23</lastWordCount>
|
||||||
|
<autoReplace/>
|
||||||
|
<status>
|
||||||
|
<entry blue="100" green="100" red="100">New</entry>
|
||||||
|
<entry blue="0" green="50" red="200">Note</entry>
|
||||||
|
<entry blue="0" green="150" red="200">Draft</entry>
|
||||||
|
<entry blue="0" green="200" red="50">Finished</entry>
|
||||||
|
</status>
|
||||||
|
<importance>
|
||||||
|
<entry blue="100" green="100" red="100">New</entry>
|
||||||
|
<entry blue="0" green="50" red="200">Minor</entry>
|
||||||
|
<entry blue="0" green="150" red="200">Major</entry>
|
||||||
|
<entry blue="0" green="200" red="50">Main</entry>
|
||||||
|
</importance>
|
||||||
|
</settings>
|
||||||
|
<content count="12">
|
||||||
|
<item handle="095c337ad50c8" order="0" parent="None">
|
||||||
|
<name>Novel</name>
|
||||||
|
<type>ROOT</type>
|
||||||
|
<class>NOVEL</class>
|
||||||
|
<status>New</status>
|
||||||
|
<expanded>True</expanded>
|
||||||
|
</item>
|
||||||
|
<item handle="04d47c4b31af7" order="0" parent="095c337ad50c8">
|
||||||
|
<name>Chapter One</name>
|
||||||
|
<type>FOLDER</type>
|
||||||
|
<class>NOVEL</class>
|
||||||
|
<status>New</status>
|
||||||
|
<expanded>True</expanded>
|
||||||
|
</item>
|
||||||
|
<item handle="f528d831f5b24" order="0" parent="04d47c4b31af7">
|
||||||
|
<name>Scene One</name>
|
||||||
|
<type>FILE</type>
|
||||||
|
<class>NOVEL</class>
|
||||||
|
<status>New</status>
|
||||||
|
<expanded>False</expanded>
|
||||||
|
<layout>SCENE</layout>
|
||||||
|
<charCount>18</charCount>
|
||||||
|
<wordCount>4</wordCount>
|
||||||
|
<paraCount>1</paraCount>
|
||||||
|
<cursorPos>3</cursorPos>
|
||||||
|
</item>
|
||||||
|
<item handle="88124a4292d8b" order="1" parent="04d47c4b31af7">
|
||||||
|
<name>Scene Two</name>
|
||||||
|
<type>FILE</type>
|
||||||
|
<class>NOVEL</class>
|
||||||
|
<status>New</status>
|
||||||
|
<expanded>False</expanded>
|
||||||
|
<layout>SCENE</layout>
|
||||||
|
<charCount>18</charCount>
|
||||||
|
<wordCount>4</wordCount>
|
||||||
|
<paraCount>1</paraCount>
|
||||||
|
<cursorPos>2</cursorPos>
|
||||||
|
</item>
|
||||||
|
<item handle="91239bf2f8b69" order="2" parent="04d47c4b31af7">
|
||||||
|
<name>Scene Three</name>
|
||||||
|
<type>FILE</type>
|
||||||
|
<class>NOVEL</class>
|
||||||
|
<status>New</status>
|
||||||
|
<expanded>False</expanded>
|
||||||
|
<layout>SCENE</layout>
|
||||||
|
<charCount>22</charCount>
|
||||||
|
<wordCount>4</wordCount>
|
||||||
|
<paraCount>1</paraCount>
|
||||||
|
<cursorPos>2</cursorPos>
|
||||||
|
</item>
|
||||||
|
<item handle="19752e7f9d8af" order="3" parent="04d47c4b31af7">
|
||||||
|
<name>Scene Four</name>
|
||||||
|
<type>FILE</type>
|
||||||
|
<class>NOVEL</class>
|
||||||
|
<status>New</status>
|
||||||
|
<expanded>False</expanded>
|
||||||
|
<layout>SCENE</layout>
|
||||||
|
<charCount>20</charCount>
|
||||||
|
<wordCount>4</wordCount>
|
||||||
|
<paraCount>1</paraCount>
|
||||||
|
<cursorPos>2</cursorPos>
|
||||||
|
</item>
|
||||||
|
<item handle="a764d5acf5a21" order="4" parent="04d47c4b31af7">
|
||||||
|
<name>Scene Five</name>
|
||||||
|
<type>FILE</type>
|
||||||
|
<class>NOVEL</class>
|
||||||
|
<status>New</status>
|
||||||
|
<expanded>False</expanded>
|
||||||
|
<layout>SCENE</layout>
|
||||||
|
<charCount>20</charCount>
|
||||||
|
<wordCount>4</wordCount>
|
||||||
|
<paraCount>1</paraCount>
|
||||||
|
<cursorPos>2</cursorPos>
|
||||||
|
</item>
|
||||||
|
<item handle="a11282c943444" order="1" parent="None">
|
||||||
|
<name>Characters</name>
|
||||||
|
<type>ROOT</type>
|
||||||
|
<class>CHARACTER</class>
|
||||||
|
<status>New</status>
|
||||||
|
<expanded>True</expanded>
|
||||||
|
</item>
|
||||||
|
<item handle="9058ae29f0dfd" order="0" parent="a11282c943444">
|
||||||
|
<name>Protagonist</name>
|
||||||
|
<type>FILE</type>
|
||||||
|
<class>CHARACTER</class>
|
||||||
|
<status>New</status>
|
||||||
|
<expanded>False</expanded>
|
||||||
|
<layout>NOTE</layout>
|
||||||
|
<charCount>11</charCount>
|
||||||
|
<wordCount>1</wordCount>
|
||||||
|
<paraCount>0</paraCount>
|
||||||
|
<cursorPos>28</cursorPos>
|
||||||
|
</item>
|
||||||
|
<item handle="7ff63b8afc4cd" order="1" parent="a11282c943444">
|
||||||
|
<name>Antagonist</name>
|
||||||
|
<type>FILE</type>
|
||||||
|
<class>CHARACTER</class>
|
||||||
|
<status>New</status>
|
||||||
|
<expanded>False</expanded>
|
||||||
|
<layout>NOTE</layout>
|
||||||
|
<charCount>13</charCount>
|
||||||
|
<wordCount>2</wordCount>
|
||||||
|
<paraCount>1</paraCount>
|
||||||
|
<cursorPos>26</cursorPos>
|
||||||
|
</item>
|
||||||
|
<item handle="dabb9d158b2b2" order="2" parent="None">
|
||||||
|
<name>Plot</name>
|
||||||
|
<type>ROOT</type>
|
||||||
|
<class>PLOT</class>
|
||||||
|
<status>New</status>
|
||||||
|
<expanded>False</expanded>
|
||||||
|
</item>
|
||||||
|
<item handle="4a817a3a84b42" order="3" parent="None">
|
||||||
|
<name>World</name>
|
||||||
|
<type>ROOT</type>
|
||||||
|
<class>WORLD</class>
|
||||||
|
<status>New</status>
|
||||||
|
<expanded>False</expanded>
|
||||||
|
</item>
|
||||||
|
</content>
|
||||||
|
</novelWriterXML>
|
||||||
@@ -2,15 +2,14 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
import pstats
|
import pstats
|
||||||
|
import os
|
||||||
|
|
||||||
from os import path
|
profDir = os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir, "prof"))
|
||||||
|
|
||||||
profDir = path.abspath(path.join(path.dirname(__file__), path.pardir, "prof"))
|
|
||||||
|
|
||||||
print("")
|
print("")
|
||||||
print("Profiles directory: %s" % profDir)
|
print("Profiles directory: %s" % profDir)
|
||||||
print("")
|
print("")
|
||||||
|
|
||||||
profMainWindows = pstats.Stats(path.join(profDir, "testMainWindows.prof"))
|
profMainWindows = pstats.Stats(os.path.join(profDir, "testMainWindows.prof"))
|
||||||
profMainWindows.sort_stats("cumtime")
|
profMainWindows.sort_stats("cumtime")
|
||||||
profMainWindows.print_stats("nw/")
|
profMainWindows.print_stats("nw/")
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
{
|
||||||
|
"meta": {
|
||||||
|
"workingTitle": "Lorem Ipsum",
|
||||||
|
"novelTitle": "Lorem Ipsum",
|
||||||
|
"authors": [
|
||||||
|
"lipsum.com"
|
||||||
|
],
|
||||||
|
"buildTime": 1601120680
|
||||||
|
},
|
||||||
|
"text": {
|
||||||
|
"css": [
|
||||||
|
"p {text-align: justify;}",
|
||||||
|
"h1, h2 {color: rgb(66, 113, 174);}",
|
||||||
|
"h3, h4 {color: rgb(50, 50, 50);}",
|
||||||
|
"h1, h2, h3, h4 {page-break-after: avoid;}",
|
||||||
|
"a {color: rgb(66, 113, 174);}",
|
||||||
|
".title {font-size: 2.5em;}",
|
||||||
|
".tags {color: rgb(245, 135, 31); font-weight: bold;}",
|
||||||
|
".break {text-align: left;}",
|
||||||
|
".sep {text-align: center; margin-top: 1em; margin-bottom: 1em;}",
|
||||||
|
".skip {margin-top: 1em; margin-bottom: 1em;}",
|
||||||
|
".synopsis {font-style: italic;}",
|
||||||
|
".comment {font-style: italic; color: rgb(100, 100, 100);}"
|
||||||
|
],
|
||||||
|
"html": [
|
||||||
|
[
|
||||||
|
"<h1 class='title' style='text-align: center; page-break-before: never;'>Lorem Ipsum</h1>"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
""
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"<h1 style='page-break-before: always;'>Prologue</h1>",
|
||||||
|
"<p class='synopsis'><strong>Synopsis: </strong>Explanation from the lipsum.com website.</p>"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"<h1 style='text-align: center; page-break-before: always;'>Act One</h1>"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"<h1 style='page-break-before: always;'>Chapter One: Chapter One</h1>",
|
||||||
|
"<div><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque at aliquam quam.</p>"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"<h2>Scene 1: Scene One</h2>",
|
||||||
|
"<div><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Aenean ut placerat velit. Etiam laoreet ullamcorper risus, eget lobortis enim scelerisque non. Suspendisse id maximus nunc, et mollis sapien. Curabitur vel semper sapien, non pulvinar dolor. Etiam finibus nisi vel mi molestie consectetur.</p>",
|
||||||
|
"<h3>Section: Scene One, Section Two</h3>"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"<h2>Scene 2: Scene Two</h2>",
|
||||||
|
"<div><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer sapien nulla, dictum at lacus a, dignissim consectetur dolor. Nunc vel eleifend lacus, eu dapibus orci.</p>",
|
||||||
|
"<h3>Section: Scene Two, Section Two</h3>"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"<h1 style='page-break-before: always;'>Chapter Two: Chapter Two</h1>",
|
||||||
|
"<div><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Curabitur a elit posuere, varius ex et, convallis neque. Phasellus sagittis pharetra sem vitae dapibus. Curabitur varius lorem non pulvinar congue.</p>"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"<h2>Scene 3: Scene Three</h2>",
|
||||||
|
"<div><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Aenean ut libero ut lectus porttitor rhoncus vel et massa. Nam pretium, nibh et varius vehicula, urna metus blandit eros, euismod pharetra diam diam et libero. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.</p>"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"<h2>Scene 4: Scene Four</h2>",
|
||||||
|
"<div><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Nam tempor blandit magna laoreet aliquet. Vestibulum auctor posuere leo, ac gravida nisi rhoncus varius. Aenean posuere dolor vitae condimentum volutpat. Donec egestas volutpat risus, quis luctus justo.</p>"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"<h2>Scene 5: Scene Five</h2>",
|
||||||
|
"<div><span class='tags'>Point of View:</span> <a href='#tag_Bod'>Bod</a></div><div><span class='tags'>Plot:</span> <a href='#tag_Main'>Main</a></div><div><span class='tags'>Locations:</span> <a href='#tag_Europe'>Europe</a></div><p class='synopsis'><strong>Synopsis: </strong>Praesent eget est porta, dictum ante in, egestas risus. Mauris risus mauris, consequat aliquam mauris et, feugiat iaculis ipsum. Aliquam arcu ipsum, fermentum ut arcu sed, lobortis euismod sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.</p>"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
{
|
||||||
|
"meta": {
|
||||||
|
"workingTitle": "Lorem Ipsum",
|
||||||
|
"novelTitle": "Lorem Ipsum",
|
||||||
|
"authors": [
|
||||||
|
"lipsum.com"
|
||||||
|
],
|
||||||
|
"buildTime": 1601120788
|
||||||
|
},
|
||||||
|
"text": {
|
||||||
|
"nwd": [
|
||||||
|
[
|
||||||
|
"# Lorem Ipsum",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"## Prologue",
|
||||||
|
"",
|
||||||
|
"% Synopsis:Explanation from the lipsum.com website.",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"# Act One",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"## Chapter One",
|
||||||
|
"",
|
||||||
|
"@pov: Bod",
|
||||||
|
"@plot: Main",
|
||||||
|
"@location: Europe",
|
||||||
|
"",
|
||||||
|
"% Synopsis: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque at aliquam quam.",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"### Scene One",
|
||||||
|
"",
|
||||||
|
"@pov: Bod",
|
||||||
|
"@plot: Main",
|
||||||
|
"@location: Europe",
|
||||||
|
"",
|
||||||
|
"% Synopsis: Aenean ut placerat velit. Etiam laoreet ullamcorper risus, eget lobortis enim scelerisque non. Suspendisse id maximus nunc, et mollis sapien. Curabitur vel semper sapien, non pulvinar dolor. Etiam finibus nisi vel mi molestie consectetur.",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"#### Scene One, Section Two",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"### Scene Two",
|
||||||
|
"",
|
||||||
|
"@pov: Bod",
|
||||||
|
"@plot: Main",
|
||||||
|
"@location: Europe",
|
||||||
|
"",
|
||||||
|
"% Synopsis: Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer sapien nulla, dictum at lacus a, dignissim consectetur dolor. Nunc vel eleifend lacus, eu dapibus orci. ",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"#### Scene Two, Section Two",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"## Chapter Two",
|
||||||
|
"",
|
||||||
|
"@pov: Bod",
|
||||||
|
"@plot: Main",
|
||||||
|
"@location: Europe",
|
||||||
|
"",
|
||||||
|
"% Synopsis: Curabitur a elit posuere, varius ex et, convallis neque. Phasellus sagittis pharetra sem vitae dapibus. Curabitur varius lorem non pulvinar congue.",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"### Scene Three",
|
||||||
|
"",
|
||||||
|
"@pov: Bod",
|
||||||
|
"@plot: Main",
|
||||||
|
"@location: Europe",
|
||||||
|
"",
|
||||||
|
"% Synopsis: Aenean ut libero ut lectus porttitor rhoncus vel et massa. Nam pretium, nibh et varius vehicula, urna metus blandit eros, euismod pharetra diam diam et libero. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"### Scene Four",
|
||||||
|
"",
|
||||||
|
"@pov: Bod",
|
||||||
|
"@plot: Main",
|
||||||
|
"@location: Europe",
|
||||||
|
"",
|
||||||
|
"% Synopsis: Nam tempor blandit magna laoreet aliquet. Vestibulum auctor posuere leo, ac gravida nisi rhoncus varius. Aenean posuere dolor vitae condimentum volutpat. Donec egestas volutpat risus, quis luctus justo.",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"### Scene Five",
|
||||||
|
"",
|
||||||
|
"@pov: Bod",
|
||||||
|
"@plot: Main",
|
||||||
|
"@location: Europe",
|
||||||
|
"",
|
||||||
|
"% Synopsis: Praesent eget est porta, dictum ante in, egestas risus. Mauris risus mauris, consequat aliquam mauris et, feugiat iaculis ipsum. Aliquam arcu ipsum, fermentum ut arcu sed, lobortis euismod sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
""
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,5 +19,9 @@
|
|||||||
|
|
||||||
This is a paragraph of dummy text.
|
This is a paragraph of dummy text.
|
||||||
|
|
||||||
This is another paragraph of much longer dummy text. It is in fact very very dumb dummy text! We can also try replacing “quotes”, even single’s quotes are replaced. We can hyphen-ate, make dashes – and even longer dashes — if we want. Ellipsis? Not a problem either …
|
This is another paragraph of much longer dummy text. It is in fact very very dumb dummy text! We can also try replacing “quotes”, even single ‘quotes’ are replaced. Isn’t that nice? We can hyphen-ate, make dashes – and even longer dashes — if we want. Ellipsis? Not a problem either … How about three hyphens — for long dash? It works too.
|
||||||
|
|
||||||
|
“Full line double quoted text.”
|
||||||
|
|
||||||
|
‘Full line single quoted text.’
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
<novelWriterXML appVersion="1.0b1" hexVersion="0x010000b1" fileVersion="1.2" timeStamp="2020-09-05 17:34:07">
|
<novelWriterXML appVersion="1.0b3" hexVersion="0x010000b3" fileVersion="1.2" timeStamp="2020-09-21 19:58:53">
|
||||||
<project>
|
<project>
|
||||||
<name>New Project</name>
|
<name>New Project</name>
|
||||||
<title></title>
|
<title></title>
|
||||||
<saveCount>4</saveCount>
|
<saveCount>4</saveCount>
|
||||||
<autoCount>1</autoCount>
|
<autoCount>1</autoCount>
|
||||||
<editTime>8</editTime>
|
<editTime>11</editTime>
|
||||||
</project>
|
</project>
|
||||||
<settings>
|
<settings>
|
||||||
<doBackup>True</doBackup>
|
<doBackup>True</doBackup>
|
||||||
@@ -14,8 +14,8 @@
|
|||||||
<autoOutline>True</autoOutline>
|
<autoOutline>True</autoOutline>
|
||||||
<lastEdited>0e17daca5f3e1</lastEdited>
|
<lastEdited>0e17daca5f3e1</lastEdited>
|
||||||
<lastViewed>None</lastViewed>
|
<lastViewed>None</lastViewed>
|
||||||
<lastWordCount>90</lastWordCount>
|
<lastWordCount>113</lastWordCount>
|
||||||
<novelWordCount>63</novelWordCount>
|
<novelWordCount>86</novelWordCount>
|
||||||
<notesWordCount>27</notesWordCount>
|
<notesWordCount>27</notesWordCount>
|
||||||
<autoReplace/>
|
<autoReplace/>
|
||||||
<titleFormat>
|
<titleFormat>
|
||||||
@@ -84,10 +84,10 @@
|
|||||||
<status>New</status>
|
<status>New</status>
|
||||||
<exported>True</exported>
|
<exported>True</exported>
|
||||||
<layout>SCENE</layout>
|
<layout>SCENE</layout>
|
||||||
<charCount>331</charCount>
|
<charCount>464</charCount>
|
||||||
<wordCount>59</wordCount>
|
<wordCount>82</wordCount>
|
||||||
<paraCount>2</paraCount>
|
<paraCount>4</paraCount>
|
||||||
<cursorPos>465</cursorPos>
|
<cursorPos>602</cursorPos>
|
||||||
</item>
|
</item>
|
||||||
<item handle="44cb730c42048" order="1" parent="None">
|
<item handle="44cb730c42048" order="1" parent="None">
|
||||||
<name>Plot</name>
|
<name>Plot</name>
|
||||||
|
|||||||
@@ -44,7 +44,7 @@
|
|||||||
<type>ROOT</type>
|
<type>ROOT</type>
|
||||||
<class>NOVEL</class>
|
<class>NOVEL</class>
|
||||||
<status>New</status>
|
<status>New</status>
|
||||||
<expanded>False</expanded>
|
<expanded>True</expanded>
|
||||||
</item>
|
</item>
|
||||||
<item handle="25fc0e7096fc6" order="0" parent="73475cb40a568">
|
<item handle="25fc0e7096fc6" order="0" parent="73475cb40a568">
|
||||||
<name>Title Page</name>
|
<name>Title Page</name>
|
||||||
@@ -63,7 +63,7 @@
|
|||||||
<type>FOLDER</type>
|
<type>FOLDER</type>
|
||||||
<class>NOVEL</class>
|
<class>NOVEL</class>
|
||||||
<status>New</status>
|
<status>New</status>
|
||||||
<expanded>False</expanded>
|
<expanded>True</expanded>
|
||||||
</item>
|
</item>
|
||||||
<item handle="98010bd9270f9" order="0" parent="31489056e0916">
|
<item handle="98010bd9270f9" order="0" parent="31489056e0916">
|
||||||
<name>New Chapter</name>
|
<name>New Chapter</name>
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ highlightquotes = False
|
|||||||
highlightemph = False
|
highlightemph = False
|
||||||
|
|
||||||
[Backup]
|
[Backup]
|
||||||
backuppath =
|
backuppath = some/dir
|
||||||
backuponclose = True
|
backuponclose = True
|
||||||
askbeforebackup = True
|
askbeforebackup = True
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,560 @@
|
|||||||
|
{
|
||||||
|
"tagIndex": {
|
||||||
|
"Bod": [
|
||||||
|
3,
|
||||||
|
"4c4f28287af27",
|
||||||
|
"CHARACTER",
|
||||||
|
"T000001"
|
||||||
|
],
|
||||||
|
"Main": [
|
||||||
|
3,
|
||||||
|
"2426c6f0ca922",
|
||||||
|
"PLOT",
|
||||||
|
"T000001"
|
||||||
|
],
|
||||||
|
"Europe": [
|
||||||
|
3,
|
||||||
|
"04468803b92e1",
|
||||||
|
"WORLD",
|
||||||
|
"T000001"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"refIndex": {
|
||||||
|
"7a992350f3eb6": {
|
||||||
|
"T000000": {
|
||||||
|
"tags": [],
|
||||||
|
"updated": 123
|
||||||
|
},
|
||||||
|
"T000001": {
|
||||||
|
"tags": [],
|
||||||
|
"updated": 123
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"8c58a65414c23": {
|
||||||
|
"T000000": {
|
||||||
|
"tags": [],
|
||||||
|
"updated": 123
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"88d59a277361b": {
|
||||||
|
"T000000": {
|
||||||
|
"tags": [],
|
||||||
|
"updated": 123
|
||||||
|
},
|
||||||
|
"T000001": {
|
||||||
|
"tags": [],
|
||||||
|
"updated": 123
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"db7e733775d4d": {
|
||||||
|
"T000000": {
|
||||||
|
"tags": [],
|
||||||
|
"updated": 123
|
||||||
|
},
|
||||||
|
"T000001": {
|
||||||
|
"tags": [],
|
||||||
|
"updated": 123
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fb609cd8319dc": {
|
||||||
|
"T000000": {
|
||||||
|
"tags": [],
|
||||||
|
"updated": 123
|
||||||
|
},
|
||||||
|
"T000001": {
|
||||||
|
"tags": [
|
||||||
|
[
|
||||||
|
3,
|
||||||
|
"@pov",
|
||||||
|
"Bod"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
4,
|
||||||
|
"@plot",
|
||||||
|
"Main"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
5,
|
||||||
|
"@location",
|
||||||
|
"Europe"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"updated": 123
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"88243afbe5ed8": {
|
||||||
|
"T000000": {
|
||||||
|
"tags": [],
|
||||||
|
"updated": 123
|
||||||
|
},
|
||||||
|
"T000001": {
|
||||||
|
"tags": [
|
||||||
|
[
|
||||||
|
3,
|
||||||
|
"@pov",
|
||||||
|
"Bod"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
4,
|
||||||
|
"@plot",
|
||||||
|
"Main"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
5,
|
||||||
|
"@location",
|
||||||
|
"Europe"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"updated": 123
|
||||||
|
},
|
||||||
|
"T000013": {
|
||||||
|
"tags": [],
|
||||||
|
"updated": 123
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"f96ec11c6a3da": {
|
||||||
|
"T000000": {
|
||||||
|
"tags": [],
|
||||||
|
"updated": 123
|
||||||
|
},
|
||||||
|
"T000001": {
|
||||||
|
"tags": [
|
||||||
|
[
|
||||||
|
3,
|
||||||
|
"@pov",
|
||||||
|
"Bod"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
4,
|
||||||
|
"@plot",
|
||||||
|
"Main"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
5,
|
||||||
|
"@location",
|
||||||
|
"Europe"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"updated": 123
|
||||||
|
},
|
||||||
|
"T000015": {
|
||||||
|
"tags": [],
|
||||||
|
"updated": 123
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"846352075de7d": {
|
||||||
|
"T000000": {
|
||||||
|
"tags": [],
|
||||||
|
"updated": 123
|
||||||
|
},
|
||||||
|
"T000001": {
|
||||||
|
"tags": [],
|
||||||
|
"updated": 123
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"441420a886d82": {
|
||||||
|
"T000000": {
|
||||||
|
"tags": [],
|
||||||
|
"updated": 123
|
||||||
|
},
|
||||||
|
"T000001": {
|
||||||
|
"tags": [
|
||||||
|
[
|
||||||
|
3,
|
||||||
|
"@pov",
|
||||||
|
"Bod"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
4,
|
||||||
|
"@plot",
|
||||||
|
"Main"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
5,
|
||||||
|
"@location",
|
||||||
|
"Europe"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"updated": 123
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"eb103bc70c90c": {
|
||||||
|
"T000000": {
|
||||||
|
"tags": [],
|
||||||
|
"updated": 123
|
||||||
|
},
|
||||||
|
"T000001": {
|
||||||
|
"tags": [
|
||||||
|
[
|
||||||
|
3,
|
||||||
|
"@pov",
|
||||||
|
"Bod"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
4,
|
||||||
|
"@plot",
|
||||||
|
"Main"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
5,
|
||||||
|
"@location",
|
||||||
|
"Europe"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"updated": 123
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"f8c0562e50f1b": {
|
||||||
|
"T000000": {
|
||||||
|
"tags": [],
|
||||||
|
"updated": 123
|
||||||
|
},
|
||||||
|
"T000001": {
|
||||||
|
"tags": [
|
||||||
|
[
|
||||||
|
3,
|
||||||
|
"@pov",
|
||||||
|
"Bod"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
4,
|
||||||
|
"@plot",
|
||||||
|
"Main"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
5,
|
||||||
|
"@location",
|
||||||
|
"Europe"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"updated": 123
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"47666c91c7ccf": {
|
||||||
|
"T000000": {
|
||||||
|
"tags": [],
|
||||||
|
"updated": 123
|
||||||
|
},
|
||||||
|
"T000001": {
|
||||||
|
"tags": [
|
||||||
|
[
|
||||||
|
3,
|
||||||
|
"@pov",
|
||||||
|
"Bod"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
4,
|
||||||
|
"@plot",
|
||||||
|
"Main"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
5,
|
||||||
|
"@location",
|
||||||
|
"Europe"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"updated": 123
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"4c4f28287af27": {
|
||||||
|
"T000000": {
|
||||||
|
"tags": [],
|
||||||
|
"updated": 123
|
||||||
|
},
|
||||||
|
"T000001": {
|
||||||
|
"tags": [],
|
||||||
|
"updated": 123
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"2426c6f0ca922": {
|
||||||
|
"T000000": {
|
||||||
|
"tags": [],
|
||||||
|
"updated": 123
|
||||||
|
},
|
||||||
|
"T000001": {
|
||||||
|
"tags": [],
|
||||||
|
"updated": 123
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"04468803b92e1": {
|
||||||
|
"T000000": {
|
||||||
|
"tags": [],
|
||||||
|
"updated": 123
|
||||||
|
},
|
||||||
|
"T000001": {
|
||||||
|
"tags": [],
|
||||||
|
"updated": 123
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"novelIndex": {
|
||||||
|
"7a992350f3eb6": {
|
||||||
|
"T000001": {
|
||||||
|
"level": "H1",
|
||||||
|
"title": "Lorem Ipsum",
|
||||||
|
"layout": "TITLE",
|
||||||
|
"synopsis": "",
|
||||||
|
"cCount": 230,
|
||||||
|
"wCount": 40,
|
||||||
|
"pCount": 3,
|
||||||
|
"updated": 123
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"8c58a65414c23": {},
|
||||||
|
"88d59a277361b": {
|
||||||
|
"T000001": {
|
||||||
|
"level": "H2",
|
||||||
|
"title": "Prologue",
|
||||||
|
"layout": "UNNUMBERED",
|
||||||
|
"synopsis": "Explanation from the lipsum.com website.",
|
||||||
|
"cCount": 584,
|
||||||
|
"wCount": 92,
|
||||||
|
"pCount": 1,
|
||||||
|
"updated": 123
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"db7e733775d4d": {
|
||||||
|
"T000001": {
|
||||||
|
"level": "H1",
|
||||||
|
"title": "Act One",
|
||||||
|
"layout": "PARTITION",
|
||||||
|
"synopsis": "",
|
||||||
|
"cCount": 35,
|
||||||
|
"wCount": 6,
|
||||||
|
"pCount": 1,
|
||||||
|
"updated": 123
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fb609cd8319dc": {
|
||||||
|
"T000001": {
|
||||||
|
"level": "H2",
|
||||||
|
"title": "Chapter One",
|
||||||
|
"layout": "CHAPTER",
|
||||||
|
"synopsis": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque at aliquam quam.",
|
||||||
|
"cCount": 419,
|
||||||
|
"wCount": 67,
|
||||||
|
"pCount": 1,
|
||||||
|
"updated": 123
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"88243afbe5ed8": {
|
||||||
|
"T000001": {
|
||||||
|
"level": "H3",
|
||||||
|
"title": "Scene One",
|
||||||
|
"layout": "SCENE",
|
||||||
|
"synopsis": "Aenean ut placerat velit. Etiam laoreet ullamcorper risus, eget lobortis enim scelerisque non. Suspendisse id maximus nunc, et mollis sapien. Curabitur vel semper sapien, non pulvinar dolor. Etiam finibus nisi vel mi molestie consectetur.",
|
||||||
|
"cCount": 1197,
|
||||||
|
"wCount": 174,
|
||||||
|
"pCount": 2,
|
||||||
|
"updated": 123
|
||||||
|
},
|
||||||
|
"T000013": {
|
||||||
|
"level": "H4",
|
||||||
|
"title": "Scene One, Section Two",
|
||||||
|
"layout": "SCENE",
|
||||||
|
"synopsis": "",
|
||||||
|
"cCount": 1561,
|
||||||
|
"wCount": 230,
|
||||||
|
"pCount": 2,
|
||||||
|
"updated": 123
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"f96ec11c6a3da": {
|
||||||
|
"T000001": {
|
||||||
|
"level": "H3",
|
||||||
|
"title": "Scene Two",
|
||||||
|
"layout": "SCENE",
|
||||||
|
"synopsis": "Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer sapien nulla, dictum at lacus a, dignissim consectetur dolor. Nunc vel eleifend lacus, eu dapibus orci.",
|
||||||
|
"cCount": 2034,
|
||||||
|
"wCount": 299,
|
||||||
|
"pCount": 3,
|
||||||
|
"updated": 123
|
||||||
|
},
|
||||||
|
"T000015": {
|
||||||
|
"level": "H4",
|
||||||
|
"title": "Scene Two, Section Two",
|
||||||
|
"layout": "SCENE",
|
||||||
|
"synopsis": "",
|
||||||
|
"cCount": 2009,
|
||||||
|
"wCount": 301,
|
||||||
|
"pCount": 3,
|
||||||
|
"updated": 123
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"846352075de7d": {
|
||||||
|
"T000001": {
|
||||||
|
"level": "H2",
|
||||||
|
"title": "Why do we use it?",
|
||||||
|
"layout": "BOOK",
|
||||||
|
"synopsis": "",
|
||||||
|
"cCount": 630,
|
||||||
|
"wCount": 109,
|
||||||
|
"pCount": 1,
|
||||||
|
"updated": 123
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"441420a886d82": {
|
||||||
|
"T000001": {
|
||||||
|
"level": "H2",
|
||||||
|
"title": "Chapter Two",
|
||||||
|
"layout": "CHAPTER",
|
||||||
|
"synopsis": "Curabitur a elit posuere, varius ex et, convallis neque. Phasellus sagittis pharetra sem vitae dapibus. Curabitur varius lorem non pulvinar congue.",
|
||||||
|
"cCount": 477,
|
||||||
|
"wCount": 70,
|
||||||
|
"pCount": 1,
|
||||||
|
"updated": 123
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"eb103bc70c90c": {
|
||||||
|
"T000001": {
|
||||||
|
"level": "H3",
|
||||||
|
"title": "Scene Three",
|
||||||
|
"layout": "SCENE",
|
||||||
|
"synopsis": "Aenean ut libero ut lectus porttitor rhoncus vel et massa. Nam pretium, nibh et varius vehicula, urna metus blandit eros, euismod pharetra diam diam et libero. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.",
|
||||||
|
"cCount": 3006,
|
||||||
|
"wCount": 439,
|
||||||
|
"pCount": 4,
|
||||||
|
"updated": 123
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"f8c0562e50f1b": {
|
||||||
|
"T000001": {
|
||||||
|
"level": "H3",
|
||||||
|
"title": "Scene Four",
|
||||||
|
"layout": "SCENE",
|
||||||
|
"synopsis": "Nam tempor blandit magna laoreet aliquet. Vestibulum auctor posuere leo, ac gravida nisi rhoncus varius. Aenean posuere dolor vitae condimentum volutpat. Donec egestas volutpat risus, quis luctus justo.",
|
||||||
|
"cCount": 3839,
|
||||||
|
"wCount": 563,
|
||||||
|
"pCount": 6,
|
||||||
|
"updated": 123
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"47666c91c7ccf": {
|
||||||
|
"T000001": {
|
||||||
|
"level": "H3",
|
||||||
|
"title": "Scene Five",
|
||||||
|
"layout": "SCENE",
|
||||||
|
"synopsis": "Praesent eget est porta, dictum ante in, egestas risus. Mauris risus mauris, consequat aliquam mauris et, feugiat iaculis ipsum. Aliquam arcu ipsum, fermentum ut arcu sed, lobortis euismod sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.",
|
||||||
|
"cCount": 3644,
|
||||||
|
"wCount": 543,
|
||||||
|
"pCount": 5,
|
||||||
|
"updated": 123
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"noteIndex": {
|
||||||
|
"4c4f28287af27": {
|
||||||
|
"T000001": {
|
||||||
|
"level": "H1",
|
||||||
|
"title": "Nobody Owens",
|
||||||
|
"layout": "NOTE",
|
||||||
|
"synopsis": "",
|
||||||
|
"cCount": 1864,
|
||||||
|
"wCount": 284,
|
||||||
|
"pCount": 3,
|
||||||
|
"updated": 123
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"2426c6f0ca922": {
|
||||||
|
"T000001": {
|
||||||
|
"level": "H1",
|
||||||
|
"title": "Main Plot",
|
||||||
|
"layout": "NOTE",
|
||||||
|
"synopsis": "",
|
||||||
|
"cCount": 1369,
|
||||||
|
"wCount": 195,
|
||||||
|
"pCount": 2,
|
||||||
|
"updated": 123
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"04468803b92e1": {
|
||||||
|
"T000001": {
|
||||||
|
"level": "H1",
|
||||||
|
"title": "Ancient Europe",
|
||||||
|
"layout": "NOTE",
|
||||||
|
"synopsis": "",
|
||||||
|
"cCount": 1770,
|
||||||
|
"wCount": 259,
|
||||||
|
"pCount": 3,
|
||||||
|
"updated": 123
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"textCounts": {
|
||||||
|
"7a992350f3eb6": [
|
||||||
|
230,
|
||||||
|
40,
|
||||||
|
3
|
||||||
|
],
|
||||||
|
"8c58a65414c23": [
|
||||||
|
1058,
|
||||||
|
176,
|
||||||
|
2
|
||||||
|
],
|
||||||
|
"88d59a277361b": [
|
||||||
|
584,
|
||||||
|
92,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"db7e733775d4d": [
|
||||||
|
35,
|
||||||
|
6,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"fb609cd8319dc": [
|
||||||
|
419,
|
||||||
|
67,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"88243afbe5ed8": [
|
||||||
|
2758,
|
||||||
|
404,
|
||||||
|
4
|
||||||
|
],
|
||||||
|
"f96ec11c6a3da": [
|
||||||
|
4043,
|
||||||
|
600,
|
||||||
|
6
|
||||||
|
],
|
||||||
|
"846352075de7d": [
|
||||||
|
630,
|
||||||
|
109,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"441420a886d82": [
|
||||||
|
477,
|
||||||
|
70,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"eb103bc70c90c": [
|
||||||
|
3006,
|
||||||
|
439,
|
||||||
|
4
|
||||||
|
],
|
||||||
|
"f8c0562e50f1b": [
|
||||||
|
3839,
|
||||||
|
563,
|
||||||
|
6
|
||||||
|
],
|
||||||
|
"47666c91c7ccf": [
|
||||||
|
3644,
|
||||||
|
543,
|
||||||
|
5
|
||||||
|
],
|
||||||
|
"4c4f28287af27": [
|
||||||
|
1864,
|
||||||
|
284,
|
||||||
|
3
|
||||||
|
],
|
||||||
|
"2426c6f0ca922": [
|
||||||
|
1369,
|
||||||
|
195,
|
||||||
|
2
|
||||||
|
],
|
||||||
|
"04468803b92e1": [
|
||||||
|
1770,
|
||||||
|
259,
|
||||||
|
3
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,178 @@
|
|||||||
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
|
<novelWriterXML appVersion="1.0b3" hexVersion="0x010000b3" fileVersion="1.2" timeStamp="2020-09-29 21:12:51">
|
||||||
|
<project>
|
||||||
|
<name>Test Custom</name>
|
||||||
|
<title>Test Novel</title>
|
||||||
|
<author>Jane Doe</author>
|
||||||
|
<author>John Doh</author>
|
||||||
|
<saveCount>1</saveCount>
|
||||||
|
<autoCount>1</autoCount>
|
||||||
|
<editTime>0</editTime>
|
||||||
|
</project>
|
||||||
|
<settings>
|
||||||
|
<doBackup>True</doBackup>
|
||||||
|
<spellCheck>False</spellCheck>
|
||||||
|
<spellLang>None</spellLang>
|
||||||
|
<autoOutline>True</autoOutline>
|
||||||
|
<lastEdited>None</lastEdited>
|
||||||
|
<lastViewed>None</lastViewed>
|
||||||
|
<lastWordCount>0</lastWordCount>
|
||||||
|
<novelWordCount>0</novelWordCount>
|
||||||
|
<notesWordCount>0</notesWordCount>
|
||||||
|
<autoReplace/>
|
||||||
|
<titleFormat>
|
||||||
|
<title>%title%</title>
|
||||||
|
<chapter>Chapter %ch%: %title%</chapter>
|
||||||
|
<unnumbered>%title%</unnumbered>
|
||||||
|
<scene>* * *</scene>
|
||||||
|
<section></section>
|
||||||
|
</titleFormat>
|
||||||
|
<status>
|
||||||
|
<entry blue="100" green="100" red="100">New</entry>
|
||||||
|
<entry blue="0" green="50" red="200">Note</entry>
|
||||||
|
<entry blue="0" green="150" red="200">Draft</entry>
|
||||||
|
<entry blue="0" green="200" red="50">Finished</entry>
|
||||||
|
</status>
|
||||||
|
<importance>
|
||||||
|
<entry blue="100" green="100" red="100">New</entry>
|
||||||
|
<entry blue="0" green="50" red="200">Minor</entry>
|
||||||
|
<entry blue="0" green="150" red="200">Major</entry>
|
||||||
|
<entry blue="0" green="200" red="50">Main</entry>
|
||||||
|
</importance>
|
||||||
|
</settings>
|
||||||
|
<content count="14">
|
||||||
|
<item handle="73475cb40a568" order="None" parent="None">
|
||||||
|
<name>Novel</name>
|
||||||
|
<type>ROOT</type>
|
||||||
|
<class>NOVEL</class>
|
||||||
|
<status>New</status>
|
||||||
|
<expanded>False</expanded>
|
||||||
|
</item>
|
||||||
|
<item handle="44cb730c42048" order="None" parent="None">
|
||||||
|
<name>Plot</name>
|
||||||
|
<type>ROOT</type>
|
||||||
|
<class>PLOT</class>
|
||||||
|
<status>New</status>
|
||||||
|
<expanded>False</expanded>
|
||||||
|
</item>
|
||||||
|
<item handle="71ee45a3c0db9" order="None" parent="None">
|
||||||
|
<name>Characters</name>
|
||||||
|
<type>ROOT</type>
|
||||||
|
<class>CHARACTER</class>
|
||||||
|
<status>New</status>
|
||||||
|
<expanded>False</expanded>
|
||||||
|
</item>
|
||||||
|
<item handle="811786ad1ae74" order="None" parent="None">
|
||||||
|
<name>Locations</name>
|
||||||
|
<type>ROOT</type>
|
||||||
|
<class>WORLD</class>
|
||||||
|
<status>New</status>
|
||||||
|
<expanded>False</expanded>
|
||||||
|
</item>
|
||||||
|
<item handle="25fc0e7096fc6" order="None" parent="None">
|
||||||
|
<name>Timeline</name>
|
||||||
|
<type>ROOT</type>
|
||||||
|
<class>TIMELINE</class>
|
||||||
|
<status>New</status>
|
||||||
|
<expanded>False</expanded>
|
||||||
|
</item>
|
||||||
|
<item handle="31489056e0916" order="None" parent="None">
|
||||||
|
<name>Objects</name>
|
||||||
|
<type>ROOT</type>
|
||||||
|
<class>OBJECT</class>
|
||||||
|
<status>New</status>
|
||||||
|
<expanded>False</expanded>
|
||||||
|
</item>
|
||||||
|
<item handle="98010bd9270f9" order="None" parent="None">
|
||||||
|
<name>Entity</name>
|
||||||
|
<type>ROOT</type>
|
||||||
|
<class>ENTITY</class>
|
||||||
|
<status>New</status>
|
||||||
|
<expanded>False</expanded>
|
||||||
|
</item>
|
||||||
|
<item handle="0e17daca5f3e1" order="None" parent="73475cb40a568">
|
||||||
|
<name>Title Page</name>
|
||||||
|
<type>FILE</type>
|
||||||
|
<class>NOVEL</class>
|
||||||
|
<status>New</status>
|
||||||
|
<exported>True</exported>
|
||||||
|
<layout>TITLE</layout>
|
||||||
|
<charCount>0</charCount>
|
||||||
|
<wordCount>0</wordCount>
|
||||||
|
<paraCount>0</paraCount>
|
||||||
|
<cursorPos>0</cursorPos>
|
||||||
|
</item>
|
||||||
|
<item handle="1a6562590ef19" order="None" parent="73475cb40a568">
|
||||||
|
<name>Scene 1</name>
|
||||||
|
<type>FILE</type>
|
||||||
|
<class>NOVEL</class>
|
||||||
|
<status>New</status>
|
||||||
|
<exported>True</exported>
|
||||||
|
<layout>SCENE</layout>
|
||||||
|
<charCount>0</charCount>
|
||||||
|
<wordCount>0</wordCount>
|
||||||
|
<paraCount>0</paraCount>
|
||||||
|
<cursorPos>0</cursorPos>
|
||||||
|
</item>
|
||||||
|
<item handle="031b4af5197ec" order="None" parent="73475cb40a568">
|
||||||
|
<name>Scene 2</name>
|
||||||
|
<type>FILE</type>
|
||||||
|
<class>NOVEL</class>
|
||||||
|
<status>New</status>
|
||||||
|
<exported>True</exported>
|
||||||
|
<layout>SCENE</layout>
|
||||||
|
<charCount>0</charCount>
|
||||||
|
<wordCount>0</wordCount>
|
||||||
|
<paraCount>0</paraCount>
|
||||||
|
<cursorPos>0</cursorPos>
|
||||||
|
</item>
|
||||||
|
<item handle="41cfc0d1f2d12" order="None" parent="73475cb40a568">
|
||||||
|
<name>Scene 3</name>
|
||||||
|
<type>FILE</type>
|
||||||
|
<class>NOVEL</class>
|
||||||
|
<status>New</status>
|
||||||
|
<exported>True</exported>
|
||||||
|
<layout>SCENE</layout>
|
||||||
|
<charCount>0</charCount>
|
||||||
|
<wordCount>0</wordCount>
|
||||||
|
<paraCount>0</paraCount>
|
||||||
|
<cursorPos>0</cursorPos>
|
||||||
|
</item>
|
||||||
|
<item handle="2858dcd1057d3" order="None" parent="73475cb40a568">
|
||||||
|
<name>Scene 4</name>
|
||||||
|
<type>FILE</type>
|
||||||
|
<class>NOVEL</class>
|
||||||
|
<status>New</status>
|
||||||
|
<exported>True</exported>
|
||||||
|
<layout>SCENE</layout>
|
||||||
|
<charCount>0</charCount>
|
||||||
|
<wordCount>0</wordCount>
|
||||||
|
<paraCount>0</paraCount>
|
||||||
|
<cursorPos>0</cursorPos>
|
||||||
|
</item>
|
||||||
|
<item handle="2fca346db6561" order="None" parent="73475cb40a568">
|
||||||
|
<name>Scene 5</name>
|
||||||
|
<type>FILE</type>
|
||||||
|
<class>NOVEL</class>
|
||||||
|
<status>New</status>
|
||||||
|
<exported>True</exported>
|
||||||
|
<layout>SCENE</layout>
|
||||||
|
<charCount>0</charCount>
|
||||||
|
<wordCount>0</wordCount>
|
||||||
|
<paraCount>0</paraCount>
|
||||||
|
<cursorPos>0</cursorPos>
|
||||||
|
</item>
|
||||||
|
<item handle="02d20bbd7e394" order="None" parent="73475cb40a568">
|
||||||
|
<name>Scene 6</name>
|
||||||
|
<type>FILE</type>
|
||||||
|
<class>NOVEL</class>
|
||||||
|
<status>New</status>
|
||||||
|
<exported>True</exported>
|
||||||
|
<layout>SCENE</layout>
|
||||||
|
<charCount>0</charCount>
|
||||||
|
<wordCount>0</wordCount>
|
||||||
|
<paraCount>0</paraCount>
|
||||||
|
<cursorPos>0</cursorPos>
|
||||||
|
</item>
|
||||||
|
</content>
|
||||||
|
</novelWriterXML>
|
||||||
+16
-15
@@ -3,13 +3,14 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
import os
|
||||||
|
|
||||||
from nwtools import cmpFiles
|
from nwtools import cmpFiles
|
||||||
from os import path
|
|
||||||
|
|
||||||
@pytest.mark.core
|
@pytest.mark.core
|
||||||
def testConfigCore(tmpConf, nwTemp, nwRef):
|
def testConfigCore(tmpConf, nwTemp, nwRef):
|
||||||
refConf = path.join(nwRef, "novelwriter.conf")
|
refConf = os.path.join(nwRef, "novelwriter.conf")
|
||||||
testConf = path.join(tmpConf.confPath, "novelwriter.conf")
|
testConf = os.path.join(tmpConf.confPath, "novelwriter.conf")
|
||||||
|
|
||||||
assert tmpConf.confPath == nwTemp
|
assert tmpConf.confPath == nwTemp
|
||||||
assert tmpConf.saveConfig()
|
assert tmpConf.saveConfig()
|
||||||
@@ -22,8 +23,8 @@ def testConfigCore(tmpConf, nwTemp, nwRef):
|
|||||||
@pytest.mark.core
|
@pytest.mark.core
|
||||||
def testConfigSetConfPath(tmpConf, nwTemp):
|
def testConfigSetConfPath(tmpConf, nwTemp):
|
||||||
assert tmpConf.setConfPath(None)
|
assert tmpConf.setConfPath(None)
|
||||||
assert not tmpConf.setConfPath(path.join("somewhere", "over", "the", "rainbow"))
|
assert not tmpConf.setConfPath(os.path.join("somewhere", "over", "the", "rainbow"))
|
||||||
assert tmpConf.setConfPath(path.join(nwTemp, "novelwriter.conf"))
|
assert tmpConf.setConfPath(os.path.join(nwTemp, "novelwriter.conf"))
|
||||||
assert tmpConf.confPath == nwTemp
|
assert tmpConf.confPath == nwTemp
|
||||||
assert tmpConf.confFile == "novelwriter.conf"
|
assert tmpConf.confFile == "novelwriter.conf"
|
||||||
assert not tmpConf.confChanged
|
assert not tmpConf.confChanged
|
||||||
@@ -31,15 +32,15 @@ def testConfigSetConfPath(tmpConf, nwTemp):
|
|||||||
@pytest.mark.core
|
@pytest.mark.core
|
||||||
def testConfigSetDataPath(tmpConf, nwTemp):
|
def testConfigSetDataPath(tmpConf, nwTemp):
|
||||||
assert tmpConf.setDataPath(None)
|
assert tmpConf.setDataPath(None)
|
||||||
assert not tmpConf.setDataPath(path.join("somewhere", "over", "the", "rainbow"))
|
assert not tmpConf.setDataPath(os.path.join("somewhere", "over", "the", "rainbow"))
|
||||||
assert tmpConf.setDataPath(nwTemp)
|
assert tmpConf.setDataPath(nwTemp)
|
||||||
assert tmpConf.dataPath == nwTemp
|
assert tmpConf.dataPath == nwTemp
|
||||||
assert not tmpConf.confChanged
|
assert not tmpConf.confChanged
|
||||||
|
|
||||||
@pytest.mark.core
|
@pytest.mark.core
|
||||||
def testConfigSetWinSize(tmpConf, nwTemp, nwRef):
|
def testConfigSetWinSize(tmpConf, nwTemp, nwRef):
|
||||||
refConf = path.join(nwRef, "novelwriter.conf")
|
refConf = os.path.join(nwRef, "novelwriter.conf")
|
||||||
testConf = path.join(tmpConf.confPath, "novelwriter.conf")
|
testConf = os.path.join(tmpConf.confPath, "novelwriter.conf")
|
||||||
tmpConf.guiScale = 1.0
|
tmpConf.guiScale = 1.0
|
||||||
|
|
||||||
assert tmpConf.confPath == nwTemp
|
assert tmpConf.confPath == nwTemp
|
||||||
@@ -55,8 +56,8 @@ def testConfigSetWinSize(tmpConf, nwTemp, nwRef):
|
|||||||
|
|
||||||
@pytest.mark.core
|
@pytest.mark.core
|
||||||
def testConfigSetTreeColWidths(tmpConf, nwTemp, nwRef):
|
def testConfigSetTreeColWidths(tmpConf, nwTemp, nwRef):
|
||||||
refConf = path.join(nwRef, "novelwriter.conf")
|
refConf = os.path.join(nwRef, "novelwriter.conf")
|
||||||
testConf = path.join(tmpConf.confPath, "novelwriter.conf")
|
testConf = os.path.join(tmpConf.confPath, "novelwriter.conf")
|
||||||
|
|
||||||
assert tmpConf.confPath == nwTemp
|
assert tmpConf.confPath == nwTemp
|
||||||
tmpConf.guiScale = 1.0
|
tmpConf.guiScale = 1.0
|
||||||
@@ -77,8 +78,8 @@ def testConfigSetTreeColWidths(tmpConf, nwTemp, nwRef):
|
|||||||
|
|
||||||
@pytest.mark.core
|
@pytest.mark.core
|
||||||
def testConfigSetPanePos(tmpConf, nwTemp, nwRef):
|
def testConfigSetPanePos(tmpConf, nwTemp, nwRef):
|
||||||
refConf = path.join(nwRef, "novelwriter.conf")
|
refConf = os.path.join(nwRef, "novelwriter.conf")
|
||||||
testConf = path.join(tmpConf.confPath, "novelwriter.conf")
|
testConf = os.path.join(tmpConf.confPath, "novelwriter.conf")
|
||||||
|
|
||||||
assert tmpConf.confPath == nwTemp
|
assert tmpConf.confPath == nwTemp
|
||||||
|
|
||||||
@@ -113,8 +114,8 @@ def testConfigSetPanePos(tmpConf, nwTemp, nwRef):
|
|||||||
|
|
||||||
@pytest.mark.core
|
@pytest.mark.core
|
||||||
def testConfigFlags(tmpConf, nwTemp, nwRef):
|
def testConfigFlags(tmpConf, nwTemp, nwRef):
|
||||||
refConf = path.join(nwRef, "novelwriter.conf")
|
refConf = os.path.join(nwRef, "novelwriter.conf")
|
||||||
testConf = path.join(tmpConf.confPath, "novelwriter.conf")
|
testConf = os.path.join(tmpConf.confPath, "novelwriter.conf")
|
||||||
|
|
||||||
assert tmpConf.confPath == nwTemp
|
assert tmpConf.confPath == nwTemp
|
||||||
|
|
||||||
@@ -150,7 +151,7 @@ def testTextSizes(tmpConf, nwTemp, nwRef):
|
|||||||
|
|
||||||
@pytest.mark.core
|
@pytest.mark.core
|
||||||
def testConfigErrors(tmpConf):
|
def testConfigErrors(tmpConf):
|
||||||
nonPath = path.join("somewhere", "over", "the", "rainbow")
|
nonPath = os.path.join("somewhere", "over", "the", "rainbow")
|
||||||
assert tmpConf.initConfig(nonPath, nonPath)
|
assert tmpConf.initConfig(nonPath, nonPath)
|
||||||
assert tmpConf.hasError
|
assert tmpConf.hasError
|
||||||
assert not tmpConf.loadConfig()
|
assert not tmpConf.loadConfig()
|
||||||
|
|||||||
+418
-139
@@ -5,51 +5,70 @@
|
|||||||
import nw
|
import nw
|
||||||
import pytest
|
import pytest
|
||||||
import json
|
import json
|
||||||
from shutil import copyfile
|
import os
|
||||||
from nwtools import cmpFiles
|
import sys
|
||||||
|
|
||||||
from os import path
|
from shutil import copyfile
|
||||||
from PyQt5.QtCore import Qt
|
from nwtools import cmpFiles, getGuiItem
|
||||||
from PyQt5.QtWidgets import QDialogButtonBox, QTreeWidgetItem
|
|
||||||
|
from PyQt5.QtCore import Qt, QItemSelectionModel
|
||||||
|
from PyQt5.QtWidgets import (
|
||||||
|
QDialogButtonBox, QTreeWidgetItem, QListWidgetItem, QDialog, QAction,
|
||||||
|
QMessageBox, QFileDialog, QFontDialog
|
||||||
|
)
|
||||||
|
|
||||||
from nw.gui import (
|
from nw.gui import (
|
||||||
GuiProjectSettings, GuiItemEditor, GuiAbout, GuiBuildNovel,
|
GuiProjectSettings, GuiItemEditor, GuiAbout, GuiBuildNovel,
|
||||||
GuiDocMerge, GuiDocSplit, GuiWritingStats, GuiProjectWizard,
|
GuiDocMerge, GuiDocSplit, GuiWritingStats, GuiProjectWizard,
|
||||||
GuiProjectLoad, GuiPreferences
|
GuiProjectLoad, GuiPreferences
|
||||||
)
|
)
|
||||||
|
from nw.gui.custom import QuotesDialog
|
||||||
from nw.constants import nwItemType, nwItemLayout, nwItemClass
|
from nw.constants import nwItemType, nwItemLayout, nwItemClass
|
||||||
|
|
||||||
keyDelay = 2
|
keyDelay = 2
|
||||||
|
typeDelay = 1
|
||||||
stepDelay = 20
|
stepDelay = 20
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
def testProjectEditor(qtbot, nwFuncTemp, nwTempGUI, nwRef, nwTemp):
|
def testProjectSettings(qtbot, monkeypatch, yesToAll, nwFuncTemp, nwTempGUI, nwRef, nwTemp):
|
||||||
nwGUI = nw.main(["--testmode", "--config=%s" % nwFuncTemp, "--data=%s" % nwTemp])
|
nwGUI = nw.main(["--testmode", "--config=%s" % nwTemp, "--data=%s" % nwTemp])
|
||||||
qtbot.addWidget(nwGUI)
|
qtbot.addWidget(nwGUI)
|
||||||
nwGUI.show()
|
nwGUI.show()
|
||||||
qtbot.waitForWindowShown(nwGUI)
|
qtbot.waitForWindowShown(nwGUI)
|
||||||
qtbot.wait(stepDelay)
|
qtbot.wait(stepDelay)
|
||||||
|
|
||||||
# Create new, save, open project
|
# Check that we cannot open when there is no project
|
||||||
|
nwGUI.mainMenu.aProjectSettings.activate(QAction.Trigger)
|
||||||
|
assert getGuiItem("GuiProjectSettings") is None
|
||||||
|
|
||||||
|
# Create new project
|
||||||
nwGUI.theProject.projTree.setSeed(42)
|
nwGUI.theProject.projTree.setSeed(42)
|
||||||
assert nwGUI.newProject({"projPath": nwFuncTemp}, True)
|
assert nwGUI.newProject({"projPath": nwFuncTemp})
|
||||||
nwGUI.mainConf.backupPath = nwFuncTemp
|
nwGUI.mainConf.backupPath = nwFuncTemp
|
||||||
|
|
||||||
projEdit = GuiProjectSettings(nwGUI, nwGUI.theProject)
|
# Get the dialog object
|
||||||
|
monkeypatch.setattr(GuiProjectSettings, "exec_", lambda *args: None)
|
||||||
|
monkeypatch.setattr(GuiProjectSettings, "result", lambda *args: QDialog.Accepted)
|
||||||
|
nwGUI.mainMenu.aProjectSettings.activate(QAction.Trigger)
|
||||||
|
qtbot.waitUntil(lambda: getGuiItem("GuiProjectSettings") is not None, timeout=1000)
|
||||||
|
|
||||||
|
projEdit = getGuiItem("GuiProjectSettings")
|
||||||
|
assert isinstance(projEdit, GuiProjectSettings)
|
||||||
projEdit.show()
|
projEdit.show()
|
||||||
qtbot.addWidget(projEdit)
|
qtbot.addWidget(projEdit)
|
||||||
|
|
||||||
|
# Main settings
|
||||||
qtbot.wait(stepDelay)
|
qtbot.wait(stepDelay)
|
||||||
projEdit.tabMain.editName.setText("")
|
projEdit.tabMain.editName.setText("")
|
||||||
for c in "Project Name":
|
for c in "Project Name":
|
||||||
qtbot.keyClick(projEdit.tabMain.editName, c, delay=keyDelay)
|
qtbot.keyClick(projEdit.tabMain.editName, c, delay=typeDelay)
|
||||||
for c in "Project Title":
|
for c in "Project Title":
|
||||||
qtbot.keyClick(projEdit.tabMain.editTitle, c, delay=keyDelay)
|
qtbot.keyClick(projEdit.tabMain.editTitle, c, delay=typeDelay)
|
||||||
for c in "Jane Doe":
|
for c in "Jane Doe":
|
||||||
qtbot.keyClick(projEdit.tabMain.editAuthors, c, delay=keyDelay)
|
qtbot.keyClick(projEdit.tabMain.editAuthors, c, delay=typeDelay)
|
||||||
qtbot.keyClick(projEdit.tabMain.editAuthors, Qt.Key_Return, delay=keyDelay)
|
qtbot.keyClick(projEdit.tabMain.editAuthors, Qt.Key_Return, delay=keyDelay)
|
||||||
for c in "John Doh":
|
for c in "John Doh":
|
||||||
qtbot.keyClick(projEdit.tabMain.editAuthors, c, delay=keyDelay)
|
qtbot.keyClick(projEdit.tabMain.editAuthors, c, delay=typeDelay)
|
||||||
|
|
||||||
# Test Status Tab
|
# Test Status Tab
|
||||||
qtbot.wait(stepDelay)
|
qtbot.wait(stepDelay)
|
||||||
@@ -59,9 +78,9 @@ def testProjectEditor(qtbot, nwFuncTemp, nwTempGUI, nwRef, nwTemp):
|
|||||||
qtbot.mouseClick(projEdit.tabStatus.newButton, Qt.LeftButton)
|
qtbot.mouseClick(projEdit.tabStatus.newButton, Qt.LeftButton)
|
||||||
projEdit.tabStatus.listBox.item(3).setSelected(True)
|
projEdit.tabStatus.listBox.item(3).setSelected(True)
|
||||||
for n in range(8):
|
for n in range(8):
|
||||||
qtbot.keyClick(projEdit.tabStatus.editName, Qt.Key_Backspace, delay=keyDelay)
|
qtbot.keyClick(projEdit.tabStatus.editName, Qt.Key_Backspace, delay=typeDelay)
|
||||||
for c in "Final":
|
for c in "Final":
|
||||||
qtbot.keyClick(projEdit.tabStatus.editName, c, delay=keyDelay)
|
qtbot.keyClick(projEdit.tabStatus.editName, c, delay=typeDelay)
|
||||||
qtbot.mouseClick(projEdit.tabStatus.saveButton, Qt.LeftButton)
|
qtbot.mouseClick(projEdit.tabStatus.saveButton, Qt.LeftButton)
|
||||||
|
|
||||||
# Auto-Replace Tab
|
# Auto-Replace Tab
|
||||||
@@ -71,9 +90,9 @@ def testProjectEditor(qtbot, nwFuncTemp, nwTempGUI, nwRef, nwTemp):
|
|||||||
qtbot.mouseClick(projEdit.tabReplace.addButton, Qt.LeftButton)
|
qtbot.mouseClick(projEdit.tabReplace.addButton, Qt.LeftButton)
|
||||||
projEdit.tabReplace.listBox.topLevelItem(0).setSelected(True)
|
projEdit.tabReplace.listBox.topLevelItem(0).setSelected(True)
|
||||||
for c in "Th is ":
|
for c in "Th is ":
|
||||||
qtbot.keyClick(projEdit.tabReplace.editKey, c, delay=keyDelay)
|
qtbot.keyClick(projEdit.tabReplace.editKey, c, delay=typeDelay)
|
||||||
for c in "With This Stuff ":
|
for c in "With This Stuff ":
|
||||||
qtbot.keyClick(projEdit.tabReplace.editValue, c, delay=keyDelay)
|
qtbot.keyClick(projEdit.tabReplace.editValue, c, delay=typeDelay)
|
||||||
qtbot.mouseClick(projEdit.tabReplace.saveButton, Qt.LeftButton)
|
qtbot.mouseClick(projEdit.tabReplace.saveButton, Qt.LeftButton)
|
||||||
|
|
||||||
qtbot.wait(stepDelay)
|
qtbot.wait(stepDelay)
|
||||||
@@ -111,9 +130,9 @@ def testProjectEditor(qtbot, nwFuncTemp, nwTempGUI, nwRef, nwTemp):
|
|||||||
qtbot.wait(stepDelay)
|
qtbot.wait(stepDelay)
|
||||||
|
|
||||||
# Check the files
|
# Check the files
|
||||||
projFile = path.join(nwFuncTemp, "nwProject.nwx")
|
projFile = os.path.join(nwFuncTemp, "nwProject.nwx")
|
||||||
testFile = path.join(nwTempGUI, "2_nwProject.nwx")
|
testFile = os.path.join(nwTempGUI, "2_nwProject.nwx")
|
||||||
refFile = path.join(nwRef, "gui", "2_nwProject.nwx")
|
refFile = os.path.join(nwRef, "gui", "2_nwProject.nwx")
|
||||||
copyfile(projFile, testFile)
|
copyfile(projFile, testFile)
|
||||||
assert cmpFiles(testFile, refFile, [2, 8, 9, 10])
|
assert cmpFiles(testFile, refFile, [2, 8, 9, 10])
|
||||||
|
|
||||||
@@ -121,8 +140,8 @@ def testProjectEditor(qtbot, nwFuncTemp, nwTempGUI, nwRef, nwTemp):
|
|||||||
nwGUI.closeMain()
|
nwGUI.closeMain()
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
def testItemEditor(qtbot, nwFuncTemp, nwTempGUI, nwRef, nwTemp):
|
def testItemEditor(qtbot, yesToAll, monkeypatch, nwFuncTemp, nwTempGUI, nwRef, nwTemp):
|
||||||
nwGUI = nw.main(["--testmode", "--config=%s" % nwFuncTemp, "--data=%s" % nwTemp])
|
nwGUI = nw.main(["--testmode", "--config=%s" % nwTemp, "--data=%s" % nwTemp])
|
||||||
qtbot.addWidget(nwGUI)
|
qtbot.addWidget(nwGUI)
|
||||||
nwGUI.show()
|
nwGUI.show()
|
||||||
qtbot.waitForWindowShown(nwGUI)
|
qtbot.waitForWindowShown(nwGUI)
|
||||||
@@ -130,10 +149,18 @@ def testItemEditor(qtbot, nwFuncTemp, nwTempGUI, nwRef, nwTemp):
|
|||||||
|
|
||||||
# Create new, save, open project
|
# Create new, save, open project
|
||||||
nwGUI.theProject.projTree.setSeed(42)
|
nwGUI.theProject.projTree.setSeed(42)
|
||||||
assert nwGUI.newProject({"projPath": nwFuncTemp}, True)
|
assert nwGUI.newProject({"projPath": nwFuncTemp})
|
||||||
assert nwGUI.openDocument("0e17daca5f3e1")
|
assert nwGUI.openDocument("0e17daca5f3e1")
|
||||||
|
assert nwGUI.treeView.setSelectedHandle("0e17daca5f3e1", doScroll=True)
|
||||||
|
|
||||||
|
monkeypatch.setattr(GuiItemEditor, "exec_", lambda *args: None)
|
||||||
|
nwGUI.mainMenu.aEditItem.activate(QAction.Trigger)
|
||||||
|
qtbot.waitUntil(lambda: getGuiItem("GuiItemEditor") is not None, timeout=1000)
|
||||||
|
|
||||||
|
itemEdit = getGuiItem("GuiItemEditor")
|
||||||
|
assert isinstance(itemEdit, GuiItemEditor)
|
||||||
|
itemEdit.show()
|
||||||
|
|
||||||
itemEdit = GuiItemEditor(nwGUI, nwGUI.theProject, "0e17daca5f3e1")
|
|
||||||
qtbot.addWidget(itemEdit)
|
qtbot.addWidget(itemEdit)
|
||||||
|
|
||||||
assert itemEdit.editName.text() == "New Scene"
|
assert itemEdit.editName.text() == "New Scene"
|
||||||
@@ -141,7 +168,7 @@ def testItemEditor(qtbot, nwFuncTemp, nwTempGUI, nwRef, nwTemp):
|
|||||||
assert itemEdit.editLayout.currentData() == nwItemLayout.SCENE
|
assert itemEdit.editLayout.currentData() == nwItemLayout.SCENE
|
||||||
|
|
||||||
for c in "Just a Page":
|
for c in "Just a Page":
|
||||||
qtbot.keyClick(itemEdit.editName, c, delay=keyDelay)
|
qtbot.keyClick(itemEdit.editName, c, delay=typeDelay)
|
||||||
itemEdit.editStatus.setCurrentIndex(1)
|
itemEdit.editStatus.setCurrentIndex(1)
|
||||||
layoutIdx = itemEdit.editLayout.findData(nwItemLayout.PAGE)
|
layoutIdx = itemEdit.editLayout.findData(nwItemLayout.PAGE)
|
||||||
itemEdit.editLayout.setCurrentIndex(layoutIdx)
|
itemEdit.editLayout.setCurrentIndex(layoutIdx)
|
||||||
@@ -150,7 +177,13 @@ def testItemEditor(qtbot, nwFuncTemp, nwTempGUI, nwRef, nwTemp):
|
|||||||
assert not itemEdit.editExport.isChecked()
|
assert not itemEdit.editExport.isChecked()
|
||||||
itemEdit._doSave()
|
itemEdit._doSave()
|
||||||
|
|
||||||
itemEdit = GuiItemEditor(nwGUI, nwGUI.theProject, "0e17daca5f3e1")
|
nwGUI.mainMenu.aEditItem.activate(QAction.Trigger)
|
||||||
|
qtbot.waitUntil(lambda: getGuiItem("GuiItemEditor") is not None, timeout=1000)
|
||||||
|
|
||||||
|
itemEdit = getGuiItem("GuiItemEditor")
|
||||||
|
assert isinstance(itemEdit, GuiItemEditor)
|
||||||
|
itemEdit.show()
|
||||||
|
|
||||||
qtbot.addWidget(itemEdit)
|
qtbot.addWidget(itemEdit)
|
||||||
assert itemEdit.editName.text() == "Just a Page"
|
assert itemEdit.editName.text() == "Just a Page"
|
||||||
assert itemEdit.editStatus.currentData() == "Note"
|
assert itemEdit.editStatus.currentData() == "Note"
|
||||||
@@ -170,18 +203,18 @@ def testItemEditor(qtbot, nwFuncTemp, nwTempGUI, nwRef, nwTemp):
|
|||||||
qtbot.wait(stepDelay)
|
qtbot.wait(stepDelay)
|
||||||
|
|
||||||
# Check the files
|
# Check the files
|
||||||
projFile = path.join(nwFuncTemp, "nwProject.nwx")
|
projFile = os.path.join(nwFuncTemp, "nwProject.nwx")
|
||||||
testFile = path.join(nwTempGUI, "3_nwProject.nwx")
|
testFile = os.path.join(nwTempGUI, "3_nwProject.nwx")
|
||||||
refFile = path.join(nwRef, "gui", "3_nwProject.nwx")
|
refFile = os.path.join(nwRef, "gui", "3_nwProject.nwx")
|
||||||
copyfile(projFile, testFile)
|
copyfile(projFile, testFile)
|
||||||
assert cmpFiles(testFile, refFile, [2, 6, 7, 8])
|
assert cmpFiles(testFile, refFile, [2, 6, 7, 8])
|
||||||
|
|
||||||
nwGUI.closeMain()
|
|
||||||
# qtbot.stopForInteraction()
|
# qtbot.stopForInteraction()
|
||||||
|
nwGUI.closeMain()
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
def testWritingStatsExport(qtbot, nwFuncTemp, nwTemp):
|
def testWritingStatsExport(qtbot, monkeypatch, yesToAll, nwFuncTemp, nwTemp):
|
||||||
nwGUI = nw.main(["--testmode", "--config=%s" % nwFuncTemp, "--data=%s" % nwTemp])
|
nwGUI = nw.main(["--testmode", "--config=%s" % nwTemp, "--data=%s" % nwTemp])
|
||||||
qtbot.addWidget(nwGUI)
|
qtbot.addWidget(nwGUI)
|
||||||
nwGUI.show()
|
nwGUI.show()
|
||||||
qtbot.waitForWindowShown(nwGUI)
|
qtbot.waitForWindowShown(nwGUI)
|
||||||
@@ -189,11 +222,15 @@ def testWritingStatsExport(qtbot, nwFuncTemp, nwTemp):
|
|||||||
|
|
||||||
# Create new, save, close project
|
# Create new, save, close project
|
||||||
nwGUI.theProject.projTree.setSeed(42)
|
nwGUI.theProject.projTree.setSeed(42)
|
||||||
assert nwGUI.newProject({"projPath": nwFuncTemp}, True)
|
assert nwGUI.newProject({"projPath": nwFuncTemp})
|
||||||
assert nwGUI.saveProject()
|
assert nwGUI.saveProject()
|
||||||
assert nwGUI.closeProject()
|
assert nwGUI.closeProject()
|
||||||
qtbot.wait(stepDelay)
|
qtbot.wait(stepDelay)
|
||||||
|
|
||||||
|
# Check that we cannot open when there is no project
|
||||||
|
nwGUI.mainMenu.aWritingStats.activate(QAction.Trigger)
|
||||||
|
assert getGuiItem("GuiWritingStats") is None
|
||||||
|
|
||||||
assert nwGUI.openProject(nwFuncTemp)
|
assert nwGUI.openProject(nwFuncTemp)
|
||||||
qtbot.wait(stepDelay)
|
qtbot.wait(stepDelay)
|
||||||
|
|
||||||
@@ -234,16 +271,23 @@ def testWritingStatsExport(qtbot, nwFuncTemp, nwTemp):
|
|||||||
qtbot.wait(stepDelay)
|
qtbot.wait(stepDelay)
|
||||||
|
|
||||||
nwGUI.mainConf.lastPath = nwFuncTemp
|
nwGUI.mainConf.lastPath = nwFuncTemp
|
||||||
sessLog = GuiWritingStats(nwGUI, nwGUI.theProject)
|
nwGUI.mainMenu.aWritingStats.activate(QAction.Trigger)
|
||||||
sessLog.show()
|
qtbot.waitUntil(lambda: getGuiItem("GuiWritingStats") is not None, timeout=1000)
|
||||||
|
|
||||||
|
sessLog = getGuiItem("GuiWritingStats")
|
||||||
|
assert isinstance(sessLog, GuiWritingStats)
|
||||||
qtbot.wait(stepDelay)
|
qtbot.wait(stepDelay)
|
||||||
|
|
||||||
|
monkeypatch.setattr(QFileDialog, "getSaveFileName", lambda *args, **kwargs: [])
|
||||||
|
assert not sessLog._saveData(sessLog.FMT_CSV)
|
||||||
|
|
||||||
|
monkeypatch.setattr(QFileDialog, "getSaveFileName", lambda ss, tt, pp, options: [pp])
|
||||||
assert sessLog._saveData(sessLog.FMT_CSV)
|
assert sessLog._saveData(sessLog.FMT_CSV)
|
||||||
qtbot.wait(stepDelay)
|
qtbot.wait(stepDelay)
|
||||||
assert sessLog._saveData(sessLog.FMT_JSON)
|
assert sessLog._saveData(sessLog.FMT_JSON)
|
||||||
qtbot.wait(stepDelay)
|
qtbot.wait(stepDelay)
|
||||||
|
|
||||||
jsonStats = path.join(nwFuncTemp, "sessionStats.json")
|
jsonStats = os.path.join(nwFuncTemp, "sessionStats.json")
|
||||||
with open(jsonStats, mode="r", encoding="utf-8") as inFile:
|
with open(jsonStats, mode="r", encoding="utf-8") as inFile:
|
||||||
jsonData = json.loads(inFile.read())
|
jsonData = json.loads(inFile.read())
|
||||||
|
|
||||||
@@ -259,7 +303,7 @@ def testWritingStatsExport(qtbot, nwFuncTemp, nwTemp):
|
|||||||
assert sessLog._saveData(sessLog.FMT_JSON)
|
assert sessLog._saveData(sessLog.FMT_JSON)
|
||||||
qtbot.wait(stepDelay)
|
qtbot.wait(stepDelay)
|
||||||
|
|
||||||
jsonStats = path.join(nwFuncTemp, "sessionStats.json")
|
jsonStats = os.path.join(nwFuncTemp, "sessionStats.json")
|
||||||
with open(jsonStats, mode="r", encoding="utf-8") as inFile:
|
with open(jsonStats, mode="r", encoding="utf-8") as inFile:
|
||||||
jsonData = json.loads(inFile.read())
|
jsonData = json.loads(inFile.read())
|
||||||
|
|
||||||
@@ -276,7 +320,7 @@ def testWritingStatsExport(qtbot, nwFuncTemp, nwTemp):
|
|||||||
assert sessLog._saveData(sessLog.FMT_JSON)
|
assert sessLog._saveData(sessLog.FMT_JSON)
|
||||||
qtbot.wait(stepDelay)
|
qtbot.wait(stepDelay)
|
||||||
|
|
||||||
jsonStats = path.join(nwFuncTemp, "sessionStats.json")
|
jsonStats = os.path.join(nwFuncTemp, "sessionStats.json")
|
||||||
with open(jsonStats, mode="r", encoding="utf-8") as inFile:
|
with open(jsonStats, mode="r", encoding="utf-8") as inFile:
|
||||||
jsonData = json.loads(inFile.read())
|
jsonData = json.loads(inFile.read())
|
||||||
|
|
||||||
@@ -293,7 +337,7 @@ def testWritingStatsExport(qtbot, nwFuncTemp, nwTemp):
|
|||||||
assert sessLog._saveData(sessLog.FMT_JSON)
|
assert sessLog._saveData(sessLog.FMT_JSON)
|
||||||
qtbot.wait(stepDelay)
|
qtbot.wait(stepDelay)
|
||||||
|
|
||||||
jsonStats = path.join(nwFuncTemp, "sessionStats.json")
|
jsonStats = os.path.join(nwFuncTemp, "sessionStats.json")
|
||||||
with open(jsonStats, mode="r", encoding="utf-8") as inFile:
|
with open(jsonStats, mode="r", encoding="utf-8") as inFile:
|
||||||
jsonData = json.loads(inFile.read())
|
jsonData = json.loads(inFile.read())
|
||||||
|
|
||||||
@@ -306,7 +350,7 @@ def testWritingStatsExport(qtbot, nwFuncTemp, nwTemp):
|
|||||||
assert sessLog._saveData(sessLog.FMT_JSON)
|
assert sessLog._saveData(sessLog.FMT_JSON)
|
||||||
qtbot.wait(stepDelay)
|
qtbot.wait(stepDelay)
|
||||||
|
|
||||||
jsonStats = path.join(nwFuncTemp, "sessionStats.json")
|
jsonStats = os.path.join(nwFuncTemp, "sessionStats.json")
|
||||||
with open(jsonStats, mode="r", encoding="utf-8") as inFile:
|
with open(jsonStats, mode="r", encoding="utf-8") as inFile:
|
||||||
jsonData = json.loads(inFile.read())
|
jsonData = json.loads(inFile.read())
|
||||||
|
|
||||||
@@ -318,7 +362,7 @@ def testWritingStatsExport(qtbot, nwFuncTemp, nwTemp):
|
|||||||
assert sessLog._saveData(sessLog.FMT_JSON)
|
assert sessLog._saveData(sessLog.FMT_JSON)
|
||||||
qtbot.wait(stepDelay)
|
qtbot.wait(stepDelay)
|
||||||
|
|
||||||
jsonStats = path.join(nwFuncTemp, "sessionStats.json")
|
jsonStats = os.path.join(nwFuncTemp, "sessionStats.json")
|
||||||
with open(jsonStats, mode="r", encoding="utf-8") as inFile:
|
with open(jsonStats, mode="r", encoding="utf-8") as inFile:
|
||||||
jsonData = json.loads(inFile.read())
|
jsonData = json.loads(inFile.read())
|
||||||
|
|
||||||
@@ -332,23 +376,39 @@ def testWritingStatsExport(qtbot, nwFuncTemp, nwTemp):
|
|||||||
nwGUI.closeMain()
|
nwGUI.closeMain()
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
def testAboutBox(qtbot, nwFuncTemp, nwTemp):
|
def testAboutBox(qtbot, monkeypatch, nwFuncTemp, nwTemp):
|
||||||
nwGUI = nw.main(["--testmode", "--config=%s" % nwFuncTemp, "--data=%s" % nwTemp])
|
nwGUI = nw.main(["--testmode", "--config=%s" % nwFuncTemp, "--data=%s" % nwTemp])
|
||||||
qtbot.addWidget(nwGUI)
|
qtbot.addWidget(nwGUI)
|
||||||
nwGUI.show()
|
nwGUI.show()
|
||||||
qtbot.waitForWindowShown(nwGUI)
|
qtbot.waitForWindowShown(nwGUI)
|
||||||
qtbot.wait(stepDelay)
|
qtbot.wait(stepDelay)
|
||||||
|
|
||||||
msgAbout = GuiAbout(nwGUI)
|
# NW About
|
||||||
|
monkeypatch.setattr(GuiAbout, "exec_", lambda *args: None)
|
||||||
|
nwGUI.mainMenu.aAboutNW.activate(QAction.Trigger)
|
||||||
|
qtbot.waitUntil(lambda: getGuiItem("GuiAbout") is not None, timeout=1000)
|
||||||
|
|
||||||
|
msgAbout = getGuiItem("GuiAbout")
|
||||||
|
assert isinstance(msgAbout, GuiAbout)
|
||||||
|
msgAbout.show()
|
||||||
|
|
||||||
assert msgAbout.pageAbout.document().characterCount() > 100
|
assert msgAbout.pageAbout.document().characterCount() > 100
|
||||||
assert msgAbout.pageLicense.document().characterCount() > 100
|
assert msgAbout.pageLicense.document().characterCount() > 100
|
||||||
|
|
||||||
|
msgAbout.mainConf.guiLang = "whatever"
|
||||||
|
msgAbout._fillLicensePage()
|
||||||
|
assert msgAbout.pageLicense.toPlainText() == "Error loading license text ..."
|
||||||
|
|
||||||
|
# Qt About
|
||||||
|
monkeypatch.setattr(QMessageBox, "aboutQt", lambda *args, **kwargs: None)
|
||||||
|
nwGUI.mainMenu.aAboutQt.activate(QAction.Trigger)
|
||||||
|
|
||||||
# qtbot.stopForInteraction()
|
# qtbot.stopForInteraction()
|
||||||
msgAbout._doClose()
|
msgAbout._doClose()
|
||||||
nwGUI.closeMain()
|
nwGUI.closeMain()
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
def testBuildTool(qtbot, nwTempBuild, nwLipsum, nwRef, nwTemp):
|
def testBuildTool(qtbot, yesToAll, nwTempBuild, nwLipsum, nwRef, nwTemp):
|
||||||
|
|
||||||
nwGUI = nw.main(["--testmode", "--config=%s" % nwLipsum, "--data=%s" % nwTemp])
|
nwGUI = nw.main(["--testmode", "--config=%s" % nwLipsum, "--data=%s" % nwTemp])
|
||||||
qtbot.addWidget(nwGUI)
|
qtbot.addWidget(nwGUI)
|
||||||
@@ -356,11 +416,20 @@ def testBuildTool(qtbot, nwTempBuild, nwLipsum, nwRef, nwTemp):
|
|||||||
qtbot.waitForWindowShown(nwGUI)
|
qtbot.waitForWindowShown(nwGUI)
|
||||||
qtbot.wait(stepDelay)
|
qtbot.wait(stepDelay)
|
||||||
|
|
||||||
assert nwGUI.openProject(nwLipsum)
|
# Check that we cannot open when there is no project
|
||||||
|
nwGUI.mainMenu.aBuildProject.activate(QAction.Trigger)
|
||||||
|
assert getGuiItem("GuiBuildNovel") is None
|
||||||
|
|
||||||
|
# Open a project
|
||||||
|
assert nwGUI.openProject(nwLipsum)
|
||||||
nwGUI.mainConf.lastPath = nwLipsum
|
nwGUI.mainConf.lastPath = nwLipsum
|
||||||
|
|
||||||
nwBuild = GuiBuildNovel(nwGUI, nwGUI.theProject)
|
# Open the tool
|
||||||
|
nwGUI.mainMenu.aBuildProject.activate(QAction.Trigger)
|
||||||
|
qtbot.waitUntil(lambda: getGuiItem("GuiBuildNovel") is not None, timeout=1000)
|
||||||
|
|
||||||
|
nwBuild = getGuiItem("GuiBuildNovel")
|
||||||
|
assert isinstance(nwBuild, GuiBuildNovel)
|
||||||
|
|
||||||
# Default Settings
|
# Default Settings
|
||||||
qtbot.mouseClick(nwBuild.buildNovel, Qt.LeftButton)
|
qtbot.mouseClick(nwBuild.buildNovel, Qt.LeftButton)
|
||||||
@@ -368,15 +437,15 @@ def testBuildTool(qtbot, nwTempBuild, nwLipsum, nwRef, nwTemp):
|
|||||||
assert nwBuild._saveDocument(nwBuild.FMT_NWD)
|
assert nwBuild._saveDocument(nwBuild.FMT_NWD)
|
||||||
assert nwBuild._saveDocument(nwBuild.FMT_HTM)
|
assert nwBuild._saveDocument(nwBuild.FMT_HTM)
|
||||||
|
|
||||||
projFile = path.join(nwLipsum, "Lorem Ipsum.nwd")
|
projFile = os.path.join(nwLipsum, "Lorem Ipsum.nwd")
|
||||||
testFile = path.join(nwTempBuild, "1_LoremIpsum.nwd")
|
testFile = os.path.join(nwTempBuild, "1_LoremIpsum.nwd")
|
||||||
refFile = path.join(nwRef, "build", "1_LoremIpsum.nwd")
|
refFile = os.path.join(nwRef, "build", "1_LoremIpsum.nwd")
|
||||||
copyfile(projFile, testFile)
|
copyfile(projFile, testFile)
|
||||||
assert cmpFiles(testFile, refFile)
|
assert cmpFiles(testFile, refFile)
|
||||||
|
|
||||||
projFile = path.join(nwLipsum, "Lorem Ipsum.htm")
|
projFile = os.path.join(nwLipsum, "Lorem Ipsum.htm")
|
||||||
testFile = path.join(nwTempBuild, "1_LoremIpsum.htm")
|
testFile = os.path.join(nwTempBuild, "1_LoremIpsum.htm")
|
||||||
refFile = path.join(nwRef, "build", "1_LoremIpsum.htm")
|
refFile = os.path.join(nwRef, "build", "1_LoremIpsum.htm")
|
||||||
copyfile(projFile, testFile)
|
copyfile(projFile, testFile)
|
||||||
assert cmpFiles(testFile, refFile)
|
assert cmpFiles(testFile, refFile)
|
||||||
|
|
||||||
@@ -407,15 +476,15 @@ def testBuildTool(qtbot, nwTempBuild, nwLipsum, nwRef, nwTemp):
|
|||||||
assert nwBuild._saveDocument(nwBuild.FMT_NWD)
|
assert nwBuild._saveDocument(nwBuild.FMT_NWD)
|
||||||
assert nwBuild._saveDocument(nwBuild.FMT_HTM)
|
assert nwBuild._saveDocument(nwBuild.FMT_HTM)
|
||||||
|
|
||||||
projFile = path.join(nwLipsum, "Lorem Ipsum.nwd")
|
projFile = os.path.join(nwLipsum, "Lorem Ipsum.nwd")
|
||||||
testFile = path.join(nwTempBuild, "2_LoremIpsum.nwd")
|
testFile = os.path.join(nwTempBuild, "2_LoremIpsum.nwd")
|
||||||
refFile = path.join(nwRef, "build", "2_LoremIpsum.nwd")
|
refFile = os.path.join(nwRef, "build", "2_LoremIpsum.nwd")
|
||||||
copyfile(projFile, testFile)
|
copyfile(projFile, testFile)
|
||||||
assert cmpFiles(testFile, refFile)
|
assert cmpFiles(testFile, refFile)
|
||||||
|
|
||||||
projFile = path.join(nwLipsum, "Lorem Ipsum.htm")
|
projFile = os.path.join(nwLipsum, "Lorem Ipsum.htm")
|
||||||
testFile = path.join(nwTempBuild, "2_LoremIpsum.htm")
|
testFile = os.path.join(nwTempBuild, "2_LoremIpsum.htm")
|
||||||
refFile = path.join(nwRef, "build", "2_LoremIpsum.htm")
|
refFile = os.path.join(nwRef, "build", "2_LoremIpsum.htm")
|
||||||
copyfile(projFile, testFile)
|
copyfile(projFile, testFile)
|
||||||
assert cmpFiles(testFile, refFile)
|
assert cmpFiles(testFile, refFile)
|
||||||
|
|
||||||
@@ -438,27 +507,74 @@ def testBuildTool(qtbot, nwTempBuild, nwLipsum, nwRef, nwTemp):
|
|||||||
|
|
||||||
qtbot.mouseClick(nwBuild.buildNovel, Qt.LeftButton)
|
qtbot.mouseClick(nwBuild.buildNovel, Qt.LeftButton)
|
||||||
|
|
||||||
|
# Save files that can be compared
|
||||||
assert nwBuild._saveDocument(nwBuild.FMT_NWD)
|
assert nwBuild._saveDocument(nwBuild.FMT_NWD)
|
||||||
|
projFile = os.path.join(nwLipsum, "Lorem Ipsum.nwd")
|
||||||
|
testFile = os.path.join(nwTempBuild, "3_LoremIpsum.nwd")
|
||||||
|
refFile = os.path.join(nwRef, "build", "3_LoremIpsum.nwd")
|
||||||
|
copyfile(projFile, testFile)
|
||||||
|
assert cmpFiles(testFile, refFile)
|
||||||
|
|
||||||
assert nwBuild._saveDocument(nwBuild.FMT_HTM)
|
assert nwBuild._saveDocument(nwBuild.FMT_HTM)
|
||||||
|
projFile = os.path.join(nwLipsum, "Lorem Ipsum.htm")
|
||||||
projFile = path.join(nwLipsum, "Lorem Ipsum.nwd")
|
testFile = os.path.join(nwTempBuild, "3_LoremIpsum.htm")
|
||||||
testFile = path.join(nwTempBuild, "3_LoremIpsum.nwd")
|
refFile = os.path.join(nwRef, "build", "3_LoremIpsum.htm")
|
||||||
refFile = path.join(nwRef, "build", "3_LoremIpsum.nwd")
|
|
||||||
copyfile(projFile, testFile)
|
copyfile(projFile, testFile)
|
||||||
assert cmpFiles(testFile, refFile)
|
assert cmpFiles(testFile, refFile)
|
||||||
|
|
||||||
projFile = path.join(nwLipsum, "Lorem Ipsum.htm")
|
# Check the JSON files too at this stage
|
||||||
testFile = path.join(nwTempBuild, "3_LoremIpsum.htm")
|
assert nwBuild._saveDocument(nwBuild.FMT_JSON_H)
|
||||||
refFile = path.join(nwRef, "build", "3_LoremIpsum.htm")
|
projFile = os.path.join(nwLipsum, "Lorem Ipsum.json")
|
||||||
|
testFile = os.path.join(nwTempBuild, "3H_LoremIpsum.json")
|
||||||
|
refFile = os.path.join(nwRef, "build", "3H_LoremIpsum.json")
|
||||||
copyfile(projFile, testFile)
|
copyfile(projFile, testFile)
|
||||||
assert cmpFiles(testFile, refFile)
|
assert cmpFiles(testFile, refFile, [8])
|
||||||
|
|
||||||
|
assert nwBuild._saveDocument(nwBuild.FMT_JSON_M)
|
||||||
|
projFile = os.path.join(nwLipsum, "Lorem Ipsum.json")
|
||||||
|
testFile = os.path.join(nwTempBuild, "3M_LoremIpsum.json")
|
||||||
|
refFile = os.path.join(nwRef, "build", "3M_LoremIpsum.json")
|
||||||
|
copyfile(projFile, testFile)
|
||||||
|
assert cmpFiles(testFile, refFile, [8])
|
||||||
|
|
||||||
|
# Save other file types handled by Qt
|
||||||
|
# We assume the export itself by the Qt library works, so we just
|
||||||
|
# check that novelWriter successfully writes the files.
|
||||||
|
assert nwBuild._saveDocument(nwBuild.FMT_ODT)
|
||||||
|
assert os.path.isfile(os.path.join(nwLipsum, "Lorem Ipsum.odt"))
|
||||||
|
|
||||||
|
if not nwGUI.mainConf.osDarwin:
|
||||||
|
assert nwBuild._saveDocument(nwBuild.FMT_PDF)
|
||||||
|
assert os.path.isfile(os.path.join(nwLipsum, "Lorem Ipsum.pdf"))
|
||||||
|
|
||||||
|
assert nwBuild._saveDocument(nwBuild.FMT_MD)
|
||||||
|
assert os.path.isfile(os.path.join(nwLipsum, "Lorem Ipsum.md"))
|
||||||
|
|
||||||
|
assert nwBuild._saveDocument(nwBuild.FMT_TXT)
|
||||||
|
assert os.path.isfile(os.path.join(nwLipsum, "Lorem Ipsum.txt"))
|
||||||
|
|
||||||
|
# Close the build tool
|
||||||
|
htmlText = nwBuild.htmlText
|
||||||
|
htmlStyle = nwBuild.htmlStyle
|
||||||
|
nwdText = nwBuild.nwdText
|
||||||
|
buildTime = nwBuild.buildTime
|
||||||
|
nwBuild._doClose()
|
||||||
|
|
||||||
|
# Re-open build dialog from cahce
|
||||||
|
nwBuild = GuiBuildNovel(nwGUI, nwGUI.theProject)
|
||||||
|
|
||||||
|
assert nwBuild.htmlText == htmlText
|
||||||
|
assert nwBuild.htmlStyle == htmlStyle
|
||||||
|
assert nwBuild.nwdText == nwdText
|
||||||
|
assert nwBuild.buildTime == buildTime
|
||||||
|
|
||||||
|
nwBuild._doClose()
|
||||||
|
|
||||||
# qtbot.stopForInteraction()
|
# qtbot.stopForInteraction()
|
||||||
nwBuild._doClose()
|
|
||||||
nwGUI.closeMain()
|
nwGUI.closeMain()
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
def testMergeSplitTools(qtbot, nwTempGUI, nwLipsum, nwRef, nwTemp):
|
def testMergeSplitTools(qtbot, monkeypatch, yesToAll, nwTempGUI, nwLipsum, nwRef, nwTemp):
|
||||||
|
|
||||||
nwGUI = nw.main(["--testmode", "--config=%s" % nwLipsum, "--data=%s" % nwTemp])
|
nwGUI = nw.main(["--testmode", "--config=%s" % nwLipsum, "--data=%s" % nwTemp])
|
||||||
qtbot.addWidget(nwGUI)
|
qtbot.addWidget(nwGUI)
|
||||||
@@ -473,7 +589,13 @@ def testMergeSplitTools(qtbot, nwTempGUI, nwLipsum, nwRef, nwTemp):
|
|||||||
assert nwGUI.treeView.setSelectedHandle("45e6b01ca35c1")
|
assert nwGUI.treeView.setSelectedHandle("45e6b01ca35c1")
|
||||||
qtbot.wait(stepDelay)
|
qtbot.wait(stepDelay)
|
||||||
|
|
||||||
nwMerge = GuiDocMerge(nwGUI, nwGUI.theProject)
|
monkeypatch.setattr(GuiDocMerge, "exec_", lambda *args: None)
|
||||||
|
nwGUI.mainMenu.aMergeDocs.activate(QAction.Trigger)
|
||||||
|
qtbot.waitUntil(lambda: getGuiItem("GuiDocMerge") is not None, timeout=1000)
|
||||||
|
|
||||||
|
nwMerge = getGuiItem("GuiDocMerge")
|
||||||
|
assert isinstance(nwMerge, GuiDocMerge)
|
||||||
|
nwMerge.show()
|
||||||
qtbot.wait(stepDelay)
|
qtbot.wait(stepDelay)
|
||||||
|
|
||||||
nwMerge._doMerge()
|
nwMerge._doMerge()
|
||||||
@@ -481,17 +603,25 @@ def testMergeSplitTools(qtbot, nwTempGUI, nwLipsum, nwRef, nwTemp):
|
|||||||
|
|
||||||
assert nwGUI.theProject.projTree["73475cb40a568"] is not None
|
assert nwGUI.theProject.projTree["73475cb40a568"] is not None
|
||||||
|
|
||||||
projFile = path.join(nwLipsum, "content", "73475cb40a568.nwd")
|
projFile = os.path.join(nwLipsum, "content", "73475cb40a568.nwd")
|
||||||
testFile = path.join(nwTempGUI, "4_73475cb40a568.nwd")
|
testFile = os.path.join(nwTempGUI, "4_73475cb40a568.nwd")
|
||||||
refFile = path.join(nwRef, "gui", "4_73475cb40a568.nwd")
|
refFile = os.path.join(nwRef, "gui", "4_73475cb40a568.nwd")
|
||||||
copyfile(projFile, testFile)
|
copyfile(projFile, testFile)
|
||||||
assert cmpFiles(testFile, refFile)
|
assert cmpFiles(testFile, refFile)
|
||||||
|
|
||||||
# Split By Chapter
|
# Split By Chapter
|
||||||
assert nwGUI.treeView.setSelectedHandle("73475cb40a568")
|
assert nwGUI.treeView.setSelectedHandle("73475cb40a568")
|
||||||
qtbot.wait(stepDelay)
|
qtbot.wait(stepDelay)
|
||||||
nwSplit = GuiDocSplit(nwGUI, nwGUI.theProject)
|
|
||||||
|
monkeypatch.setattr(GuiDocSplit, "exec_", lambda *args: None)
|
||||||
|
nwGUI.mainMenu.aSplitDoc.activate(QAction.Trigger)
|
||||||
|
qtbot.waitUntil(lambda: getGuiItem("GuiDocSplit") is not None, timeout=1000)
|
||||||
|
|
||||||
|
nwSplit = getGuiItem("GuiDocSplit")
|
||||||
|
assert isinstance(nwSplit, GuiDocSplit)
|
||||||
|
nwSplit.show()
|
||||||
qtbot.wait(stepDelay)
|
qtbot.wait(stepDelay)
|
||||||
|
|
||||||
nwSplit.splitLevel.setCurrentIndex(1)
|
nwSplit.splitLevel.setCurrentIndex(1)
|
||||||
qtbot.wait(stepDelay)
|
qtbot.wait(stepDelay)
|
||||||
|
|
||||||
@@ -499,9 +629,9 @@ def testMergeSplitTools(qtbot, nwTempGUI, nwLipsum, nwRef, nwTemp):
|
|||||||
assert nwGUI.theProject.projTree["71ee45a3c0db9"] is not None
|
assert nwGUI.theProject.projTree["71ee45a3c0db9"] is not None
|
||||||
|
|
||||||
# This should give us back the file as it was before
|
# This should give us back the file as it was before
|
||||||
projFile = path.join(nwLipsum, "content", "71ee45a3c0db9.nwd")
|
projFile = os.path.join(nwLipsum, "content", "71ee45a3c0db9.nwd")
|
||||||
testFile = path.join(nwTempGUI, "4_71ee45a3c0db9.nwd")
|
testFile = os.path.join(nwTempGUI, "4_71ee45a3c0db9.nwd")
|
||||||
refFile = path.join(nwRef, "gui", "4_73475cb40a568.nwd")
|
refFile = os.path.join(nwRef, "gui", "4_73475cb40a568.nwd")
|
||||||
copyfile(projFile, testFile)
|
copyfile(projFile, testFile)
|
||||||
assert cmpFiles(testFile, refFile, [1])
|
assert cmpFiles(testFile, refFile, [1])
|
||||||
|
|
||||||
@@ -519,21 +649,21 @@ def testMergeSplitTools(qtbot, nwTempGUI, nwLipsum, nwRef, nwTemp):
|
|||||||
assert nwGUI.theProject.projTree["31489056e0916"] is not None
|
assert nwGUI.theProject.projTree["31489056e0916"] is not None
|
||||||
assert nwGUI.theProject.projTree["98010bd9270f9"] is not None
|
assert nwGUI.theProject.projTree["98010bd9270f9"] is not None
|
||||||
|
|
||||||
projFile = path.join(nwLipsum, "content", "25fc0e7096fc6.nwd")
|
projFile = os.path.join(nwLipsum, "content", "25fc0e7096fc6.nwd")
|
||||||
testFile = path.join(nwTempGUI, "5_25fc0e7096fc6.nwd")
|
testFile = os.path.join(nwTempGUI, "5_25fc0e7096fc6.nwd")
|
||||||
refFile = path.join(nwRef, "gui", "5_25fc0e7096fc6.nwd")
|
refFile = os.path.join(nwRef, "gui", "5_25fc0e7096fc6.nwd")
|
||||||
copyfile(projFile, testFile)
|
copyfile(projFile, testFile)
|
||||||
assert cmpFiles(testFile, refFile)
|
assert cmpFiles(testFile, refFile)
|
||||||
|
|
||||||
projFile = path.join(nwLipsum, "content", "31489056e0916.nwd")
|
projFile = os.path.join(nwLipsum, "content", "31489056e0916.nwd")
|
||||||
testFile = path.join(nwTempGUI, "5_31489056e0916.nwd")
|
testFile = os.path.join(nwTempGUI, "5_31489056e0916.nwd")
|
||||||
refFile = path.join(nwRef, "gui", "5_31489056e0916.nwd")
|
refFile = os.path.join(nwRef, "gui", "5_31489056e0916.nwd")
|
||||||
copyfile(projFile, testFile)
|
copyfile(projFile, testFile)
|
||||||
assert cmpFiles(testFile, refFile)
|
assert cmpFiles(testFile, refFile)
|
||||||
|
|
||||||
projFile = path.join(nwLipsum, "content", "98010bd9270f9.nwd")
|
projFile = os.path.join(nwLipsum, "content", "98010bd9270f9.nwd")
|
||||||
testFile = path.join(nwTempGUI, "5_98010bd9270f9.nwd")
|
testFile = os.path.join(nwTempGUI, "5_98010bd9270f9.nwd")
|
||||||
refFile = path.join(nwRef, "gui", "5_98010bd9270f9.nwd")
|
refFile = os.path.join(nwRef, "gui", "5_98010bd9270f9.nwd")
|
||||||
copyfile(projFile, testFile)
|
copyfile(projFile, testFile)
|
||||||
assert cmpFiles(testFile, refFile)
|
assert cmpFiles(testFile, refFile)
|
||||||
|
|
||||||
@@ -553,33 +683,33 @@ def testMergeSplitTools(qtbot, nwTempGUI, nwLipsum, nwRef, nwTemp):
|
|||||||
assert nwGUI.theProject.projTree["2858dcd1057d3"] is not None
|
assert nwGUI.theProject.projTree["2858dcd1057d3"] is not None
|
||||||
assert nwGUI.theProject.projTree["2fca346db6561"] is not None
|
assert nwGUI.theProject.projTree["2fca346db6561"] is not None
|
||||||
|
|
||||||
projFile = path.join(nwLipsum, "content", "1a6562590ef19.nwd")
|
projFile = os.path.join(nwLipsum, "content", "1a6562590ef19.nwd")
|
||||||
testFile = path.join(nwTempGUI, "5_25fc0e7096fc6.nwd")
|
testFile = os.path.join(nwTempGUI, "5_25fc0e7096fc6.nwd")
|
||||||
refFile = path.join(nwRef, "gui", "5_25fc0e7096fc6.nwd")
|
refFile = os.path.join(nwRef, "gui", "5_25fc0e7096fc6.nwd")
|
||||||
copyfile(projFile, testFile)
|
copyfile(projFile, testFile)
|
||||||
assert cmpFiles(testFile, refFile, [1])
|
assert cmpFiles(testFile, refFile, [1])
|
||||||
|
|
||||||
projFile = path.join(nwLipsum, "content", "031b4af5197ec.nwd")
|
projFile = os.path.join(nwLipsum, "content", "031b4af5197ec.nwd")
|
||||||
testFile = path.join(nwTempGUI, "5_031b4af5197ec.nwd")
|
testFile = os.path.join(nwTempGUI, "5_031b4af5197ec.nwd")
|
||||||
refFile = path.join(nwRef, "gui", "5_031b4af5197ec.nwd")
|
refFile = os.path.join(nwRef, "gui", "5_031b4af5197ec.nwd")
|
||||||
copyfile(projFile, testFile)
|
copyfile(projFile, testFile)
|
||||||
assert cmpFiles(testFile, refFile)
|
assert cmpFiles(testFile, refFile)
|
||||||
|
|
||||||
projFile = path.join(nwLipsum, "content", "41cfc0d1f2d12.nwd")
|
projFile = os.path.join(nwLipsum, "content", "41cfc0d1f2d12.nwd")
|
||||||
testFile = path.join(nwTempGUI, "5_41cfc0d1f2d12.nwd")
|
testFile = os.path.join(nwTempGUI, "5_41cfc0d1f2d12.nwd")
|
||||||
refFile = path.join(nwRef, "gui", "5_41cfc0d1f2d12.nwd")
|
refFile = os.path.join(nwRef, "gui", "5_41cfc0d1f2d12.nwd")
|
||||||
copyfile(projFile, testFile)
|
copyfile(projFile, testFile)
|
||||||
assert cmpFiles(testFile, refFile)
|
assert cmpFiles(testFile, refFile)
|
||||||
|
|
||||||
projFile = path.join(nwLipsum, "content", "2858dcd1057d3.nwd")
|
projFile = os.path.join(nwLipsum, "content", "2858dcd1057d3.nwd")
|
||||||
testFile = path.join(nwTempGUI, "5_2858dcd1057d3.nwd")
|
testFile = os.path.join(nwTempGUI, "5_2858dcd1057d3.nwd")
|
||||||
refFile = path.join(nwRef, "gui", "5_2858dcd1057d3.nwd")
|
refFile = os.path.join(nwRef, "gui", "5_2858dcd1057d3.nwd")
|
||||||
copyfile(projFile, testFile)
|
copyfile(projFile, testFile)
|
||||||
assert cmpFiles(testFile, refFile)
|
assert cmpFiles(testFile, refFile)
|
||||||
|
|
||||||
projFile = path.join(nwLipsum, "content", "2fca346db6561.nwd")
|
projFile = os.path.join(nwLipsum, "content", "2fca346db6561.nwd")
|
||||||
testFile = path.join(nwTempGUI, "5_2fca346db6561.nwd")
|
testFile = os.path.join(nwTempGUI, "5_2fca346db6561.nwd")
|
||||||
refFile = path.join(nwRef, "gui", "5_2fca346db6561.nwd")
|
refFile = os.path.join(nwRef, "gui", "5_2fca346db6561.nwd")
|
||||||
copyfile(projFile, testFile)
|
copyfile(projFile, testFile)
|
||||||
assert cmpFiles(testFile, refFile)
|
assert cmpFiles(testFile, refFile)
|
||||||
|
|
||||||
@@ -587,7 +717,11 @@ def testMergeSplitTools(qtbot, nwTempGUI, nwLipsum, nwRef, nwTemp):
|
|||||||
nwGUI.closeMain()
|
nwGUI.closeMain()
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
def testNewProjectWizard(qtbot, nwLipsum, nwTemp):
|
def testNewProjectWizard(qtbot, monkeypatch, yesToAll, nwMinimal, nwTemp):
|
||||||
|
|
||||||
|
if sys.platform.startswith("darwin"):
|
||||||
|
# Disable for macOS because the test segfaults on QWizard.show()
|
||||||
|
return
|
||||||
|
|
||||||
from PyQt5.QtWidgets import QWizard
|
from PyQt5.QtWidgets import QWizard
|
||||||
from nw.gui.projwizard import (
|
from nw.gui.projwizard import (
|
||||||
@@ -595,18 +729,54 @@ def testNewProjectWizard(qtbot, nwLipsum, nwTemp):
|
|||||||
ProjWizardCustomPage, ProjWizardFinalPage
|
ProjWizardCustomPage, ProjWizardFinalPage
|
||||||
)
|
)
|
||||||
|
|
||||||
nwGUI = nw.main(["--testmode", "--config=%s" % nwLipsum, "--data=%s" % nwTemp])
|
nwGUI = nw.main(["--testmode", "--config=%s" % nwTemp, "--data=%s" % nwTemp])
|
||||||
qtbot.addWidget(nwGUI)
|
qtbot.addWidget(nwGUI)
|
||||||
nwGUI.show()
|
nwGUI.show()
|
||||||
qtbot.waitForWindowShown(nwGUI)
|
qtbot.waitForWindowShown(nwGUI)
|
||||||
qtbot.wait(stepDelay)
|
qtbot.wait(stepDelay)
|
||||||
|
|
||||||
for wStep in range(3):
|
##
|
||||||
|
# Test New Project Function
|
||||||
|
##
|
||||||
|
|
||||||
# The Wizard
|
# New with a project open should cause an error
|
||||||
nwWiz = GuiProjectWizard(nwGUI)
|
assert nwGUI.openProject(nwMinimal)
|
||||||
nwWiz.show()
|
assert not nwGUI.newProject()
|
||||||
qtbot.waitForWindowShown(nwWiz)
|
|
||||||
|
# Close project, but call with invalid path
|
||||||
|
assert nwGUI.closeProject()
|
||||||
|
monkeypatch.setattr(nwGUI, "showNewProjectDialog", lambda *args: None)
|
||||||
|
assert not nwGUI.newProject()
|
||||||
|
|
||||||
|
# Now, with an empty dictionary
|
||||||
|
monkeypatch.setattr(nwGUI, "showNewProjectDialog", lambda *args: {})
|
||||||
|
assert not nwGUI.newProject()
|
||||||
|
|
||||||
|
# Now, with a non-empty folder
|
||||||
|
monkeypatch.setattr(nwGUI, "showNewProjectDialog", lambda *args: {"projPath": nwMinimal})
|
||||||
|
assert not nwGUI.newProject()
|
||||||
|
|
||||||
|
monkeypatch.undo()
|
||||||
|
|
||||||
|
##
|
||||||
|
# Test the Wizard
|
||||||
|
##
|
||||||
|
|
||||||
|
monkeypatch.setattr(GuiProjectWizard, "exec_", lambda *args: None)
|
||||||
|
nwGUI.mainConf.lastPath = " "
|
||||||
|
|
||||||
|
nwGUI.closeProject()
|
||||||
|
nwGUI.showNewProjectDialog()
|
||||||
|
qtbot.waitUntil(lambda: getGuiItem("GuiProjectWizard") is not None, timeout=1000)
|
||||||
|
|
||||||
|
nwWiz = getGuiItem("GuiProjectWizard")
|
||||||
|
assert isinstance(nwWiz, GuiProjectWizard)
|
||||||
|
nwWiz.show()
|
||||||
|
qtbot.wait(stepDelay)
|
||||||
|
|
||||||
|
for wStep in range(4):
|
||||||
|
# This does not actually create the project, it just generates the
|
||||||
|
# dictionary that defines it.
|
||||||
|
|
||||||
# Intro Page
|
# Intro Page
|
||||||
introPage = nwWiz.currentPage()
|
introPage = nwWiz.currentPage()
|
||||||
@@ -614,16 +784,16 @@ def testNewProjectWizard(qtbot, nwLipsum, nwTemp):
|
|||||||
assert not nwWiz.button(QWizard.NextButton).isEnabled()
|
assert not nwWiz.button(QWizard.NextButton).isEnabled()
|
||||||
|
|
||||||
qtbot.wait(stepDelay)
|
qtbot.wait(stepDelay)
|
||||||
for c in "Test Minimal":
|
for c in ("Test Minimal %d" % wStep):
|
||||||
qtbot.keyClick(introPage.projName, c, delay=keyDelay)
|
qtbot.keyClick(introPage.projName, c, delay=typeDelay)
|
||||||
|
|
||||||
qtbot.wait(stepDelay)
|
qtbot.wait(stepDelay)
|
||||||
for c in "Minimal Novel":
|
for c in "Minimal Novel":
|
||||||
qtbot.keyClick(introPage.projTitle, c, delay=keyDelay)
|
qtbot.keyClick(introPage.projTitle, c, delay=typeDelay)
|
||||||
|
|
||||||
qtbot.wait(stepDelay)
|
qtbot.wait(stepDelay)
|
||||||
for c in "Jane Doe":
|
for c in "Jane Doe":
|
||||||
qtbot.keyClick(introPage.projAuthors, c, delay=keyDelay)
|
qtbot.keyClick(introPage.projAuthors, c, delay=typeDelay)
|
||||||
|
|
||||||
# Setting projName should activate the button
|
# Setting projName should activate the button
|
||||||
assert nwWiz.button(QWizard.NextButton).isEnabled()
|
assert nwWiz.button(QWizard.NextButton).isEnabled()
|
||||||
@@ -636,10 +806,20 @@ def testNewProjectWizard(qtbot, nwLipsum, nwTemp):
|
|||||||
assert isinstance(storagePage, ProjWizardFolderPage)
|
assert isinstance(storagePage, ProjWizardFolderPage)
|
||||||
assert not nwWiz.button(QWizard.NextButton).isEnabled()
|
assert not nwWiz.button(QWizard.NextButton).isEnabled()
|
||||||
|
|
||||||
|
if wStep == 0:
|
||||||
|
# Check invalid path first, the first time we reach here
|
||||||
|
monkeypatch.setattr(QFileDialog, "getExistingDirectory", lambda *a, **kw: "")
|
||||||
|
qtbot.wait(stepDelay)
|
||||||
|
qtbot.mouseClick(storagePage.browseButton, Qt.LeftButton, delay=100)
|
||||||
|
assert storagePage.projPath.text() == ""
|
||||||
|
|
||||||
|
# Then, we always return nwMinimal as path
|
||||||
|
monkeypatch.setattr(QFileDialog, "getExistingDirectory", lambda *a, **kw: nwMinimal)
|
||||||
|
|
||||||
qtbot.wait(stepDelay)
|
qtbot.wait(stepDelay)
|
||||||
projPath = path.join(nwTemp, "dummy")
|
qtbot.mouseClick(storagePage.browseButton, Qt.LeftButton, delay=100)
|
||||||
for c in projPath:
|
projPath = os.path.join(nwMinimal, "Test Minimal %d" % wStep)
|
||||||
qtbot.keyClick(storagePage.projPath, c, delay=keyDelay)
|
assert storagePage.projPath.text() == projPath
|
||||||
|
|
||||||
# Setting projPath should activate the button
|
# Setting projPath should activate the button
|
||||||
assert nwWiz.button(QWizard.NextButton).isEnabled()
|
assert nwWiz.button(QWizard.NextButton).isEnabled()
|
||||||
@@ -658,13 +838,15 @@ def testNewProjectWizard(qtbot, nwLipsum, nwTemp):
|
|||||||
elif wStep == 1:
|
elif wStep == 1:
|
||||||
popPage.popCustom.setChecked(True)
|
popPage.popCustom.setChecked(True)
|
||||||
elif wStep == 2:
|
elif wStep == 2:
|
||||||
|
popPage.popCustom.setChecked(True)
|
||||||
|
elif wStep == 3:
|
||||||
popPage.popSample.setChecked(True)
|
popPage.popSample.setChecked(True)
|
||||||
|
|
||||||
qtbot.wait(stepDelay)
|
qtbot.wait(stepDelay)
|
||||||
qtbot.mouseClick(nwWiz.button(QWizard.NextButton), Qt.LeftButton)
|
qtbot.mouseClick(nwWiz.button(QWizard.NextButton), Qt.LeftButton)
|
||||||
|
|
||||||
# Custom Page
|
# Custom Page
|
||||||
if wStep == 1:
|
if wStep == 1 or wStep == 2:
|
||||||
customPage = nwWiz.currentPage()
|
customPage = nwWiz.currentPage()
|
||||||
assert isinstance(customPage, ProjWizardCustomPage)
|
assert isinstance(customPage, ProjWizardCustomPage)
|
||||||
assert nwWiz.button(QWizard.NextButton).isEnabled()
|
assert nwWiz.button(QWizard.NextButton).isEnabled()
|
||||||
@@ -676,25 +858,29 @@ def testNewProjectWizard(qtbot, nwLipsum, nwTemp):
|
|||||||
customPage.addObject.setChecked(True)
|
customPage.addObject.setChecked(True)
|
||||||
customPage.addEntity.setChecked(True)
|
customPage.addEntity.setChecked(True)
|
||||||
|
|
||||||
|
if wStep == 2:
|
||||||
|
customPage.numChapters.setValue(0)
|
||||||
|
customPage.numScenes.setValue(10)
|
||||||
|
customPage.chFolders.setChecked(False)
|
||||||
|
|
||||||
qtbot.wait(stepDelay)
|
qtbot.wait(stepDelay)
|
||||||
qtbot.mouseClick(nwWiz.button(QWizard.NextButton), Qt.LeftButton)
|
qtbot.mouseClick(nwWiz.button(QWizard.NextButton), Qt.LeftButton)
|
||||||
|
|
||||||
# Final Page
|
# Final Page
|
||||||
finalPage = nwWiz.currentPage()
|
finalPage = nwWiz.currentPage()
|
||||||
assert isinstance(finalPage, ProjWizardFinalPage)
|
assert isinstance(finalPage, ProjWizardFinalPage)
|
||||||
assert nwWiz.button(QWizard.FinishButton).isEnabled()
|
assert nwWiz.button(QWizard.FinishButton).isEnabled() # But we don't click it
|
||||||
qtbot.mouseClick(nwWiz.button(QWizard.FinishButton), Qt.LeftButton)
|
|
||||||
|
|
||||||
# Check Data
|
# Check Data
|
||||||
projData = nwGUI._assembleProjectWizardData(nwWiz)
|
projData = nwGUI._assembleProjectWizardData(nwWiz)
|
||||||
assert projData["projName"] == "Test Minimal"
|
assert projData["projName"] == "Test Minimal %d" % wStep
|
||||||
assert projData["projTitle"] == "Minimal Novel"
|
assert projData["projTitle"] == "Minimal Novel"
|
||||||
assert projData["projAuthors"] == "Jane Doe"
|
assert projData["projAuthors"] == "Jane Doe"
|
||||||
assert projData["projPath"] == projPath
|
assert projData["projPath"] == projPath
|
||||||
assert projData["popMinimal"] == (wStep == 0)
|
assert projData["popMinimal"] == (wStep == 0)
|
||||||
assert projData["popCustom"] == (wStep == 1)
|
assert projData["popCustom"] == (wStep == 1 or wStep == 2)
|
||||||
assert projData["popSample"] == (wStep == 2)
|
assert projData["popSample"] == (wStep == 3)
|
||||||
if wStep == 1:
|
if wStep == 1 or wStep == 2:
|
||||||
assert projData["addRoots"] == [
|
assert projData["addRoots"] == [
|
||||||
nwItemClass.PLOT,
|
nwItemClass.PLOT,
|
||||||
nwItemClass.CHARACTER,
|
nwItemClass.CHARACTER,
|
||||||
@@ -703,20 +889,32 @@ def testNewProjectWizard(qtbot, nwLipsum, nwTemp):
|
|||||||
nwItemClass.OBJECT,
|
nwItemClass.OBJECT,
|
||||||
nwItemClass.ENTITY,
|
nwItemClass.ENTITY,
|
||||||
]
|
]
|
||||||
assert projData["numChapters"] == 5
|
if wStep == 1:
|
||||||
assert projData["numScenes"] == 5
|
assert projData["numChapters"] == 5
|
||||||
assert projData["chFolders"]
|
assert projData["numScenes"] == 5
|
||||||
|
assert projData["chFolders"]
|
||||||
|
else:
|
||||||
|
assert projData["numChapters"] == 0
|
||||||
|
assert projData["numScenes"] == 10
|
||||||
|
assert not projData["chFolders"]
|
||||||
else:
|
else:
|
||||||
assert projData["addRoots"] == []
|
assert projData["addRoots"] == []
|
||||||
assert projData["numChapters"] == 0
|
assert projData["numChapters"] == 0
|
||||||
assert projData["numScenes"] == 0
|
assert projData["numScenes"] == 0
|
||||||
assert not projData["chFolders"]
|
assert not projData["chFolders"]
|
||||||
|
|
||||||
|
# Restart the wizard for next iteration
|
||||||
|
nwWiz.restart()
|
||||||
|
|
||||||
|
nwWiz.reject()
|
||||||
|
nwWiz.close()
|
||||||
|
|
||||||
# qtbot.stopForInteraction()
|
# qtbot.stopForInteraction()
|
||||||
nwGUI.closeMain()
|
nwGUI.closeMain()
|
||||||
|
nwGUI.close()
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
def testLoadProject(qtbot, nwMinimal, nwTemp):
|
def testLoadProject(qtbot, monkeypatch, yesToAll, nwMinimal, nwTemp):
|
||||||
nwGUI = nw.main(["--testmode", "--config=%s" % nwMinimal, "--data=%s" % nwTemp])
|
nwGUI = nw.main(["--testmode", "--config=%s" % nwMinimal, "--data=%s" % nwTemp])
|
||||||
qtbot.addWidget(nwGUI)
|
qtbot.addWidget(nwGUI)
|
||||||
nwGUI.show()
|
nwGUI.show()
|
||||||
@@ -726,7 +924,13 @@ def testLoadProject(qtbot, nwMinimal, nwTemp):
|
|||||||
assert nwGUI.openProject(nwMinimal)
|
assert nwGUI.openProject(nwMinimal)
|
||||||
assert nwGUI.closeProject()
|
assert nwGUI.closeProject()
|
||||||
|
|
||||||
nwLoad = GuiProjectLoad(nwGUI)
|
monkeypatch.setattr(GuiProjectLoad, "exec_", lambda *args: None)
|
||||||
|
monkeypatch.setattr(GuiProjectLoad, "result", lambda *args: QDialog.Accepted)
|
||||||
|
nwGUI.mainMenu.aOpenProject.activate(QAction.Trigger)
|
||||||
|
qtbot.waitUntil(lambda: getGuiItem("GuiProjectLoad") is not None, timeout=1000)
|
||||||
|
|
||||||
|
nwLoad = getGuiItem("GuiProjectLoad")
|
||||||
|
assert isinstance(nwLoad, GuiProjectLoad)
|
||||||
nwLoad.show()
|
nwLoad.show()
|
||||||
|
|
||||||
recentCount = nwLoad.listBox.topLevelItemCount()
|
recentCount = nwLoad.listBox.topLevelItemCount()
|
||||||
@@ -745,6 +949,7 @@ def testLoadProject(qtbot, nwMinimal, nwTemp):
|
|||||||
assert nwLoad.openPath == selPath
|
assert nwLoad.openPath == selPath
|
||||||
assert nwLoad.openState == nwLoad.OPEN_STATE
|
assert nwLoad.openState == nwLoad.OPEN_STATE
|
||||||
|
|
||||||
|
# Just create a new project load from scratch for the rest of the test
|
||||||
del nwLoad
|
del nwLoad
|
||||||
nwLoad = GuiProjectLoad(nwGUI)
|
nwLoad = GuiProjectLoad(nwGUI)
|
||||||
nwLoad.show()
|
nwLoad.show()
|
||||||
@@ -762,12 +967,19 @@ def testLoadProject(qtbot, nwMinimal, nwTemp):
|
|||||||
nwLoad._keyPressDelete()
|
nwLoad._keyPressDelete()
|
||||||
assert nwLoad.listBox.topLevelItemCount() == recentCount - 1
|
assert nwLoad.listBox.topLevelItemCount() == recentCount - 1
|
||||||
|
|
||||||
nwLoad.close()
|
getFile = os.path.join(nwMinimal, "nwProject.nwx")
|
||||||
|
monkeypatch.setattr(QFileDialog, "getOpenFileName", lambda *args, **kwargs: (getFile, None))
|
||||||
|
qtbot.mouseClick(nwLoad.browseButton, Qt.LeftButton)
|
||||||
|
assert nwLoad.openPath == nwMinimal
|
||||||
|
assert nwLoad.openState == nwLoad.OPEN_STATE
|
||||||
# qtbot.stopForInteraction()
|
# qtbot.stopForInteraction()
|
||||||
|
|
||||||
|
nwLoad.close()
|
||||||
nwGUI.closeMain()
|
nwGUI.closeMain()
|
||||||
|
nwGUI.close()
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
def testPreferences(qtbot, nwMinimal, nwTemp, nwRef, tmpConf):
|
def testPreferences(qtbot, monkeypatch, yesToAll, nwMinimal, nwTemp, nwRef, tmpConf):
|
||||||
nwGUI = nw.main(["--testmode", "--config=%s" % nwMinimal, "--data=%s" % nwTemp])
|
nwGUI = nw.main(["--testmode", "--config=%s" % nwMinimal, "--data=%s" % nwTemp])
|
||||||
qtbot.addWidget(nwGUI)
|
qtbot.addWidget(nwGUI)
|
||||||
nwGUI.show()
|
nwGUI.show()
|
||||||
@@ -775,12 +987,19 @@ def testPreferences(qtbot, nwMinimal, nwTemp, nwRef, tmpConf):
|
|||||||
qtbot.wait(stepDelay)
|
qtbot.wait(stepDelay)
|
||||||
|
|
||||||
assert nwGUI.openProject(nwMinimal)
|
assert nwGUI.openProject(nwMinimal)
|
||||||
nwPrefs = GuiPreferences(nwGUI, nwGUI.theProject)
|
|
||||||
|
monkeypatch.setattr(GuiPreferences, "exec_", lambda *args: None)
|
||||||
|
monkeypatch.setattr(GuiPreferences, "result", lambda *args: QDialog.Accepted)
|
||||||
|
nwGUI.mainMenu.aPreferences.activate(QAction.Trigger)
|
||||||
|
qtbot.waitUntil(lambda: getGuiItem("GuiPreferences") is not None, timeout=1000)
|
||||||
|
|
||||||
|
nwPrefs = getGuiItem("GuiPreferences")
|
||||||
|
assert isinstance(nwPrefs, GuiPreferences)
|
||||||
nwPrefs.show()
|
nwPrefs.show()
|
||||||
|
|
||||||
# Override Config
|
# Override Config
|
||||||
tmpConf.showGUI = False
|
|
||||||
tmpConf.confPath = nwMinimal
|
tmpConf.confPath = nwMinimal
|
||||||
|
tmpConf.showGUI = False
|
||||||
nwGUI.mainConf = tmpConf
|
nwGUI.mainConf = tmpConf
|
||||||
nwPrefs.mainConf = tmpConf
|
nwPrefs.mainConf = tmpConf
|
||||||
nwPrefs.tabGeneral.mainConf = tmpConf
|
nwPrefs.tabGeneral.mainConf = tmpConf
|
||||||
@@ -792,7 +1011,7 @@ def testPreferences(qtbot, nwMinimal, nwTemp, nwRef, tmpConf):
|
|||||||
qtbot.wait(keyDelay)
|
qtbot.wait(keyDelay)
|
||||||
tabGeneral = nwPrefs.tabGeneral
|
tabGeneral = nwPrefs.tabGeneral
|
||||||
nwPrefs._tabBox.setCurrentWidget(tabGeneral)
|
nwPrefs._tabBox.setCurrentWidget(tabGeneral)
|
||||||
tabGeneral.backupPath = nwTemp
|
tabGeneral.backupPath = "no/where"
|
||||||
|
|
||||||
qtbot.wait(keyDelay)
|
qtbot.wait(keyDelay)
|
||||||
assert not tabGeneral.preferDarkIcons.isChecked()
|
assert not tabGeneral.preferDarkIcons.isChecked()
|
||||||
@@ -804,6 +1023,16 @@ def testPreferences(qtbot, nwMinimal, nwTemp, nwRef, tmpConf):
|
|||||||
qtbot.mouseClick(tabGeneral.showFullPath, Qt.LeftButton)
|
qtbot.mouseClick(tabGeneral.showFullPath, Qt.LeftButton)
|
||||||
assert not tabGeneral.showFullPath.isChecked()
|
assert not tabGeneral.showFullPath.isChecked()
|
||||||
|
|
||||||
|
# Check Browse button
|
||||||
|
monkeypatch.setattr(QFileDialog, "getExistingDirectory", lambda *args, **kwargs: "")
|
||||||
|
assert not tabGeneral._backupFolder()
|
||||||
|
monkeypatch.setattr(QFileDialog, "getExistingDirectory", lambda *args, **kwargs: "some/dir")
|
||||||
|
qtbot.mouseClick(tabGeneral.backupGetPath, Qt.LeftButton)
|
||||||
|
|
||||||
|
# Check font button
|
||||||
|
monkeypatch.setattr(QFontDialog, "getFont", lambda font, obj: (font, True))
|
||||||
|
qtbot.mouseClick(tabGeneral.fontButton, Qt.LeftButton)
|
||||||
|
|
||||||
qtbot.wait(keyDelay)
|
qtbot.wait(keyDelay)
|
||||||
assert not tabGeneral.backupOnClose.isChecked()
|
assert not tabGeneral.backupOnClose.isChecked()
|
||||||
qtbot.mouseClick(tabGeneral.backupOnClose, Qt.LeftButton)
|
qtbot.mouseClick(tabGeneral.backupOnClose, Qt.LeftButton)
|
||||||
@@ -819,6 +1048,9 @@ def testPreferences(qtbot, nwMinimal, nwTemp, nwRef, tmpConf):
|
|||||||
tabLayout = nwPrefs.tabLayout
|
tabLayout = nwPrefs.tabLayout
|
||||||
nwPrefs._tabBox.setCurrentWidget(tabLayout)
|
nwPrefs._tabBox.setCurrentWidget(tabLayout)
|
||||||
|
|
||||||
|
qtbot.wait(keyDelay)
|
||||||
|
qtbot.mouseClick(tabLayout.fontButton, Qt.LeftButton)
|
||||||
|
|
||||||
qtbot.wait(keyDelay)
|
qtbot.wait(keyDelay)
|
||||||
tabLayout.textStyleSize.setValue(13)
|
tabLayout.textStyleSize.setValue(13)
|
||||||
tabLayout.textFlowMax.setValue(700)
|
tabLayout.textFlowMax.setValue(700)
|
||||||
@@ -890,26 +1122,73 @@ def testPreferences(qtbot, nwMinimal, nwTemp, nwRef, tmpConf):
|
|||||||
assert not tabAutoRep.autoReplaceDash.isEnabled()
|
assert not tabAutoRep.autoReplaceDash.isEnabled()
|
||||||
assert not tabAutoRep.autoReplaceDots.isEnabled()
|
assert not tabAutoRep.autoReplaceDots.isEnabled()
|
||||||
|
|
||||||
|
monkeypatch.setattr(QuotesDialog, "selectedQuote", "'")
|
||||||
|
monkeypatch.setattr(QuotesDialog, "exec_", lambda *args: QDialog.Accepted)
|
||||||
|
qtbot.mouseClick(tabAutoRep.btnDoubleStyleC, Qt.LeftButton)
|
||||||
|
|
||||||
# Save and Check Config
|
# Save and Check Config
|
||||||
# qtbot.stopForInteraction()
|
|
||||||
qtbot.mouseClick(nwPrefs.buttonBox.button(QDialogButtonBox.Ok), Qt.LeftButton)
|
qtbot.mouseClick(nwPrefs.buttonBox.button(QDialogButtonBox.Ok), Qt.LeftButton)
|
||||||
|
|
||||||
assert tmpConf.confChanged
|
assert tmpConf.confChanged
|
||||||
assert tmpConf.backupPath == nwTemp
|
|
||||||
tmpConf.backupPath = ""
|
|
||||||
tmpConf.lastPath = ""
|
tmpConf.lastPath = ""
|
||||||
|
|
||||||
assert nwGUI.mainConf.saveConfig()
|
assert nwGUI.mainConf.saveConfig()
|
||||||
|
|
||||||
|
# qtbot.stopForInteraction()
|
||||||
nwGUI.closeMain()
|
nwGUI.closeMain()
|
||||||
|
|
||||||
refConf = path.join(nwRef, "novelwriter_prefs.conf")
|
refConf = os.path.join(nwRef, "novelwriter_prefs.conf")
|
||||||
projConf = path.join(nwGUI.mainConf.confPath, "novelwriter.conf")
|
projConf = os.path.join(nwGUI.mainConf.confPath, "novelwriter.conf")
|
||||||
testConf = path.join(nwTemp, "novelwriter_prefs.conf")
|
testConf = os.path.join(nwTemp, "novelwriter_prefs.conf")
|
||||||
copyfile(projConf, testConf)
|
copyfile(projConf, testConf)
|
||||||
ignoreLines = [
|
ignoreLines = [
|
||||||
2, # Timestamp
|
2, # Timestamp
|
||||||
11, 12, 13, 14, 15, 16, 17, # Window sizes
|
11, 12, 13, 14, 15, 16, 17, # Window sizes
|
||||||
7, 25, # Fonts (depends in system default)
|
7, 25, # Fonts (depends on system default)
|
||||||
]
|
]
|
||||||
assert cmpFiles(testConf, refConf, ignoreLines)
|
assert cmpFiles(testConf, refConf, ignoreLines)
|
||||||
|
|
||||||
|
@pytest.mark.gui
|
||||||
|
def testQuotesDialog(qtbot, yesToAll, nwMinimal, nwTemp):
|
||||||
|
nwGUI = nw.main(["--testmode", "--config=%s" % nwMinimal, "--data=%s" % nwTemp, nwMinimal])
|
||||||
|
qtbot.addWidget(nwGUI)
|
||||||
|
nwGUI.show()
|
||||||
|
qtbot.waitForWindowShown(nwGUI)
|
||||||
|
qtbot.wait(stepDelay)
|
||||||
|
|
||||||
|
nwQuot = QuotesDialog(nwGUI)
|
||||||
|
nwQuot.show()
|
||||||
|
|
||||||
|
lastItem = ""
|
||||||
|
for i in range(nwQuot.listBox.count()):
|
||||||
|
anItem = nwQuot.listBox.item(i)
|
||||||
|
assert isinstance(anItem, QListWidgetItem)
|
||||||
|
nwQuot.listBox.clearSelection()
|
||||||
|
nwQuot.listBox.setCurrentItem(anItem, QItemSelectionModel.Select)
|
||||||
|
lastItem = anItem.text()[2]
|
||||||
|
assert nwQuot.previewLabel.text() == lastItem
|
||||||
|
|
||||||
|
nwQuot._doAccept()
|
||||||
|
assert nwQuot.result() == QDialog.Accepted
|
||||||
|
assert nwQuot.selectedQuote == lastItem
|
||||||
|
|
||||||
|
# qtbot.stopForInteraction()
|
||||||
|
nwQuot._doReject()
|
||||||
|
nwQuot.close()
|
||||||
|
nwGUI.closeMain()
|
||||||
|
nwGUI.close()
|
||||||
|
|
||||||
|
@pytest.mark.gui
|
||||||
|
def testDialogsOpenClose(qtbot, monkeypatch, yesToAll, nwMinimal, nwTemp):
|
||||||
|
nwGUI = nw.main(["--testmode", "--config=%s" % nwMinimal, "--data=%s" % nwTemp, nwMinimal])
|
||||||
|
qtbot.addWidget(nwGUI)
|
||||||
|
nwGUI.show()
|
||||||
|
qtbot.waitForWindowShown(nwGUI)
|
||||||
|
qtbot.wait(stepDelay)
|
||||||
|
|
||||||
|
monkeypatch.setattr(QFileDialog, "getExistingDirectory", lambda *args, **kwargs: nwTemp)
|
||||||
|
assert nwGUI.selectProjectPath() == nwTemp
|
||||||
|
|
||||||
|
# qtbot.stopForInteraction()
|
||||||
|
nwGUI.closeMain()
|
||||||
|
nwGUI.close()
|
||||||
|
|||||||
+1025
-90
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,538 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""novelWriter Project Class Tester
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
|
||||||
|
from shutil import copyfile
|
||||||
|
|
||||||
|
from nwtools import cmpFiles
|
||||||
|
|
||||||
|
from nw.core.project import NWProject
|
||||||
|
from nw.core.index import NWIndex
|
||||||
|
from nw.constants import nwItemClass, nwItemLayout
|
||||||
|
|
||||||
|
@pytest.mark.project
|
||||||
|
def testIndexBuildCheck(monkeypatch, nwLipsum, nwDummy, nwTempProj, nwRef):
|
||||||
|
"""Test core functionality of scaning, saving, loading and checking
|
||||||
|
the index cache file.
|
||||||
|
"""
|
||||||
|
projFile = os.path.join(nwLipsum, "meta", "tagsIndex.json")
|
||||||
|
testFile = os.path.join(nwTempProj, "1_tagsIndex.json")
|
||||||
|
refFile = os.path.join(nwRef, "proj", "1_tagsIndex.json")
|
||||||
|
|
||||||
|
theProject = NWProject(nwDummy)
|
||||||
|
theProject.projTree.setSeed(42)
|
||||||
|
assert theProject.openProject(nwLipsum)
|
||||||
|
|
||||||
|
theProject.mainConf.debugInfo = True
|
||||||
|
monkeypatch.setattr("nw.core.index.time", lambda: 123.4)
|
||||||
|
|
||||||
|
theIndex = NWIndex(theProject, nwDummy)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
for tItem in theProject.projTree:
|
||||||
|
assert theIndex.reIndexHandle(tItem.itemHandle) is notIndexable.get(tItem.itemHandle, True)
|
||||||
|
|
||||||
|
assert not theIndex.reIndexHandle(None)
|
||||||
|
|
||||||
|
# Dummy exception function
|
||||||
|
def doPanic(*arg, **kwargs):
|
||||||
|
raise Exception
|
||||||
|
|
||||||
|
# Make the save fail
|
||||||
|
monkeypatch.setattr(json, "dumps", doPanic)
|
||||||
|
assert not theIndex.saveIndex()
|
||||||
|
|
||||||
|
# Make the save pass
|
||||||
|
monkeypatch.undo()
|
||||||
|
assert theIndex.saveIndex()
|
||||||
|
|
||||||
|
# Take a copy of the index
|
||||||
|
tagIndex = str(theIndex.tagIndex)
|
||||||
|
refIndex = str(theIndex.refIndex)
|
||||||
|
novelIndex = str(theIndex.novelIndex)
|
||||||
|
noteIndex = str(theIndex.noteIndex)
|
||||||
|
textCounts = str(theIndex.textCounts)
|
||||||
|
|
||||||
|
# Delete a handle
|
||||||
|
assert theIndex.tagIndex.get("Bod", None) is not None
|
||||||
|
assert theIndex.refIndex.get("4c4f28287af27", None) is not None
|
||||||
|
assert theIndex.noteIndex.get("4c4f28287af27", None) is not None
|
||||||
|
assert theIndex.textCounts.get("4c4f28287af27", None) is not None
|
||||||
|
theIndex.deleteHandle("4c4f28287af27")
|
||||||
|
assert theIndex.tagIndex.get("Bod", None) is None
|
||||||
|
assert theIndex.refIndex.get("4c4f28287af27", None) is None
|
||||||
|
assert theIndex.noteIndex.get("4c4f28287af27", None) is None
|
||||||
|
assert theIndex.textCounts.get("4c4f28287af27", None) is None
|
||||||
|
|
||||||
|
# Clear the index
|
||||||
|
theIndex.clearIndex()
|
||||||
|
assert not theIndex.tagIndex
|
||||||
|
assert not theIndex.refIndex
|
||||||
|
assert not theIndex.novelIndex
|
||||||
|
assert not theIndex.noteIndex
|
||||||
|
assert not theIndex.textCounts
|
||||||
|
|
||||||
|
# Make the load fail
|
||||||
|
monkeypatch.setattr(json, "loads", doPanic)
|
||||||
|
assert not theIndex.loadIndex()
|
||||||
|
|
||||||
|
# Make the load pass
|
||||||
|
monkeypatch.undo()
|
||||||
|
assert theIndex.loadIndex()
|
||||||
|
|
||||||
|
assert str(theIndex.tagIndex) == tagIndex
|
||||||
|
assert str(theIndex.refIndex) == refIndex
|
||||||
|
assert str(theIndex.novelIndex) == novelIndex
|
||||||
|
assert str(theIndex.noteIndex) == noteIndex
|
||||||
|
assert str(theIndex.textCounts) == textCounts
|
||||||
|
|
||||||
|
# Break the index and check that we notice
|
||||||
|
assert not theIndex.indexBroken
|
||||||
|
theIndex.tagIndex["Bod"].append("Stuff") # No longer len() == 4
|
||||||
|
theIndex.checkIndex()
|
||||||
|
assert theIndex.indexBroken
|
||||||
|
|
||||||
|
assert theIndex.loadIndex()
|
||||||
|
assert not theIndex.indexBroken
|
||||||
|
theIndex.refIndex["fb609cd8319dc"]["T000001"]["tags"].append("Stuff") # No longer len() == 3
|
||||||
|
theIndex.checkIndex()
|
||||||
|
assert theIndex.indexBroken
|
||||||
|
|
||||||
|
assert theIndex.loadIndex()
|
||||||
|
assert not theIndex.indexBroken
|
||||||
|
theIndex.novelIndex["7a992350f3eb6"]["T000001"]["Stuff"] = "" # No longer len(keys()) == 8
|
||||||
|
theIndex.checkIndex()
|
||||||
|
assert theIndex.indexBroken
|
||||||
|
|
||||||
|
assert theIndex.loadIndex()
|
||||||
|
assert not theIndex.indexBroken
|
||||||
|
theIndex.noteIndex["4c4f28287af27"]["T000001"]["Stuff"] = "" # No longer len(keys()) == 8
|
||||||
|
theIndex.checkIndex()
|
||||||
|
assert theIndex.indexBroken
|
||||||
|
|
||||||
|
assert theIndex.loadIndex()
|
||||||
|
assert not theIndex.indexBroken
|
||||||
|
theIndex.textCounts["7a992350f3eb6"].append("Stuff") # No longer len() == 3
|
||||||
|
theIndex.checkIndex()
|
||||||
|
assert theIndex.indexBroken
|
||||||
|
|
||||||
|
# Make the try/except trigger as well
|
||||||
|
assert theIndex.loadIndex()
|
||||||
|
assert not theIndex.indexBroken
|
||||||
|
theIndex.refIndex["fb609cd8319dc"]["T000001"] = {"tagssss": []} # Wrong key name
|
||||||
|
theIndex.checkIndex()
|
||||||
|
assert theIndex.indexBroken
|
||||||
|
|
||||||
|
# Finalise
|
||||||
|
assert theProject.closeProject()
|
||||||
|
|
||||||
|
copyfile(projFile, testFile)
|
||||||
|
assert cmpFiles(testFile, refFile)
|
||||||
|
|
||||||
|
@pytest.mark.project
|
||||||
|
def testIndexScanThis(nwMinimal, nwDummy):
|
||||||
|
"""Test the tag scanner function scanThis.
|
||||||
|
"""
|
||||||
|
theProject = NWProject(nwDummy)
|
||||||
|
theProject.projTree.setSeed(42)
|
||||||
|
assert theProject.openProject(nwMinimal)
|
||||||
|
|
||||||
|
theIndex = NWIndex(theProject, nwDummy)
|
||||||
|
|
||||||
|
isValid, theBits, thePos = theIndex.scanThis("tag: this, and this")
|
||||||
|
assert not isValid
|
||||||
|
|
||||||
|
isValid, theBits, thePos = theIndex.scanThis("@")
|
||||||
|
assert not isValid
|
||||||
|
|
||||||
|
isValid, theBits, thePos = theIndex.scanThis("@:")
|
||||||
|
assert not isValid
|
||||||
|
|
||||||
|
isValid, theBits, thePos = theIndex.scanThis(" @a: b")
|
||||||
|
assert not isValid
|
||||||
|
|
||||||
|
isValid, theBits, thePos = theIndex.scanThis("@a:")
|
||||||
|
assert isValid
|
||||||
|
assert str(theBits) == "['@a']"
|
||||||
|
assert str(thePos) == "[0]"
|
||||||
|
|
||||||
|
isValid, theBits, thePos = theIndex.scanThis("@a:b")
|
||||||
|
assert isValid
|
||||||
|
assert str(theBits) == "['@a', 'b']"
|
||||||
|
assert str(thePos) == "[0, 3]"
|
||||||
|
|
||||||
|
isValid, theBits, thePos = theIndex.scanThis("@a:b,c,d")
|
||||||
|
assert isValid
|
||||||
|
assert str(theBits) == "['@a', 'b', 'c', 'd']"
|
||||||
|
assert str(thePos) == "[0, 3, 5, 7]"
|
||||||
|
|
||||||
|
isValid, theBits, thePos = theIndex.scanThis("@a : b , c , d")
|
||||||
|
assert isValid
|
||||||
|
assert str(theBits) == "['@a', 'b', 'c', 'd']"
|
||||||
|
assert str(thePos) == "[0, 5, 9, 13]"
|
||||||
|
|
||||||
|
isValid, theBits, thePos = theIndex.scanThis("@tag: this, and this")
|
||||||
|
assert isValid
|
||||||
|
assert str(theBits) == "['@tag', 'this', 'and this']"
|
||||||
|
assert str(thePos) == "[0, 6, 12]"
|
||||||
|
|
||||||
|
assert theProject.closeProject()
|
||||||
|
|
||||||
|
@pytest.mark.project
|
||||||
|
def testIndexCheckThese(nwMinimal, nwDummy):
|
||||||
|
"""Test the tag checker function checkThese.
|
||||||
|
"""
|
||||||
|
theProject = NWProject(nwDummy)
|
||||||
|
theProject.projTree.setSeed(42)
|
||||||
|
assert theProject.openProject(nwMinimal)
|
||||||
|
|
||||||
|
theIndex = NWIndex(theProject, nwDummy)
|
||||||
|
nHandle = theProject.newFile("Hello", nwItemClass.NOVEL, "a508bb932959c")
|
||||||
|
cHandle = theProject.newFile("Jane", nwItemClass.CHARACTER, "afb3043c7b2b3")
|
||||||
|
nItem = theProject.projTree[nHandle]
|
||||||
|
cItem = theProject.projTree[cHandle]
|
||||||
|
|
||||||
|
assert theIndex.scanText(cHandle, (
|
||||||
|
"# Jane Smith\n"
|
||||||
|
"@tag: Jane"
|
||||||
|
))
|
||||||
|
assert theIndex.scanText(nHandle, (
|
||||||
|
"# Hello World!\n"
|
||||||
|
"@pov: Jane"
|
||||||
|
))
|
||||||
|
assert str(theIndex.tagIndex) == "{'Jane': [2, '%s', 'CHARACTER', 'T000001']}" % cHandle
|
||||||
|
assert theIndex.novelIndex[nHandle]["T000001"]["title"] == "Hello World!"
|
||||||
|
|
||||||
|
assert str(theIndex.checkThese(["@tag", "Jane"], cItem)) == "[True, True]"
|
||||||
|
assert str(theIndex.checkThese(["@tag", "John"], cItem)) == "[True, True]"
|
||||||
|
assert str(theIndex.checkThese(["@tag", "Jane"], nItem)) == "[True, False]"
|
||||||
|
assert str(theIndex.checkThese(["@tag", "John"], nItem)) == "[True, True]"
|
||||||
|
assert str(theIndex.checkThese(["@pov", "John"], nItem)) == "[True, False]"
|
||||||
|
assert str(theIndex.checkThese(["@pov", "Jane"], nItem)) == "[True, True]"
|
||||||
|
assert str(theIndex.checkThese(["@ pov", "Jane"], nItem)) == "[False, False]"
|
||||||
|
assert str(theIndex.checkThese(["@what", "Jane"], nItem)) == "[False, False]"
|
||||||
|
|
||||||
|
assert theProject.closeProject()
|
||||||
|
|
||||||
|
@pytest.mark.project
|
||||||
|
def testIndexScanText(nwMinimal, nwDummy):
|
||||||
|
"""Check the index data extraction functions.
|
||||||
|
"""
|
||||||
|
theProject = NWProject(nwDummy)
|
||||||
|
theProject.projTree.setSeed(42)
|
||||||
|
assert theProject.openProject(nwMinimal)
|
||||||
|
|
||||||
|
theIndex = NWIndex(theProject, nwDummy)
|
||||||
|
|
||||||
|
# Some items for fail to scan tests
|
||||||
|
dHandle = theProject.newFolder("Folder", nwItemClass.NOVEL, "a508bb932959c")
|
||||||
|
xHandle = theProject.newFile("No Layout", nwItemClass.NOVEL, "a508bb932959c")
|
||||||
|
xItem = theProject.projTree[xHandle]
|
||||||
|
xItem.setLayout(nwItemLayout.NO_LAYOUT)
|
||||||
|
|
||||||
|
# Check invalid data
|
||||||
|
assert not theIndex.scanText(None, "Hello World!")
|
||||||
|
assert not theIndex.scanText(dHandle, "Hello World!")
|
||||||
|
assert not theIndex.scanText(xHandle, "Hello World!")
|
||||||
|
|
||||||
|
xItem.setLayout(nwItemLayout.SCENE)
|
||||||
|
xItem.setParent(None)
|
||||||
|
assert not theIndex.scanText(xHandle, "Hello World!")
|
||||||
|
|
||||||
|
# Create the trash folder
|
||||||
|
tHandle = theProject.trashFolder()
|
||||||
|
assert theProject.projTree[tHandle] is not None
|
||||||
|
xItem.setParent(tHandle)
|
||||||
|
assert not theIndex.scanText(xHandle, "Hello World!")
|
||||||
|
|
||||||
|
# Create the archive root
|
||||||
|
aHandle = theProject.newRoot("Outtakes", nwItemClass.ARCHIVE)
|
||||||
|
assert theProject.projTree[aHandle] is not None
|
||||||
|
xItem.setParent(aHandle)
|
||||||
|
assert not theIndex.scanText(xHandle, "Hello World!")
|
||||||
|
|
||||||
|
# Make some usable items
|
||||||
|
nHandle = theProject.newFile("Hello", nwItemClass.NOVEL, "a508bb932959c")
|
||||||
|
cHandle = theProject.newFile("Jane", nwItemClass.CHARACTER, "afb3043c7b2b3")
|
||||||
|
sHandle = theProject.newFile("Scene", nwItemClass.NOVEL, "a508bb932959c")
|
||||||
|
|
||||||
|
# Index correct text
|
||||||
|
assert theIndex.scanText(cHandle, (
|
||||||
|
"# Jane Smith\n"
|
||||||
|
"@tag: Jane\n"
|
||||||
|
))
|
||||||
|
assert theIndex.scanText(nHandle, (
|
||||||
|
"# Hello World!\n"
|
||||||
|
"@pov: Jane\n"
|
||||||
|
"@char: Jane\n\n"
|
||||||
|
"% this is a comment\n\n"
|
||||||
|
"This is a story about Jane Smith.\n\n"
|
||||||
|
"Well, not really.\n"
|
||||||
|
))
|
||||||
|
assert str(theIndex.tagIndex) == "{'Jane': [2, '%s', 'CHARACTER', 'T000001']}" % cHandle
|
||||||
|
assert theIndex.novelIndex[nHandle]["T000001"]["title"] == "Hello World!"
|
||||||
|
|
||||||
|
# Check that title sections are indexed properly
|
||||||
|
assert theIndex.scanText(nHandle, (
|
||||||
|
"# Title One\n\n"
|
||||||
|
"% synopsis: Synopsis One.\n\n"
|
||||||
|
"Paragraph One.\n\n"
|
||||||
|
"## Title Two\n\n"
|
||||||
|
"% synopsis: Synopsis Two.\n\n"
|
||||||
|
"Paragraph Two.\n\n"
|
||||||
|
"### Title Three\n\n"
|
||||||
|
"% synopsis: Synopsis Three.\n\n"
|
||||||
|
"Paragraph Three.\n\n"
|
||||||
|
"#### Title Four\n\n"
|
||||||
|
"% synopsis: Synopsis Four.\n\n"
|
||||||
|
"Paragraph Four.\n\n"
|
||||||
|
"##### Title Five\n\n" # Not interpreted as a title, the hashes is 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("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("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("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("T000020", None) is None
|
||||||
|
assert theIndex.refIndex[nHandle].get("T000021", None) is None
|
||||||
|
assert theIndex.refIndex[nHandle].get("T000022", None) is None
|
||||||
|
assert theIndex.refIndex[nHandle].get("T000023", None) is None
|
||||||
|
assert theIndex.refIndex[nHandle].get("T000024", None) is None
|
||||||
|
assert theIndex.refIndex[nHandle].get("T000025", None) is None
|
||||||
|
assert theIndex.refIndex[nHandle].get("T000026", None) is None
|
||||||
|
|
||||||
|
assert theIndex.novelIndex[nHandle]["T000001"]["level"] == "H1"
|
||||||
|
assert theIndex.novelIndex[nHandle]["T000007"]["level"] == "H2"
|
||||||
|
assert theIndex.novelIndex[nHandle]["T000013"]["level"] == "H3"
|
||||||
|
assert theIndex.novelIndex[nHandle]["T000019"]["level"] == "H4"
|
||||||
|
|
||||||
|
assert theIndex.novelIndex[nHandle]["T000001"]["title"] == "Title One"
|
||||||
|
assert theIndex.novelIndex[nHandle]["T000007"]["title"] == "Title Two"
|
||||||
|
assert theIndex.novelIndex[nHandle]["T000013"]["title"] == "Title Three"
|
||||||
|
assert theIndex.novelIndex[nHandle]["T000019"]["title"] == "Title Four"
|
||||||
|
|
||||||
|
assert theIndex.novelIndex[nHandle]["T000001"]["layout"] == "SCENE"
|
||||||
|
assert theIndex.novelIndex[nHandle]["T000007"]["layout"] == "SCENE"
|
||||||
|
assert theIndex.novelIndex[nHandle]["T000013"]["layout"] == "SCENE"
|
||||||
|
assert theIndex.novelIndex[nHandle]["T000019"]["layout"] == "SCENE"
|
||||||
|
|
||||||
|
assert theIndex.novelIndex[nHandle]["T000001"]["synopsis"] == "Synopsis One."
|
||||||
|
assert theIndex.novelIndex[nHandle]["T000007"]["synopsis"] == "Synopsis Two."
|
||||||
|
assert theIndex.novelIndex[nHandle]["T000013"]["synopsis"] == "Synopsis Three."
|
||||||
|
assert theIndex.novelIndex[nHandle]["T000019"]["synopsis"] == "Synopsis Four."
|
||||||
|
|
||||||
|
assert theIndex.novelIndex[nHandle]["T000001"]["cCount"] == 23
|
||||||
|
assert theIndex.novelIndex[nHandle]["T000007"]["cCount"] == 23
|
||||||
|
assert theIndex.novelIndex[nHandle]["T000013"]["cCount"] == 27
|
||||||
|
assert theIndex.novelIndex[nHandle]["T000019"]["cCount"] == 56
|
||||||
|
|
||||||
|
assert theIndex.novelIndex[nHandle]["T000001"]["wCount"] == 4
|
||||||
|
assert theIndex.novelIndex[nHandle]["T000007"]["wCount"] == 4
|
||||||
|
assert theIndex.novelIndex[nHandle]["T000013"]["wCount"] == 4
|
||||||
|
assert theIndex.novelIndex[nHandle]["T000019"]["wCount"] == 9
|
||||||
|
|
||||||
|
assert theIndex.novelIndex[nHandle]["T000001"]["pCount"] == 1
|
||||||
|
assert theIndex.novelIndex[nHandle]["T000007"]["pCount"] == 1
|
||||||
|
assert theIndex.novelIndex[nHandle]["T000013"]["pCount"] == 1
|
||||||
|
assert theIndex.novelIndex[nHandle]["T000019"]["pCount"] == 3
|
||||||
|
|
||||||
|
assert theIndex.scanText(cHandle, (
|
||||||
|
"# Title One\n\n"
|
||||||
|
"@tag: One\n\n"
|
||||||
|
"% synopsis: Synopsis One.\n\n"
|
||||||
|
"Paragraph One.\n\n"
|
||||||
|
))
|
||||||
|
assert theIndex.refIndex[cHandle].get("T000000", None) is not None
|
||||||
|
assert theIndex.refIndex[cHandle].get("T000001", None) is not None
|
||||||
|
assert theIndex.refIndex[cHandle].get("T000002", None) is None
|
||||||
|
assert theIndex.refIndex[cHandle].get("T000003", None) is None
|
||||||
|
assert theIndex.refIndex[cHandle].get("T000004", None) is None
|
||||||
|
assert theIndex.refIndex[cHandle].get("T000005", None) is None
|
||||||
|
assert theIndex.refIndex[cHandle].get("T000006", None) is None
|
||||||
|
assert theIndex.refIndex[cHandle].get("T000007", None) is None
|
||||||
|
|
||||||
|
assert theIndex.noteIndex[cHandle]["T000001"]["level"] == "H1"
|
||||||
|
assert theIndex.noteIndex[cHandle]["T000001"]["title"] == "Title One"
|
||||||
|
assert theIndex.noteIndex[cHandle]["T000001"]["layout"] == "NOTE"
|
||||||
|
assert theIndex.noteIndex[cHandle]["T000001"]["synopsis"] == "Synopsis One."
|
||||||
|
assert theIndex.noteIndex[cHandle]["T000001"]["cCount"] == 23
|
||||||
|
assert theIndex.noteIndex[cHandle]["T000001"]["wCount"] == 4
|
||||||
|
assert theIndex.noteIndex[cHandle]["T000001"]["pCount"] == 1
|
||||||
|
|
||||||
|
assert theIndex.scanText(sHandle, (
|
||||||
|
"# Title One\n\n"
|
||||||
|
"@pov: One\n\n" # Valid
|
||||||
|
"@char: Two\n\n" # Invalid tag
|
||||||
|
"@:\n\n" # Invalid line
|
||||||
|
"% synopsis: Synopsis One.\n\n"
|
||||||
|
"Paragraph One.\n\n"
|
||||||
|
))
|
||||||
|
assert str(theIndex.refIndex[sHandle]["T000001"]["tags"]) == (
|
||||||
|
"[[3, '@pov', 'One'], [5, '@char', 'Two']]"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert theProject.closeProject()
|
||||||
|
|
||||||
|
@pytest.mark.project
|
||||||
|
def testIndexExtractData(nwMinimal, nwDummy):
|
||||||
|
"""Check the index data extraction functions.
|
||||||
|
"""
|
||||||
|
theProject = NWProject(nwDummy)
|
||||||
|
theProject.projTree.setSeed(42)
|
||||||
|
assert theProject.openProject(nwMinimal)
|
||||||
|
|
||||||
|
theIndex = NWIndex(theProject, nwDummy)
|
||||||
|
nHandle = theProject.newFile("Hello", nwItemClass.NOVEL, "a508bb932959c")
|
||||||
|
cHandle = theProject.newFile("Jane", nwItemClass.CHARACTER, "afb3043c7b2b3")
|
||||||
|
|
||||||
|
assert theIndex.scanText(cHandle, (
|
||||||
|
"# Jane Smith\n"
|
||||||
|
"@tag: Jane\n"
|
||||||
|
))
|
||||||
|
assert theIndex.scanText(nHandle, (
|
||||||
|
"# Hello World!\n"
|
||||||
|
"@pov: Jane\n"
|
||||||
|
"@char: Jane\n\n"
|
||||||
|
"% this is a comment\n\n"
|
||||||
|
"This is a story about Jane Smith.\n\n"
|
||||||
|
"Well, not really.\n"
|
||||||
|
))
|
||||||
|
|
||||||
|
# The novel structure should contain the pointer to the novel file header
|
||||||
|
assert str(theIndex.getNovelStructure()) == "['%s:T000001']" % nHandle
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
##
|
||||||
|
# getReferences
|
||||||
|
##
|
||||||
|
|
||||||
|
# Look up an ivalid handle
|
||||||
|
theRefs = theIndex.getReferences("Not a handle")
|
||||||
|
assert theRefs["@pov"] == []
|
||||||
|
assert theRefs["@char"] == []
|
||||||
|
|
||||||
|
# The novel file should now refer to Jane as @pov and @char
|
||||||
|
theRefs = theIndex.getReferences(nHandle)
|
||||||
|
assert str(theRefs["@pov"]) == "['Jane']"
|
||||||
|
assert str(theRefs["@char"]) == "['Jane']"
|
||||||
|
|
||||||
|
##
|
||||||
|
# getBackReferenceList
|
||||||
|
##
|
||||||
|
|
||||||
|
# None handle should return an empty dict
|
||||||
|
assert theIndex.getBackReferenceList(None) == {}
|
||||||
|
|
||||||
|
# The character file should have a record of the reference from the novel file
|
||||||
|
theRefs = theIndex.getBackReferenceList(cHandle)
|
||||||
|
assert str(theRefs) == "{'%s': 'T000001'}" % nHandle
|
||||||
|
|
||||||
|
##
|
||||||
|
# getTagSource
|
||||||
|
##
|
||||||
|
|
||||||
|
assert theIndex.getTagSource("Jane") == (cHandle, 2, "T000001")
|
||||||
|
assert theIndex.getTagSource("John") == (None, 0, "T000000")
|
||||||
|
|
||||||
|
##
|
||||||
|
# getCounts for whole text and sections
|
||||||
|
##
|
||||||
|
|
||||||
|
# Get section counts for a novel file
|
||||||
|
assert theIndex.scanText(nHandle, (
|
||||||
|
"# Hello World!\n"
|
||||||
|
"@pov: Jane\n"
|
||||||
|
"@char: Jane\n\n"
|
||||||
|
"% this is a comment\n\n"
|
||||||
|
"This is a story about Jane Smith.\n\n"
|
||||||
|
"Well, not really.\n\n"
|
||||||
|
"# Hello World!\n"
|
||||||
|
"@pov: Jane\n"
|
||||||
|
"@char: Jane\n\n"
|
||||||
|
"% this is a comment\n\n"
|
||||||
|
"This is a story about Jane Smith.\n\n"
|
||||||
|
"Well, not really.\n"
|
||||||
|
))
|
||||||
|
# Whole document
|
||||||
|
cC, wC, pC = theIndex.getCounts(nHandle)
|
||||||
|
assert cC == 124
|
||||||
|
assert wC == 24
|
||||||
|
assert pC == 4
|
||||||
|
|
||||||
|
# First part
|
||||||
|
cC, wC, pC = theIndex.getCounts(nHandle, "T000001")
|
||||||
|
assert cC == 62
|
||||||
|
assert wC == 12
|
||||||
|
assert pC == 2
|
||||||
|
|
||||||
|
# First part
|
||||||
|
cC, wC, pC = theIndex.getCounts(nHandle, "T000011")
|
||||||
|
assert cC == 62
|
||||||
|
assert wC == 12
|
||||||
|
assert pC == 2
|
||||||
|
|
||||||
|
# Get section counts for a note file
|
||||||
|
assert theIndex.scanText(cHandle, (
|
||||||
|
"# Hello World!\n"
|
||||||
|
"@pov: Jane\n"
|
||||||
|
"@char: Jane\n\n"
|
||||||
|
"% this is a comment\n\n"
|
||||||
|
"This is a story about Jane Smith.\n\n"
|
||||||
|
"Well, not really.\n\n"
|
||||||
|
"# Hello World!\n"
|
||||||
|
"@pov: Jane\n"
|
||||||
|
"@char: Jane\n\n"
|
||||||
|
"% this is a comment\n\n"
|
||||||
|
"This is a story about Jane Smith.\n\n"
|
||||||
|
"Well, not really.\n"
|
||||||
|
))
|
||||||
|
# Whole document
|
||||||
|
cC, wC, pC = theIndex.getCounts(cHandle)
|
||||||
|
assert cC == 124
|
||||||
|
assert wC == 24
|
||||||
|
assert pC == 4
|
||||||
|
|
||||||
|
# First part
|
||||||
|
cC, wC, pC = theIndex.getCounts(cHandle, "T000001")
|
||||||
|
assert cC == 62
|
||||||
|
assert wC == 12
|
||||||
|
assert pC == 2
|
||||||
|
|
||||||
|
# First part
|
||||||
|
cC, wC, pC = theIndex.getCounts(cHandle, "T000011")
|
||||||
|
assert cC == 62
|
||||||
|
assert wC == 12
|
||||||
|
assert pC == 2
|
||||||
|
|
||||||
|
assert theProject.closeProject()
|
||||||
+336
-158
@@ -3,31 +3,41 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from os import path
|
import os
|
||||||
|
|
||||||
from shutil import copyfile
|
from shutil import copyfile
|
||||||
|
from zipfile import ZipFile
|
||||||
|
|
||||||
from nwtools import cmpFiles
|
from nwtools import cmpFiles
|
||||||
|
|
||||||
from nw.core.project import NWProject
|
from nw.core.project import NWProject
|
||||||
from nw.core.document import NWDoc
|
from nw.core.document import NWDoc
|
||||||
from nw.core.index import NWIndex
|
|
||||||
from nw.core.spellcheck import NWSpellEnchant, NWSpellSimple
|
from nw.core.spellcheck import NWSpellEnchant, NWSpellSimple
|
||||||
from nw.constants import nwItemClass, nwItemType, nwItemLayout, nwFiles
|
from nw.constants import nwItemClass, nwItemType, nwItemLayout, nwFiles
|
||||||
|
|
||||||
@pytest.mark.project
|
@pytest.mark.project
|
||||||
def testProjectNewOpenSave(nwFuncTemp, nwTempProj, nwRef, nwTemp, nwDummy):
|
def testProjectNewOpenSave(nwFuncTemp, nwTempProj, nwRef, nwTemp, nwDummy):
|
||||||
projFile = path.join(nwFuncTemp, "nwProject.nwx")
|
"""Test that a basic project can be created, and opened and saved.
|
||||||
testFile = path.join(nwTempProj, "1_nwProject.nwx")
|
"""
|
||||||
refFile = path.join(nwRef, "proj", "1_nwProject.nwx")
|
projFile = os.path.join(nwFuncTemp, "nwProject.nwx")
|
||||||
|
testFile = os.path.join(nwTempProj, "1_nwProject.nwx")
|
||||||
|
refFile = os.path.join(nwRef, "proj", "1_nwProject.nwx")
|
||||||
|
|
||||||
theProject = NWProject(nwDummy)
|
theProject = NWProject(nwDummy)
|
||||||
theProject.projTree.setSeed(42)
|
theProject.projTree.setSeed(42)
|
||||||
|
|
||||||
|
# Setting no data should fail
|
||||||
|
assert not theProject.newProject({})
|
||||||
|
|
||||||
|
# Try again with a proper path
|
||||||
assert theProject.newProject({"projPath": nwFuncTemp})
|
assert theProject.newProject({"projPath": nwFuncTemp})
|
||||||
assert theProject.setProjectPath(nwFuncTemp)
|
assert theProject.setProjectPath(nwFuncTemp)
|
||||||
assert theProject.saveProject()
|
assert theProject.saveProject()
|
||||||
assert theProject.closeProject()
|
assert theProject.closeProject()
|
||||||
|
|
||||||
|
# Creating the project once more should fail
|
||||||
|
assert not theProject.newProject({"projPath": nwFuncTemp})
|
||||||
|
|
||||||
# Check the new project
|
# Check the new project
|
||||||
copyfile(projFile, testFile)
|
copyfile(projFile, testFile)
|
||||||
assert cmpFiles(testFile, refFile, [2, 6, 7, 8])
|
assert cmpFiles(testFile, refFile, [2, 6, 7, 8])
|
||||||
@@ -53,9 +63,11 @@ def testProjectNewOpenSave(nwFuncTemp, nwTempProj, nwRef, nwTemp, nwDummy):
|
|||||||
|
|
||||||
@pytest.mark.project
|
@pytest.mark.project
|
||||||
def testProjectNewRoot(nwFuncTemp, nwTempProj, nwRef, nwDummy):
|
def testProjectNewRoot(nwFuncTemp, nwTempProj, nwRef, nwDummy):
|
||||||
projFile = path.join(nwFuncTemp, "nwProject.nwx")
|
"""Check that new root folders can be added to the project.
|
||||||
testFile = path.join(nwTempProj, "2_nwProject.nwx")
|
"""
|
||||||
refFile = path.join(nwRef, "proj", "2_nwProject.nwx")
|
projFile = os.path.join(nwFuncTemp, "nwProject.nwx")
|
||||||
|
testFile = os.path.join(nwTempProj, "2_nwProject.nwx")
|
||||||
|
refFile = os.path.join(nwRef, "proj", "2_nwProject.nwx")
|
||||||
|
|
||||||
theProject = NWProject(nwDummy)
|
theProject = NWProject(nwDummy)
|
||||||
theProject.projTree.setSeed(42)
|
theProject.projTree.setSeed(42)
|
||||||
@@ -85,9 +97,11 @@ def testProjectNewRoot(nwFuncTemp, nwTempProj, nwRef, nwDummy):
|
|||||||
|
|
||||||
@pytest.mark.project
|
@pytest.mark.project
|
||||||
def testProjectNewFile(nwFuncTemp, nwTempProj, nwRef, nwDummy):
|
def testProjectNewFile(nwFuncTemp, nwTempProj, nwRef, nwDummy):
|
||||||
projFile = path.join(nwFuncTemp, "nwProject.nwx")
|
"""Check that new files can be added to the project.
|
||||||
testFile = path.join(nwTempProj, "3_nwProject.nwx")
|
"""
|
||||||
refFile = path.join(nwRef, "proj", "3_nwProject.nwx")
|
projFile = os.path.join(nwFuncTemp, "nwProject.nwx")
|
||||||
|
testFile = os.path.join(nwTempProj, "3_nwProject.nwx")
|
||||||
|
refFile = os.path.join(nwRef, "proj", "3_nwProject.nwx")
|
||||||
|
|
||||||
theProject = NWProject(nwDummy)
|
theProject = NWProject(nwDummy)
|
||||||
theProject.projTree.setSeed(42)
|
theProject.projTree.setSeed(42)
|
||||||
@@ -109,143 +123,13 @@ def testProjectNewFile(nwFuncTemp, nwTempProj, nwRef, nwDummy):
|
|||||||
assert not theProject.projChanged
|
assert not theProject.projChanged
|
||||||
|
|
||||||
@pytest.mark.project
|
@pytest.mark.project
|
||||||
def testIndexScanThis(nwMinimal, nwDummy):
|
def testProjectNewCustomA(nwFuncTemp, nwTempProj, nwRef, nwDummy):
|
||||||
|
"""Create a new project from a project wizard dictionary.
|
||||||
theProject = NWProject(nwDummy)
|
Custom type with chapters and scenes.
|
||||||
theProject.projTree.setSeed(42)
|
"""
|
||||||
assert theProject.openProject(nwMinimal)
|
projFile = os.path.join(nwFuncTemp, "nwProject.nwx")
|
||||||
|
testFile = os.path.join(nwTempProj, "4_nwProject.nwx")
|
||||||
theIndex = NWIndex(theProject, nwDummy)
|
refFile = os.path.join(nwRef, "proj", "4_nwProject.nwx")
|
||||||
|
|
||||||
isValid, theBits, thePos = theIndex.scanThis("tag: this, and this")
|
|
||||||
assert not isValid
|
|
||||||
|
|
||||||
isValid, theBits, thePos = theIndex.scanThis("@")
|
|
||||||
assert not isValid
|
|
||||||
|
|
||||||
isValid, theBits, thePos = theIndex.scanThis("@:")
|
|
||||||
assert not isValid
|
|
||||||
|
|
||||||
isValid, theBits, thePos = theIndex.scanThis(" @a: b")
|
|
||||||
assert not isValid
|
|
||||||
|
|
||||||
isValid, theBits, thePos = theIndex.scanThis("@a:")
|
|
||||||
assert isValid
|
|
||||||
assert str(theBits) == "['@a']"
|
|
||||||
assert str(thePos) == "[0]"
|
|
||||||
|
|
||||||
isValid, theBits, thePos = theIndex.scanThis("@a:b")
|
|
||||||
assert isValid
|
|
||||||
assert str(theBits) == "['@a', 'b']"
|
|
||||||
assert str(thePos) == "[0, 3]"
|
|
||||||
|
|
||||||
isValid, theBits, thePos = theIndex.scanThis("@a:b,c,d")
|
|
||||||
assert isValid
|
|
||||||
assert str(theBits) == "['@a', 'b', 'c', 'd']"
|
|
||||||
assert str(thePos) == "[0, 3, 5, 7]"
|
|
||||||
|
|
||||||
isValid, theBits, thePos = theIndex.scanThis("@a : b , c , d")
|
|
||||||
assert isValid
|
|
||||||
assert str(theBits) == "['@a', 'b', 'c', 'd']"
|
|
||||||
assert str(thePos) == "[0, 5, 9, 13]"
|
|
||||||
|
|
||||||
isValid, theBits, thePos = theIndex.scanThis("@tag: this, and this")
|
|
||||||
assert isValid
|
|
||||||
assert str(theBits) == "['@tag', 'this', 'and this']"
|
|
||||||
assert str(thePos) == "[0, 6, 12]"
|
|
||||||
|
|
||||||
assert theProject.closeProject()
|
|
||||||
|
|
||||||
@pytest.mark.project
|
|
||||||
def testIndexCheckThese(nwMinimal, nwDummy):
|
|
||||||
|
|
||||||
theProject = NWProject(nwDummy)
|
|
||||||
theProject.projTree.setSeed(42)
|
|
||||||
assert theProject.openProject(nwMinimal)
|
|
||||||
|
|
||||||
theIndex = NWIndex(theProject, nwDummy)
|
|
||||||
nHandle = theProject.newFile("Hello", nwItemClass.NOVEL, "a508bb932959c")
|
|
||||||
cHandle = theProject.newFile("Jane", nwItemClass.CHARACTER, "afb3043c7b2b3")
|
|
||||||
nItem = theProject.projTree[nHandle]
|
|
||||||
cItem = theProject.projTree[cHandle]
|
|
||||||
|
|
||||||
assert theIndex.scanText(cHandle, (
|
|
||||||
"# Jane Smith\n"
|
|
||||||
"@tag: Jane"
|
|
||||||
))
|
|
||||||
assert theIndex.scanText(nHandle, (
|
|
||||||
"# Hello World!\n"
|
|
||||||
"@pov: Jane"
|
|
||||||
))
|
|
||||||
assert str(theIndex.tagIndex) == "{'Jane': [2, '%s', 'CHARACTER', 'T000001']}" % cHandle
|
|
||||||
assert theIndex.novelIndex[nHandle]["T000001"]["title"] == "Hello World!"
|
|
||||||
|
|
||||||
assert str(theIndex.checkThese(["@tag", "Jane"], cItem)) == "[True, True]"
|
|
||||||
assert str(theIndex.checkThese(["@tag", "John"], cItem)) == "[True, True]"
|
|
||||||
assert str(theIndex.checkThese(["@tag", "Jane"], nItem)) == "[True, False]"
|
|
||||||
assert str(theIndex.checkThese(["@tag", "John"], nItem)) == "[True, True]"
|
|
||||||
assert str(theIndex.checkThese(["@pov", "John"], nItem)) == "[True, False]"
|
|
||||||
assert str(theIndex.checkThese(["@pov", "Jane"], nItem)) == "[True, True]"
|
|
||||||
assert str(theIndex.checkThese(["@ pov", "Jane"], nItem)) == "[False, False]"
|
|
||||||
assert str(theIndex.checkThese(["@what", "Jane"], nItem)) == "[False, False]"
|
|
||||||
|
|
||||||
assert theProject.closeProject()
|
|
||||||
|
|
||||||
@pytest.mark.project
|
|
||||||
def testIndexMeta(nwMinimal, nwDummy):
|
|
||||||
|
|
||||||
theProject = NWProject(nwDummy)
|
|
||||||
theProject.projTree.setSeed(42)
|
|
||||||
assert theProject.openProject(nwMinimal)
|
|
||||||
|
|
||||||
theIndex = NWIndex(theProject, nwDummy)
|
|
||||||
nHandle = theProject.newFile("Hello", nwItemClass.NOVEL, "a508bb932959c")
|
|
||||||
cHandle = theProject.newFile("Jane", nwItemClass.CHARACTER, "afb3043c7b2b3")
|
|
||||||
|
|
||||||
assert theIndex.scanText(cHandle, (
|
|
||||||
"# Jane Smith\n"
|
|
||||||
"@tag: Jane\n"
|
|
||||||
))
|
|
||||||
assert theIndex.scanText(nHandle, (
|
|
||||||
"# Hello World!\n"
|
|
||||||
"@pov: Jane\n"
|
|
||||||
"@char: Jane\n"
|
|
||||||
"\n"
|
|
||||||
"% this is a comment\n"
|
|
||||||
"\n"
|
|
||||||
"This is a story about Jane Smith.\n"
|
|
||||||
"\n"
|
|
||||||
"Well, not really.\n"
|
|
||||||
))
|
|
||||||
assert str(theIndex.tagIndex) == "{'Jane': [2, '%s', 'CHARACTER', 'T000001']}" % cHandle
|
|
||||||
assert theIndex.novelIndex[nHandle]["T000001"]["title"] == "Hello World!"
|
|
||||||
|
|
||||||
# The novel structure should contain the pointer to the novel file header
|
|
||||||
assert str(theIndex.getNovelStructure()) == "['%s:T000001']" % nHandle
|
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
# The novel file should now refer to Jane as @pov and @char
|
|
||||||
theRefs = theIndex.getReferences(nHandle)
|
|
||||||
assert str(theRefs["@pov"]) == "['Jane']"
|
|
||||||
assert str(theRefs["@char"]) == "['Jane']"
|
|
||||||
|
|
||||||
# The character file should have a record of the reference from the novel file
|
|
||||||
theRefs = theIndex.getBackReferenceList(cHandle)
|
|
||||||
assert str(theRefs) == "{'%s': 'T000001'}" % nHandle
|
|
||||||
|
|
||||||
assert theProject.closeProject()
|
|
||||||
|
|
||||||
@pytest.mark.project
|
|
||||||
def testProjectNewCustom(nwFuncTemp, nwTempProj, nwRef, nwDummy):
|
|
||||||
|
|
||||||
projFile = path.join(nwFuncTemp, "nwProject.nwx")
|
|
||||||
testFile = path.join(nwTempProj, "4_nwProject.nwx")
|
|
||||||
refFile = path.join(nwRef, "proj", "4_nwProject.nwx")
|
|
||||||
|
|
||||||
projData = {
|
projData = {
|
||||||
"projName": "Test Custom",
|
"projName": "Test Custom",
|
||||||
@@ -278,7 +162,49 @@ def testProjectNewCustom(nwFuncTemp, nwTempProj, nwRef, nwDummy):
|
|||||||
assert cmpFiles(testFile, refFile, [2, 6, 7, 8])
|
assert cmpFiles(testFile, refFile, [2, 6, 7, 8])
|
||||||
|
|
||||||
@pytest.mark.project
|
@pytest.mark.project
|
||||||
def testProjectNewSample(nwFuncTemp, nwRef, nwConf, nwDummy):
|
def testProjectNewCustomB(nwFuncTemp, nwTempProj, nwRef, nwDummy):
|
||||||
|
"""Create a new project from a project wizard dictionary.
|
||||||
|
Custom type without chapters, but with scenes.
|
||||||
|
"""
|
||||||
|
projFile = os.path.join(nwFuncTemp, "nwProject.nwx")
|
||||||
|
testFile = os.path.join(nwTempProj, "5_nwProject.nwx")
|
||||||
|
refFile = os.path.join(nwRef, "proj", "5_nwProject.nwx")
|
||||||
|
|
||||||
|
projData = {
|
||||||
|
"projName": "Test Custom",
|
||||||
|
"projTitle": "Test Novel",
|
||||||
|
"projAuthors": "Jane Doe\nJohn Doh\n",
|
||||||
|
"projPath": nwFuncTemp,
|
||||||
|
"popSample": False,
|
||||||
|
"popMinimal": False,
|
||||||
|
"popCustom": True,
|
||||||
|
"addRoots": [
|
||||||
|
nwItemClass.PLOT,
|
||||||
|
nwItemClass.CHARACTER,
|
||||||
|
nwItemClass.WORLD,
|
||||||
|
nwItemClass.TIMELINE,
|
||||||
|
nwItemClass.OBJECT,
|
||||||
|
nwItemClass.ENTITY,
|
||||||
|
],
|
||||||
|
"numChapters": 0,
|
||||||
|
"numScenes": 6,
|
||||||
|
"chFolders": True,
|
||||||
|
}
|
||||||
|
theProject = NWProject(nwDummy)
|
||||||
|
theProject.projTree.setSeed(42)
|
||||||
|
|
||||||
|
assert theProject.newProject(projData)
|
||||||
|
assert theProject.saveProject()
|
||||||
|
assert theProject.closeProject()
|
||||||
|
|
||||||
|
copyfile(projFile, testFile)
|
||||||
|
assert cmpFiles(testFile, refFile, [2, 6, 7, 8])
|
||||||
|
|
||||||
|
@pytest.mark.project
|
||||||
|
def testProjectNewSampleA(nwFuncTemp, nwConf, nwDummy, nwTemp):
|
||||||
|
"""Check that we can create a new project can be created from the
|
||||||
|
provided sample project via a zip file.
|
||||||
|
"""
|
||||||
projData = {
|
projData = {
|
||||||
"projName": "Test Sample",
|
"projName": "Test Sample",
|
||||||
"projTitle": "Test Novel",
|
"projTitle": "Test Novel",
|
||||||
@@ -292,14 +218,117 @@ def testProjectNewSample(nwFuncTemp, nwRef, nwConf, nwDummy):
|
|||||||
theProject.projTree.setSeed(42)
|
theProject.projTree.setSeed(42)
|
||||||
theProject.mainConf = nwConf
|
theProject.mainConf = nwConf
|
||||||
|
|
||||||
|
# Sample set, but no path
|
||||||
|
assert not theProject.newProject({"popSample": True})
|
||||||
|
|
||||||
|
# Force the lookup path for assets to our temp folder
|
||||||
|
srcSample = os.path.abspath(os.path.join(nwConf.appRoot, "sample"))
|
||||||
|
dstSample = os.path.join(nwTemp, "sample.zip")
|
||||||
|
nwConf.assetPath = nwTemp
|
||||||
|
|
||||||
|
# Create and open a defective zip file
|
||||||
|
with open(dstSample, mode="w+") as outFile:
|
||||||
|
outFile.write("foo")
|
||||||
|
|
||||||
|
assert not theProject.newProject(projData)
|
||||||
|
os.unlink(dstSample)
|
||||||
|
|
||||||
|
# Create a real zip file, and unpack it
|
||||||
|
with ZipFile(dstSample, "w") as zipObj:
|
||||||
|
zipObj.write(os.path.join(srcSample, "nwProject.nwx"), "nwProject.nwx")
|
||||||
|
for docFile in os.listdir(os.path.join(srcSample, "content")):
|
||||||
|
srcDoc = os.path.join(srcSample, "content", docFile)
|
||||||
|
zipObj.write(srcDoc, "content/"+docFile)
|
||||||
|
|
||||||
|
assert theProject.newProject(projData)
|
||||||
|
assert theProject.openProject(nwFuncTemp)
|
||||||
|
assert theProject.projName == "Sample Project"
|
||||||
|
assert theProject.saveProject()
|
||||||
|
assert theProject.closeProject()
|
||||||
|
os.unlink(dstSample)
|
||||||
|
|
||||||
|
@pytest.mark.project
|
||||||
|
def testProjectNewSampleB(monkeypatch, nwFuncTemp, nwConf, nwDummy, nwTemp):
|
||||||
|
"""Check that we can create a new project can be created from the
|
||||||
|
provided sample project folder.
|
||||||
|
"""
|
||||||
|
projData = {
|
||||||
|
"projName": "Test Sample",
|
||||||
|
"projTitle": "Test Novel",
|
||||||
|
"projAuthors": "Jane Doe\nJohn Doh\n",
|
||||||
|
"projPath": nwFuncTemp,
|
||||||
|
"popSample": True,
|
||||||
|
"popMinimal": False,
|
||||||
|
"popCustom": False,
|
||||||
|
}
|
||||||
|
theProject = NWProject(nwDummy)
|
||||||
|
theProject.projTree.setSeed(42)
|
||||||
|
theProject.mainConf = nwConf
|
||||||
|
|
||||||
|
# Make sure we do not pick up the nw/assets/sample.zip file
|
||||||
|
nwConf.assetPath = nwTemp
|
||||||
|
|
||||||
|
# Set a fake project file name
|
||||||
|
monkeypatch.setattr(nwFiles, "PROJ_FILE", "nothing.nwx")
|
||||||
|
assert not theProject.newProject(projData)
|
||||||
|
|
||||||
|
monkeypatch.setattr(nwFiles, "PROJ_FILE", "nwProject.nwx")
|
||||||
assert theProject.newProject(projData)
|
assert theProject.newProject(projData)
|
||||||
assert theProject.openProject(nwFuncTemp)
|
assert theProject.openProject(nwFuncTemp)
|
||||||
assert theProject.projName == "Sample Project"
|
assert theProject.projName == "Sample Project"
|
||||||
assert theProject.saveProject()
|
assert theProject.saveProject()
|
||||||
assert theProject.closeProject()
|
assert theProject.closeProject()
|
||||||
|
|
||||||
|
# Misdirect the appRoot path so neither is possible
|
||||||
|
nwConf.appRoot = nwTemp
|
||||||
|
assert not theProject.newProject(projData)
|
||||||
|
|
||||||
|
@pytest.mark.project
|
||||||
|
def testProjectMethods(monkeypatch, nwMinimal, nwDummy):
|
||||||
|
"""Test other project class methods and functions.
|
||||||
|
"""
|
||||||
|
theProject = NWProject(nwDummy)
|
||||||
|
theProject.projTree.setSeed(42)
|
||||||
|
assert theProject.openProject(nwMinimal)
|
||||||
|
assert theProject.projPath == nwMinimal
|
||||||
|
|
||||||
|
# Setting project path
|
||||||
|
assert theProject.setProjectPath(None)
|
||||||
|
assert theProject.projPath is None
|
||||||
|
assert theProject.setProjectPath("")
|
||||||
|
assert theProject.projPath is None
|
||||||
|
assert theProject.setProjectPath("~")
|
||||||
|
assert theProject.projPath == os.path.expanduser("~")
|
||||||
|
|
||||||
|
# Create a new folder and populate it
|
||||||
|
projPath = os.path.join(nwMinimal, "dummy1")
|
||||||
|
assert theProject.setProjectPath(projPath, newProject=True)
|
||||||
|
|
||||||
|
# Make os.mkdir fail
|
||||||
|
def altMkdir(*args):
|
||||||
|
raise Exception("Oops!")
|
||||||
|
|
||||||
|
monkeypatch.setattr("os.mkdir", altMkdir)
|
||||||
|
projPath = os.path.join(nwMinimal, "dummy2")
|
||||||
|
assert not theProject.setProjectPath(projPath, newProject=True)
|
||||||
|
|
||||||
|
# Project Name
|
||||||
|
assert theProject.setProjectName(" A Name ")
|
||||||
|
assert theProject.projName == "A Name"
|
||||||
|
|
||||||
|
# Project Title
|
||||||
|
assert theProject.setBookTitle(" A Title ")
|
||||||
|
assert theProject.bookTitle == "A Title"
|
||||||
|
|
||||||
|
# Project Authors
|
||||||
|
assert not theProject.setBookAuthors([])
|
||||||
|
assert theProject.setBookAuthors(" Jane Doe \n John Doh \n ")
|
||||||
|
assert theProject.bookAuthors == ["Jane Doe", "John Doh"]
|
||||||
|
|
||||||
@pytest.mark.project
|
@pytest.mark.project
|
||||||
def testDocMeta(nwDummy, nwLipsum):
|
def testDocMeta(nwDummy, nwLipsum):
|
||||||
|
"""Check that the document meta data string is parsed correctly.
|
||||||
|
"""
|
||||||
theProject = NWProject(nwDummy)
|
theProject = NWProject(nwDummy)
|
||||||
theProject.projTree.setSeed(42)
|
theProject.projTree.setSeed(42)
|
||||||
assert theProject.openProject(nwLipsum)
|
assert theProject.openProject(nwLipsum)
|
||||||
@@ -325,7 +354,7 @@ def testDocMeta(nwDummy, nwLipsum):
|
|||||||
|
|
||||||
@pytest.mark.project
|
@pytest.mark.project
|
||||||
def testSpellEnchant(nwTemp, nwConf):
|
def testSpellEnchant(nwTemp, nwConf):
|
||||||
wList = path.join(nwTemp, "wordlist.txt")
|
wList = os.path.join(nwTemp, "wordlist.txt")
|
||||||
with open(wList, mode="w") as wFile:
|
with open(wList, mode="w") as wFile:
|
||||||
wFile.write("a_word\nb_word\nc_word\n")
|
wFile.write("a_word\nb_word\nc_word\n")
|
||||||
|
|
||||||
@@ -350,7 +379,7 @@ def testSpellEnchant(nwTemp, nwConf):
|
|||||||
|
|
||||||
@pytest.mark.project
|
@pytest.mark.project
|
||||||
def testSpellSimple(nwTemp, nwConf):
|
def testSpellSimple(nwTemp, nwConf):
|
||||||
wList = path.join(nwTemp, "wordlist.txt")
|
wList = os.path.join(nwTemp, "wordlist.txt")
|
||||||
with open(wList, mode="w") as wFile:
|
with open(wList, mode="w") as wFile:
|
||||||
wFile.write("a_word\nb_word\nc_word\n")
|
wFile.write("a_word\nb_word\nc_word\n")
|
||||||
|
|
||||||
@@ -375,6 +404,10 @@ def testSpellSimple(nwTemp, nwConf):
|
|||||||
|
|
||||||
@pytest.mark.project
|
@pytest.mark.project
|
||||||
def testProjectOptions(nwDummy, nwLipsum):
|
def testProjectOptions(nwDummy, nwLipsum):
|
||||||
|
"""Test the class that holds all the GUI state user options that are
|
||||||
|
tied to the current open project. Non-project related GUI options
|
||||||
|
are handled by the Config class.
|
||||||
|
"""
|
||||||
theProject = NWProject(nwDummy)
|
theProject = NWProject(nwDummy)
|
||||||
assert theProject.projMeta is None
|
assert theProject.projMeta is None
|
||||||
|
|
||||||
@@ -389,7 +422,7 @@ def testProjectOptions(nwDummy, nwLipsum):
|
|||||||
assert str(theOpts.theState) == r"{}"
|
assert str(theOpts.theState) == r"{}"
|
||||||
|
|
||||||
# Read Invalid Settings and Filter
|
# Read Invalid Settings and Filter
|
||||||
stateFile = path.join(theProject.projMeta, nwFiles.OPTS_FILE)
|
stateFile = os.path.join(theProject.projMeta, nwFiles.OPTS_FILE)
|
||||||
with open(stateFile, mode="w", encoding="utf8") as outFile:
|
with open(stateFile, mode="w", encoding="utf8") as outFile:
|
||||||
outFile.write(
|
outFile.write(
|
||||||
r'{"GuiProjectSettings": {"winWidth": 100, "winHeight": 50}, "NoGroup": {"NoName": 0}}'
|
r'{"GuiProjectSettings": {"winWidth": 100, "winHeight": 50}, "NoGroup": {"NoName": 0}}'
|
||||||
@@ -433,35 +466,40 @@ def testProjectOptions(nwDummy, nwLipsum):
|
|||||||
assert theOpts.getFloat("GuiWritingStats", "winWidth", False) is False
|
assert theOpts.getFloat("GuiWritingStats", "winWidth", False) is False
|
||||||
|
|
||||||
@pytest.mark.project
|
@pytest.mark.project
|
||||||
def testOrphanedFiles(nwDummy, nwLipsum):
|
def testProjectOrphanedFiles(nwDummy, nwLipsum):
|
||||||
|
"""Check that files in the content folder that are not tracked in
|
||||||
|
the project XML file are handled correctly by the orphaned files
|
||||||
|
function. It should also restore as much meta data as possible from
|
||||||
|
the meta line at the top of the document file.
|
||||||
|
"""
|
||||||
theProject = NWProject(nwDummy)
|
theProject = NWProject(nwDummy)
|
||||||
assert theProject.openProject(nwLipsum)
|
assert theProject.openProject(nwLipsum)
|
||||||
assert theProject.projTree["636b6aa9b697b"] is None
|
assert theProject.projTree["636b6aa9b697b"] is None
|
||||||
assert theProject.closeProject()
|
assert theProject.closeProject()
|
||||||
|
|
||||||
# First Item with Meta Data
|
# First Item with Meta Data
|
||||||
orphPath = path.join(nwLipsum, "content", "636b6aa9b697b.nwd")
|
orphPath = os.path.join(nwLipsum, "content", "636b6aa9b697b.nwd")
|
||||||
with open(orphPath, mode="w", encoding="utf8") as outFile:
|
with open(orphPath, mode="w", encoding="utf8") as outFile:
|
||||||
outFile.write(r"%%~ 5eaea4e8cdee8:15c4492bd5107:WORLD:NOTE:Mars")
|
outFile.write(r"%%~ 5eaea4e8cdee8:15c4492bd5107:WORLD:NOTE:Mars")
|
||||||
outFile.write("\n")
|
outFile.write("\n")
|
||||||
|
|
||||||
# Second Item without Meta Data
|
# Second Item without Meta Data
|
||||||
orphPath = path.join(nwLipsum, "content", "736b6aa9b697b.nwd")
|
orphPath = os.path.join(nwLipsum, "content", "736b6aa9b697b.nwd")
|
||||||
with open(orphPath, mode="w", encoding="utf8") as outFile:
|
with open(orphPath, mode="w", encoding="utf8") as outFile:
|
||||||
outFile.write("\n")
|
outFile.write("\n")
|
||||||
|
|
||||||
# Invalid File Name
|
# Invalid File Name
|
||||||
dummyPath = path.join(nwLipsum, "content", "636b6aa9b697b.txt")
|
dummyPath = os.path.join(nwLipsum, "content", "636b6aa9b697b.txt")
|
||||||
with open(dummyPath, mode="w", encoding="utf8") as outFile:
|
with open(dummyPath, mode="w", encoding="utf8") as outFile:
|
||||||
outFile.write("\n")
|
outFile.write("\n")
|
||||||
|
|
||||||
# Invalid File Name
|
# Invalid File Name
|
||||||
dummyPath = path.join(nwLipsum, "content", "636b6aa9b697bb.nwd")
|
dummyPath = os.path.join(nwLipsum, "content", "636b6aa9b697bb.nwd")
|
||||||
with open(dummyPath, mode="w", encoding="utf8") as outFile:
|
with open(dummyPath, mode="w", encoding="utf8") as outFile:
|
||||||
outFile.write("\n")
|
outFile.write("\n")
|
||||||
|
|
||||||
# Invalid File Name
|
# Invalid File Name
|
||||||
dummyPath = path.join(nwLipsum, "content", "abcdefghijklm.nwd")
|
dummyPath = os.path.join(nwLipsum, "content", "abcdefghijklm.nwd")
|
||||||
with open(dummyPath, mode="w", encoding="utf8") as outFile:
|
with open(dummyPath, mode="w", encoding="utf8") as outFile:
|
||||||
outFile.write("\n")
|
outFile.write("\n")
|
||||||
|
|
||||||
@@ -492,3 +530,143 @@ def testOrphanedFiles(nwDummy, nwLipsum):
|
|||||||
|
|
||||||
assert theProject.saveProject(nwLipsum)
|
assert theProject.saveProject(nwLipsum)
|
||||||
assert theProject.closeProject()
|
assert theProject.closeProject()
|
||||||
|
|
||||||
|
@pytest.mark.project
|
||||||
|
def testProjectOldFormat(nwDummy, nwOldProj):
|
||||||
|
"""Test that a project folder structure of version 1.0 can be
|
||||||
|
converted to the latest folder structure. Version 1.0 split the
|
||||||
|
documents into 'data_0' ... 'data_f' folders, which are now all
|
||||||
|
contained in a single 'content' folder.
|
||||||
|
"""
|
||||||
|
theProject = NWProject(nwDummy)
|
||||||
|
theProject.mainConf.showGUI = False
|
||||||
|
|
||||||
|
# Create dummy files for known legacy files
|
||||||
|
deleteFiles = [
|
||||||
|
os.path.join(nwOldProj, "cache", "nwProject.nwx.0"),
|
||||||
|
os.path.join(nwOldProj, "cache", "nwProject.nwx.1"),
|
||||||
|
os.path.join(nwOldProj, "cache", "nwProject.nwx.2"),
|
||||||
|
os.path.join(nwOldProj, "cache", "nwProject.nwx.3"),
|
||||||
|
os.path.join(nwOldProj, "cache", "nwProject.nwx.4"),
|
||||||
|
os.path.join(nwOldProj, "cache", "nwProject.nwx.5"),
|
||||||
|
os.path.join(nwOldProj, "cache", "nwProject.nwx.6"),
|
||||||
|
os.path.join(nwOldProj, "cache", "nwProject.nwx.7"),
|
||||||
|
os.path.join(nwOldProj, "cache", "nwProject.nwx.8"),
|
||||||
|
os.path.join(nwOldProj, "cache", "nwProject.nwx.9"),
|
||||||
|
os.path.join(nwOldProj, "meta", "mainOptions.json"),
|
||||||
|
os.path.join(nwOldProj, "meta", "exportOptions.json"),
|
||||||
|
os.path.join(nwOldProj, "meta", "outlineOptions.json"),
|
||||||
|
os.path.join(nwOldProj, "meta", "timelineOptions.json"),
|
||||||
|
os.path.join(nwOldProj, "meta", "docMergeOptions.json"),
|
||||||
|
os.path.join(nwOldProj, "meta", "sessionLogOptions.json"),
|
||||||
|
]
|
||||||
|
|
||||||
|
# Add some files that shouldn't be there
|
||||||
|
deleteFiles.append(os.path.join(nwOldProj, "data_f", "whatnow.nwd"))
|
||||||
|
deleteFiles.append(os.path.join(nwOldProj, "data_f", "whatnow.txt"))
|
||||||
|
|
||||||
|
# Add some folders that shouldn't be there
|
||||||
|
os.mkdir(os.path.join(nwOldProj, "stuff"))
|
||||||
|
os.mkdir(os.path.join(nwOldProj, "data_1", "stuff"))
|
||||||
|
|
||||||
|
# Create dummy files
|
||||||
|
os.mkdir(os.path.join(nwOldProj, "cache"))
|
||||||
|
for aFile in deleteFiles:
|
||||||
|
with open(aFile, mode="w+", encoding="utf8") as outFile:
|
||||||
|
outFile.write("Hi")
|
||||||
|
for aFile in deleteFiles:
|
||||||
|
assert os.path.isfile(aFile)
|
||||||
|
|
||||||
|
# Open project and check that files that are not supposed to be
|
||||||
|
# there have been removed
|
||||||
|
assert theProject.openProject(nwOldProj)
|
||||||
|
for aFile in deleteFiles:
|
||||||
|
assert not os.path.isfile(aFile)
|
||||||
|
|
||||||
|
assert not os.path.isdir(os.path.join(nwOldProj, "data_1", "stuff"))
|
||||||
|
assert not os.path.isdir(os.path.join(nwOldProj, "data_1"))
|
||||||
|
assert not os.path.isdir(os.path.join(nwOldProj, "data_7"))
|
||||||
|
assert not os.path.isdir(os.path.join(nwOldProj, "data_8"))
|
||||||
|
assert not os.path.isdir(os.path.join(nwOldProj, "data_9"))
|
||||||
|
assert not os.path.isdir(os.path.join(nwOldProj, "data_a"))
|
||||||
|
assert not os.path.isdir(os.path.join(nwOldProj, "data_f"))
|
||||||
|
|
||||||
|
# Check stuff that has been moved
|
||||||
|
assert os.path.isdir(os.path.join(nwOldProj, "junk"))
|
||||||
|
assert os.path.isdir(os.path.join(nwOldProj, "junk", "stuff"))
|
||||||
|
assert os.path.isfile(os.path.join(nwOldProj, "junk", "whatnow.nwd"))
|
||||||
|
assert os.path.isfile(os.path.join(nwOldProj, "junk", "whatnow.txt"))
|
||||||
|
|
||||||
|
# Check that files we want to keep are in the right place
|
||||||
|
assert os.path.isdir(os.path.join(nwOldProj, "cache"))
|
||||||
|
assert os.path.isdir(os.path.join(nwOldProj, "content"))
|
||||||
|
assert os.path.isdir(os.path.join(nwOldProj, "meta"))
|
||||||
|
|
||||||
|
assert os.path.isfile(os.path.join(nwOldProj, "content", "f528d831f5b24.nwd"))
|
||||||
|
assert os.path.isfile(os.path.join(nwOldProj, "content", "88124a4292d8b.nwd"))
|
||||||
|
assert os.path.isfile(os.path.join(nwOldProj, "content", "91239bf2f8b69.nwd"))
|
||||||
|
assert os.path.isfile(os.path.join(nwOldProj, "content", "19752e7f9d8af.nwd"))
|
||||||
|
assert os.path.isfile(os.path.join(nwOldProj, "content", "a764d5acf5a21.nwd"))
|
||||||
|
assert os.path.isfile(os.path.join(nwOldProj, "content", "9058ae29f0dfd.nwd"))
|
||||||
|
assert os.path.isfile(os.path.join(nwOldProj, "content", "7ff63b8afc4cd.nwd"))
|
||||||
|
|
||||||
|
assert os.path.isfile(os.path.join(nwOldProj, "meta", "tagsIndex.json"))
|
||||||
|
assert os.path.isfile(os.path.join(nwOldProj, "meta", "sessionInfo.log"))
|
||||||
|
|
||||||
|
# Close the project
|
||||||
|
theProject.closeProject()
|
||||||
|
|
||||||
|
# Check that new files have been created
|
||||||
|
assert os.path.isfile(os.path.join(nwOldProj, "meta", "guiOptions.json"))
|
||||||
|
assert os.path.isfile(os.path.join(nwOldProj, "meta", "sessionStats.log"))
|
||||||
|
assert os.path.isfile(os.path.join(nwOldProj, "ToC.json"))
|
||||||
|
assert os.path.isfile(os.path.join(nwOldProj, "ToC.txt"))
|
||||||
|
|
||||||
|
@pytest.mark.project
|
||||||
|
def testProjectBackup(nwDummy, nwMinimal, nwTemp):
|
||||||
|
"""Test the automated backup feature of the project class. The test
|
||||||
|
creates a backup of the Minimal test project, and then unzips the
|
||||||
|
backupd file and checks that the project XML file is identical to
|
||||||
|
the original file.
|
||||||
|
"""
|
||||||
|
theProject = NWProject(nwDummy)
|
||||||
|
assert theProject.openProject(nwMinimal)
|
||||||
|
|
||||||
|
# Test faulty settings
|
||||||
|
# Invalid path
|
||||||
|
theProject.mainConf.backupPath = None
|
||||||
|
assert not theProject.zipIt(doNotify=False)
|
||||||
|
|
||||||
|
# Missing project name
|
||||||
|
theProject.mainConf.backupPath = nwTemp
|
||||||
|
theProject.projName = ""
|
||||||
|
assert not theProject.zipIt(doNotify=False)
|
||||||
|
|
||||||
|
# Non-existent folder
|
||||||
|
theProject.mainConf.backupPath = os.path.join(nwTemp, "nonexistent")
|
||||||
|
theProject.projName = "Test Minimal"
|
||||||
|
assert not theProject.zipIt(doNotify=False)
|
||||||
|
|
||||||
|
# Same folder as project (causes infinite loop in zipping)
|
||||||
|
theProject.mainConf.backupPath = nwMinimal
|
||||||
|
assert not theProject.zipIt(doNotify=False)
|
||||||
|
|
||||||
|
# Test correct settings
|
||||||
|
theProject.mainConf.backupPath = nwTemp
|
||||||
|
assert theProject.zipIt(doNotify=False)
|
||||||
|
|
||||||
|
theFiles = os.listdir(os.path.join(nwTemp, "Test Minimal"))
|
||||||
|
assert len(theFiles) == 1
|
||||||
|
|
||||||
|
theZip = theFiles[0]
|
||||||
|
assert theZip[:12] == "Backup from "
|
||||||
|
assert theZip[-4:] == ".zip"
|
||||||
|
|
||||||
|
# Extract the archive
|
||||||
|
with ZipFile(os.path.join(nwTemp, "Test Minimal", theZip), "r") as inZip:
|
||||||
|
inZip.extractall(os.path.join(nwTemp, "extract"))
|
||||||
|
|
||||||
|
# Check that the main project file was restored
|
||||||
|
assert cmpFiles(
|
||||||
|
os.path.join(nwMinimal, "nwProject.nwx"), os.path.join(nwTemp, "extract", "nwProject.nwx")
|
||||||
|
)
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ from nw.core.tools import countWords, numberToRoman, numberToWord
|
|||||||
|
|
||||||
@pytest.mark.core
|
@pytest.mark.core
|
||||||
def testCountWords():
|
def testCountWords():
|
||||||
|
"""Test the word counter and the exclusion filers.
|
||||||
|
"""
|
||||||
testText = (
|
testText = (
|
||||||
"# Heading One\n"
|
"# Heading One\n"
|
||||||
"## Heading Two\n"
|
"## Heading Two\n"
|
||||||
@@ -33,6 +35,8 @@ def testCountWords():
|
|||||||
|
|
||||||
@pytest.mark.core
|
@pytest.mark.core
|
||||||
def testNumberWords():
|
def testNumberWords():
|
||||||
|
"""Test the conversion of integer to English words.
|
||||||
|
"""
|
||||||
assert numberToWord(0, "en") == "Zero"
|
assert numberToWord(0, "en") == "Zero"
|
||||||
assert numberToWord(1, "en") == "One"
|
assert numberToWord(1, "en") == "One"
|
||||||
assert numberToWord(2, "en") == "Two"
|
assert numberToWord(2, "en") == "Two"
|
||||||
@@ -60,8 +64,15 @@ def testNumberWords():
|
|||||||
assert numberToWord(142, "en") == "One Hundred Forty-Two"
|
assert numberToWord(142, "en") == "One Hundred Forty-Two"
|
||||||
assert numberToWord(999, "en") == "Nine Hundred Ninety-Nine"
|
assert numberToWord(999, "en") == "Nine Hundred Ninety-Nine"
|
||||||
|
|
||||||
|
# Check a few with a nonsense language setting
|
||||||
|
assert numberToWord(1, "foo") == "One"
|
||||||
|
assert numberToWord(2, "foo") == "Two"
|
||||||
|
assert numberToWord(3, "foo") == "Three"
|
||||||
|
|
||||||
@pytest.mark.core
|
@pytest.mark.core
|
||||||
def testRomanNumbers():
|
def testRomanNumbers():
|
||||||
|
"""Test conversion of integers to Roman numbers.
|
||||||
|
"""
|
||||||
assert numberToRoman(None, False) == "NAN"
|
assert numberToRoman(None, False) == "NAN"
|
||||||
assert numberToRoman(0, False) == "OOR"
|
assert numberToRoman(0, False) == "OOR"
|
||||||
assert numberToRoman(1, False) == "I"
|
assert numberToRoman(1, False) == "I"
|
||||||
|
|||||||
Reference in New Issue
Block a user