Merge branch 'main' into new_project_wizard
This commit is contained in:
+42
-13
@@ -4,29 +4,58 @@ services:
|
|||||||
- xvfb
|
- xvfb
|
||||||
language: python
|
language: python
|
||||||
cache: bundler
|
cache: bundler
|
||||||
|
|
||||||
addons:
|
addons:
|
||||||
apt:
|
apt:
|
||||||
packages:
|
packages:
|
||||||
- libenchant-dev
|
- libenchant-dev
|
||||||
- python3-pyqt5
|
- python3-pyqt5
|
||||||
- python3-pyqt5.qtsvg
|
- python3-pyqt5.qtsvg
|
||||||
python:
|
- python3-lxml
|
||||||
- "3.6"
|
|
||||||
- "3.7"
|
|
||||||
- "3.8"
|
|
||||||
install:
|
install:
|
||||||
- pip install --upgrade pip
|
# - pip install --upgrade pip
|
||||||
- pip install -r requirements.txt
|
- pip install -r requirements.txt
|
||||||
# - pip install pytest-faulthandler
|
|
||||||
- pip install PyVirtualDisplay
|
- pip install PyVirtualDisplay
|
||||||
- pip install pytest-xvfb
|
|
||||||
- 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
|
||||||
script:
|
|
||||||
- python -m pytest --cov=nw -m "project|core|gui" -v
|
|
||||||
after_success:
|
|
||||||
- codecov
|
|
||||||
after_failure:
|
|
||||||
- cat /sys/fs/cgroup/memory/memory.max_usage_in_bytes
|
|
||||||
|
|
||||||
|
stages:
|
||||||
|
- name: Main
|
||||||
|
- name: Supported
|
||||||
|
# - name: Future
|
||||||
|
# if: branch = main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
include:
|
||||||
|
- stage:
|
||||||
|
- Main
|
||||||
|
python:
|
||||||
|
- 3.8
|
||||||
|
script:
|
||||||
|
- python -m pytest --cov=nw -v
|
||||||
|
after_success:
|
||||||
|
- codecov
|
||||||
|
after_failure:
|
||||||
|
- cat /sys/fs/cgroup/memory/memory.max_usage_in_bytes
|
||||||
|
|
||||||
|
- stage:
|
||||||
|
- Supported
|
||||||
|
python:
|
||||||
|
- 3.6
|
||||||
|
script:
|
||||||
|
- python -m pytest -v
|
||||||
|
|
||||||
|
- python:
|
||||||
|
- 3.7
|
||||||
|
script:
|
||||||
|
- python -m pytest -v
|
||||||
|
|
||||||
|
# - stage:
|
||||||
|
# - Future
|
||||||
|
# python:
|
||||||
|
# - 3.9-dev
|
||||||
|
# script:
|
||||||
|
# - python -m pytest -v
|
||||||
|
|||||||
@@ -1,5 +1,26 @@
|
|||||||
# novelWriter ChangeLog
|
# novelWriter ChangeLog
|
||||||
|
|
||||||
|
## Version 0.11.1 [2020-08-09]
|
||||||
|
|
||||||
|
**Bugfixes**
|
||||||
|
|
||||||
|
* The modality of the dialogs have been made more consistent and a few issues with conflicting settings resolved. Mostly the latter relates to some dialogs both having the `exec_()` call and the `show()` call. The former implies modal, the latter does not, and the latter usually took precedence. All dialogs are now modal with the exception of the Writing Statistics and Build Novel Project tools. PR #389.
|
||||||
|
|
||||||
|
**User Interface**
|
||||||
|
|
||||||
|
* The Help menu entries for the documentation have been improved a bit. If the local copy of the documentations is present (both files are checked now), and the Qt Assistant is installed, the "Documentation (Local)" entry is visible with `F1` as keyboard shortcut. The "Documentation (Online)" is always visible with `Shift+F1` keyboard shortcut. The `F1` key redirects to this too if the local copy isn't available. PR #386.
|
||||||
|
* The Writing Statistics tool now has the ability to set a cap between 100 and 100 000 words on the word count histogram bars. This is useful if the user has added a large chunk of text, in which case the histogram bar is dominated by this one entry. Now, anything on and above the cap value will have a full bar, and all other entries scale from 0 to the cap value. PR #387.
|
||||||
|
|
||||||
|
**Documentation**
|
||||||
|
|
||||||
|
* The main index page of the documentation now has a build date on it. PR #390.
|
||||||
|
|
||||||
|
**Other Changes**
|
||||||
|
|
||||||
|
* The Travis CI build system has been altered to first check that the tests pass for Python 3.8, for then to move to the other supported Python versions. These are currently 3.6 and 3.7. Python 3.9 will be added when it is released in October. PR #388.
|
||||||
|
* Some clean-up of the source code, mostly in terms of unused imports and missing docstrings. PR #391.
|
||||||
|
|
||||||
|
|
||||||
## Version 0.11 [2020-08-08]
|
## Version 0.11 [2020-08-08]
|
||||||
|
|
||||||
Note: The source code has now switched to a default branch named `main` ahead of the changes planned by GitHub.
|
Note: The source code has now switched to a default branch named `main` ahead of the changes planned by GitHub.
|
||||||
|
|||||||
+8
-28
@@ -15,7 +15,7 @@
|
|||||||
# import os
|
# import os
|
||||||
# import sys
|
# import sys
|
||||||
# sys.path.insert(0, os.path.abspath("."))
|
# sys.path.insert(0, os.path.abspath("."))
|
||||||
import sphinx_rtd_theme
|
import os, time, sphinx_rtd_theme
|
||||||
|
|
||||||
# -- Project information -----------------------------------------------------
|
# -- Project information -----------------------------------------------------
|
||||||
|
|
||||||
@@ -24,46 +24,26 @@ copyright = "2018-2020, Veronica Berglyd Olsen"
|
|||||||
author = "Veronica Berglyd Olsen"
|
author = "Veronica Berglyd Olsen"
|
||||||
|
|
||||||
# The short X.Y version
|
# The short X.Y version
|
||||||
version = "0.11.0"
|
version = "0.11.1"
|
||||||
# The full version, including alpha/beta/rc tags
|
# The full version, including alpha/beta/rc tags
|
||||||
release = "0.11.0"
|
release = "0.11.1"
|
||||||
|
|
||||||
|
|
||||||
# -- General configuration ---------------------------------------------------
|
# -- General configuration ---------------------------------------------------
|
||||||
|
|
||||||
# If your documentation needs a minimal Sphinx version, state it here.
|
os.environ["TZ"] = "Europe/Oslo"
|
||||||
# needs_sphinx = "1.0"
|
time.tzset()
|
||||||
|
|
||||||
# Add any Sphinx extension module names here, as strings. They can be
|
# needs_sphinx = "1.0"
|
||||||
# extensions coming with Sphinx (named "sphinx.ext.*") or your custom
|
|
||||||
# ones.
|
|
||||||
extensions = [
|
extensions = [
|
||||||
"sphinx_rtd_theme",
|
"sphinx_rtd_theme",
|
||||||
]
|
]
|
||||||
|
|
||||||
# Add any paths that contain templates here, relative to this directory.
|
|
||||||
templates_path = ["_templates"]
|
templates_path = ["_templates"]
|
||||||
|
|
||||||
# The suffix(es) of source filenames.
|
|
||||||
# You can specify multiple suffix as a list of string:
|
|
||||||
# source_suffix = [".rst", ".md"]
|
|
||||||
source_suffix = ".rst"
|
source_suffix = ".rst"
|
||||||
|
|
||||||
# The master toctree document.
|
|
||||||
master_doc = "index"
|
master_doc = "index"
|
||||||
|
today_fmt = "%A, %B %d %Y at %H:%M"
|
||||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
|
||||||
# for a list of supported languages.
|
|
||||||
# This is also used if you do content translation via gettext catalogs.
|
|
||||||
# Usually you set "language" from the command line for these cases.
|
|
||||||
language = None
|
language = None
|
||||||
|
|
||||||
# List of patterns, relative to source directory, that match files and
|
|
||||||
# directories to ignore when looking for source files.
|
|
||||||
# This pattern also affects html_static_path and html_extra_path.
|
|
||||||
exclude_patterns = []
|
exclude_patterns = []
|
||||||
|
|
||||||
# The name of the Pygments (syntax highlighting) style to use.
|
|
||||||
pygments_style = None
|
pygments_style = None
|
||||||
|
|
||||||
|
|
||||||
@@ -98,7 +78,7 @@ html_css_files = [
|
|||||||
# -- Options for HTMLHelp output ---------------------------------------------
|
# -- Options for HTMLHelp output ---------------------------------------------
|
||||||
|
|
||||||
# Output file base name for HTML help builder.
|
# Output file base name for HTML help builder.
|
||||||
htmlhelp_basename = "novelWriterdoc"
|
htmlhelp_basename = "novelWriterDoc"
|
||||||
|
|
||||||
|
|
||||||
# -- Options for LaTeX output ------------------------------------------------
|
# -- Options for LaTeX output ------------------------------------------------
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ novelWriter |release|
|
|||||||
:target: https://pypi.org/project/novelWriter/
|
:target: https://pypi.org/project/novelWriter/
|
||||||
:alt: Python Version
|
:alt: Python Version
|
||||||
|
|
||||||
|
**Last Updated:** |today|
|
||||||
|
|
||||||
novelWriter is a markdown-like text editor designed for writing novels and larger projects of many
|
novelWriter is a markdown-like text editor designed for writing novels and larger projects of many
|
||||||
smaller plain text documents. It uses its own flavour of markdown that supports a meta data syntax
|
smaller plain text documents. It uses its own flavour of markdown that supports a meta data syntax
|
||||||
for comments, synopsis and cross-referencing between files. The idea is to have a simple text editor
|
for comments, synopsis and cross-referencing between files. The idea is to have a simple text editor
|
||||||
|
|||||||
@@ -277,3 +277,20 @@ Settings`.
|
|||||||
For the backup to be able to run, the :guilabel:`Working Title` must be set in :guilabel:`Project
|
For the backup to be able to run, the :guilabel:`Working Title` must be set in :guilabel:`Project
|
||||||
Settings`. This value is used to generate the folder name for the zip files. Without it, the
|
Settings`. This value is used to generate the folder name for the zip files. Without it, the
|
||||||
backup will not run at all, but produce a warning message.
|
backup will not run at all, but produce a warning message.
|
||||||
|
|
||||||
|
.. _a_proj_stats:
|
||||||
|
|
||||||
|
Writing Statistics
|
||||||
|
==================
|
||||||
|
|
||||||
|
When you work on your project, a log file records when you opened it, when you closed it, and how
|
||||||
|
many words you added to your novel and note files during the session. You can view this file in the
|
||||||
|
``meta`` folder in the directory where you saved your project. The file is named
|
||||||
|
``sessionStats.log``.
|
||||||
|
|
||||||
|
A small tool to view the content of this file is available in the :guilabel:`Tools` menu under
|
||||||
|
:guilabel:`Writing Statistics`. You can also launch it by pressing :kbd:`F6`.
|
||||||
|
|
||||||
|
The tool will show a list of all your sessions, and a set of filters to apply to it. You can also
|
||||||
|
export the filtered data to a JSON file or to a CSV file that can be opened by a spreadsheet
|
||||||
|
application like for instance Libre Office Calc.
|
||||||
|
|||||||
+3
-3
@@ -41,9 +41,9 @@ __package__ = "nw"
|
|||||||
__author__ = "Veronica Berglyd Olsen"
|
__author__ = "Veronica Berglyd Olsen"
|
||||||
__copyright__ = "Copyright 2018–2020, Veronica Berglyd Olsen"
|
__copyright__ = "Copyright 2018–2020, Veronica Berglyd Olsen"
|
||||||
__license__ = "GPLv3"
|
__license__ = "GPLv3"
|
||||||
__version__ = "0.11.0"
|
__version__ = "0.11.1"
|
||||||
__hexversion__ = "0x001100f0"
|
__hexversion__ = "0x001101f0"
|
||||||
__date__ = "2020-08-08"
|
__date__ = "2020-08-09"
|
||||||
__maintainer__ = "Veronica Berglyd Olsen"
|
__maintainer__ = "Veronica Berglyd Olsen"
|
||||||
__email__ = "code@vkbo.net"
|
__email__ = "code@vkbo.net"
|
||||||
__status__ = "Beta"
|
__status__ = "Beta"
|
||||||
|
|||||||
+9
-3
@@ -35,6 +35,8 @@ from nw.constants import nwConst
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
def checkString(checkValue, defaultValue, allowNone=False):
|
def checkString(checkValue, defaultValue, allowNone=False):
|
||||||
|
"""Check if a variable is a string or a none.
|
||||||
|
"""
|
||||||
if allowNone:
|
if allowNone:
|
||||||
if checkValue == None:
|
if checkValue == None:
|
||||||
return None
|
return None
|
||||||
@@ -45,6 +47,8 @@ def checkString(checkValue, defaultValue, allowNone=False):
|
|||||||
return defaultValue
|
return defaultValue
|
||||||
|
|
||||||
def checkInt(checkValue, defaultValue, allowNone=False):
|
def checkInt(checkValue, defaultValue, allowNone=False):
|
||||||
|
"""Check if a variable is an integer or a none.
|
||||||
|
"""
|
||||||
if allowNone:
|
if allowNone:
|
||||||
if checkValue == None:
|
if checkValue == None:
|
||||||
return None
|
return None
|
||||||
@@ -56,6 +60,8 @@ def checkInt(checkValue, defaultValue, allowNone=False):
|
|||||||
return defaultValue
|
return defaultValue
|
||||||
|
|
||||||
def checkBool(checkValue, defaultValue, allowNone=False):
|
def checkBool(checkValue, defaultValue, allowNone=False):
|
||||||
|
"""Check if a variable is a boolean or a none.
|
||||||
|
"""
|
||||||
if allowNone:
|
if allowNone:
|
||||||
if checkValue == None:
|
if checkValue == None:
|
||||||
return None
|
return None
|
||||||
@@ -92,7 +98,8 @@ def isHandle(theString):
|
|||||||
return not invalidChar
|
return not invalidChar
|
||||||
|
|
||||||
def colRange(rgbStart, rgbEnd, nStep):
|
def colRange(rgbStart, rgbEnd, nStep):
|
||||||
|
"""Generate a range of colours from one RGB value to another.
|
||||||
|
"""
|
||||||
if len(rgbStart) != 3 and len(rgbEnd) != 3 and nStep < 1:
|
if len(rgbStart) != 3 and len(rgbEnd) != 3 and nStep < 1:
|
||||||
logger.error("Cannot create colour range from given parameters")
|
logger.error("Cannot create colour range from given parameters")
|
||||||
return None
|
return None
|
||||||
@@ -124,7 +131,7 @@ def colRange(rgbStart, rgbEnd, nStep):
|
|||||||
def formatInt(theInt):
|
def formatInt(theInt):
|
||||||
"""Formats an integer with k, M, G etc.
|
"""Formats an integer with k, M, G etc.
|
||||||
"""
|
"""
|
||||||
postFix = ["k","M","G","T","P","E"]
|
postFix = ["k", "M", "G", "T", "P", "E"]
|
||||||
theVal = float(theInt)
|
theVal = float(theInt)
|
||||||
|
|
||||||
if theVal > 1000.0:
|
if theVal > 1000.0:
|
||||||
@@ -153,7 +160,6 @@ def splitVersionNumber(vString):
|
|||||||
""" Splits a version string on the form aa.bb.cc into major, minor
|
""" Splits a version string on the form aa.bb.cc into major, minor
|
||||||
and patch, and computes an integer value aabbcc.
|
and patch, and computes an integer value aabbcc.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
vMajor = 0
|
vMajor = 0
|
||||||
vMinor = 0
|
vMinor = 0
|
||||||
vPatch = 0
|
vPatch = 0
|
||||||
|
|||||||
+11
-3
@@ -88,7 +88,7 @@ class Config:
|
|||||||
self.guiSyntax = "default_light"
|
self.guiSyntax = "default_light"
|
||||||
self.guiIcons = "typicons_colour_light"
|
self.guiIcons = "typicons_colour_light"
|
||||||
self.guiDark = False
|
self.guiDark = False
|
||||||
self.guiLang = "en" # Hardcoded for now
|
self.guiLang = "en" # Hardcoded for now since the GUI is only in English
|
||||||
self.guiFont = ""
|
self.guiFont = ""
|
||||||
self.guiFontSize = 11
|
self.guiFontSize = 11
|
||||||
self.guiScale = 1.0 # Set automatically by Theme class
|
self.guiScale = 1.0 # Set automatically by Theme class
|
||||||
@@ -286,7 +286,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 path.isfile(path.join(self.confPath, self.confFile)):
|
||||||
# If it exists, load it
|
# If it exists, load it
|
||||||
self.loadConfig()
|
self.loadConfig()
|
||||||
else:
|
else:
|
||||||
@@ -855,7 +855,9 @@ class Config:
|
|||||||
##
|
##
|
||||||
|
|
||||||
def _unpackList(self, inStr, listLen, listDefault, castTo=int):
|
def _unpackList(self, inStr, listLen, listDefault, castTo=int):
|
||||||
inData = inStr.split(",")
|
"""Unpack a comma separated string of items into a list.
|
||||||
|
"""
|
||||||
|
inData = inStr.split(",")
|
||||||
outData = []
|
outData = []
|
||||||
for i in range(listLen):
|
for i in range(listLen):
|
||||||
try:
|
try:
|
||||||
@@ -865,9 +867,13 @@ class Config:
|
|||||||
return outData
|
return outData
|
||||||
|
|
||||||
def _packList(self, inData):
|
def _packList(self, inData):
|
||||||
|
"""Pack a list of items into a comma separated string.
|
||||||
|
"""
|
||||||
return ", ".join(str(inVal) for inVal in inData)
|
return ", ".join(str(inVal) for inVal in inData)
|
||||||
|
|
||||||
def _parseLine(self, cnfParse, cnfSec, cnfName, cnfType, cnfDefault):
|
def _parseLine(self, cnfParse, cnfSec, cnfName, cnfType, cnfDefault):
|
||||||
|
"""Parse a line and return the correct datatype.
|
||||||
|
"""
|
||||||
if cnfParse.has_section(cnfSec):
|
if cnfParse.has_section(cnfSec):
|
||||||
if cnfParse.has_option(cnfSec, cnfName):
|
if cnfParse.has_option(cnfSec, cnfName):
|
||||||
if cnfType == self.CNF_STR:
|
if cnfType == self.CNF_STR:
|
||||||
@@ -883,6 +889,8 @@ class Config:
|
|||||||
return cnfDefault
|
return cnfDefault
|
||||||
|
|
||||||
def _checkNone(self, checkVal):
|
def _checkNone(self, checkVal):
|
||||||
|
"""Convert a string to a none type.
|
||||||
|
"""
|
||||||
if checkVal is None:
|
if checkVal is None:
|
||||||
return None
|
return None
|
||||||
if isinstance(checkVal, str):
|
if isinstance(checkVal, str):
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from nw.constants.enum import nwItemClass, nwItemLayout, nwOutline, nwDocInsert
|
from nw.constants.enum import nwItemClass, nwItemLayout, nwOutline
|
||||||
|
|
||||||
class nwConst():
|
class nwConst():
|
||||||
|
|
||||||
|
|||||||
+6
-5
@@ -28,7 +28,7 @@
|
|||||||
import logging
|
import logging
|
||||||
import nw
|
import nw
|
||||||
|
|
||||||
from os import path, mkdir, rename, unlink
|
from os import path, rename, unlink
|
||||||
|
|
||||||
from nw.core.item import NWItem
|
from nw.core.item import NWItem
|
||||||
from nw.constants import nwAlert
|
from nw.constants import nwAlert
|
||||||
@@ -44,10 +44,11 @@ class NWDoc():
|
|||||||
self.mainConf = nw.CONFIG
|
self.mainConf = nw.CONFIG
|
||||||
self.theProject = theProject
|
self.theProject = theProject
|
||||||
self.theParent = theParent
|
self.theParent = theParent
|
||||||
self.theItem = None
|
|
||||||
self.docHandle = None
|
self.theItem = None
|
||||||
self.fileLoc = None
|
self.docHandle = None
|
||||||
self.docMeta = ""
|
self.fileLoc = None
|
||||||
|
self.docMeta = ""
|
||||||
|
|
||||||
# Internal Mapping
|
# Internal Mapping
|
||||||
self.makeAlert = self.theParent.makeAlert
|
self.makeAlert = self.theParent.makeAlert
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ class OptionState():
|
|||||||
"hideZeros",
|
"hideZeros",
|
||||||
"hideNegative",
|
"hideNegative",
|
||||||
"groupByDay",
|
"groupByDay",
|
||||||
|
"histMax",
|
||||||
},
|
},
|
||||||
"GuiDocSplit": {
|
"GuiDocSplit": {
|
||||||
"spLevel",
|
"spLevel",
|
||||||
|
|||||||
@@ -195,7 +195,6 @@ class NWSpellEnchant(NWSpellCheck):
|
|||||||
class NWSpellEnchantDummy:
|
class NWSpellEnchantDummy:
|
||||||
"""Fallback for when Enchant is selected, but not installed.
|
"""Fallback for when Enchant is selected, but not installed.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@@ -227,7 +227,6 @@ class Tokenizer():
|
|||||||
"""Set the text for the tokenizer from a handle. If theText is
|
"""Set the text for the tokenizer from a handle. If theText is
|
||||||
not set, load it from the file.
|
not set, load it from the file.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.theHandle = theHandle
|
self.theHandle = theHandle
|
||||||
self.theItem = self.theProject.projTree[theHandle]
|
self.theItem = self.theProject.projTree[theHandle]
|
||||||
if self.theItem is None:
|
if self.theItem is None:
|
||||||
@@ -308,7 +307,6 @@ class Tokenizer():
|
|||||||
4: The internal formatting map of the text, self.FMT_*
|
4: The internal formatting map of the text, self.FMT_*
|
||||||
5: The style of the block, self.A_*
|
5: The style of the block, self.A_*
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# RegExes for adding formatting tags within text lines
|
# RegExes for adding formatting tags within text lines
|
||||||
rxFormats = [
|
rxFormats = [
|
||||||
(QRegularExpression(nwRegEx.FMT_I), [None, self.FMT_I_B, None, self.FMT_I_E]),
|
(QRegularExpression(nwRegEx.FMT_I), [None, self.FMT_I_B, None, self.FMT_I_E]),
|
||||||
@@ -457,7 +455,6 @@ class Tokenizer():
|
|||||||
"""Apply formatting to the text headers according to document
|
"""Apply formatting to the text headers according to document
|
||||||
layout and user settings.
|
layout and user settings.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# No special header formatting for notes and no-layout files
|
# No special header formatting for notes and no-layout files
|
||||||
if self.isNone or self.isNote:
|
if self.isNone or self.isNote:
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -30,8 +30,6 @@
|
|||||||
import logging
|
import logging
|
||||||
import nw
|
import nw
|
||||||
|
|
||||||
from os import path, unlink, rmdir
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# =============================================================================================== #
|
# =============================================================================================== #
|
||||||
|
|||||||
+3
-3
@@ -46,7 +46,7 @@ def formatHtmlErrMsg(exType, exValue, exTrace):
|
|||||||
"<p>Please report this error by submitting an issue report on "
|
"<p>Please report this error by submitting an issue report on "
|
||||||
"GitHub, providing a description and this error message. "
|
"GitHub, providing a description and this error message. "
|
||||||
"URL: <{issueUrl}>.</p>"
|
"URL: <{issueUrl}>.</p>"
|
||||||
"<p><b>Environment</b><br>Version: {nwVersion}, OS: {osType} ({osKernel}),"
|
"<p><b>Environment</b><br>Version: {nwVersion}, OS: {osType} ({osKernel}), "
|
||||||
"Python: {pyVersion} ({pyHexVer:#x}), Qt: {qtVers}, PyQt: {pyqtVers}</p>"
|
"Python: {pyVersion} ({pyHexVer:#x}), Qt: {qtVers}, PyQt: {pyqtVers}</p>"
|
||||||
"<p><b>Error Type</b><br>{exType}: {exMessage}</p>"
|
"<p><b>Error Type</b><br>{exType}: {exMessage}</p>"
|
||||||
"<p><b>Traceback</b><br>{exTrace}</p>"
|
"<p><b>Traceback</b><br>{exTrace}</p>"
|
||||||
@@ -76,9 +76,9 @@ def exceptionHandler(exType, exValue, exTrace):
|
|||||||
"""Function to catch unhandled global exceptions.
|
"""Function to catch unhandled global exceptions.
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
from traceback import print_tb, format_tb
|
from traceback import print_tb
|
||||||
from nw import CONFIG
|
from nw import CONFIG
|
||||||
from PyQt5.QtWidgets import qApp, QApplication, QErrorMessage, QMessageBox
|
from PyQt5.QtWidgets import qApp, QErrorMessage
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
logger.error("%s: %s" % (exType.__name__, str(exValue)))
|
logger.error("%s: %s" % (exType.__name__, str(exValue)))
|
||||||
|
|||||||
+1
-1
@@ -39,7 +39,7 @@ from PyQt5.QtCore import (
|
|||||||
Qt, QSize, QThread, QTimer, pyqtSlot, QRegExp, QRegularExpression
|
Qt, QSize, QThread, QTimer, pyqtSlot, QRegExp, QRegularExpression
|
||||||
)
|
)
|
||||||
from PyQt5.QtGui import (
|
from PyQt5.QtGui import (
|
||||||
QTextCursor, QTextOption, QKeySequence, QFont, QColor, QPalette, QIcon,
|
QTextCursor, QTextOption, QKeySequence, QFont, QColor, QPalette,
|
||||||
QTextDocument, QCursor, QPixmap
|
QTextDocument, QCursor, QPixmap
|
||||||
)
|
)
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
|
|||||||
@@ -79,7 +79,6 @@ class GuiDocMerge(QDialog):
|
|||||||
self.setLayout(self.outerBox)
|
self.setLayout(self.outerBox)
|
||||||
|
|
||||||
self.rejected.connect(self._doClose)
|
self.rejected.connect(self._doClose)
|
||||||
self.show()
|
|
||||||
|
|
||||||
self._populateList()
|
self._populateList()
|
||||||
|
|
||||||
@@ -96,7 +95,6 @@ class GuiDocMerge(QDialog):
|
|||||||
create a new file in the same parent folder. The old files are
|
create a new file in the same parent folder. The old files are
|
||||||
not removed in the merge process, and must be deleted manually.
|
not removed in the merge process, and must be deleted manually.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
logger.verbose("GuiDocMerge merge button clicked")
|
logger.verbose("GuiDocMerge merge button clicked")
|
||||||
|
|
||||||
finalOrder = []
|
finalOrder = []
|
||||||
@@ -143,7 +141,6 @@ class GuiDocMerge(QDialog):
|
|||||||
are then added to the list view in order. The list itself can be
|
are then added to the list view in order. The list itself can be
|
||||||
reordered by the user.
|
reordered by the user.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
tHandle = self.theParent.treeView.getSelectedHandle()
|
tHandle = self.theParent.treeView.getSelectedHandle()
|
||||||
self.sourceItem = tHandle
|
self.sourceItem = tHandle
|
||||||
if tHandle is None:
|
if tHandle is None:
|
||||||
|
|||||||
+2
-4
@@ -33,6 +33,7 @@ from PyQt5.QtWidgets import (
|
|||||||
QDialog, QVBoxLayout, QComboBox, QListWidget, QAbstractItemView,
|
QDialog, QVBoxLayout, QComboBox, QListWidget, QAbstractItemView,
|
||||||
QListWidgetItem, QDialogButtonBox, QLabel
|
QListWidgetItem, QDialogButtonBox, QLabel
|
||||||
)
|
)
|
||||||
|
|
||||||
from nw.constants import nwAlert, nwItemType, nwItemClass, nwItemLayout
|
from nw.constants import nwAlert, nwItemType, nwItemClass, nwItemLayout
|
||||||
from nw.gui.custom import QHelpLabel
|
from nw.gui.custom import QHelpLabel
|
||||||
from nw.core import NWDoc
|
from nw.core import NWDoc
|
||||||
@@ -92,7 +93,6 @@ class GuiDocSplit(QDialog):
|
|||||||
self.setLayout(self.outerBox)
|
self.setLayout(self.outerBox)
|
||||||
|
|
||||||
self.rejected.connect(self._doClose)
|
self.rejected.connect(self._doClose)
|
||||||
self.show()
|
|
||||||
|
|
||||||
self._populateList()
|
self._populateList()
|
||||||
|
|
||||||
@@ -110,7 +110,6 @@ class GuiDocSplit(QDialog):
|
|||||||
settings. The old file is not removed in the merge process, and
|
settings. The old file is not removed in the merge process, and
|
||||||
must be deleted manually.
|
must be deleted manually.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
logger.verbose("GuiDocSplit split button clicked")
|
logger.verbose("GuiDocSplit split button clicked")
|
||||||
|
|
||||||
if self.sourceItem is None:
|
if self.sourceItem is None:
|
||||||
@@ -132,7 +131,7 @@ class GuiDocSplit(QDialog):
|
|||||||
nLines = len(theLines)
|
nLines = len(theLines)
|
||||||
theLines.insert(0, "%Split Doc")
|
theLines.insert(0, "%Split Doc")
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"Splitting document %s with %d lines" % (self.sourceItem,nLines)
|
"Splitting document %s with %d lines" % (self.sourceItem, nLines)
|
||||||
)
|
)
|
||||||
|
|
||||||
finalOrder = []
|
finalOrder = []
|
||||||
@@ -210,7 +209,6 @@ class GuiDocSplit(QDialog):
|
|||||||
are then added to the list view in order. The list itself can be
|
are then added to the list view in order. The list itself can be
|
||||||
reordered by the user.
|
reordered by the user.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.sourceItem is None:
|
if self.sourceItem is None:
|
||||||
self.sourceItem = self.theParent.treeView.getSelectedHandle()
|
self.sourceItem = self.theParent.treeView.getSelectedHandle()
|
||||||
|
|
||||||
|
|||||||
@@ -29,11 +29,11 @@ import logging
|
|||||||
import nw
|
import nw
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt
|
from PyQt5.QtCore import Qt
|
||||||
from PyQt5.QtGui import QFont, QIcon, QPixmap
|
from PyQt5.QtGui import QFont, QPixmap
|
||||||
from PyQt5.QtWidgets import QWidget, QGridLayout, QLabel
|
from PyQt5.QtWidgets import QWidget, QGridLayout, QLabel
|
||||||
|
|
||||||
from nw.constants import (
|
from nw.constants import (
|
||||||
nwLabels, nwItemClass, nwItemType, nwItemLayout, nwUnicode
|
nwLabels, nwItemClass, nwItemType, nwItemLayout
|
||||||
)
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
@@ -28,7 +28,6 @@
|
|||||||
import logging
|
import logging
|
||||||
import nw
|
import nw
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt
|
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
QDialog, QVBoxLayout, QGridLayout, QLineEdit, QComboBox, QLabel,
|
QDialog, QVBoxLayout, QGridLayout, QLineEdit, QComboBox, QLabel,
|
||||||
QDialogButtonBox
|
QDialogButtonBox
|
||||||
@@ -151,7 +150,6 @@ class GuiItemEditor(QDialog):
|
|||||||
def _doSave(self):
|
def _doSave(self):
|
||||||
"""Save the setting to the item.
|
"""Save the setting to the item.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
logger.verbose("ItemEditor save button clicked")
|
logger.verbose("ItemEditor save button clicked")
|
||||||
|
|
||||||
itemName = self.editName.text()
|
itemName = self.editName.text()
|
||||||
@@ -172,6 +170,8 @@ class GuiItemEditor(QDialog):
|
|||||||
return
|
return
|
||||||
|
|
||||||
def _doClose(self):
|
def _doClose(self):
|
||||||
|
"""Close the dialog without saving the settings.
|
||||||
|
"""
|
||||||
logger.verbose("ItemEditor close button clicked")
|
logger.verbose("ItemEditor close button clicked")
|
||||||
self.close()
|
self.close()
|
||||||
return
|
return
|
||||||
|
|||||||
+21
-30
@@ -28,13 +28,10 @@
|
|||||||
import logging
|
import logging
|
||||||
import nw
|
import nw
|
||||||
|
|
||||||
from os import path
|
|
||||||
|
|
||||||
from PyQt5.QtCore import QUrl, QProcess
|
from PyQt5.QtCore import QUrl, QProcess
|
||||||
from PyQt5.QtGui import QDesktopServices
|
from PyQt5.QtGui import QDesktopServices
|
||||||
from PyQt5.QtWidgets import QMenuBar, QAction, QMessageBox
|
from PyQt5.QtWidgets import QMenuBar, QAction
|
||||||
|
|
||||||
from nw.gui.about import GuiAbout
|
|
||||||
from nw.constants import nwItemType, nwItemClass, nwDocAction, nwDocInsert
|
from nw.constants import nwItemType, nwItemClass, nwDocAction, nwDocInsert
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -150,21 +147,6 @@ class GuiMainMenu(QMenuBar):
|
|||||||
self.theProject.setAutoOutline(theMode)
|
self.theProject.setAutoOutline(theMode)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _showAbout(self):
|
|
||||||
"""Show the about dialog.
|
|
||||||
"""
|
|
||||||
if self.mainConf.showGUI:
|
|
||||||
msgAbout = GuiAbout(self.theParent)
|
|
||||||
msgAbout.exec_()
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _showAboutQt(self):
|
|
||||||
"""Show Qt's own About dialog.
|
|
||||||
"""
|
|
||||||
msgBox = QMessageBox()
|
|
||||||
msgBox.aboutQt(self.theParent,"About Qt")
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _openAssistant(self):
|
def _openAssistant(self):
|
||||||
"""Open the documentation in Qt Assistant.
|
"""Open the documentation in Qt Assistant.
|
||||||
"""
|
"""
|
||||||
@@ -210,7 +192,8 @@ class GuiMainMenu(QMenuBar):
|
|||||||
##
|
##
|
||||||
|
|
||||||
def _buildProjectMenu(self):
|
def _buildProjectMenu(self):
|
||||||
|
"""Assemble the Project menu.
|
||||||
|
"""
|
||||||
# Project
|
# Project
|
||||||
self.projMenu = self.addMenu("&Project")
|
self.projMenu = self.addMenu("&Project")
|
||||||
|
|
||||||
@@ -313,7 +296,8 @@ class GuiMainMenu(QMenuBar):
|
|||||||
return
|
return
|
||||||
|
|
||||||
def _buildDocumentMenu(self):
|
def _buildDocumentMenu(self):
|
||||||
|
"""Assemble the Document menu.
|
||||||
|
"""
|
||||||
# Document
|
# Document
|
||||||
self.docuMenu = self.addMenu("&Document")
|
self.docuMenu = self.addMenu("&Document")
|
||||||
|
|
||||||
@@ -395,7 +379,8 @@ class GuiMainMenu(QMenuBar):
|
|||||||
return
|
return
|
||||||
|
|
||||||
def _buildEditMenu(self):
|
def _buildEditMenu(self):
|
||||||
|
"""Assemble the Edit menu.
|
||||||
|
"""
|
||||||
# Edit
|
# Edit
|
||||||
self.editMenu = self.addMenu("&Edit")
|
self.editMenu = self.addMenu("&Edit")
|
||||||
|
|
||||||
@@ -457,7 +442,8 @@ class GuiMainMenu(QMenuBar):
|
|||||||
return
|
return
|
||||||
|
|
||||||
def _buildViewMenu(self):
|
def _buildViewMenu(self):
|
||||||
|
"""Assemble the View menu.
|
||||||
|
"""
|
||||||
# View
|
# View
|
||||||
self.viewMenu = self.addMenu("&View")
|
self.viewMenu = self.addMenu("&View")
|
||||||
|
|
||||||
@@ -504,7 +490,8 @@ class GuiMainMenu(QMenuBar):
|
|||||||
return
|
return
|
||||||
|
|
||||||
def _buildInsertMenu(self):
|
def _buildInsertMenu(self):
|
||||||
|
"""Assemble the Insert menu.
|
||||||
|
"""
|
||||||
# Insert
|
# Insert
|
||||||
self.insertMenu = self.addMenu("&Insert")
|
self.insertMenu = self.addMenu("&Insert")
|
||||||
|
|
||||||
@@ -594,7 +581,8 @@ class GuiMainMenu(QMenuBar):
|
|||||||
return
|
return
|
||||||
|
|
||||||
def _buildSearchMenu(self):
|
def _buildSearchMenu(self):
|
||||||
|
"""Assemble the Search menu.
|
||||||
|
"""
|
||||||
# Search
|
# Search
|
||||||
self.srcMenu = self.addMenu("&Search")
|
self.srcMenu = self.addMenu("&Search")
|
||||||
|
|
||||||
@@ -645,7 +633,8 @@ class GuiMainMenu(QMenuBar):
|
|||||||
return
|
return
|
||||||
|
|
||||||
def _buildFormatMenu(self):
|
def _buildFormatMenu(self):
|
||||||
|
"""Assemble the Format menu.
|
||||||
|
"""
|
||||||
# Format
|
# Format
|
||||||
self.fmtMenu = self.addMenu("&Format")
|
self.fmtMenu = self.addMenu("&Format")
|
||||||
|
|
||||||
@@ -750,7 +739,8 @@ class GuiMainMenu(QMenuBar):
|
|||||||
return
|
return
|
||||||
|
|
||||||
def _buildToolsMenu(self):
|
def _buildToolsMenu(self):
|
||||||
|
"""Assemble the Tools menu.
|
||||||
|
"""
|
||||||
# Tools
|
# Tools
|
||||||
self.toolsMenu = self.addMenu("&Tools")
|
self.toolsMenu = self.addMenu("&Tools")
|
||||||
|
|
||||||
@@ -846,20 +836,21 @@ class GuiMainMenu(QMenuBar):
|
|||||||
return
|
return
|
||||||
|
|
||||||
def _buildHelpMenu(self):
|
def _buildHelpMenu(self):
|
||||||
|
"""Assemble the Help menu.
|
||||||
|
"""
|
||||||
# Help
|
# Help
|
||||||
self.helpMenu = self.addMenu("&Help")
|
self.helpMenu = self.addMenu("&Help")
|
||||||
|
|
||||||
# Help > About
|
# Help > About
|
||||||
self.aAboutNW = QAction("About %s" % self.mainConf.appName, self)
|
self.aAboutNW = QAction("About %s" % self.mainConf.appName, self)
|
||||||
self.aAboutNW.setStatusTip("About %s" % self.mainConf.appName)
|
self.aAboutNW.setStatusTip("About %s" % self.mainConf.appName)
|
||||||
self.aAboutNW.triggered.connect(self._showAbout)
|
self.aAboutNW.triggered.connect(self.theParent.showAboutNWDialog)
|
||||||
self.helpMenu.addAction(self.aAboutNW)
|
self.helpMenu.addAction(self.aAboutNW)
|
||||||
|
|
||||||
# Help > About Qt5
|
# Help > About Qt5
|
||||||
self.aAboutQt = QAction("About Qt5", self)
|
self.aAboutQt = QAction("About Qt5", self)
|
||||||
self.aAboutQt.setStatusTip("About Qt5")
|
self.aAboutQt.setStatusTip("About Qt5")
|
||||||
self.aAboutQt.triggered.connect(self._showAboutQt)
|
self.aAboutQt.triggered.connect(self.theParent.showAboutQtDialog)
|
||||||
self.helpMenu.addAction(self.aAboutQt)
|
self.helpMenu.addAction(self.aAboutQt)
|
||||||
|
|
||||||
# Help > Separator
|
# Help > Separator
|
||||||
|
|||||||
@@ -195,8 +195,10 @@ class GuiOutline(QTreeWidget):
|
|||||||
tLine = int(tItem.text(self.colIndex[nwOutline.LINE]))
|
tLine = int(tItem.text(self.colIndex[nwOutline.LINE]))
|
||||||
except:
|
except:
|
||||||
tLine = 1
|
tLine = 1
|
||||||
|
|
||||||
logger.verbose("User selected entry with handle %s on line %s" % (tHandle, tLine))
|
logger.verbose("User selected entry with handle %s on line %s" % (tHandle, tLine))
|
||||||
self.theParent.openDocument(tHandle, tLine=tLine-1, doScroll=True)
|
self.theParent.openDocument(tHandle, tLine=tLine-1, doScroll=True)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def _itemSelected(self):
|
def _itemSelected(self):
|
||||||
@@ -208,6 +210,7 @@ class GuiOutline(QTreeWidget):
|
|||||||
tHandle = selItems[0].data(self.colIndex[nwOutline.TITLE], Qt.UserRole)
|
tHandle = selItems[0].data(self.colIndex[nwOutline.TITLE], Qt.UserRole)
|
||||||
sTitle = selItems[0].data(self.colIndex[nwOutline.LINE], Qt.UserRole)
|
sTitle = selItems[0].data(self.colIndex[nwOutline.LINE], Qt.UserRole)
|
||||||
self.theParent.projMeta.showItem(tHandle, sTitle)
|
self.theParent.projMeta.showItem(tHandle, sTitle)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def _headerRightClick(self, clickPos):
|
def _headerRightClick(self, clickPos):
|
||||||
@@ -232,6 +235,7 @@ class GuiOutline(QTreeWidget):
|
|||||||
if theItem in self.colIndex:
|
if theItem in self.colIndex:
|
||||||
self.setColumnHidden(self.colIndex[theItem], not isChecked)
|
self.setColumnHidden(self.colIndex[theItem], not isChecked)
|
||||||
self._saveHeaderState()
|
self._saveHeaderState()
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
##
|
##
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ import nw
|
|||||||
|
|
||||||
from PyQt5.QtCore import Qt
|
from PyQt5.QtCore import Qt
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
QScrollArea, QWidget, QGridLayout, QHBoxLayout, QGroupBox, QLabel, QSizePolicy
|
QScrollArea, QWidget, QGridLayout, QHBoxLayout, QGroupBox, QLabel,
|
||||||
|
QSizePolicy
|
||||||
)
|
)
|
||||||
|
|
||||||
from nw.constants import nwLabels, nwKeyWords
|
from nw.constants import nwLabels, nwKeyWords
|
||||||
|
|||||||
+17
-7
@@ -71,8 +71,6 @@ class GuiPreferences(PagedDialog):
|
|||||||
self.buttonBox.rejected.connect(self._doClose)
|
self.buttonBox.rejected.connect(self._doClose)
|
||||||
self.addControls(self.buttonBox)
|
self.addControls(self.buttonBox)
|
||||||
|
|
||||||
self.show()
|
|
||||||
|
|
||||||
logger.debug("GuiPreferences initialisation complete")
|
logger.debug("GuiPreferences initialisation complete")
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -82,7 +80,9 @@ class GuiPreferences(PagedDialog):
|
|||||||
##
|
##
|
||||||
|
|
||||||
def _doSave(self):
|
def _doSave(self):
|
||||||
|
"""Trigger all the save functions in the tabs, and collect the
|
||||||
|
status of the saves.
|
||||||
|
"""
|
||||||
logger.verbose("ConfigEditor save button clicked")
|
logger.verbose("ConfigEditor save button clicked")
|
||||||
|
|
||||||
validEntries = True
|
validEntries = True
|
||||||
@@ -117,6 +117,8 @@ class GuiPreferences(PagedDialog):
|
|||||||
return
|
return
|
||||||
|
|
||||||
def _doClose(self):
|
def _doClose(self):
|
||||||
|
"""Close the preferences without saving the changes.
|
||||||
|
"""
|
||||||
logger.verbose("ConfigEditor close button clicked")
|
logger.verbose("ConfigEditor close button clicked")
|
||||||
self.close()
|
self.close()
|
||||||
return
|
return
|
||||||
@@ -286,7 +288,8 @@ class GuiConfigEditGeneralTab(QWidget):
|
|||||||
return
|
return
|
||||||
|
|
||||||
def saveValues(self):
|
def saveValues(self):
|
||||||
|
"""Save the values set for this tab.
|
||||||
|
"""
|
||||||
validEntries = True
|
validEntries = True
|
||||||
needsRestart = False
|
needsRestart = False
|
||||||
|
|
||||||
@@ -516,7 +519,8 @@ class GuiConfigEditLayoutTab(QWidget):
|
|||||||
return
|
return
|
||||||
|
|
||||||
def saveValues(self):
|
def saveValues(self):
|
||||||
|
"""Save the values set for this tab.
|
||||||
|
"""
|
||||||
validEntries = True
|
validEntries = True
|
||||||
needsRestart = False
|
needsRestart = False
|
||||||
|
|
||||||
@@ -682,7 +686,8 @@ class GuiConfigEditEditingTab(QWidget):
|
|||||||
return
|
return
|
||||||
|
|
||||||
def saveValues(self):
|
def saveValues(self):
|
||||||
|
"""Save the values set for this tab.
|
||||||
|
"""
|
||||||
validEntries = True
|
validEntries = True
|
||||||
needsRestart = False
|
needsRestart = False
|
||||||
|
|
||||||
@@ -713,6 +718,8 @@ class GuiConfigEditEditingTab(QWidget):
|
|||||||
##
|
##
|
||||||
|
|
||||||
def _disableComboItem(self, theList, theValue):
|
def _disableComboItem(self, theList, theValue):
|
||||||
|
"""Disable a list item in the combo box.
|
||||||
|
"""
|
||||||
theIdx = theList.findData(theValue)
|
theIdx = theList.findData(theValue)
|
||||||
theModel = theList.model()
|
theModel = theList.model()
|
||||||
anItem = theModel.item(1)
|
anItem = theModel.item(1)
|
||||||
@@ -720,6 +727,8 @@ class GuiConfigEditEditingTab(QWidget):
|
|||||||
return theModel
|
return theModel
|
||||||
|
|
||||||
def _doUpdateSpellTool(self, currIdx):
|
def _doUpdateSpellTool(self, currIdx):
|
||||||
|
"""Update the list of dictionaries based on spell tool selected.
|
||||||
|
"""
|
||||||
spellTool = self.spellToolList.currentData()
|
spellTool = self.spellToolList.currentData()
|
||||||
self._updateLanguageList(spellTool)
|
self._updateLanguageList(spellTool)
|
||||||
return
|
return
|
||||||
@@ -904,7 +913,8 @@ class GuiConfigEditAutoReplaceTab(QWidget):
|
|||||||
return
|
return
|
||||||
|
|
||||||
def saveValues(self):
|
def saveValues(self):
|
||||||
|
"""Save the values set for this tab.
|
||||||
|
"""
|
||||||
validEntries = True
|
validEntries = True
|
||||||
needsRestart = False
|
needsRestart = False
|
||||||
|
|
||||||
|
|||||||
@@ -153,6 +153,7 @@ class GuiProjectLoad(QDialog):
|
|||||||
"""
|
"""
|
||||||
logger.verbose("GuiProjectLoad open button clicked")
|
logger.verbose("GuiProjectLoad open button clicked")
|
||||||
self._saveDialogState()
|
self._saveDialogState()
|
||||||
|
|
||||||
selItems = self.listBox.selectedItems()
|
selItems = self.listBox.selectedItems()
|
||||||
if selItems:
|
if selItems:
|
||||||
self.openPath = selItems[0].data(self.C_NAME, Qt.UserRole)
|
self.openPath = selItems[0].data(self.C_NAME, Qt.UserRole)
|
||||||
@@ -161,6 +162,7 @@ class GuiProjectLoad(QDialog):
|
|||||||
else:
|
else:
|
||||||
self.openPath = None
|
self.openPath = None
|
||||||
self.openState = self.NONE_STATE
|
self.openState = self.NONE_STATE
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def _doSelectRecent(self):
|
def _doSelectRecent(self):
|
||||||
@@ -189,6 +191,7 @@ class GuiProjectLoad(QDialog):
|
|||||||
self.openPath = thePath
|
self.openPath = thePath
|
||||||
self.openState = self.OPEN_STATE
|
self.openState = self.OPEN_STATE
|
||||||
self.accept()
|
self.accept()
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def _doClose(self):
|
def _doClose(self):
|
||||||
|
|||||||
+44
-9
@@ -97,6 +97,7 @@ class GuiProjectSettings(PagedDialog):
|
|||||||
bookTitle = self.tabMain.editTitle.text()
|
bookTitle = self.tabMain.editTitle.text()
|
||||||
bookAuthors = self.tabMain.editAuthors.toPlainText()
|
bookAuthors = self.tabMain.editAuthors.toPlainText()
|
||||||
doBackup = not self.tabMain.doBackup.isChecked()
|
doBackup = not self.tabMain.doBackup.isChecked()
|
||||||
|
|
||||||
self.theProject.setProjectName(projName)
|
self.theProject.setProjectName(projName)
|
||||||
self.theProject.setBookTitle(bookTitle)
|
self.theProject.setBookTitle(bookTitle)
|
||||||
self.theProject.setBookAuthors(bookAuthors)
|
self.theProject.setBookAuthors(bookAuthors)
|
||||||
@@ -105,11 +106,14 @@ class GuiProjectSettings(PagedDialog):
|
|||||||
if self.tabStatus.colChanged:
|
if self.tabStatus.colChanged:
|
||||||
statusCol = self.tabStatus.getNewList()
|
statusCol = self.tabStatus.getNewList()
|
||||||
self.theProject.setStatusColours(statusCol)
|
self.theProject.setStatusColours(statusCol)
|
||||||
|
|
||||||
if self.tabImport.colChanged:
|
if self.tabImport.colChanged:
|
||||||
importCol = self.tabImport.getNewList()
|
importCol = self.tabImport.getNewList()
|
||||||
self.theProject.setImportColours(importCol)
|
self.theProject.setImportColours(importCol)
|
||||||
|
|
||||||
if self.tabStatus.colChanged or self.tabImport.colChanged:
|
if self.tabStatus.colChanged or self.tabImport.colChanged:
|
||||||
self.theParent.rebuildTree()
|
self.theParent.rebuildTree()
|
||||||
|
|
||||||
if self.tabReplace.arChanged:
|
if self.tabReplace.arChanged:
|
||||||
newList = self.tabReplace.getNewList()
|
newList = self.tabReplace.getNewList()
|
||||||
self.theProject.setAutoReplace(newList)
|
self.theProject.setAutoReplace(newList)
|
||||||
@@ -119,7 +123,7 @@ class GuiProjectSettings(PagedDialog):
|
|||||||
return
|
return
|
||||||
|
|
||||||
def _doClose(self):
|
def _doClose(self):
|
||||||
"""Close the dialog.
|
"""Save settings and close the dialog.
|
||||||
"""
|
"""
|
||||||
winWidth = self.mainConf.rpxInt(self.width())
|
winWidth = self.mainConf.rpxInt(self.width())
|
||||||
winHeight = self.mainConf.rpxInt(self.height())
|
winHeight = self.mainConf.rpxInt(self.height())
|
||||||
@@ -372,6 +376,8 @@ class GuiProjectEditStatus(QWidget):
|
|||||||
##
|
##
|
||||||
|
|
||||||
def _selectColour(self):
|
def _selectColour(self):
|
||||||
|
"""Open a dialog to select the status icon colour.
|
||||||
|
"""
|
||||||
logger.verbose("Item colour button clicked")
|
logger.verbose("Item colour button clicked")
|
||||||
if self.selColour is not None:
|
if self.selColour is not None:
|
||||||
newCol = QColorDialog.getColor(
|
newCol = QColorDialog.getColor(
|
||||||
@@ -386,6 +392,8 @@ class GuiProjectEditStatus(QWidget):
|
|||||||
return
|
return
|
||||||
|
|
||||||
def _newItem(self):
|
def _newItem(self):
|
||||||
|
"""Create a new status item.
|
||||||
|
"""
|
||||||
logger.verbose("New item button clicked")
|
logger.verbose("New item button clicked")
|
||||||
newItem = self._addItem("New Item", (0, 0, 0), None, 0)
|
newItem = self._addItem("New Item", (0, 0, 0), None, 0)
|
||||||
newItem.setBackground(QBrush(QColor(0, 255, 0, 80)))
|
newItem.setBackground(QBrush(QColor(0, 255, 0, 80)))
|
||||||
@@ -393,6 +401,8 @@ class GuiProjectEditStatus(QWidget):
|
|||||||
return
|
return
|
||||||
|
|
||||||
def _delItem(self):
|
def _delItem(self):
|
||||||
|
"""Delete a status item.
|
||||||
|
"""
|
||||||
logger.verbose("Delete item button clicked")
|
logger.verbose("Delete item button clicked")
|
||||||
selItem = self._getSelectedItem()
|
selItem = self._getSelectedItem()
|
||||||
if selItem is not None:
|
if selItem is not None:
|
||||||
@@ -408,6 +418,8 @@ class GuiProjectEditStatus(QWidget):
|
|||||||
return
|
return
|
||||||
|
|
||||||
def _saveItem(self):
|
def _saveItem(self):
|
||||||
|
"""Save changes made to a status item.
|
||||||
|
"""
|
||||||
logger.verbose("Save item button clicked")
|
logger.verbose("Save item button clicked")
|
||||||
selItem = self._getSelectedItem()
|
selItem = self._getSelectedItem()
|
||||||
iRow = self.listBox.row(selItem)
|
iRow = self.listBox.row(selItem)
|
||||||
@@ -427,6 +439,8 @@ class GuiProjectEditStatus(QWidget):
|
|||||||
return
|
return
|
||||||
|
|
||||||
def _addItem(self, iName, iCol, oName, nUse):
|
def _addItem(self, iName, iCol, oName, nUse):
|
||||||
|
"""Add a status item to the list.
|
||||||
|
"""
|
||||||
newIcon = QPixmap(self.iPx, self.iPx)
|
newIcon = QPixmap(self.iPx, self.iPx)
|
||||||
newIcon.fill(QColor(*iCol))
|
newIcon.fill(QColor(*iCol))
|
||||||
newItem = QListWidgetItem()
|
newItem = QListWidgetItem()
|
||||||
@@ -439,11 +453,14 @@ class GuiProjectEditStatus(QWidget):
|
|||||||
return newItem
|
return newItem
|
||||||
|
|
||||||
def _selectedItem(self):
|
def _selectedItem(self):
|
||||||
|
"""Extract the info of a selected item and populate the settings
|
||||||
|
boxes and button.
|
||||||
|
"""
|
||||||
logger.verbose("Item selected")
|
logger.verbose("Item selected")
|
||||||
selItem = self._getSelectedItem()
|
selItem = self._getSelectedItem()
|
||||||
if selItem is not None:
|
if selItem is not None:
|
||||||
selIdx = selItem.data(Qt.UserRole)
|
selIdx = selItem.data(Qt.UserRole)
|
||||||
selVal = self.colData[selIdx]
|
selVal = self.colData[selIdx]
|
||||||
self.selColour = QColor(selVal[1], selVal[2], selVal[3])
|
self.selColour = QColor(selVal[1], selVal[2], selVal[3])
|
||||||
newIcon = QPixmap(self.iPx, self.iPx)
|
newIcon = QPixmap(self.iPx, self.iPx)
|
||||||
newIcon.fill(self.selColour)
|
newIcon.fill(self.selColour)
|
||||||
@@ -459,6 +476,8 @@ class GuiProjectEditStatus(QWidget):
|
|||||||
##
|
##
|
||||||
|
|
||||||
def _getSelectedItem(self):
|
def _getSelectedItem(self):
|
||||||
|
"""Get the currently selected item.
|
||||||
|
"""
|
||||||
selItem = self.listBox.selectedItems()
|
selItem = self.listBox.selectedItems()
|
||||||
if len(selItem) == 0:
|
if len(selItem) == 0:
|
||||||
return None
|
return None
|
||||||
@@ -467,6 +486,8 @@ class GuiProjectEditStatus(QWidget):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def _rowsMoved(self):
|
def _rowsMoved(self):
|
||||||
|
"""A row has been moved, so sett the changed flag.
|
||||||
|
"""
|
||||||
logger.verbose("A drag move event occurred")
|
logger.verbose("A drag move event occurred")
|
||||||
self.colChanged = True
|
self.colChanged = True
|
||||||
return
|
return
|
||||||
@@ -506,9 +527,9 @@ class GuiProjectEditReplace(QWidget):
|
|||||||
|
|
||||||
self.editKey = QLineEdit()
|
self.editKey = QLineEdit()
|
||||||
self.editValue = QLineEdit()
|
self.editValue = QLineEdit()
|
||||||
self.saveButton = QPushButton(self.theTheme.getIcon("done"),"")
|
self.saveButton = QPushButton(self.theTheme.getIcon("done"), "")
|
||||||
self.addButton = QPushButton(self.theTheme.getIcon("add"),"")
|
self.addButton = QPushButton(self.theTheme.getIcon("add"), "")
|
||||||
self.delButton = QPushButton(self.theTheme.getIcon("remove"),"")
|
self.delButton = QPushButton(self.theTheme.getIcon("remove"), "")
|
||||||
self.saveButton.setToolTip("Save entry")
|
self.saveButton.setToolTip("Save entry")
|
||||||
self.addButton.setToolTip("Add new entry")
|
self.addButton.setToolTip("Add new entry")
|
||||||
self.delButton.setToolTip("Delete selected entry")
|
self.delButton.setToolTip("Delete selected entry")
|
||||||
@@ -536,11 +557,13 @@ class GuiProjectEditReplace(QWidget):
|
|||||||
return
|
return
|
||||||
|
|
||||||
def getNewList(self):
|
def getNewList(self):
|
||||||
|
"""Extract the list from the widget.
|
||||||
|
"""
|
||||||
newList = {}
|
newList = {}
|
||||||
for n in range(self.listBox.topLevelItemCount()):
|
for n in range(self.listBox.topLevelItemCount()):
|
||||||
tItem = self.listBox.topLevelItem(n)
|
tItem = self.listBox.topLevelItem(n)
|
||||||
aKey = self._stripNotAllowed(tItem.text(0))
|
aKey = self._stripNotAllowed(tItem.text(0))
|
||||||
aVal = tItem.text(1)
|
aVal = tItem.text(1)
|
||||||
if len(aKey) > 0:
|
if len(aKey) > 0:
|
||||||
newList[aKey] = aVal
|
newList[aKey] = aVal
|
||||||
return newList
|
return newList
|
||||||
@@ -550,6 +573,9 @@ class GuiProjectEditReplace(QWidget):
|
|||||||
##
|
##
|
||||||
|
|
||||||
def _selectedItem(self):
|
def _selectedItem(self):
|
||||||
|
"""Extract the details from the selected item and populate the
|
||||||
|
edit form.
|
||||||
|
"""
|
||||||
selItem = self._getSelectedItem()
|
selItem = self._getSelectedItem()
|
||||||
if selItem is None:
|
if selItem is None:
|
||||||
return False
|
return False
|
||||||
@@ -564,7 +590,8 @@ class GuiProjectEditReplace(QWidget):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def _saveEntry(self):
|
def _saveEntry(self):
|
||||||
|
"""Save the form data into the list widget.
|
||||||
|
"""
|
||||||
selItem = self._getSelectedItem()
|
selItem = self._getSelectedItem()
|
||||||
if selItem is None:
|
if selItem is None:
|
||||||
return False
|
return False
|
||||||
@@ -586,6 +613,8 @@ class GuiProjectEditReplace(QWidget):
|
|||||||
return
|
return
|
||||||
|
|
||||||
def _addEntry(self):
|
def _addEntry(self):
|
||||||
|
"""Add a new list entry.
|
||||||
|
"""
|
||||||
saveKey = "<keyword%d>" % (self.listBox.topLevelItemCount() + 1)
|
saveKey = "<keyword%d>" % (self.listBox.topLevelItemCount() + 1)
|
||||||
newVal = ""
|
newVal = ""
|
||||||
newItem = QTreeWidgetItem([saveKey, newVal])
|
newItem = QTreeWidgetItem([saveKey, newVal])
|
||||||
@@ -593,6 +622,8 @@ class GuiProjectEditReplace(QWidget):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def _delEntry(self):
|
def _delEntry(self):
|
||||||
|
"""Delete the selected entry.
|
||||||
|
"""
|
||||||
selItem = self._getSelectedItem()
|
selItem = self._getSelectedItem()
|
||||||
if selItem is None:
|
if selItem is None:
|
||||||
return False
|
return False
|
||||||
@@ -601,12 +632,16 @@ class GuiProjectEditReplace(QWidget):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def _getSelectedItem(self):
|
def _getSelectedItem(self):
|
||||||
|
"""Extract the currently selected item.
|
||||||
|
"""
|
||||||
selItem = self.listBox.selectedItems()
|
selItem = self.listBox.selectedItems()
|
||||||
if len(selItem) == 0:
|
if len(selItem) == 0:
|
||||||
return None
|
return None
|
||||||
return selItem[0]
|
return selItem[0]
|
||||||
|
|
||||||
def _stripNotAllowed(self, theKey):
|
def _stripNotAllowed(self, theKey):
|
||||||
|
"""Clean up the replace key string.
|
||||||
|
"""
|
||||||
retKey = ""
|
retKey = ""
|
||||||
for c in theKey:
|
for c in theKey:
|
||||||
if c.isalnum():
|
if c.isalnum():
|
||||||
|
|||||||
+30
-4
@@ -30,15 +30,15 @@ import logging
|
|||||||
import nw
|
import nw
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt, QSize
|
from PyQt5.QtCore import Qt, QSize
|
||||||
from PyQt5.QtGui import QFont, QColor, QIcon
|
from PyQt5.QtGui import QIcon
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
qApp, QTreeWidget, QTreeWidgetItem, QAbstractItemView, QMessageBox,
|
qApp, QTreeWidget, QTreeWidgetItem, QAbstractItemView, QMessageBox,
|
||||||
QHeaderView, QMenu, QAction
|
QMenu, QAction
|
||||||
)
|
)
|
||||||
|
|
||||||
from nw.core import NWDoc
|
from nw.core import NWDoc
|
||||||
from nw.constants import (
|
from nw.constants import (
|
||||||
nwLabels, nwItemType, nwItemClass, nwItemLayout, nwAlert, nwUnicode
|
nwLabels, nwItemType, nwItemClass, nwItemLayout, nwAlert
|
||||||
)
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -90,7 +90,7 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
# for some fonts like the Ubuntu font.
|
# for some fonts like the Ubuntu font.
|
||||||
treeHeader = self.header()
|
treeHeader = self.header()
|
||||||
treeHeader.setStretchLastSection(True)
|
treeHeader.setStretchLastSection(True)
|
||||||
treeHeader.setMinimumSectionSize(iPx+6)
|
treeHeader.setMinimumSectionSize(iPx + 6)
|
||||||
|
|
||||||
# Allow Move by Drag & Drop
|
# Allow Move by Drag & Drop
|
||||||
self.setDragEnabled(True)
|
self.setDragEnabled(True)
|
||||||
@@ -237,6 +237,7 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
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:
|
if qApp.focusWidget() == self and self.theParent.hasProject:
|
||||||
|
|
||||||
tHandle = self.getSelectedHandle()
|
tHandle = self.getSelectedHandle()
|
||||||
tItem = self._getTreeItem(tHandle)
|
tItem = self._getTreeItem(tHandle)
|
||||||
pItem = tItem.parent()
|
pItem = tItem.parent()
|
||||||
@@ -248,6 +249,7 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
return False
|
return False
|
||||||
cItem = self.takeTopLevelItem(tIndex)
|
cItem = self.takeTopLevelItem(tIndex)
|
||||||
self.insertTopLevelItem(nIndex, cItem)
|
self.insertTopLevelItem(nIndex, cItem)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
tIndex = pItem.indexOfChild(tItem)
|
tIndex = pItem.indexOfChild(tItem)
|
||||||
nChild = pItem.childCount()
|
nChild = pItem.childCount()
|
||||||
@@ -256,11 +258,14 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
return False
|
return False
|
||||||
cItem = pItem.takeChild(tIndex)
|
cItem = pItem.takeChild(tIndex)
|
||||||
pItem.insertChild(nIndex, cItem)
|
pItem.insertChild(nIndex, cItem)
|
||||||
|
|
||||||
self.clearSelection()
|
self.clearSelection()
|
||||||
cItem.setSelected(True)
|
cItem.setSelected(True)
|
||||||
self._setTreeChanged(True)
|
self._setTreeChanged(True)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def saveTreeOrder(self):
|
def saveTreeOrder(self):
|
||||||
@@ -516,8 +521,10 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
for i in range(pItem.childCount()):
|
for i in range(pItem.childCount()):
|
||||||
pCount += int(pItem.child(i).text(self.C_COUNT))
|
pCount += int(pItem.child(i).text(self.C_COUNT))
|
||||||
pHandle = pItem.data(self.C_NAME, Qt.UserRole)
|
pHandle = pItem.data(self.C_NAME, Qt.UserRole)
|
||||||
|
|
||||||
if not nDepth > 200 and pHandle != "":
|
if not nDepth > 200 and pHandle != "":
|
||||||
self.propagateCount(pHandle, pCount, nDepth+1)
|
self.propagateCount(pHandle, pCount, nDepth+1)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def projectWordCount(self):
|
def projectWordCount(self):
|
||||||
@@ -533,9 +540,11 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
if tItem == self.orphRoot:
|
if tItem == self.orphRoot:
|
||||||
continue
|
continue
|
||||||
nWords += int(tItem.text(self.C_COUNT))
|
nWords += int(tItem.text(self.C_COUNT))
|
||||||
|
|
||||||
self.theProject.setProjectWordCount(nWords)
|
self.theProject.setProjectWordCount(nWords)
|
||||||
sWords = self.theProject.getSessionWordCount()
|
sWords = self.theProject.getSessionWordCount()
|
||||||
self.theParent.statusBar.setStats(nWords,sWords)
|
self.theParent.statusBar.setStats(nWords,sWords)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def buildTree(self):
|
def buildTree(self):
|
||||||
@@ -547,9 +556,11 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
logger.debug("Building project tree ...")
|
logger.debug("Building project tree ...")
|
||||||
self.clear()
|
self.clear()
|
||||||
iCount = 0
|
iCount = 0
|
||||||
|
|
||||||
for nwItem in self.theProject.getProjectItems():
|
for nwItem in self.theProject.getProjectItems():
|
||||||
iCount += 1
|
iCount += 1
|
||||||
self._addTreeItem(nwItem)
|
self._addTreeItem(nwItem)
|
||||||
|
|
||||||
logger.debug("%d items added to project tree" % iCount)
|
logger.debug("%d items added to project tree" % iCount)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -558,10 +569,13 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
selected, return the first.
|
selected, return the first.
|
||||||
"""
|
"""
|
||||||
selItem = self.selectedItems()
|
selItem = self.selectedItems()
|
||||||
|
|
||||||
if len(selItem) == 0:
|
if len(selItem) == 0:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if isinstance(selItem[0], QTreeWidgetItem):
|
if isinstance(selItem[0], QTreeWidgetItem):
|
||||||
return selItem[0].data(self.C_NAME, Qt.UserRole)
|
return selItem[0].data(self.C_NAME, Qt.UserRole)
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def getSelectedHandles(self):
|
def getSelectedHandles(self):
|
||||||
@@ -572,6 +586,7 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
for n in range(len(selItems)):
|
for n in range(len(selItems)):
|
||||||
if isinstance(selItems[n], QTreeWidgetItem):
|
if isinstance(selItems[n], QTreeWidgetItem):
|
||||||
selHandles.append(selItems[n].data(self.C_NAME, Qt.UserRole))
|
selHandles.append(selItems[n].data(self.C_NAME, Qt.UserRole))
|
||||||
|
|
||||||
return selHandles
|
return selHandles
|
||||||
|
|
||||||
def setSelectedHandle(self, tHandle, doScroll=False):
|
def setSelectedHandle(self, tHandle, doScroll=False):
|
||||||
@@ -580,12 +595,14 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
if tHandle in self.theMap:
|
if tHandle in self.theMap:
|
||||||
self.clearSelection()
|
self.clearSelection()
|
||||||
self.theMap[tHandle].setSelected(True)
|
self.theMap[tHandle].setSelected(True)
|
||||||
|
|
||||||
selItems = self.selectedIndexes()
|
selItems = self.selectedIndexes()
|
||||||
if selItems and doScroll:
|
if selItems and doScroll:
|
||||||
self.scrollTo(
|
self.scrollTo(
|
||||||
selItems[0], QAbstractItemView.PositionAtCenter
|
selItems[0], QAbstractItemView.PositionAtCenter
|
||||||
)
|
)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
##
|
##
|
||||||
@@ -601,9 +618,11 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
tHandle = selItem.data(self.C_NAME, Qt.UserRole)
|
tHandle = selItem.data(self.C_NAME, Qt.UserRole)
|
||||||
tItem = self.theProject.projTree[tHandle]
|
tItem = self.theProject.projTree[tHandle]
|
||||||
self.setSelectedHandle(tHandle) # Just to be safe
|
self.setSelectedHandle(tHandle) # Just to be safe
|
||||||
|
|
||||||
if self.ctxMenu.filterActions(tItem):
|
if self.ctxMenu.filterActions(tItem):
|
||||||
# Only open menu if any actions remain after filter
|
# Only open menu if any actions remain after filter
|
||||||
self.ctxMenu.exec_(self.viewport().mapToGlobal(clickPos))
|
self.ctxMenu.exec_(self.viewport().mapToGlobal(clickPos))
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
##
|
##
|
||||||
@@ -615,9 +634,11 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
mouse in a blank area of the tree view.
|
mouse in a blank area of the tree view.
|
||||||
"""
|
"""
|
||||||
QTreeWidget.mousePressEvent(self, theEvent)
|
QTreeWidget.mousePressEvent(self, theEvent)
|
||||||
|
|
||||||
selItem = self.indexAt(theEvent.pos())
|
selItem = self.indexAt(theEvent.pos())
|
||||||
if not selItem.isValid():
|
if not selItem.isValid():
|
||||||
self.clearSelection()
|
self.clearSelection()
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def dropEvent(self, theEvent):
|
def dropEvent(self, theEvent):
|
||||||
@@ -766,6 +787,7 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
trashHandle = self.theProject.trashFolder()
|
trashHandle = self.theProject.trashFolder()
|
||||||
if trashHandle is None:
|
if trashHandle is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
trItem = self._getTreeItem(trashHandle)
|
trItem = self._getTreeItem(trashHandle)
|
||||||
if trItem is None:
|
if trItem is None:
|
||||||
trItem = self._addTreeItem(
|
trItem = self._addTreeItem(
|
||||||
@@ -773,6 +795,7 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
)
|
)
|
||||||
trItem.setExpanded(True)
|
trItem.setExpanded(True)
|
||||||
self._setTreeChanged(True)
|
self._setTreeChanged(True)
|
||||||
|
|
||||||
return trItem
|
return trItem
|
||||||
|
|
||||||
def _addOrphanedRoot(self):
|
def _addOrphanedRoot(self):
|
||||||
@@ -790,6 +813,7 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
newItem.setExpanded(True)
|
newItem.setExpanded(True)
|
||||||
newItem.setData(self.C_NAME, Qt.UserRole, "")
|
newItem.setData(self.C_NAME, Qt.UserRole, "")
|
||||||
newItem.setIcon(self.C_NAME, self.theTheme.getIcon("proj_orphan"))
|
newItem.setIcon(self.C_NAME, self.theTheme.getIcon("proj_orphan"))
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def _cleanOrphanedRoot(self):
|
def _cleanOrphanedRoot(self):
|
||||||
@@ -834,10 +858,12 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
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
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
def _setTreeChanged(self, theState):
|
def _setTreeChanged(self, theState):
|
||||||
|
|||||||
+3
-3
@@ -30,8 +30,8 @@ import nw
|
|||||||
|
|
||||||
from time import time
|
from time import time
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt, QTimer
|
from PyQt5.QtCore import QTimer
|
||||||
from PyQt5.QtGui import QColor, QPixmap, QFont, QPainter
|
from PyQt5.QtGui import QColor, QPainter
|
||||||
from PyQt5.QtWidgets import qApp, QStatusBar, QLabel, QAbstractButton
|
from PyQt5.QtWidgets import qApp, QStatusBar, QLabel, QAbstractButton
|
||||||
|
|
||||||
from nw.core import NWSpellCheck
|
from nw.core import NWSpellCheck
|
||||||
@@ -264,7 +264,7 @@ class StatusLED(QAbstractButton):
|
|||||||
qPaint.setPen(qPalette.dark().color())
|
qPaint.setPen(qPalette.dark().color())
|
||||||
qPaint.setBrush(self._theCol)
|
qPaint.setBrush(self._theCol)
|
||||||
qPaint.setOpacity(1.0)
|
qPaint.setOpacity(1.0)
|
||||||
qPaint.drawEllipse(1, 1, self.width()-2, self.height()-2)
|
qPaint.drawEllipse(1, 1, self.width() - 2, self.height() - 2)
|
||||||
return
|
return
|
||||||
|
|
||||||
# END Class StatusLED
|
# END Class StatusLED
|
||||||
|
|||||||
+56
-52
@@ -77,11 +77,11 @@ class GuiTheme:
|
|||||||
self.themeLicenseUrl = ""
|
self.themeLicenseUrl = ""
|
||||||
|
|
||||||
## GUI
|
## GUI
|
||||||
self.treeWCount = [ 0, 0, 0]
|
self.treeWCount = [ 0, 0, 0]
|
||||||
self.statNone = [120,120,120]
|
self.statNone = [120, 120, 120]
|
||||||
self.statUnsaved = [120,120, 40]
|
self.statUnsaved = [120, 120, 40]
|
||||||
self.statSaved = [ 40,120, 0]
|
self.statSaved = [ 40, 120, 0]
|
||||||
self.helpText = [ 0, 0, 0]
|
self.helpText = [ 0, 0, 0]
|
||||||
|
|
||||||
# Loaded Syntax Settings
|
# Loaded Syntax Settings
|
||||||
|
|
||||||
@@ -95,22 +95,22 @@ class GuiTheme:
|
|||||||
self.syntaxLicenseUrl = ""
|
self.syntaxLicenseUrl = ""
|
||||||
|
|
||||||
## Colours
|
## Colours
|
||||||
self.colBack = [255,255,255]
|
self.colBack = [255, 255, 255]
|
||||||
self.colText = [ 0, 0, 0]
|
self.colText = [ 0, 0, 0]
|
||||||
self.colLink = [ 0, 0, 0]
|
self.colLink = [ 0, 0, 0]
|
||||||
self.colHead = [ 0, 0, 0]
|
self.colHead = [ 0, 0, 0]
|
||||||
self.colHeadH = [ 0, 0, 0]
|
self.colHeadH = [ 0, 0, 0]
|
||||||
self.colEmph = [ 0, 0, 0]
|
self.colEmph = [ 0, 0, 0]
|
||||||
self.colDialN = [ 0, 0, 0]
|
self.colDialN = [ 0, 0, 0]
|
||||||
self.colDialD = [ 0, 0, 0]
|
self.colDialD = [ 0, 0, 0]
|
||||||
self.colDialS = [ 0, 0, 0]
|
self.colDialS = [ 0, 0, 0]
|
||||||
self.colComm = [ 0, 0, 0]
|
self.colComm = [ 0, 0, 0]
|
||||||
self.colKey = [ 0, 0, 0]
|
self.colKey = [ 0, 0, 0]
|
||||||
self.colVal = [ 0, 0, 0]
|
self.colVal = [ 0, 0, 0]
|
||||||
self.colSpell = [ 0, 0, 0]
|
self.colSpell = [ 0, 0, 0]
|
||||||
self.colTagErr = [ 0, 0, 0]
|
self.colTagErr = [ 0, 0, 0]
|
||||||
self.colRepTag = [ 0, 0, 0]
|
self.colRepTag = [ 0, 0, 0]
|
||||||
self.colMod = [ 0, 0, 0]
|
self.colMod = [ 0, 0, 0]
|
||||||
|
|
||||||
# Changeable Settings
|
# Changeable Settings
|
||||||
self.guiTheme = None
|
self.guiTheme = None
|
||||||
@@ -144,9 +144,9 @@ class GuiTheme:
|
|||||||
qMetric = QFontMetrics(self.guiFont)
|
qMetric = QFontMetrics(self.guiFont)
|
||||||
self.fontPointSize = self.guiFont.pointSizeF()
|
self.fontPointSize = self.guiFont.pointSizeF()
|
||||||
self.fontPixelSize = int(round(qMetric.height()))
|
self.fontPixelSize = int(round(qMetric.height()))
|
||||||
self.baseIconSize = int(round(qMetric.ascent()))
|
self.baseIconSize = int(round(qMetric.ascent()))
|
||||||
self.textNHeight = qMetric.boundingRect("N").height()
|
self.textNHeight = qMetric.boundingRect("N").height()
|
||||||
self.textNWidth = qMetric.boundingRect("N").width()
|
self.textNWidth= qMetric.boundingRect("N").width()
|
||||||
|
|
||||||
logger.verbose("GUI Font Family: %s" % self.guiFont.family())
|
logger.verbose("GUI Font Family: %s" % self.guiFont.family())
|
||||||
logger.verbose("GUI Font Point Size: %.2f" % self.fontPointSize)
|
logger.verbose("GUI Font Point Size: %.2f" % self.fontPointSize)
|
||||||
@@ -223,10 +223,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 = path.join(self.mainConf.themeRoot, self.guiPath, self.guiTheme)
|
||||||
self.syntaxFile = path.join(self.themeRoot,self.syntaxPath,self.guiSyntax+".conf")
|
self.syntaxFile = path.join(self.themeRoot, self.syntaxPath, self.guiSyntax+".conf")
|
||||||
self.confFile = path.join(self.themePath,self.confName)
|
self.confFile = path.join(self.themePath, self.confName)
|
||||||
self.cssFile = path.join(self.themePath,self.cssName)
|
self.cssFile = path.join(self.themePath, self.cssName)
|
||||||
|
|
||||||
self.loadTheme()
|
self.loadTheme()
|
||||||
self.loadSyntax()
|
self.loadSyntax()
|
||||||
@@ -256,7 +256,7 @@ class GuiTheme:
|
|||||||
cssData = ""
|
cssData = ""
|
||||||
try:
|
try:
|
||||||
if path.isfile(self.cssFile):
|
if 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:
|
||||||
logger.error("Could not load theme css file")
|
logger.error("Could not load theme css file")
|
||||||
@@ -329,13 +329,13 @@ class GuiTheme:
|
|||||||
## Main
|
## Main
|
||||||
cnfSec = "Main"
|
cnfSec = "Main"
|
||||||
if confParser.has_section(cnfSec):
|
if confParser.has_section(cnfSec):
|
||||||
self.syntaxName = self._parseLine( confParser, cnfSec, "name", "")
|
self.syntaxName = self._parseLine(confParser, cnfSec, "name", "")
|
||||||
self.syntaxDescription = self._parseLine( confParser, cnfSec, "description", "")
|
self.syntaxDescription = self._parseLine(confParser, cnfSec, "description", "")
|
||||||
self.syntaxAuthor = self._parseLine( confParser, cnfSec, "author", "")
|
self.syntaxAuthor = self._parseLine(confParser, cnfSec, "author", "")
|
||||||
self.syntaxCredit = self._parseLine( confParser, cnfSec, "credit", "")
|
self.syntaxCredit = self._parseLine(confParser, cnfSec, "credit", "")
|
||||||
self.syntaxUrl = self._parseLine( confParser, cnfSec, "url", "")
|
self.syntaxUrl = self._parseLine(confParser, cnfSec, "url", "")
|
||||||
self.syntaxLicense = self._parseLine( confParser, cnfSec, "license", "")
|
self.syntaxLicense = self._parseLine(confParser, cnfSec, "license", "")
|
||||||
self.syntaxLicenseUrl = self._parseLine( confParser, cnfSec, "licenseurl", "")
|
self.syntaxLicenseUrl = self._parseLine(confParser, cnfSec, "licenseurl", "")
|
||||||
|
|
||||||
## Syntax
|
## Syntax
|
||||||
cnfSec = "Syntax"
|
cnfSec = "Syntax"
|
||||||
@@ -376,7 +376,7 @@ class GuiTheme:
|
|||||||
confParser.read_file(inFile)
|
confParser.read_file(inFile)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.theParent.makeAlert(
|
self.theParent.makeAlert(
|
||||||
["Could not load theme config file.",str(e)], nwAlert.ERROR
|
["Could not load theme config file.", str(e)], nwAlert.ERROR
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
themeName = ""
|
themeName = ""
|
||||||
@@ -409,7 +409,7 @@ class GuiTheme:
|
|||||||
confParser.read_file(inFile)
|
confParser.read_file(inFile)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.theParent.makeAlert(
|
self.theParent.makeAlert(
|
||||||
["Could not load syntax file.",str(e)], nwAlert.ERROR
|
["Could not load syntax file.", str(e)], nwAlert.ERROR
|
||||||
)
|
)
|
||||||
return []
|
return []
|
||||||
syntaxName = ""
|
syntaxName = ""
|
||||||
@@ -429,8 +429,10 @@ class GuiTheme:
|
|||||||
##
|
##
|
||||||
|
|
||||||
def _loadColour(self, confParser, cnfSec, cnfName):
|
def _loadColour(self, confParser, cnfSec, cnfName):
|
||||||
|
"""Load a colour value from a config string.
|
||||||
|
"""
|
||||||
if confParser.has_option(cnfSec,cnfName):
|
if confParser.has_option(cnfSec,cnfName):
|
||||||
inData = confParser.get(cnfSec,cnfName).split(",")
|
inData = confParser.get(cnfSec,cnfName).split(",")
|
||||||
outData = []
|
outData = []
|
||||||
try:
|
try:
|
||||||
outData.append(int(inData[0]))
|
outData.append(int(inData[0]))
|
||||||
@@ -438,16 +440,18 @@ class GuiTheme:
|
|||||||
outData.append(int(inData[2]))
|
outData.append(int(inData[2]))
|
||||||
except:
|
except:
|
||||||
logger.error("Could not load theme colours for '%s' from config file" % cnfName)
|
logger.error("Could not load theme colours for '%s' from config file" % cnfName)
|
||||||
outData = [0,0,0]
|
outData = [0, 0, 0]
|
||||||
else:
|
else:
|
||||||
logger.warning("Could not find theme colours for '%s' in config file" % cnfName)
|
logger.warning("Could not find theme colours for '%s' in config file" % cnfName)
|
||||||
outData = [0,0,0]
|
outData = [0, 0, 0]
|
||||||
return outData
|
return outData
|
||||||
|
|
||||||
def _setPalette(self, confParser, cnfSec, cnfName, paletteVal):
|
def _setPalette(self, confParser, cnfSec, cnfName, paletteVal):
|
||||||
|
"""Set a palette colour value from a config string.
|
||||||
|
"""
|
||||||
readCol = []
|
readCol = []
|
||||||
if confParser.has_option(cnfSec,cnfName):
|
if confParser.has_option(cnfSec,cnfName):
|
||||||
inData = confParser.get(cnfSec,cnfName).split(",")
|
inData = confParser.get(cnfSec,cnfName).split(",")
|
||||||
try:
|
try:
|
||||||
readCol.append(int(inData[0]))
|
readCol.append(int(inData[0]))
|
||||||
readCol.append(int(inData[1]))
|
readCol.append(int(inData[1]))
|
||||||
@@ -548,8 +552,8 @@ class GuiIcons:
|
|||||||
"reference" : (None, None),
|
"reference" : (None, None),
|
||||||
|
|
||||||
## Switches
|
## Switches
|
||||||
"sticky-on" : (None, None),
|
"sticky-on" : (None, None),
|
||||||
"sticky-off" : (None, None),
|
"sticky-off" : (None, None),
|
||||||
}
|
}
|
||||||
|
|
||||||
DECO_MAP = {
|
DECO_MAP = {
|
||||||
@@ -616,13 +620,13 @@ class GuiIcons:
|
|||||||
## Main
|
## Main
|
||||||
cnfSec = "Main"
|
cnfSec = "Main"
|
||||||
if confParser.has_section(cnfSec):
|
if confParser.has_section(cnfSec):
|
||||||
self.themeName = self._parseLine( confParser, cnfSec, "name", "")
|
self.themeName = self._parseLine(confParser, cnfSec, "name", "")
|
||||||
self.themeDescription = self._parseLine( confParser, cnfSec, "description", "")
|
self.themeDescription = self._parseLine(confParser, cnfSec, "description", "")
|
||||||
self.themeAuthor = self._parseLine( confParser, cnfSec, "author", "")
|
self.themeAuthor = self._parseLine(confParser, cnfSec, "author", "")
|
||||||
self.themeCredit = self._parseLine( confParser, cnfSec, "credit", "")
|
self.themeCredit = self._parseLine(confParser, cnfSec, "credit", "")
|
||||||
self.themeUrl = self._parseLine( confParser, cnfSec, "url", "")
|
self.themeUrl = self._parseLine(confParser, cnfSec, "url", "")
|
||||||
self.themeLicense = self._parseLine( confParser, cnfSec, "license", "")
|
self.themeLicense = self._parseLine(confParser, cnfSec, "license", "")
|
||||||
self.themeLicenseUrl = self._parseLine( confParser, cnfSec, "licenseurl", "")
|
self.themeLicenseUrl = self._parseLine(confParser, cnfSec, "licenseurl", "")
|
||||||
|
|
||||||
## Palette
|
## Palette
|
||||||
cnfSec = "Map"
|
cnfSec = "Map"
|
||||||
@@ -708,7 +712,7 @@ class GuiIcons:
|
|||||||
confParser.read_file(inFile)
|
confParser.read_file(inFile)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.theParent.makeAlert(
|
self.theParent.makeAlert(
|
||||||
["Could not load theme config file.",str(e)], nwAlert.ERROR
|
["Could not load theme config file.", str(e)], nwAlert.ERROR
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
themeName = ""
|
themeName = ""
|
||||||
|
|||||||
+36
-10
@@ -36,7 +36,7 @@ from PyQt5.QtCore import Qt
|
|||||||
from PyQt5.QtGui import QFont, QPixmap
|
from PyQt5.QtGui import QFont, QPixmap
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
qApp, QDialog, QTreeWidget, QTreeWidgetItem, QDialogButtonBox, QGridLayout,
|
qApp, QDialog, QTreeWidget, QTreeWidgetItem, QDialogButtonBox, QGridLayout,
|
||||||
QLabel, QGroupBox, QMenu, QAction, QFileDialog
|
QLabel, QGroupBox, QMenu, QAction, QFileDialog, QSpinBox, QHBoxLayout
|
||||||
)
|
)
|
||||||
|
|
||||||
from nw.constants import nwConst, nwFiles, nwAlert
|
from nw.constants import nwConst, nwFiles, nwAlert
|
||||||
@@ -90,7 +90,7 @@ class GuiWritingStats(QDialog):
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.listBox = QTreeWidget()
|
self.listBox = QTreeWidget()
|
||||||
self.listBox.setHeaderLabels(["Session Start","Length","Words","Histogram"])
|
self.listBox.setHeaderLabels(["Session Start", "Length", "Words", "Histogram"])
|
||||||
self.listBox.setIndentation(0)
|
self.listBox.setIndentation(0)
|
||||||
self.listBox.setColumnWidth(self.C_TIME, wCol0)
|
self.listBox.setColumnWidth(self.C_TIME, wCol0)
|
||||||
self.listBox.setColumnWidth(self.C_LENGTH, wCol1)
|
self.listBox.setColumnWidth(self.C_LENGTH, wCol1)
|
||||||
@@ -207,11 +207,31 @@ class GuiWritingStats(QDialog):
|
|||||||
self.filterForm.addWidget(self.groupByDay, 4, 1)
|
self.filterForm.addWidget(self.groupByDay, 4, 1)
|
||||||
self.filterForm.setRowStretch(5, 1)
|
self.filterForm.setRowStretch(5, 1)
|
||||||
|
|
||||||
|
# Settings
|
||||||
|
self.histMax = QSpinBox(self)
|
||||||
|
self.histMax.setMinimum(100)
|
||||||
|
self.histMax.setMaximum(100000)
|
||||||
|
self.histMax.setSingleStep(100)
|
||||||
|
self.histMax.setValue(
|
||||||
|
self.optState.getInt("GuiWritingStats", "histMax", 2000)
|
||||||
|
)
|
||||||
|
self.histMax.valueChanged.connect(self._updateListBox)
|
||||||
|
|
||||||
|
self.optsBox = QHBoxLayout()
|
||||||
|
self.optsBox.addStretch(1)
|
||||||
|
self.optsBox.addWidget(QLabel("Word count cap for the histogram"), 0)
|
||||||
|
self.optsBox.addWidget(self.histMax, 0)
|
||||||
|
|
||||||
# Buttons
|
# Buttons
|
||||||
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Close)
|
self.buttonBox = QDialogButtonBox()
|
||||||
self.buttonBox.rejected.connect(self._doClose)
|
self.buttonBox.rejected.connect(self._doClose)
|
||||||
|
|
||||||
|
self.btnClose = self.buttonBox.addButton(QDialogButtonBox.Close)
|
||||||
|
self.btnClose.setAutoDefault(False)
|
||||||
|
|
||||||
self.btnSave = self.buttonBox.addButton("Save As", QDialogButtonBox.ActionRole)
|
self.btnSave = self.buttonBox.addButton("Save As", QDialogButtonBox.ActionRole)
|
||||||
|
self.btnSave.setAutoDefault(False)
|
||||||
|
|
||||||
self.saveMenu = QMenu(self)
|
self.saveMenu = QMenu(self)
|
||||||
self.btnSave.setMenu(self.saveMenu)
|
self.btnSave.setMenu(self.saveMenu)
|
||||||
|
|
||||||
@@ -226,9 +246,10 @@ class GuiWritingStats(QDialog):
|
|||||||
# Assemble
|
# Assemble
|
||||||
self.outerBox = QGridLayout()
|
self.outerBox = QGridLayout()
|
||||||
self.outerBox.addWidget(self.listBox, 0, 0, 1, 2)
|
self.outerBox.addWidget(self.listBox, 0, 0, 1, 2)
|
||||||
self.outerBox.addWidget(self.infoBox, 1, 0)
|
self.outerBox.addLayout(self.optsBox, 1, 0, 1, 2)
|
||||||
self.outerBox.addWidget(self.filterBox, 1, 1)
|
self.outerBox.addWidget(self.infoBox, 2, 0)
|
||||||
self.outerBox.addWidget(self.buttonBox, 2, 0, 1, 2)
|
self.outerBox.addWidget(self.filterBox, 2, 1)
|
||||||
|
self.outerBox.addWidget(self.buttonBox, 3, 0, 1, 2)
|
||||||
self.outerBox.setRowStretch(0, 1)
|
self.outerBox.setRowStretch(0, 1)
|
||||||
|
|
||||||
self.setLayout(self.outerBox)
|
self.setLayout(self.outerBox)
|
||||||
@@ -262,6 +283,7 @@ class GuiWritingStats(QDialog):
|
|||||||
hideZeros = self.hideZeros.isChecked()
|
hideZeros = self.hideZeros.isChecked()
|
||||||
hideNegative = self.hideNegative.isChecked()
|
hideNegative = self.hideNegative.isChecked()
|
||||||
groupByDay = self.groupByDay.isChecked()
|
groupByDay = self.groupByDay.isChecked()
|
||||||
|
histMax = self.histMax.value()
|
||||||
|
|
||||||
self.optState.setValue("GuiWritingStats", "winWidth", winWidth)
|
self.optState.setValue("GuiWritingStats", "winWidth", winWidth)
|
||||||
self.optState.setValue("GuiWritingStats", "winHeight", winHeight)
|
self.optState.setValue("GuiWritingStats", "winHeight", winHeight)
|
||||||
@@ -275,6 +297,7 @@ class GuiWritingStats(QDialog):
|
|||||||
self.optState.setValue("GuiWritingStats", "hideZeros", hideZeros)
|
self.optState.setValue("GuiWritingStats", "hideZeros", hideZeros)
|
||||||
self.optState.setValue("GuiWritingStats", "hideNegative", hideNegative)
|
self.optState.setValue("GuiWritingStats", "hideNegative", hideNegative)
|
||||||
self.optState.setValue("GuiWritingStats", "groupByDay", groupByDay)
|
self.optState.setValue("GuiWritingStats", "groupByDay", groupByDay)
|
||||||
|
self.optState.setValue("GuiWritingStats", "histMax", histMax)
|
||||||
|
|
||||||
self.optState.saveSettings()
|
self.optState.saveSettings()
|
||||||
self.close()
|
self.close()
|
||||||
@@ -438,8 +461,10 @@ class GuiWritingStats(QDialog):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _updateListBox(self):
|
def _updateListBox(self, dummyVar=None):
|
||||||
"""Load/reload the content of the list box.
|
"""Load/reload the content of the list box. The dummyVar
|
||||||
|
variable captures the variable sent from the widgets connecting
|
||||||
|
to it and discards it.
|
||||||
"""
|
"""
|
||||||
self.listBox.clear()
|
self.listBox.clear()
|
||||||
self.timeFilter = 0.0
|
self.timeFilter = 0.0
|
||||||
@@ -449,6 +474,7 @@ class GuiWritingStats(QDialog):
|
|||||||
hideZeros = self.hideZeros.isChecked()
|
hideZeros = self.hideZeros.isChecked()
|
||||||
hideNegative = self.hideNegative.isChecked()
|
hideNegative = self.hideNegative.isChecked()
|
||||||
groupByDay = self.groupByDay.isChecked()
|
groupByDay = self.groupByDay.isChecked()
|
||||||
|
histMax = self.histMax.value()
|
||||||
|
|
||||||
# Group the data
|
# Group the data
|
||||||
if groupByDay:
|
if groupByDay:
|
||||||
@@ -509,7 +535,7 @@ class GuiWritingStats(QDialog):
|
|||||||
sStart = dStart.strftime(nwConst.tStampFmt)
|
sStart = dStart.strftime(nwConst.tStampFmt)
|
||||||
|
|
||||||
self.filterData.append((dStart, sStart, sDiff, dwTotal, wcNovel, wcNotes))
|
self.filterData.append((dStart, sStart, sDiff, dwTotal, wcNovel, wcNotes))
|
||||||
listMax = max(listMax, dwTotal)
|
listMax = min(max(listMax, dwTotal), histMax)
|
||||||
pcTotal = wcTotal
|
pcTotal = wcTotal
|
||||||
|
|
||||||
# Populate the list
|
# Populate the list
|
||||||
@@ -522,7 +548,7 @@ class GuiWritingStats(QDialog):
|
|||||||
|
|
||||||
if nWords > 0 and listMax > 0:
|
if nWords > 0 and listMax > 0:
|
||||||
theBar = self.barImage.scaled(
|
theBar = self.barImage.scaled(
|
||||||
int(200*nWords/listMax),
|
int(200*min(nWords, histMax)/listMax),
|
||||||
self.barHeight,
|
self.barHeight,
|
||||||
Qt.IgnoreAspectRatio,
|
Qt.IgnoreAspectRatio,
|
||||||
Qt.FastTransformation
|
Qt.FastTransformation
|
||||||
|
|||||||
+95
-52
@@ -43,10 +43,11 @@ from nw.gui import (
|
|||||||
GuiBuildNovel, GuiDocEditor, GuiDocMerge, GuiDocSplit, GuiDocViewDetails,
|
GuiBuildNovel, GuiDocEditor, GuiDocMerge, GuiDocSplit, GuiDocViewDetails,
|
||||||
GuiDocViewer, GuiItemDetails, GuiItemEditor, GuiMainMenu, GuiMainStatus,
|
GuiDocViewer, GuiItemDetails, GuiItemEditor, GuiMainMenu, GuiMainStatus,
|
||||||
GuiOutline, GuiOutlineDetails, GuiPreferences, GuiProjectLoad, GuiTheme,
|
GuiOutline, GuiOutlineDetails, GuiPreferences, GuiProjectLoad, GuiTheme,
|
||||||
GuiProjectSettings, GuiProjectTree, GuiWritingStats, GuiProjectWizard
|
GuiProjectSettings, GuiProjectTree, GuiWritingStats, GuiProjectWizard,
|
||||||
|
GuiAbout
|
||||||
)
|
)
|
||||||
from nw.core import NWProject, NWDoc, NWIndex
|
from nw.core import NWProject, NWDoc, NWIndex
|
||||||
from nw.constants import nwFiles, nwItemType, nwAlert
|
from nw.constants import nwItemType, nwAlert
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -128,7 +129,7 @@ class GuiMain(QMainWindow):
|
|||||||
self.tabWidget = QTabWidget()
|
self.tabWidget = QTabWidget()
|
||||||
self.tabWidget.setTabPosition(QTabWidget.East)
|
self.tabWidget.setTabPosition(QTabWidget.East)
|
||||||
self.tabWidget.setStyleSheet("QTabWidget::pane {border: 0;}")
|
self.tabWidget.setStyleSheet("QTabWidget::pane {border: 0;}")
|
||||||
self.tabWidget.addTab(self.splitDocs, "Editor")
|
self.tabWidget.addTab(self.splitDocs, "Editor")
|
||||||
self.tabWidget.addTab(self.splitOutline, "Outline")
|
self.tabWidget.addTab(self.splitOutline, "Outline")
|
||||||
self.tabWidget.currentChanged.connect(self._mainTabChanged)
|
self.tabWidget.currentChanged.connect(self._mainTabChanged)
|
||||||
|
|
||||||
@@ -139,8 +140,6 @@ class GuiMain(QMainWindow):
|
|||||||
self.splitMain.addWidget(self.tabWidget)
|
self.splitMain.addWidget(self.tabWidget)
|
||||||
self.splitMain.setSizes(self.mainConf.getMainPanePos())
|
self.splitMain.setSizes(self.mainConf.getMainPanePos())
|
||||||
|
|
||||||
self.setCentralWidget(self.splitMain)
|
|
||||||
|
|
||||||
self.idxTree = self.splitMain.indexOf(self.treePane)
|
self.idxTree = self.splitMain.indexOf(self.treePane)
|
||||||
self.idxMain = self.splitMain.indexOf(self.tabWidget)
|
self.idxMain = self.splitMain.indexOf(self.tabWidget)
|
||||||
self.idxEditor = self.splitDocs.indexOf(self.docEditor)
|
self.idxEditor = self.splitDocs.indexOf(self.docEditor)
|
||||||
@@ -167,8 +166,8 @@ class GuiMain(QMainWindow):
|
|||||||
|
|
||||||
# Set Main Window Elements
|
# Set Main Window Elements
|
||||||
self.setMenuBar(self.mainMenu)
|
self.setMenuBar(self.mainMenu)
|
||||||
|
self.setCentralWidget(self.splitMain)
|
||||||
self.setStatusBar(self.statusBar)
|
self.setStatusBar(self.statusBar)
|
||||||
self.statusBar.setStatus("Ready")
|
|
||||||
|
|
||||||
# Finalise Initialisation
|
# Finalise Initialisation
|
||||||
##########################
|
##########################
|
||||||
@@ -222,6 +221,7 @@ class GuiMain(QMainWindow):
|
|||||||
self.manageProjects()
|
self.manageProjects()
|
||||||
|
|
||||||
logger.debug("novelWriter is ready ...")
|
logger.debug("novelWriter is ready ...")
|
||||||
|
self.statusBar.setStatus("novelWriter is ready ...")
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -348,9 +348,7 @@ class GuiMain(QMainWindow):
|
|||||||
return saveOK
|
return saveOK
|
||||||
|
|
||||||
def openProject(self, projFile):
|
def openProject(self, projFile):
|
||||||
"""Open a project. The parameter projFile is passed from the
|
"""Open a project from a projFile path.
|
||||||
open recent projects menu, and must be set to be forwarded to
|
|
||||||
the project class. Otherwise, we just return.
|
|
||||||
"""
|
"""
|
||||||
if projFile is None:
|
if projFile is None:
|
||||||
return False
|
return False
|
||||||
@@ -365,43 +363,47 @@ class GuiMain(QMainWindow):
|
|||||||
|
|
||||||
# Try to open the project
|
# Try to open the project
|
||||||
if not self.theProject.openProject(projFile):
|
if not self.theProject.openProject(projFile):
|
||||||
if self.theProject.lockedBy is not None:
|
# The project open failed.
|
||||||
if self.mainConf.showGUI:
|
|
||||||
try:
|
|
||||||
lockDetails = (
|
|
||||||
"<br><br>The project was locked by the computer "
|
|
||||||
"'%s' (%s %s), last active on %s"
|
|
||||||
) % (
|
|
||||||
self.theProject.lockedBy[0],
|
|
||||||
self.theProject.lockedBy[1],
|
|
||||||
self.theProject.lockedBy[2],
|
|
||||||
datetime.fromtimestamp(
|
|
||||||
int(self.theProject.lockedBy[3])
|
|
||||||
).strftime("%x %X")
|
|
||||||
)
|
|
||||||
except:
|
|
||||||
lockDetails = ""
|
|
||||||
|
|
||||||
msgBox = QMessageBox()
|
if self.theProject.lockedBy is None:
|
||||||
msgRes = msgBox.warning(
|
# The project is not locked, so failed for some other
|
||||||
self, "Project Locked", (
|
# reason handled by the project class.
|
||||||
"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
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
if self.mainConf.showGUI:
|
||||||
|
try:
|
||||||
|
lockDetails = (
|
||||||
|
"<br><br>The project was locked by the computer "
|
||||||
|
"'%s' (%s %s), last active on %s"
|
||||||
|
) % (
|
||||||
|
self.theProject.lockedBy[0],
|
||||||
|
self.theProject.lockedBy[1],
|
||||||
|
self.theProject.lockedBy[2],
|
||||||
|
datetime.fromtimestamp(
|
||||||
|
int(self.theProject.lockedBy[3])
|
||||||
|
).strftime("%x %X")
|
||||||
|
)
|
||||||
|
except:
|
||||||
|
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:
|
||||||
|
if not self.theProject.openProject(projFile, overrideLock=True):
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
# Project is loaded
|
# Project is loaded
|
||||||
self.hasProject = True
|
self.hasProject = True
|
||||||
|
|
||||||
@@ -570,7 +572,7 @@ class GuiMain(QMainWindow):
|
|||||||
dlgOpt = QFileDialog.Options()
|
dlgOpt = QFileDialog.Options()
|
||||||
dlgOpt |= QFileDialog.DontUseNativeDialog
|
dlgOpt |= QFileDialog.DontUseNativeDialog
|
||||||
inPath = QFileDialog.getOpenFileName(
|
inPath = QFileDialog.getOpenFileName(
|
||||||
self,"Import File",lastPath,options=dlgOpt,filter=";;".join(extFilter)
|
self, "Import File", lastPath, options=dlgOpt, filter=";;".join(extFilter)
|
||||||
)
|
)
|
||||||
if inPath:
|
if inPath:
|
||||||
loadFile = inPath[0]
|
loadFile = inPath[0]
|
||||||
@@ -582,7 +584,7 @@ class GuiMain(QMainWindow):
|
|||||||
|
|
||||||
theText = None
|
theText = None
|
||||||
try:
|
try:
|
||||||
with open(loadFile,mode="rt",encoding="utf8") as inFile:
|
with open(loadFile, mode="rt", encoding="utf8") as inFile:
|
||||||
theText = inFile.read()
|
theText = inFile.read()
|
||||||
self.mainConf.setLastPath(loadFile)
|
self.mainConf.setLastPath(loadFile)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -602,7 +604,7 @@ class GuiMain(QMainWindow):
|
|||||||
if not self.docEditor.isEmpty():
|
if not self.docEditor.isEmpty():
|
||||||
if self.mainConf.showGUI:
|
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?"
|
||||||
))
|
))
|
||||||
@@ -805,16 +807,34 @@ class GuiMain(QMainWindow):
|
|||||||
"""Open the build project dialog.
|
"""Open the build project dialog.
|
||||||
"""
|
"""
|
||||||
if self.hasProject:
|
if self.hasProject:
|
||||||
dlgExport = GuiBuildNovel(self, self.theProject)
|
dlgBuild = GuiBuildNovel(self, self.theProject)
|
||||||
dlgExport.exec_()
|
dlgBuild.setModal(False)
|
||||||
|
dlgBuild.show()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def showWritingStatsDialog(self):
|
def showWritingStatsDialog(self):
|
||||||
"""Open the session log dialog.
|
"""Open the session log dialog.
|
||||||
"""
|
"""
|
||||||
if self.hasProject:
|
if self.hasProject:
|
||||||
dlgTLine = GuiWritingStats(self, self.theProject)
|
dlgStats = GuiWritingStats(self, self.theProject)
|
||||||
dlgTLine.exec_()
|
dlgStats.setModal(False)
|
||||||
|
dlgStats.show()
|
||||||
|
return True
|
||||||
|
|
||||||
|
def showAboutNWDialog(self):
|
||||||
|
"""Show the about dialog for novelWriter.
|
||||||
|
"""
|
||||||
|
if self.mainConf.showGUI:
|
||||||
|
dlgAbout = GuiAbout(self)
|
||||||
|
dlgAbout.exec_()
|
||||||
|
return True
|
||||||
|
|
||||||
|
def showAboutQtDialog(self):
|
||||||
|
"""Show the about dialog for Qt.
|
||||||
|
"""
|
||||||
|
if self.mainConf.showGUI:
|
||||||
|
msgBox = QMessageBox()
|
||||||
|
msgBox.aboutQt(self, "About Qt")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def makeAlert(self, theMessage, theLevel=nwAlert.INFO):
|
def makeAlert(self, theMessage, theLevel=nwAlert.INFO):
|
||||||
@@ -901,6 +921,8 @@ class GuiMain(QMainWindow):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def setFocus(self, paneNo):
|
def setFocus(self, paneNo):
|
||||||
|
"""Switch focus to one of the three main gUi panes.
|
||||||
|
"""
|
||||||
if paneNo == 1:
|
if paneNo == 1:
|
||||||
self.treeView.setFocus()
|
self.treeView.setFocus()
|
||||||
elif paneNo == 2:
|
elif paneNo == 2:
|
||||||
@@ -910,6 +932,8 @@ class GuiMain(QMainWindow):
|
|||||||
return
|
return
|
||||||
|
|
||||||
def closeDocEditor(self):
|
def closeDocEditor(self):
|
||||||
|
"""Close the document edit panel. This does not hide the editor.
|
||||||
|
"""
|
||||||
self.closeDocument()
|
self.closeDocument()
|
||||||
self.theProject.setLastEdited(None)
|
self.theProject.setLastEdited(None)
|
||||||
return
|
return
|
||||||
@@ -1043,6 +1067,8 @@ class GuiMain(QMainWindow):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def _setWindowTitle(self, projName=None):
|
def _setWindowTitle(self, projName=None):
|
||||||
|
"""Set the window title and add the project's working title.
|
||||||
|
"""
|
||||||
winTitle = self.mainConf.appName
|
winTitle = self.mainConf.appName
|
||||||
if projName is not None:
|
if projName is not None:
|
||||||
winTitle += " - %s" % projName
|
winTitle += " - %s" % projName
|
||||||
@@ -1050,19 +1076,30 @@ class GuiMain(QMainWindow):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def _autoSaveProject(self):
|
def _autoSaveProject(self):
|
||||||
if (self.hasProject and self.theProject.projChanged and
|
"""Triggered by the auto-save project timer to save the project.
|
||||||
self.theProject.projPath is not None):
|
"""
|
||||||
|
doSave = self.hasProject
|
||||||
|
doSave &= self.theProject.projChanged
|
||||||
|
doSave &= self.theProject.projPath is not None
|
||||||
|
|
||||||
|
if doSave:
|
||||||
logger.debug("Autosaving project")
|
logger.debug("Autosaving project")
|
||||||
self.saveProject(autoSave=True)
|
self.saveProject(autoSave=True)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def _autoSaveDocument(self):
|
def _autoSaveDocument(self):
|
||||||
|
"""Triggered by the auto-save document timer to save the
|
||||||
|
document.
|
||||||
|
"""
|
||||||
if self.hasProject and self.docEditor.docChanged:
|
if self.hasProject and self.docEditor.docChanged:
|
||||||
logger.debug("Autosaving document")
|
logger.debug("Autosaving document")
|
||||||
self.saveDocument()
|
self.saveDocument()
|
||||||
return
|
return
|
||||||
|
|
||||||
def _makeStatusIcons(self):
|
def _makeStatusIcons(self):
|
||||||
|
"""Generate all the item status icons based on project settings.
|
||||||
|
"""
|
||||||
self.statusIcons = {}
|
self.statusIcons = {}
|
||||||
iPx = self.mainConf.pxInt(32)
|
iPx = self.mainConf.pxInt(32)
|
||||||
for sLabel, sCol, _ in self.theProject.statusItems:
|
for sLabel, sCol, _ in self.theProject.statusItems:
|
||||||
@@ -1072,6 +1109,9 @@ class GuiMain(QMainWindow):
|
|||||||
return
|
return
|
||||||
|
|
||||||
def _makeImportIcons(self):
|
def _makeImportIcons(self):
|
||||||
|
"""Generate all the item importance icons based on project
|
||||||
|
settings.
|
||||||
|
"""
|
||||||
self.importIcons = {}
|
self.importIcons = {}
|
||||||
iPx = self.mainConf.pxInt(32)
|
iPx = self.mainConf.pxInt(32)
|
||||||
for sLabel, sCol, _ in self.theProject.importItems:
|
for sLabel, sCol, _ in self.theProject.importItems:
|
||||||
@@ -1085,6 +1125,9 @@ class GuiMain(QMainWindow):
|
|||||||
##
|
##
|
||||||
|
|
||||||
def closeEvent(self, theEvent):
|
def closeEvent(self, theEvent):
|
||||||
|
"""Capture the closing event of the GUI and call the close
|
||||||
|
function to handle all the close process steps.
|
||||||
|
"""
|
||||||
if self.closeMain():
|
if self.closeMain():
|
||||||
theEvent.accept()
|
theEvent.accept()
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
<novelWriterXML appVersion="0.11.0" hexVersion="0x001100f0" fileVersion="1.2" timeStamp="2020-08-08 14:25:39">
|
<novelWriterXML appVersion="0.11.1" hexVersion="0x001101f0" fileVersion="1.2" timeStamp="2020-08-09 15:42:40">
|
||||||
<project>
|
<project>
|
||||||
<name>Sample Project</name>
|
<name>Sample Project</name>
|
||||||
<title>Sample Project</title>
|
<title>Sample Project</title>
|
||||||
<author>Jane Smith</author>
|
<author>Jane Smith</author>
|
||||||
<author>Jay Doh</author>
|
<author>Jay Doh</author>
|
||||||
<saveCount>663</saveCount>
|
<saveCount>675</saveCount>
|
||||||
<autoCount>122</autoCount>
|
<autoCount>122</autoCount>
|
||||||
<editTime>32747</editTime>
|
<editTime>32866</editTime>
|
||||||
</project>
|
</project>
|
||||||
<settings>
|
<settings>
|
||||||
<doBackup>False</doBackup>
|
<doBackup>False</doBackup>
|
||||||
@@ -119,7 +119,7 @@
|
|||||||
<charCount>1811</charCount>
|
<charCount>1811</charCount>
|
||||||
<wordCount>318</wordCount>
|
<wordCount>318</wordCount>
|
||||||
<paraCount>8</paraCount>
|
<paraCount>8</paraCount>
|
||||||
<cursorPos>1143</cursorPos>
|
<cursorPos>42</cursorPos>
|
||||||
</item>
|
</item>
|
||||||
<item handle="bc0cbd2a407f3" order="2" parent="e7ded148d6e4a">
|
<item handle="bc0cbd2a407f3" order="2" parent="e7ded148d6e4a">
|
||||||
<name>Another Scene</name>
|
<name>Another Scene</name>
|
||||||
|
|||||||
Reference in New Issue
Block a user