mirror of
https://github.com/olivierkes/manuskript.git
synced 2024-05-20 21:02:23 +12:00
Merge pull request #667 from worstje/arguments-and-logging
Logging and command-line arguments
This commit is contained in:
commit
0a615bdef2
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -12,12 +12,14 @@
|
||||||
.project
|
.project
|
||||||
.pydevproject
|
.pydevproject
|
||||||
.settings/org.eclipse.core.resources.prefs
|
.settings/org.eclipse.core.resources.prefs
|
||||||
|
.vscode
|
||||||
ExportTest
|
ExportTest
|
||||||
Notes.t2t
|
Notes.t2t
|
||||||
dist
|
dist
|
||||||
build
|
build
|
||||||
icons/Numix
|
icons/Numix
|
||||||
manuskript/pycallgraph.txt
|
manuskript/pycallgraph.txt
|
||||||
|
manuskript.log
|
||||||
snowflake*
|
snowflake*
|
||||||
test-projects
|
test-projects
|
||||||
main.pyproject.user
|
main.pyproject.user
|
||||||
|
|
|
@ -11,6 +11,9 @@ from PyQt5.QtGui import QCursor
|
||||||
from manuskript.converters import abstractConverter
|
from manuskript.converters import abstractConverter
|
||||||
from manuskript.functions import mainWindow
|
from manuskript.functions import mainWindow
|
||||||
|
|
||||||
|
import logging
|
||||||
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import markdown as MD
|
import markdown as MD
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
@ -31,7 +34,7 @@ class markdownConverter(abstractConverter):
|
||||||
@classmethod
|
@classmethod
|
||||||
def convert(self, markdown):
|
def convert(self, markdown):
|
||||||
if not self.isValid:
|
if not self.isValid:
|
||||||
print("ERROR: markdownConverter is called but not valid.")
|
LOGGER.error("markdownConverter is called but not valid.")
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
html = MD.markdown(markdown)
|
html = MD.markdown(markdown)
|
||||||
|
|
|
@ -11,6 +11,8 @@ from PyQt5.QtGui import QCursor
|
||||||
from manuskript.converters import abstractConverter
|
from manuskript.converters import abstractConverter
|
||||||
from manuskript.functions import mainWindow
|
from manuskript.functions import mainWindow
|
||||||
|
|
||||||
|
import logging
|
||||||
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
class pandocConverter(abstractConverter):
|
class pandocConverter(abstractConverter):
|
||||||
|
|
||||||
|
@ -38,7 +40,7 @@ class pandocConverter(abstractConverter):
|
||||||
@classmethod
|
@classmethod
|
||||||
def convert(self, src, _from="markdown", to="html", args=None, outputfile=None):
|
def convert(self, src, _from="markdown", to="html", args=None, outputfile=None):
|
||||||
if not self.isValid:
|
if not self.isValid:
|
||||||
print("ERROR: pandocConverter is called but not valid.")
|
LOGGER.error("pandocConverter is called but not valid.")
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
cmd = [self.runCmd()]
|
cmd = [self.runCmd()]
|
||||||
|
@ -70,7 +72,7 @@ class pandocConverter(abstractConverter):
|
||||||
|
|
||||||
if stderr:
|
if stderr:
|
||||||
err = stderr.decode("utf-8")
|
err = stderr.decode("utf-8")
|
||||||
print(err)
|
LOGGER.error(err)
|
||||||
QMessageBox.critical(mainWindow().dialog,
|
QMessageBox.critical(mainWindow().dialog,
|
||||||
qApp.translate("Export", "Error"), err)
|
qApp.translate("Export", "Error"), err)
|
||||||
return None
|
return None
|
||||||
|
|
|
@ -10,6 +10,8 @@ from PyQt5.QtWidgets import QWidget
|
||||||
from manuskript.models import outlineItem
|
from manuskript.models import outlineItem
|
||||||
from manuskript.functions import mainWindow
|
from manuskript.functions import mainWindow
|
||||||
|
|
||||||
|
import logging
|
||||||
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
class basicExporter:
|
class basicExporter:
|
||||||
|
|
||||||
|
@ -58,7 +60,7 @@ class basicExporter:
|
||||||
elif self.isValid() == 1:
|
elif self.isValid() == 1:
|
||||||
run = self.customPath
|
run = self.customPath
|
||||||
else:
|
else:
|
||||||
print("Error: no command for", self.name)
|
LOGGER.error("No command for %s.", self.name)
|
||||||
return None
|
return None
|
||||||
r = subprocess.check_output([run] + args) # timeout=.2
|
r = subprocess.check_output([run] + args) # timeout=.2
|
||||||
return r.decode("utf-8")
|
return r.decode("utf-8")
|
||||||
|
@ -71,7 +73,7 @@ class basicExporter:
|
||||||
# try:
|
# try:
|
||||||
# output = subprocess.check_output(cmdl, stdin=cmd.stdout, stderr=subprocess.STDOUT) # , cwd="/tmp"
|
# output = subprocess.check_output(cmdl, stdin=cmd.stdout, stderr=subprocess.STDOUT) # , cwd="/tmp"
|
||||||
# except subprocess.CalledProcessError as e:
|
# except subprocess.CalledProcessError as e:
|
||||||
# print("Error!")
|
# LOGGER.error("Failed to read from process output.")
|
||||||
# return text
|
# return text
|
||||||
# cmd.wait()
|
# cmd.wait()
|
||||||
#
|
#
|
||||||
|
|
|
@ -10,6 +10,9 @@ from manuskript.models import outlineItem
|
||||||
from manuskript.ui.exporters.manuskript.plainTextSettings import exporterSettings
|
from manuskript.ui.exporters.manuskript.plainTextSettings import exporterSettings
|
||||||
import codecs
|
import codecs
|
||||||
|
|
||||||
|
import logging
|
||||||
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
class plainText(basicFormat):
|
class plainText(basicFormat):
|
||||||
name = qApp.translate("Export", "Plain text")
|
name = qApp.translate("Export", "Plain text")
|
||||||
description = qApp.translate("Export", """Simplest export to plain text. Allows you to use your own markup not understood
|
description = qApp.translate("Export", """Simplest export to plain text. Allows you to use your own markup not understood
|
||||||
|
@ -90,7 +93,7 @@ class plainText(basicFormat):
|
||||||
content = self.output(settingsWidget)
|
content = self.output(settingsWidget)
|
||||||
|
|
||||||
if not content:
|
if not content:
|
||||||
print("Error: No content. Nothing saved.")
|
LOGGER.error("No content. Nothing saved.")
|
||||||
return
|
return
|
||||||
|
|
||||||
with open(filename, "w", encoding='utf8') as f:
|
with open(filename, "w", encoding='utf8') as f:
|
||||||
|
|
|
@ -13,6 +13,8 @@ from manuskript.exporter.pandoc.outputFormats import ePub, OpenDocument, DocX
|
||||||
from manuskript.exporter.pandoc.plainText import reST, markdown, latex, OPML
|
from manuskript.exporter.pandoc.plainText import reST, markdown, latex, OPML
|
||||||
from manuskript.functions import mainWindow
|
from manuskript.functions import mainWindow
|
||||||
|
|
||||||
|
import logging
|
||||||
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
class pandocExporter(basicExporter):
|
class pandocExporter(basicExporter):
|
||||||
|
|
||||||
|
@ -53,7 +55,7 @@ class pandocExporter(basicExporter):
|
||||||
elif self.isValid() == 1:
|
elif self.isValid() == 1:
|
||||||
run = self.customPath
|
run = self.customPath
|
||||||
else:
|
else:
|
||||||
print("Error: no command for pandoc")
|
LOGGER.error("No command for pandoc.")
|
||||||
return None
|
return None
|
||||||
args = [run] + args
|
args = [run] + args
|
||||||
|
|
||||||
|
@ -101,7 +103,7 @@ class pandocExporter(basicExporter):
|
||||||
+ "Return code" + ": %d\n" % (p.returncode) \
|
+ "Return code" + ": %d\n" % (p.returncode) \
|
||||||
+ "Command and parameters" + ":\n%s\n" % (p.args) \
|
+ "Command and parameters" + ":\n%s\n" % (p.args) \
|
||||||
+ "Stderr content" + ":\n" + stderr.decode("utf-8")
|
+ "Stderr content" + ":\n" + stderr.decode("utf-8")
|
||||||
print(err)
|
LOGGER.error(err)
|
||||||
QMessageBox.critical(mainWindow().dialog, qApp.translate("Export", "Error"), err)
|
QMessageBox.critical(mainWindow().dialog, qApp.translate("Export", "Error"), err)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import sys
|
||||||
|
import pathlib
|
||||||
from random import *
|
from random import *
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt, QRect, QStandardPaths, QObject, QRegExp, QDir
|
from PyQt5.QtCore import Qt, QRect, QStandardPaths, QObject, QRegExp, QDir
|
||||||
|
@ -13,6 +15,9 @@ from PyQt5.QtWidgets import qApp, QFileDialog
|
||||||
|
|
||||||
from manuskript.enums import Outline
|
from manuskript.enums import Outline
|
||||||
|
|
||||||
|
import logging
|
||||||
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
# Used to detect multiple connections
|
# Used to detect multiple connections
|
||||||
AUC = Qt.AutoConnection | Qt.UniqueConnection
|
AUC = Qt.AutoConnection | Qt.UniqueConnection
|
||||||
MW = None
|
MW = None
|
||||||
|
@ -493,5 +498,47 @@ def getSearchResultContext(text, startPos, endPos):
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
# Based on answer by jfs at:
|
||||||
|
# https://stackoverflow.com/questions/3718657/how-to-properly-determine-current-script-directory
|
||||||
|
def getManuskriptPath(follow_symlinks=True):
|
||||||
|
"""Used to obtain the path Manuskript is located at."""
|
||||||
|
if getattr(sys, 'frozen', False): # py2exe, PyInstaller, cx_Freeze
|
||||||
|
path = os.path.abspath(sys.executable)
|
||||||
|
else:
|
||||||
|
import inspect
|
||||||
|
path = inspect.getabsfile(getManuskriptPath) + "/../.."
|
||||||
|
if follow_symlinks:
|
||||||
|
path = os.path.realpath(path)
|
||||||
|
return os.path.dirname(path)
|
||||||
|
|
||||||
|
# Based on answer by kagronik at:
|
||||||
|
# https://stackoverflow.com/questions/14989858/get-the-current-git-hash-in-a-python-script
|
||||||
|
def getGitRevision(base_path):
|
||||||
|
"""Get git revision without relying on external processes or libraries."""
|
||||||
|
git_dir = pathlib.Path(base_path) / '.git'
|
||||||
|
if not git_dir.exists():
|
||||||
|
return None
|
||||||
|
|
||||||
|
with (git_dir / 'HEAD').open('r') as head:
|
||||||
|
ref = head.readline().split(' ')[-1].strip()
|
||||||
|
|
||||||
|
with (git_dir / ref).open('r') as git_hash:
|
||||||
|
return git_hash.readline().strip()
|
||||||
|
|
||||||
|
def getGitRevisionAsString(base_path, short=False):
|
||||||
|
"""Catches errors and presents a nice string."""
|
||||||
|
try:
|
||||||
|
rev = getGitRevision(base_path)
|
||||||
|
if rev is not None:
|
||||||
|
if short:
|
||||||
|
rev = rev[:7]
|
||||||
|
return "#" + rev
|
||||||
|
else:
|
||||||
|
return "" # not a git repository
|
||||||
|
except Exception as e:
|
||||||
|
LOGGER.warning("Failed to obtain Git revision: %s", e)
|
||||||
|
return "#ERROR"
|
||||||
|
|
||||||
# Spellchecker loads writablePath from this file, so we need to load it after they get defined
|
# Spellchecker loads writablePath from this file, so we need to load it after they get defined
|
||||||
from manuskript.functions.spellchecker import Spellchecker
|
from manuskript.functions.spellchecker import Spellchecker
|
||||||
|
|
|
@ -9,6 +9,8 @@ import zipfile
|
||||||
import manuskript.load_save.version_0 as v0
|
import manuskript.load_save.version_0 as v0
|
||||||
import manuskript.load_save.version_1 as v1
|
import manuskript.load_save.version_1 as v1
|
||||||
|
|
||||||
|
import logging
|
||||||
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
def saveProject(version=None):
|
def saveProject(version=None):
|
||||||
|
|
||||||
|
@ -57,8 +59,8 @@ def loadProject(project):
|
||||||
with open(project, "r", encoding="utf-8") as f:
|
with open(project, "r", encoding="utf-8") as f:
|
||||||
version = int(f.read())
|
version = int(f.read())
|
||||||
|
|
||||||
print("Loading:", project)
|
LOGGER.info("Loading: %s", project)
|
||||||
print("Detected file format version: {}. Zip: {}.".format(version, isZip))
|
LOGGER.info("Detected file format version: {}. Zip: {}.".format(version, isZip))
|
||||||
|
|
||||||
if version == 0:
|
if version == 0:
|
||||||
v0.loadProject(project)
|
v0.loadProject(project)
|
||||||
|
|
|
@ -16,6 +16,9 @@ from manuskript import settings
|
||||||
from manuskript.functions import iconColor, iconFromColorString, mainWindow
|
from manuskript.functions import iconColor, iconFromColorString, mainWindow
|
||||||
from manuskript.models.characterModel import Character, CharacterInfo
|
from manuskript.models.characterModel import Character, CharacterInfo
|
||||||
|
|
||||||
|
import logging
|
||||||
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import zlib # Used with zipfile for compression
|
import zlib # Used with zipfile for compression
|
||||||
|
|
||||||
|
@ -37,7 +40,7 @@ def saveProject():
|
||||||
|
|
||||||
files.append((saveStandardItemModelXML(mw.mdlFlatData),
|
files.append((saveStandardItemModelXML(mw.mdlFlatData),
|
||||||
"flatModel.xml"))
|
"flatModel.xml"))
|
||||||
print("ERROR: file format 0 does not save characters !")
|
LOGGER.error("File format 0 does not save characters!")
|
||||||
# files.append((saveStandardItemModelXML(mw.mdlCharacter),
|
# files.append((saveStandardItemModelXML(mw.mdlCharacter),
|
||||||
# "perso.xml"))
|
# "perso.xml"))
|
||||||
files.append((saveStandardItemModelXML(mw.mdlWorld),
|
files.append((saveStandardItemModelXML(mw.mdlWorld),
|
||||||
|
@ -91,7 +94,7 @@ def saveStandardItemModelXML(mdl, xml=None):
|
||||||
data = ET.SubElement(root, "data")
|
data = ET.SubElement(root, "data")
|
||||||
saveItem(data, mdl)
|
saveItem(data, mdl)
|
||||||
|
|
||||||
# print(qApp.tr("Saving to {}.").format(xml))
|
# LOGGER.info("Saving to {}.".format(xml))
|
||||||
if xml:
|
if xml:
|
||||||
ET.ElementTree(root).write(xml, encoding="UTF-8", xml_declaration=True, pretty_print=True)
|
ET.ElementTree(root).write(xml, encoding="UTF-8", xml_declaration=True, pretty_print=True)
|
||||||
else:
|
else:
|
||||||
|
@ -189,13 +192,13 @@ def loadStandardItemModelXML(mdl, xml, fromString=False):
|
||||||
"""Load data to a QStandardItemModel mdl from xml.
|
"""Load data to a QStandardItemModel mdl from xml.
|
||||||
By default xml is a filename. If fromString=True, xml is a string containing the data."""
|
By default xml is a filename. If fromString=True, xml is a string containing the data."""
|
||||||
|
|
||||||
# print(qApp.tr("Loading {}... ").format(xml), end="")
|
# LOGGER.info("Loading {}...".format(xml))
|
||||||
|
|
||||||
if not fromString:
|
if not fromString:
|
||||||
try:
|
try:
|
||||||
tree = ET.parse(xml)
|
tree = ET.parse(xml)
|
||||||
except:
|
except:
|
||||||
print("Failed.")
|
LOGGER.error("Failed to load XML for QStandardItemModel (%s).", xml)
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
root = ET.fromstring(xml)
|
root = ET.fromstring(xml)
|
||||||
|
@ -210,7 +213,7 @@ def loadStandardItemModelXML(mdl, xml, fromString=False):
|
||||||
for l in root.find("header").find("vertical").findall("label"):
|
for l in root.find("header").find("vertical").findall("label"):
|
||||||
vLabels.append(l.attrib["text"])
|
vLabels.append(l.attrib["text"])
|
||||||
|
|
||||||
# print(root.find("header").find("vertical").text)
|
# LOGGER.debug(root.find("header").find("vertical").text)
|
||||||
|
|
||||||
# mdl.setVerticalHeaderLabels(vLabels)
|
# mdl.setVerticalHeaderLabels(vLabels)
|
||||||
# mdl.setHorizontalHeaderLabels(hLabels)
|
# mdl.setHorizontalHeaderLabels(hLabels)
|
||||||
|
|
|
@ -26,6 +26,9 @@ from manuskript.load_save.version_0 import loadFilesFromZip
|
||||||
from manuskript.models.characterModel import CharacterInfo
|
from manuskript.models.characterModel import CharacterInfo
|
||||||
from manuskript.models import outlineItem
|
from manuskript.models import outlineItem
|
||||||
|
|
||||||
|
import logging
|
||||||
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import zlib # Used with zipfile for compression
|
import zlib # Used with zipfile for compression
|
||||||
|
|
||||||
|
@ -51,8 +54,6 @@ characterMap = OrderedDict([
|
||||||
(Character.notes, "Notes")
|
(Character.notes, "Notes")
|
||||||
])
|
])
|
||||||
|
|
||||||
# If true, logs infos while saving and loading.
|
|
||||||
LOG = False
|
|
||||||
|
|
||||||
def formatMetaData(name, value, tabLength=10):
|
def formatMetaData(name, value, tabLength=10):
|
||||||
|
|
||||||
|
@ -92,11 +93,6 @@ def slugify(name):
|
||||||
return newName
|
return newName
|
||||||
|
|
||||||
|
|
||||||
def log(*args):
|
|
||||||
if LOG:
|
|
||||||
print(" ".join(str(a) for a in args))
|
|
||||||
|
|
||||||
|
|
||||||
def saveProject(zip=None):
|
def saveProject(zip=None):
|
||||||
"""
|
"""
|
||||||
Saves the project. If zip is False, the project is saved as a multitude of plain-text files for the most parts
|
Saves the project. If zip is False, the project is saved as a multitude of plain-text files for the most parts
|
||||||
|
@ -110,7 +106,7 @@ def saveProject(zip=None):
|
||||||
if zip == None:
|
if zip == None:
|
||||||
zip = settings.saveToZip
|
zip = settings.saveToZip
|
||||||
|
|
||||||
log("\n\nSaving to:", "zip" if zip else "folder")
|
LOGGER.info("Saving to: %s", "zip" if zip else "folder")
|
||||||
|
|
||||||
# List of files to be written
|
# List of files to be written
|
||||||
files = []
|
files = []
|
||||||
|
@ -125,7 +121,7 @@ def saveProject(zip=None):
|
||||||
|
|
||||||
# Sanity check (see PR-583): make sure we actually have a current project.
|
# Sanity check (see PR-583): make sure we actually have a current project.
|
||||||
if project == None:
|
if project == None:
|
||||||
print("Error: cannot save project because there is no current project in the UI.")
|
LOGGER.error("Cannot save project because there is no current project in the UI.")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# File format version
|
# File format version
|
||||||
|
@ -307,7 +303,7 @@ def saveProject(zip=None):
|
||||||
# not exist, we check the parent folder, because it might be a new project.
|
# not exist, we check the parent folder, because it might be a new project.
|
||||||
if os.path.exists(project) and not os.access(project, os.W_OK) or \
|
if os.path.exists(project) and not os.access(project, os.W_OK) or \
|
||||||
not os.path.exists(project) and not os.access(os.path.dirname(project), os.W_OK):
|
not os.path.exists(project) and not os.access(os.path.dirname(project), os.W_OK):
|
||||||
print("Error: you don't have write access to save this project there.")
|
LOGGER.error("You don't have write access to save this project there.")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
####################################################################################################################
|
####################################################################################################################
|
||||||
|
@ -341,7 +337,7 @@ def saveProject(zip=None):
|
||||||
folder = os.path.splitext(os.path.basename(project))[0]
|
folder = os.path.splitext(os.path.basename(project))[0]
|
||||||
|
|
||||||
# Debug
|
# Debug
|
||||||
log("\nSaving to folder", folder)
|
LOGGER.debug("Saving to folder %s", folder)
|
||||||
|
|
||||||
# If cache is empty (meaning we haven't loaded from disk), we wipe folder, just to be sure.
|
# If cache is empty (meaning we haven't loaded from disk), we wipe folder, just to be sure.
|
||||||
if not cache:
|
if not cache:
|
||||||
|
@ -358,7 +354,7 @@ def saveProject(zip=None):
|
||||||
# Move the old file to the new place
|
# Move the old file to the new place
|
||||||
try:
|
try:
|
||||||
os.replace(oldPath, newPath)
|
os.replace(oldPath, newPath)
|
||||||
log("* Renaming/moving {} to {}".format(old, new))
|
LOGGER.debug("* Renaming/moving {} to {}".format(old, new))
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
# Maybe parent folder has been renamed
|
# Maybe parent folder has been renamed
|
||||||
pass
|
pass
|
||||||
|
@ -368,7 +364,7 @@ def saveProject(zip=None):
|
||||||
for f in cache:
|
for f in cache:
|
||||||
f2 = f.replace(old, new)
|
f2 = f.replace(old, new)
|
||||||
if f2 != f:
|
if f2 != f:
|
||||||
log(" * Updating cache:", f, f2)
|
LOGGER.debug(" * Updating cache: %s, %s", f, f2)
|
||||||
cache2[f2] = cache[f]
|
cache2[f2] = cache[f]
|
||||||
cache = cache2
|
cache = cache2
|
||||||
|
|
||||||
|
@ -379,7 +375,7 @@ def saveProject(zip=None):
|
||||||
|
|
||||||
# Check if content is in cache, and write if necessary
|
# Check if content is in cache, and write if necessary
|
||||||
if path not in cache or cache[path] != content:
|
if path not in cache or cache[path] != content:
|
||||||
log("* Writing file {} ({})".format(path, "not in cache" if path not in cache else "different"))
|
LOGGER.debug("* Writing file {} ({})".format(path, "not in cache" if path not in cache else "different"))
|
||||||
# mode = "w" + ("b" if type(content) == bytes else "")
|
# mode = "w" + ("b" if type(content) == bytes else "")
|
||||||
if type(content) == bytes:
|
if type(content) == bytes:
|
||||||
with open(filename, "wb") as f:
|
with open(filename, "wb") as f:
|
||||||
|
@ -393,7 +389,7 @@ def saveProject(zip=None):
|
||||||
# Removing phantoms
|
# Removing phantoms
|
||||||
for path in [p for p in cache if p not in [p for p, c in files]]:
|
for path in [p for p in cache if p not in [p for p, c in files]]:
|
||||||
filename = os.path.join(dir, folder, path)
|
filename = os.path.join(dir, folder, path)
|
||||||
log("* Removing", path)
|
LOGGER.debug("* Removing %s", path)
|
||||||
|
|
||||||
if os.path.isdir(filename):
|
if os.path.isdir(filename):
|
||||||
shutil.rmtree(filename)
|
shutil.rmtree(filename)
|
||||||
|
@ -410,7 +406,7 @@ def saveProject(zip=None):
|
||||||
newDir = os.path.join(root, dir)
|
newDir = os.path.join(root, dir)
|
||||||
try:
|
try:
|
||||||
os.removedirs(newDir)
|
os.removedirs(newDir)
|
||||||
log("* Removing empty directory:", newDir)
|
LOGGER.debug("* Removing empty directory: %s", newDir)
|
||||||
except:
|
except:
|
||||||
# Directory not empty, we don't remove.
|
# Directory not empty, we don't remove.
|
||||||
pass
|
pass
|
||||||
|
@ -536,8 +532,8 @@ def exportOutlineItem(root):
|
||||||
lp = child._lastPath
|
lp = child._lastPath
|
||||||
if lp and spath != lp:
|
if lp and spath != lp:
|
||||||
moves.append((lp, spath))
|
moves.append((lp, spath))
|
||||||
log(child.title(), "has been renamed (", lp, " → ", spath, ")")
|
LOGGER.debug("%s has been renamed (%s → %s)", child.title(), lp, spath)
|
||||||
log(" → We mark for moving:", lp)
|
LOGGER.debug(" → We mark for moving: %s", lp)
|
||||||
|
|
||||||
# Updates item last's path
|
# Updates item last's path
|
||||||
child._lastPath = spath
|
child._lastPath = spath
|
||||||
|
@ -553,7 +549,7 @@ def exportOutlineItem(root):
|
||||||
files.append((spath, content))
|
files.append((spath, content))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
log("Unknown type")
|
LOGGER.debug("Unknown type: %s", child.type())
|
||||||
|
|
||||||
f, m, r = exportOutlineItem(child)
|
f, m, r = exportOutlineItem(child)
|
||||||
files += f
|
files += f
|
||||||
|
@ -631,7 +627,7 @@ def loadProject(project, zip=None):
|
||||||
####################################################################################################################
|
####################################################################################################################
|
||||||
# Read and store everything in a dict
|
# Read and store everything in a dict
|
||||||
|
|
||||||
log("\nLoading {} ({})".format(project, "ZIP" if zip else "not zip"))
|
LOGGER.debug("Loading {} ({})".format(project, "zip" if zip else "folder"))
|
||||||
if zip:
|
if zip:
|
||||||
files = loadFilesFromZip(project)
|
files = loadFilesFromZip(project)
|
||||||
|
|
||||||
|
@ -695,7 +691,7 @@ def loadProject(project, zip=None):
|
||||||
mdl = mw.mdlLabels
|
mdl = mw.mdlLabels
|
||||||
mdl.appendRow(QStandardItem("")) # Empty = No labels
|
mdl.appendRow(QStandardItem("")) # Empty = No labels
|
||||||
if "labels.txt" in files:
|
if "labels.txt" in files:
|
||||||
log("\nReading labels:")
|
LOGGER.debug("Reading labels:")
|
||||||
for s in files["labels.txt"].split("\n"):
|
for s in files["labels.txt"].split("\n"):
|
||||||
if not s:
|
if not s:
|
||||||
continue
|
continue
|
||||||
|
@ -703,7 +699,7 @@ def loadProject(project, zip=None):
|
||||||
m = re.search(r"^(.*?):\s*(.*)$", s)
|
m = re.search(r"^(.*?):\s*(.*)$", s)
|
||||||
txt = m.group(1)
|
txt = m.group(1)
|
||||||
col = m.group(2)
|
col = m.group(2)
|
||||||
log("* Add status: {} ({})".format(txt, col))
|
LOGGER.debug("* Add status: {} ({})".format(txt, col))
|
||||||
icon = iconFromColorString(col)
|
icon = iconFromColorString(col)
|
||||||
mdl.appendRow(QStandardItem(icon, txt))
|
mdl.appendRow(QStandardItem(icon, txt))
|
||||||
|
|
||||||
|
@ -716,11 +712,11 @@ def loadProject(project, zip=None):
|
||||||
mdl = mw.mdlStatus
|
mdl = mw.mdlStatus
|
||||||
mdl.appendRow(QStandardItem("")) # Empty = No status
|
mdl.appendRow(QStandardItem("")) # Empty = No status
|
||||||
if "status.txt" in files:
|
if "status.txt" in files:
|
||||||
log("\nReading Status:")
|
LOGGER.debug("Reading status:")
|
||||||
for s in files["status.txt"].split("\n"):
|
for s in files["status.txt"].split("\n"):
|
||||||
if not s:
|
if not s:
|
||||||
continue
|
continue
|
||||||
log("* Add status:", s)
|
LOGGER.debug("* Add status: %s", s)
|
||||||
mdl.appendRow(QStandardItem(s))
|
mdl.appendRow(QStandardItem(s))
|
||||||
else:
|
else:
|
||||||
errors.append("status.txt")
|
errors.append("status.txt")
|
||||||
|
@ -762,7 +758,7 @@ def loadProject(project, zip=None):
|
||||||
|
|
||||||
mdl = mw.mdlPlots
|
mdl = mw.mdlPlots
|
||||||
if "plots.xml" in files:
|
if "plots.xml" in files:
|
||||||
log("\nReading plots:")
|
LOGGER.debug("Reading plots:")
|
||||||
# xml = bytearray(files["plots.xml"], "utf-8")
|
# xml = bytearray(files["plots.xml"], "utf-8")
|
||||||
root = ET.fromstring(files["plots.xml"])
|
root = ET.fromstring(files["plots.xml"])
|
||||||
|
|
||||||
|
@ -771,7 +767,7 @@ def loadProject(project, zip=None):
|
||||||
row = getStandardItemRowFromXMLEnum(plot, Plot)
|
row = getStandardItemRowFromXMLEnum(plot, Plot)
|
||||||
|
|
||||||
# Log
|
# Log
|
||||||
log("* Add plot: ", row[0].text())
|
LOGGER.debug("* Add plot: %s", row[0].text())
|
||||||
|
|
||||||
# Characters
|
# Characters
|
||||||
if row[Plot.characters].text():
|
if row[Plot.characters].text():
|
||||||
|
@ -798,7 +794,7 @@ def loadProject(project, zip=None):
|
||||||
|
|
||||||
mdl = mw.mdlWorld
|
mdl = mw.mdlWorld
|
||||||
if "world.opml" in files:
|
if "world.opml" in files:
|
||||||
log("\nReading World:")
|
LOGGER.debug("Reading World:")
|
||||||
# xml = bytearray(files["plots.xml"], "utf-8")
|
# xml = bytearray(files["plots.xml"], "utf-8")
|
||||||
root = ET.fromstring(files["world.opml"])
|
root = ET.fromstring(files["world.opml"])
|
||||||
body = root.find("body")
|
body = root.find("body")
|
||||||
|
@ -814,7 +810,7 @@ def loadProject(project, zip=None):
|
||||||
# Characters
|
# Characters
|
||||||
|
|
||||||
mdl = mw.mdlCharacter
|
mdl = mw.mdlCharacter
|
||||||
log("\nReading Characters:")
|
LOGGER.debug("Reading Characters:")
|
||||||
for f in [f for f in files if "characters" in f]:
|
for f in [f for f in files if "characters" in f]:
|
||||||
md, body = parseMMDFile(files[f])
|
md, body = parseMMDFile(files[f])
|
||||||
c = mdl.addCharacter()
|
c = mdl.addCharacter()
|
||||||
|
@ -840,7 +836,7 @@ def loadProject(project, zip=None):
|
||||||
else:
|
else:
|
||||||
c.infos.append(CharacterInfo(c, desc, val))
|
c.infos.append(CharacterInfo(c, desc, val))
|
||||||
|
|
||||||
log("* Adds {} ({})".format(c.name(), c.ID()))
|
LOGGER.debug("* Adds {} ({})".format(c.name(), c.ID()))
|
||||||
|
|
||||||
####################################################################################################################
|
####################################################################################################################
|
||||||
# Texts
|
# Texts
|
||||||
|
@ -848,14 +844,14 @@ def loadProject(project, zip=None):
|
||||||
# everything, but the outline folder takes precedence (in cases it's been edited outside of manuskript.
|
# everything, but the outline folder takes precedence (in cases it's been edited outside of manuskript.
|
||||||
|
|
||||||
mdl = mw.mdlOutline
|
mdl = mw.mdlOutline
|
||||||
log("\nReading outline:")
|
LOGGER.debug("Reading outline:")
|
||||||
paths = [f for f in files if "outline" in f]
|
paths = [f for f in files if "outline" in f]
|
||||||
outline = OrderedDict()
|
outline = OrderedDict()
|
||||||
|
|
||||||
# We create a structure of imbricated OrderedDict to store the whole tree.
|
# We create a structure of imbricated OrderedDict to store the whole tree.
|
||||||
for f in paths:
|
for f in paths:
|
||||||
split = f.split(os.path.sep)[1:]
|
split = f.split(os.path.sep)[1:]
|
||||||
# log("* ", split)
|
# LOGGER.debug("* %s", split)
|
||||||
|
|
||||||
last = ""
|
last = ""
|
||||||
parent = outline
|
parent = outline
|
||||||
|
@ -910,7 +906,7 @@ def addTextItems(mdl, odict, parent=None):
|
||||||
if type(odict[k]) == OrderedDict and "folder.txt" in odict[k]:
|
if type(odict[k]) == OrderedDict and "folder.txt" in odict[k]:
|
||||||
|
|
||||||
# Adds folder
|
# Adds folder
|
||||||
log("{}* Adds {} to {} (folder)".format(" " * parent.level(), k, parent.title()))
|
LOGGER.debug("{}* Adds {} to {} (folder)".format(" " * parent.level(), k, parent.title()))
|
||||||
item = outlineFromMMD(odict[k]["folder.txt"], parent=parent)
|
item = outlineFromMMD(odict[k]["folder.txt"], parent=parent)
|
||||||
item._lastPath = odict[k + ":lastPath"]
|
item._lastPath = odict[k + ":lastPath"]
|
||||||
|
|
||||||
|
@ -919,12 +915,12 @@ def addTextItems(mdl, odict, parent=None):
|
||||||
|
|
||||||
# k is not a folder
|
# k is not a folder
|
||||||
elif type(odict[k]) == str and k != "folder.txt" and not ":lastPath" in k:
|
elif type(odict[k]) == str and k != "folder.txt" and not ":lastPath" in k:
|
||||||
log("{}* Adds {} to {} (file)".format(" " * parent.level(), k, parent.title()))
|
LOGGER.debug("{}* Adds {} to {} (file)".format(" " * parent.level(), k, parent.title()))
|
||||||
item = outlineFromMMD(odict[k], parent=parent)
|
item = outlineFromMMD(odict[k], parent=parent)
|
||||||
item._lastPath = odict[k + ":lastPath"]
|
item._lastPath = odict[k + ":lastPath"]
|
||||||
|
|
||||||
elif not ":lastPath" in k and k != "folder.txt":
|
elif not ":lastPath" in k and k != "folder.txt":
|
||||||
print("* Strange things in file {}".format(k))
|
LOGGER.debug("Strange things in file %s".format(k))
|
||||||
|
|
||||||
|
|
||||||
def outlineFromMMD(text, parent):
|
def outlineFromMMD(text, parent):
|
||||||
|
@ -976,17 +972,19 @@ def appendRevisions(mdl, root):
|
||||||
# Get root's ID
|
# Get root's ID
|
||||||
ID = root.attrib["ID"]
|
ID = root.attrib["ID"]
|
||||||
if not ID:
|
if not ID:
|
||||||
log("* Serious problem: no ID!")
|
LOGGER.debug("* Serious problem: no ID!")
|
||||||
|
LOGGER.error("Revision has no ID associated!")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Find outline item in model
|
# Find outline item in model
|
||||||
item = mdl.getItemByID(ID)
|
item = mdl.getItemByID(ID)
|
||||||
if not item:
|
if not item:
|
||||||
log("* Error: no item whose ID is", ID)
|
LOGGER.debug("* Error: no item whose ID is %s", ID)
|
||||||
|
LOGGER.error("Could not identify the item matching the revision ID.")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Store revision
|
# Store revision
|
||||||
log("* Appends revision ({}) to {}".format(child.attrib["timestamp"], item.title()))
|
LOGGER.debug("* Appends revision ({}) to {}".format(child.attrib["timestamp"], item.title()))
|
||||||
item.appendRevision(child.attrib["timestamp"], child.attrib["text"])
|
item.appendRevision(child.attrib["timestamp"], child.attrib["text"])
|
||||||
|
|
||||||
|
|
||||||
|
@ -998,7 +996,7 @@ def getOutlineItem(item, enum):
|
||||||
@return: [QStandardItem]
|
@return: [QStandardItem]
|
||||||
"""
|
"""
|
||||||
row = getStandardItemRowFromXMLEnum(item, enum)
|
row = getStandardItemRowFromXMLEnum(item, enum)
|
||||||
log("* Add worldItem:", row[0].text())
|
LOGGER.debug("* Add worldItem: %s", row[0].text())
|
||||||
for child in item:
|
for child in item:
|
||||||
sub = getOutlineItem(child, enum)
|
sub = getOutlineItem(child, enum)
|
||||||
row[0].appendRow(sub)
|
row[0].appendRow(sub)
|
||||||
|
|
308
manuskript/logging.py
Normal file
308
manuskript/logging.py
Normal file
|
@ -0,0 +1,308 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# While all logging should be done through the facilities offered by the
|
||||||
|
# standard python `logging` module, this module will take care of specific
|
||||||
|
# manuskript needs to keep it separate from the rest of the logic.
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
import pathlib
|
||||||
|
|
||||||
|
from manuskript.functions import writablePath
|
||||||
|
from importlib import import_module
|
||||||
|
from pprint import pformat
|
||||||
|
|
||||||
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
LOGFORMAT_CONSOLE = "%(levelname)s> %(message)s"
|
||||||
|
LOGFORMAT_FILE = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
||||||
|
|
||||||
|
def setUp(console_level=logging.WARN):
|
||||||
|
"""Sets up a convenient environment for logging.
|
||||||
|
|
||||||
|
To console: >WARNING, plain. (Only the essence.)"""
|
||||||
|
|
||||||
|
# The root_logger should merely trigger on warnings since it is the final
|
||||||
|
# stop after all categories we really care about didn't match.
|
||||||
|
root_logger = logging.getLogger()
|
||||||
|
root_logger.setLevel(logging.WARN)
|
||||||
|
# The manuskript_logger is what all of our own code will come by.
|
||||||
|
# Obviously, we care greatly about logging every single message.
|
||||||
|
manuskript_logger = logging.getLogger("manuskript")
|
||||||
|
manuskript_logger.setLevel(logging.DEBUG)
|
||||||
|
# The qt_logger sees all the Qt nonsense when it breaks.
|
||||||
|
# We don't really want to know... but we have to know.
|
||||||
|
qt_logger = logging.getLogger("qt")
|
||||||
|
qt_logger.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
# Send logs of WARNING+ to STDERR for higher visibility.
|
||||||
|
ch = logging.StreamHandler()
|
||||||
|
ch.setLevel(console_level)
|
||||||
|
ch.setFormatter(logging.Formatter(LOGFORMAT_CONSOLE))
|
||||||
|
root_logger.addHandler(ch)
|
||||||
|
|
||||||
|
# Any exceptions we did not account for need to be logged.
|
||||||
|
logFutureExceptions()
|
||||||
|
|
||||||
|
LOGGER.debug("Logging to STDERR.")
|
||||||
|
|
||||||
|
|
||||||
|
def logToFile(file_level=logging.DEBUG, logfile=None):
|
||||||
|
"""Sets up the FileHandler that logs to a file.
|
||||||
|
|
||||||
|
This is being done separately due to relying on QApplication being properly
|
||||||
|
configured; without it we cannot detect the proper location for the log file.
|
||||||
|
|
||||||
|
To log file: >DEBUG, timestamped. (All the details.)"""
|
||||||
|
|
||||||
|
if logfile is None:
|
||||||
|
logfile = os.path.join(writablePath(), "manuskript.log")
|
||||||
|
|
||||||
|
# Log with extreme prejudice; everything goes to the log file.
|
||||||
|
# Because Qt gave me a megabyte-sized logfile while testing, it
|
||||||
|
# makes sense that the default behaviour of appending to existing
|
||||||
|
# log files may not be in our users best interest for the time
|
||||||
|
# being. (Unfortunately.)
|
||||||
|
try:
|
||||||
|
fh = logging.FileHandler(logfile, mode='w', encoding='utf-8')
|
||||||
|
fh.setLevel(file_level)
|
||||||
|
fh.setFormatter(logging.Formatter(LOGFORMAT_FILE))
|
||||||
|
|
||||||
|
root_logger = logging.getLogger()
|
||||||
|
root_logger.addHandler(fh)
|
||||||
|
|
||||||
|
# Use INFO level to make it easier to find for users.
|
||||||
|
LOGGER.info("Logging to file: %s", logfile)
|
||||||
|
except Exception as ex:
|
||||||
|
LOGGER.warning("Cannot log to file '%s'. Reason: %s", logfile, ex)
|
||||||
|
|
||||||
|
# Log uncaught and unraisable exceptions.
|
||||||
|
|
||||||
|
# Uncaught exceptions trigger moments before a thread is terminated due to
|
||||||
|
# an uncaught exception. It is the final stop, and as such is very likely
|
||||||
|
# to be the reason Manuskript suddenly closed on the user without warning.
|
||||||
|
# (It can also happen on other threads, but it is a bad thing regardless!)
|
||||||
|
def handle_uncaught_exception(exc_type, exc_value, exc_traceback):
|
||||||
|
# Allow Ctrl+C for script execution to keep functioning as-is.
|
||||||
|
if issubclass(exc_type, KeyboardInterrupt):
|
||||||
|
sys.__excepthook__(exc_type, exc_value, exc_traceback)
|
||||||
|
return # default exception hook handled it
|
||||||
|
|
||||||
|
# Anything that reaches this handler can be considered a deal-breaker.
|
||||||
|
LOGGER.critical("An unhandled exception has occurred!", exc_info=(exc_type, exc_value, exc_traceback))
|
||||||
|
|
||||||
|
# Exit the program to preserve PyQt 'functionality' that is broken by
|
||||||
|
# having our own uncaught exception hook. For more information, see:
|
||||||
|
# https://stackoverflow.com/questions/49065371/why-does-sys-excepthook-behave-differently-when-wrapped
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Note that without it, unhandled Python exceptions thrown while in the
|
||||||
|
# bowels of Qt may be written to the log multiple times. Under the motto
|
||||||
|
# of failing fast and not having a misleading log file, this appears to
|
||||||
|
# be the best course of action.
|
||||||
|
|
||||||
|
|
||||||
|
# The situation with threads and uncaught exceptions is fraught in peril.
|
||||||
|
# Hopefully this solves our problems on more recent versions of Python.
|
||||||
|
def handle_uncaught_thread_exception(args):
|
||||||
|
if issubclass(exc_type, SystemExit):
|
||||||
|
return # match behaviour of default hook, see manual
|
||||||
|
|
||||||
|
# Anything that reaches this handler can be considered a minor deal-breaker.
|
||||||
|
LOGGER.error("An unhandled exception has occurred in a thread: %s", repr(args.thread),
|
||||||
|
exc_info=(args.exc_type, args.exc_value, args.exc_traceback))
|
||||||
|
|
||||||
|
|
||||||
|
# Unraisable exceptions are exceptions that failed to be raised to a caller
|
||||||
|
# due to the nature of the exception. Examples: __del__(), GC error, etc.
|
||||||
|
# Logging these may expose bugs / errors that would otherwise go unnoticed.
|
||||||
|
def handle_unraisable_exception(unraisable):
|
||||||
|
# Log as warning because the application is likely to limp along with
|
||||||
|
# no serious side effects; a resource leak is the most likely.
|
||||||
|
LOGGER.warning("%s: %s", unraisable.err_msg or "Exception ignored in", repr(unraisable.object),
|
||||||
|
exc_info=(unraisable.exc_type, unraisable.exc_value, unraisable.exc_traceback))
|
||||||
|
|
||||||
|
|
||||||
|
# Because we are already somewhat careful in regards to the order of code
|
||||||
|
# execution when it comes to setting up the logging environment, this has
|
||||||
|
# been put in its own function as opposed to letting a direct import handle it.
|
||||||
|
def logFutureExceptions():
|
||||||
|
"""Log all the interesting exceptions that may happen in the future."""
|
||||||
|
sys.excepthook = handle_uncaught_exception
|
||||||
|
try:
|
||||||
|
import threading # threading module was optional pre-3.7
|
||||||
|
if hasattr(threading, "excepthook"): # Python 3.8+
|
||||||
|
threading.excepthook = handle_uncaught_thread_exception
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
if hasattr(sys, "unraisablehook"): # Python 3.8+
|
||||||
|
sys.unraisablehook = handle_unraisable_exception
|
||||||
|
|
||||||
|
|
||||||
|
# Qt has its own logging facility that we would like to integrate into our own.
|
||||||
|
# See: http://thispageintentionally.blogspot.com/2014/03/trapping-qt-log-messages.html
|
||||||
|
|
||||||
|
from PyQt5.QtCore import qInstallMessageHandler, QLibraryInfo, QMessageLogContext
|
||||||
|
from PyQt5.Qt import QtMsgType
|
||||||
|
|
||||||
|
def qtMessageHandler(msg_type, msg_log_context, msg_string):
|
||||||
|
"""Forwards Qt messages to Python logging system."""
|
||||||
|
# Convert Qt msg type to logging level
|
||||||
|
log_level = [logging.DEBUG,
|
||||||
|
logging.WARNING,
|
||||||
|
logging.ERROR,
|
||||||
|
logging.FATAL] [ int(msg_type) ]
|
||||||
|
qtcl = logging.getLogger(msg_log_context.category or "qt.???")
|
||||||
|
# Some information may not be available unless using a PyQt debug build.
|
||||||
|
# See: https://www.riverbankcomputing.com/static/Docs/PyQt5/api/qtcore/qmessagelogcontext.html
|
||||||
|
if QLibraryInfo.isDebugBuild():
|
||||||
|
qtcl.log(logging.DEBUG,
|
||||||
|
' @ {0} : {1}'.format((msg_log_context.file or "<unknown source file>"), msg_log_context.line)
|
||||||
|
)
|
||||||
|
qtcl.log(logging.DEBUG,
|
||||||
|
' ! {0}'.format((msg_log_context.function or "<unknown function>"))
|
||||||
|
)
|
||||||
|
qtcl.log(log_level, msg_string)
|
||||||
|
|
||||||
|
def integrateQtLogging():
|
||||||
|
"""Integrates Qt logging facilities to be a part of our own."""
|
||||||
|
|
||||||
|
# Note: the qtlogger is initialized in setUp() because it fits in
|
||||||
|
# nicely with the initialization of the other loggers over there.
|
||||||
|
# I also feel a lot safer this way. Qt is a curse that just keeps
|
||||||
|
# on giving, even when it isn't actually at fault. I hate you, Qt.
|
||||||
|
|
||||||
|
qInstallMessageHandler(qtMessageHandler)
|
||||||
|
|
||||||
|
|
||||||
|
def versionTupleToString(t):
|
||||||
|
"""A bit of generic tuple conversion code that hopefully handles all the
|
||||||
|
different sorts of tuples we may come across while logging versions.
|
||||||
|
|
||||||
|
None -> "N/A"
|
||||||
|
(,) -> "N/A"
|
||||||
|
(2, 4, 6) -> "2.4.6"
|
||||||
|
(2, 4, "alpha", 8) -> "2.4-alpha.8"
|
||||||
|
"""
|
||||||
|
|
||||||
|
s = []
|
||||||
|
if t is None or len(t) == 0:
|
||||||
|
return "N/A"
|
||||||
|
else:
|
||||||
|
s.append(str(t[0]))
|
||||||
|
|
||||||
|
def version_chunk(v):
|
||||||
|
if isinstance(v, str):
|
||||||
|
return "-", str(v)
|
||||||
|
else:
|
||||||
|
return ".", str(v)
|
||||||
|
|
||||||
|
s.extend(f for p in t[1:] for f in version_chunk(p))
|
||||||
|
return "".join(s)
|
||||||
|
|
||||||
|
def attributesFromOptionalModule(module, *attributes):
|
||||||
|
"""It is nice to cut down on the try-except boilerplate by
|
||||||
|
putting this logic into its own function.
|
||||||
|
|
||||||
|
Returns as many values as there are attributes.
|
||||||
|
A value will be None if it failed to get the attribute."""
|
||||||
|
|
||||||
|
assert(len(attributes) != 0)
|
||||||
|
v = []
|
||||||
|
try:
|
||||||
|
m = import_module(module)
|
||||||
|
|
||||||
|
for a in attributes:
|
||||||
|
v.append(getattr(m, a, None))
|
||||||
|
except ImportError:
|
||||||
|
v.extend(None for _ in range(len(attributes)))
|
||||||
|
|
||||||
|
if len(v) == 1:
|
||||||
|
# Return the value directly so we can use it in an expression.
|
||||||
|
return v[0]
|
||||||
|
else:
|
||||||
|
# The list is consumed as a part of the unpacking syntax.
|
||||||
|
return v
|
||||||
|
|
||||||
|
def logRuntimeInformation(logger=None):
|
||||||
|
"""Logs all important runtime information neatly together.
|
||||||
|
|
||||||
|
Due to the generic nature, use the manuskript logger by default."""
|
||||||
|
|
||||||
|
if not logger:
|
||||||
|
logger = logging.getLogger("manuskript")
|
||||||
|
|
||||||
|
vt2s = versionTupleToString
|
||||||
|
afom = attributesFromOptionalModule
|
||||||
|
|
||||||
|
# Basic system information.
|
||||||
|
from platform import python_version, platform, processor, machine
|
||||||
|
logger.info("Operating System: %s", platform())
|
||||||
|
logger.info("Hardware: %s / %s", machine(), processor())
|
||||||
|
|
||||||
|
# Information about the running instance. See:
|
||||||
|
# https://pyinstaller.readthedocs.io/en/v3.3.1/runtime-information.html
|
||||||
|
# http://www.py2exe.org/index.cgi/Py2exeEnvironment
|
||||||
|
# https://cx-freeze.readthedocs.io/en/latest/faq.html#data-files
|
||||||
|
frozen = getattr(sys, 'frozen', False)
|
||||||
|
if frozen:
|
||||||
|
logger.info("Running in a frozen (packaged) state.")
|
||||||
|
logger.debug("* sys.frozen = %s", pformat(frozen))
|
||||||
|
|
||||||
|
# PyInstaller, py2exe and cx_Freeze modules are not accessible while frozen,
|
||||||
|
# so logging their version is (to my knowledge) impossible without including
|
||||||
|
# special steps into the distribution process. But some traces do exist...
|
||||||
|
logger.debug("* sys._MEIPASS = %s", getattr(sys, '_MEIPASS', "N/A")) # PyInstaller bundle
|
||||||
|
# cx_Freeze and py2exe do not appear to leave anything similar exposed.
|
||||||
|
else:
|
||||||
|
logger.info("Running from unpackaged source code.")
|
||||||
|
|
||||||
|
# File not found? These bits of information might help.
|
||||||
|
logger.debug("* sys.executable = %s", pformat(sys.executable))
|
||||||
|
logger.debug("* sys.argv = %s", pformat(sys.argv))
|
||||||
|
logger.debug("* sys.path = %s", pformat(sys.path))
|
||||||
|
logger.debug("* sys.prefix = %s", pformat(sys.prefix))
|
||||||
|
|
||||||
|
# Manuskript and Python info.
|
||||||
|
from manuskript.functions import getGitRevisionAsString, getManuskriptPath
|
||||||
|
from manuskript.version import getVersion
|
||||||
|
logger.info("Manuskript %s%s (Python %s)", getVersion(),
|
||||||
|
getGitRevisionAsString(getManuskriptPath(), short=True),
|
||||||
|
python_version())
|
||||||
|
|
||||||
|
# Installed Python packages.
|
||||||
|
|
||||||
|
# PyQt + Qt
|
||||||
|
from PyQt5.Qt import PYQT_VERSION_STR, qVersion
|
||||||
|
from PyQt5.QtCore import QT_VERSION_STR
|
||||||
|
logger.info("* PyQt %s (compiled against Qt %s)", PYQT_VERSION_STR, QT_VERSION_STR)
|
||||||
|
logger.info(" * Qt %s (runtime)", qVersion())
|
||||||
|
|
||||||
|
# Lxml
|
||||||
|
# See: https://lxml.de/FAQ.html#i-think-i-have-found-a-bug-in-lxml-what-should-i-do
|
||||||
|
from lxml import etree
|
||||||
|
logger.info("* lxml.etree %s", vt2s(etree.LXML_VERSION))
|
||||||
|
logger.info(" * libxml %s (compiled: %s)", vt2s(etree.LIBXML_VERSION), vt2s(etree.LIBXML_COMPILED_VERSION))
|
||||||
|
logger.info(" * libxslt %s (compiled: %s)", vt2s(etree.LIBXSLT_VERSION), vt2s(etree.LIBXSLT_COMPILED_VERSION))
|
||||||
|
|
||||||
|
# Spellcheckers. (Optional)
|
||||||
|
enchant_mod_ver, enchant_lib_ver = afom("enchant", "__version__", "get_enchant_version")
|
||||||
|
if enchant_lib_ver:
|
||||||
|
enchant_lib_ver = enchant_lib_ver()
|
||||||
|
if isinstance(enchant_lib_ver, bytes): # PyEnchant version < 3.0.2
|
||||||
|
enchant_lib_ver = enchant_lib_ver.decode('utf-8')
|
||||||
|
logger.info("* pyEnchant %s (libenchant: %s)", enchant_mod_ver or "N/A", enchant_lib_ver or "N/A")
|
||||||
|
|
||||||
|
logger.info("* pySpellChecker %s", afom("spellchecker", "__version__") or "N/A")
|
||||||
|
logger.info("* Symspellpy %s", afom("symspellpy", "__version__") or "N/A")
|
||||||
|
|
||||||
|
# Markdown. (Optional)
|
||||||
|
logger.info("* Markdown %s", afom("markdown", "__version__") or "N/A")
|
||||||
|
|
||||||
|
# Web rendering engine
|
||||||
|
from manuskript.ui.views.webView import webEngine
|
||||||
|
logger.info("Web rendering engine: %s", webEngine)
|
||||||
|
|
||||||
|
# Do not collect version information for Pandoc; that would require
|
||||||
|
# executing `pandov -v` and parsing the output, all of which is too slow.
|
|
@ -6,7 +6,7 @@ import platform
|
||||||
import sys
|
import sys
|
||||||
import signal
|
import signal
|
||||||
|
|
||||||
import manuskript.ui.views.webView
|
import manuskript.logging
|
||||||
from PyQt5.QtCore import QLocale, QTranslator, QSettings, Qt
|
from PyQt5.QtCore import QLocale, QTranslator, QSettings, Qt
|
||||||
from PyQt5.QtGui import QIcon, QColor, QPalette
|
from PyQt5.QtGui import QIcon, QColor, QPalette
|
||||||
from PyQt5.QtWidgets import QApplication, qApp, QStyleFactory
|
from PyQt5.QtWidgets import QApplication, qApp, QStyleFactory
|
||||||
|
@ -16,15 +16,26 @@ from manuskript.version import getVersion
|
||||||
|
|
||||||
faulthandler.enable()
|
faulthandler.enable()
|
||||||
|
|
||||||
|
import logging
|
||||||
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
def prepare(tests=False):
|
def prepare(arguments, tests=False):
|
||||||
app = QApplication(sys.argv)
|
app = QApplication(sys.argv)
|
||||||
app.setOrganizationName("manuskript" + ("_tests" if tests else ""))
|
app.setOrganizationName("manuskript" + ("_tests" if tests else ""))
|
||||||
app.setOrganizationDomain("www.theologeek.ch")
|
app.setOrganizationDomain("www.theologeek.ch")
|
||||||
app.setApplicationName("manuskript" + ("_tests" if tests else ""))
|
app.setApplicationName("manuskript" + ("_tests" if tests else ""))
|
||||||
app.setApplicationVersion(getVersion())
|
app.setApplicationVersion(getVersion())
|
||||||
|
|
||||||
print("Running manuskript version {}.".format(getVersion()))
|
# Beginning logging to a file. This cannot be done earlier due to the
|
||||||
|
# default location of the log file being dependent on QApplication.
|
||||||
|
manuskript.logging.logToFile(logfile=arguments.logfile)
|
||||||
|
|
||||||
|
# Handle all sorts of Qt logging messages in Python.
|
||||||
|
manuskript.logging.integrateQtLogging()
|
||||||
|
|
||||||
|
# Log all the versions for less headaches.
|
||||||
|
manuskript.logging.logRuntimeInformation()
|
||||||
|
|
||||||
icon = QIcon()
|
icon = QIcon()
|
||||||
for i in [16, 32, 64, 128, 256, 512]:
|
for i in [16, 32, 64, 128, 256, 512]:
|
||||||
icon.addFile(appPath("icons/Manuskript/icon-{}px.png".format(i)))
|
icon.addFile(appPath("icons/Manuskript/icon-{}px.png".format(i)))
|
||||||
|
@ -47,7 +58,7 @@ def prepare(tests=False):
|
||||||
"""Tries to load and activate a given translation for use."""
|
"""Tries to load and activate a given translation for use."""
|
||||||
if appTranslator.load(translation, appPath("i18n")):
|
if appTranslator.load(translation, appPath("i18n")):
|
||||||
app.installTranslator(appTranslator)
|
app.installTranslator(appTranslator)
|
||||||
print("Loaded translation: {}".format(translation))
|
LOGGER.info("Loaded translation: {}".format(translation))
|
||||||
# Note: QTranslator.load() does some fancy heuristics where it simplifies
|
# Note: QTranslator.load() does some fancy heuristics where it simplifies
|
||||||
# the given locale until it is 'close enough' if the given filename does
|
# the given locale until it is 'close enough' if the given filename does
|
||||||
# not work out. For example, if given 'i18n/manuskript_en_US.qm', it tries:
|
# not work out. For example, if given 'i18n/manuskript_en_US.qm', it tries:
|
||||||
|
@ -62,7 +73,7 @@ def prepare(tests=False):
|
||||||
# filenames when you observe strange behaviour with the loaded translations.
|
# filenames when you observe strange behaviour with the loaded translations.
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
print("No translation found or loaded. ({})".format(translation))
|
LOGGER.info("No translation found or loaded. ({})".format(translation))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def activateTranslation(translation, source):
|
def activateTranslation(translation, source):
|
||||||
|
@ -85,7 +96,7 @@ def prepare(tests=False):
|
||||||
break
|
break
|
||||||
|
|
||||||
if using_builtin_translation:
|
if using_builtin_translation:
|
||||||
print("Using the builtin translation.")
|
LOGGER.info("Using the builtin translation. (U.S. English)")
|
||||||
|
|
||||||
# Load application translation
|
# Load application translation
|
||||||
translation = ""
|
translation = ""
|
||||||
|
@ -99,7 +110,7 @@ def prepare(tests=False):
|
||||||
translation = QLocale().uiLanguages()
|
translation = QLocale().uiLanguages()
|
||||||
source = "available ui languages"
|
source = "available ui languages"
|
||||||
|
|
||||||
print("Preferred translation: {} (based on {})".format(("builtin" if translation == "" else translation), source))
|
LOGGER.info("Preferred translation: {} (based on {})".format(("builtin" if translation == "" else translation), source))
|
||||||
activateTranslation(translation, source)
|
activateTranslation(translation, source)
|
||||||
|
|
||||||
def respectSystemDarkThemeSetting():
|
def respectSystemDarkThemeSetting():
|
||||||
|
@ -164,15 +175,16 @@ def prepare(tests=False):
|
||||||
MW._defaultCursorFlashTime = qApp.cursorFlashTime()
|
MW._defaultCursorFlashTime = qApp.cursorFlashTime()
|
||||||
|
|
||||||
# Command line project
|
# Command line project
|
||||||
if len(sys.argv) > 1 and sys.argv[1][-4:] == ".msk":
|
#if len(sys.argv) > 1 and sys.argv[1][-4:] == ".msk":
|
||||||
|
if arguments.filename is not None and arguments.filename[-4:] == ".msk":
|
||||||
|
#TODO: integrate better with argparsing.
|
||||||
if os.path.exists(sys.argv[1]):
|
if os.path.exists(sys.argv[1]):
|
||||||
path = os.path.abspath(sys.argv[1])
|
path = os.path.abspath(sys.argv[1])
|
||||||
MW._autoLoadProject = path
|
MW._autoLoadProject = path
|
||||||
|
|
||||||
return app, MW
|
return app, MW
|
||||||
|
|
||||||
|
def launch(arguments, app, MW = None):
|
||||||
def launch(app, MW=None):
|
|
||||||
if MW == None:
|
if MW == None:
|
||||||
from manuskript.functions import mainWindow
|
from manuskript.functions import mainWindow
|
||||||
MW = mainWindow()
|
MW = mainWindow()
|
||||||
|
@ -184,7 +196,7 @@ def launch(app, MW=None):
|
||||||
# Code reference :
|
# Code reference :
|
||||||
# https://github.com/ipython/ipykernel/blob/master/examples/embedding/ipkernel_qtapp.py
|
# https://github.com/ipython/ipykernel/blob/master/examples/embedding/ipkernel_qtapp.py
|
||||||
# https://github.com/ipython/ipykernel/blob/master/examples/embedding/internal_ipkernel.py
|
# https://github.com/ipython/ipykernel/blob/master/examples/embedding/internal_ipkernel.py
|
||||||
if len(sys.argv) > 1 and sys.argv[-1] == "--console":
|
if arguments.console:
|
||||||
try:
|
try:
|
||||||
from IPython.lib.kernel import connect_qtconsole
|
from IPython.lib.kernel import connect_qtconsole
|
||||||
from ipykernel.kernelapp import IPKernelApp
|
from ipykernel.kernelapp import IPKernelApp
|
||||||
|
@ -230,6 +242,8 @@ def launch(app, MW=None):
|
||||||
|
|
||||||
def sigint_handler(sig, MW):
|
def sigint_handler(sig, MW):
|
||||||
def handler(*args):
|
def handler(*args):
|
||||||
|
# Log before winding down to preserve order of cause and effect.
|
||||||
|
LOGGER.info(f'{sig} received. Quitting...')
|
||||||
MW.close()
|
MW.close()
|
||||||
print(f'{sig} received, quit.')
|
print(f'{sig} received, quit.')
|
||||||
|
|
||||||
|
@ -241,18 +255,44 @@ def setup_signal_handlers(MW):
|
||||||
signal.signal(signal.SIGTERM, sigint_handler("SIGTERM", MW))
|
signal.signal(signal.SIGTERM, sigint_handler("SIGTERM", MW))
|
||||||
|
|
||||||
|
|
||||||
|
def process_commandline(argv):
|
||||||
|
import argparse
|
||||||
|
parser = argparse.ArgumentParser(description="Run the manuskript application.")
|
||||||
|
parser.add_argument("--console", help="open the IPython Jupyter QT Console as a debugging aid",
|
||||||
|
action="store_true")
|
||||||
|
parser.add_argument("-v", "--verbose", action="count", default=1, help="lower the threshold for messages logged to the terminal")
|
||||||
|
parser.add_argument("-L", "--logfile", default=None, help="override the default log file location")
|
||||||
|
parser.add_argument("filename", nargs="?", metavar="FILENAME", help="the manuskript project (.msk) to open")
|
||||||
|
|
||||||
|
args = parser.parse_args(args=argv)
|
||||||
|
|
||||||
|
# Verbosity logic, see: https://gist.github.com/ms5/9f6df9c42a5f5435be0e
|
||||||
|
#args.verbose = 70 - (10*args.verbose) if args.verbose > 0 else 0
|
||||||
|
|
||||||
|
# Users cannot report what they do not notice: show CRITICAL, ERROR and WARNING always.
|
||||||
|
# Note that the default is set to 1, so account for that.
|
||||||
|
args.verbose = 40 - (10*args.verbose) if args.verbose > 0 else 0
|
||||||
|
|
||||||
|
return args
|
||||||
|
|
||||||
|
|
||||||
def run():
|
def run():
|
||||||
"""
|
"""
|
||||||
Run separates prepare and launch for two reasons:
|
Run separates prepare and launch for two reasons:
|
||||||
1. I've read somewhere it helps with potential segfault (see comment below)
|
1. I've read somewhere it helps with potential segfault (see comment below)
|
||||||
2. So that prepare can be used in tests, without running the whole thing
|
2. So that prepare can be used in tests, without running the whole thing
|
||||||
"""
|
"""
|
||||||
|
# Parse command-line arguments.
|
||||||
|
arguments = process_commandline(sys.argv)
|
||||||
|
# Initialize logging. (Does not include Qt integration yet.)
|
||||||
|
manuskript.logging.setUp(console_level=arguments.verbose)
|
||||||
|
|
||||||
# Need to return and keep `app` otherwise it gets deleted.
|
# Need to return and keep `app` otherwise it gets deleted.
|
||||||
app, MW = prepare()
|
app, MW = prepare(arguments)
|
||||||
setup_signal_handlers(MW)
|
setup_signal_handlers(MW)
|
||||||
# Separating launch to avoid segfault, so it seem.
|
# Separating launch to avoid segfault, so it seem.
|
||||||
# Cf. http://stackoverflow.com/questions/12433491/is-this-pyqt-4-python-bug-or-wrongly-behaving-code
|
# Cf. http://stackoverflow.com/questions/12433491/is-this-pyqt-4-python-bug-or-wrongly-behaving-code
|
||||||
launch(app, MW)
|
launch(arguments, app, MW)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
@ -38,6 +38,9 @@ from manuskript.ui.statusLabel import statusLabel
|
||||||
from manuskript.ui.views.textEditView import textEditView
|
from manuskript.ui.views.textEditView import textEditView
|
||||||
from manuskript.functions import Spellchecker
|
from manuskript.functions import Spellchecker
|
||||||
|
|
||||||
|
import logging
|
||||||
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
class MainWindow(QMainWindow, Ui_MainWindow):
|
class MainWindow(QMainWindow, Ui_MainWindow):
|
||||||
# dictChanged = pyqtSignal(str)
|
# dictChanged = pyqtSignal(str)
|
||||||
|
|
||||||
|
@ -584,7 +587,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
||||||
If ``loadFromFile`` is False, then it does not load datas from file.
|
If ``loadFromFile`` is False, then it does not load datas from file.
|
||||||
It assumes that the datas have been populated in a different way."""
|
It assumes that the datas have been populated in a different way."""
|
||||||
if loadFromFile and not os.path.exists(project):
|
if loadFromFile and not os.path.exists(project):
|
||||||
print(self.tr("The file {} does not exist. Has it been moved or deleted?").format(project))
|
LOGGER.warning("The file {} does not exist. Has it been moved or deleted?".format(project))
|
||||||
F.statusMessage(
|
F.statusMessage(
|
||||||
self.tr("The file {} does not exist. Has it been moved or deleted?").format(project), importance=3)
|
self.tr("The file {} does not exist. Has it been moved or deleted?").format(project), importance=3)
|
||||||
return
|
return
|
||||||
|
@ -639,7 +642,6 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
||||||
self.mdlCharacter.dataChanged.connect(self.startTimerNoChanges)
|
self.mdlCharacter.dataChanged.connect(self.startTimerNoChanges)
|
||||||
self.mdlPlots.dataChanged.connect(self.startTimerNoChanges)
|
self.mdlPlots.dataChanged.connect(self.startTimerNoChanges)
|
||||||
self.mdlWorld.dataChanged.connect(self.startTimerNoChanges)
|
self.mdlWorld.dataChanged.connect(self.startTimerNoChanges)
|
||||||
# self.mdlPersosInfos.dataChanged.connect(self.startTimerNoChanges)
|
|
||||||
self.mdlStatus.dataChanged.connect(self.startTimerNoChanges)
|
self.mdlStatus.dataChanged.connect(self.startTimerNoChanges)
|
||||||
self.mdlLabels.dataChanged.connect(self.startTimerNoChanges)
|
self.mdlLabels.dataChanged.connect(self.startTimerNoChanges)
|
||||||
|
|
||||||
|
@ -665,9 +667,6 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
||||||
# Add project name to Window's name
|
# Add project name to Window's name
|
||||||
self.setWindowTitle(self.projectName() + " - " + self.tr("Manuskript"))
|
self.setWindowTitle(self.projectName() + " - " + self.tr("Manuskript"))
|
||||||
|
|
||||||
# Stuff
|
|
||||||
# self.checkPersosID() # Shouldn't be necessary any longer
|
|
||||||
|
|
||||||
# Show main Window
|
# Show main Window
|
||||||
self.switchToProject()
|
self.switchToProject()
|
||||||
|
|
||||||
|
@ -854,7 +853,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
||||||
# No UI feedback here as this code path indicates a race condition that happens
|
# No UI feedback here as this code path indicates a race condition that happens
|
||||||
# after the user has already closed the project through some way. But in that
|
# after the user has already closed the project through some way. But in that
|
||||||
# scenario, this code should not be reachable to begin with.
|
# scenario, this code should not be reachable to begin with.
|
||||||
print("Bug: there is no current project to save.")
|
LOGGER.error("There is no current project to save.")
|
||||||
return
|
return
|
||||||
|
|
||||||
r = loadSave.saveProject() # version=0
|
r = loadSave.saveProject() # version=0
|
||||||
|
@ -865,18 +864,15 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
||||||
|
|
||||||
feedback = self.tr("Project {} saved.").format(projectName)
|
feedback = self.tr("Project {} saved.").format(projectName)
|
||||||
F.statusMessage(feedback, importance=0)
|
F.statusMessage(feedback, importance=0)
|
||||||
|
LOGGER.info("Project {} saved.".format(projectName))
|
||||||
else:
|
else:
|
||||||
feedback = self.tr("WARNING: Project {} not saved.").format(projectName)
|
feedback = self.tr("WARNING: Project {} not saved.").format(projectName)
|
||||||
F.statusMessage(feedback, importance=3)
|
F.statusMessage(feedback, importance=3)
|
||||||
|
LOGGER.warning("Project {} not saved.".format(projectName))
|
||||||
# Giving some feedback in console
|
|
||||||
print(feedback)
|
|
||||||
|
|
||||||
def loadEmptyDatas(self):
|
def loadEmptyDatas(self):
|
||||||
self.mdlFlatData = QStandardItemModel(self)
|
self.mdlFlatData = QStandardItemModel(self)
|
||||||
self.mdlCharacter = characterModel(self)
|
self.mdlCharacter = characterModel(self)
|
||||||
# self.mdlPersosProxy = persosProxyModel(self)
|
|
||||||
# self.mdlPersosInfos = QStandardItemModel(self)
|
|
||||||
self.mdlLabels = QStandardItemModel(self)
|
self.mdlLabels = QStandardItemModel(self)
|
||||||
self.mdlStatus = QStandardItemModel(self)
|
self.mdlStatus = QStandardItemModel(self)
|
||||||
self.mdlPlots = plotModel(self)
|
self.mdlPlots = plotModel(self)
|
||||||
|
@ -889,13 +885,13 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
||||||
|
|
||||||
# Giving some feedback
|
# Giving some feedback
|
||||||
if not errors:
|
if not errors:
|
||||||
print(self.tr("Project {} loaded.").format(project))
|
LOGGER.info("Project {} loaded.".format(project))
|
||||||
F.statusMessage(
|
F.statusMessage(
|
||||||
self.tr("Project {} loaded.").format(project), 2000)
|
self.tr("Project {} loaded.").format(project), 2000)
|
||||||
else:
|
else:
|
||||||
print(self.tr("Project {} loaded with some errors:").format(project))
|
LOGGER.error("Project {} loaded with some errors:".format(project))
|
||||||
for e in errors:
|
for e in errors:
|
||||||
print(self.tr(" * {} wasn't found in project file.").format(e))
|
LOGGER.error(" * {} wasn't found in project file.".format(e))
|
||||||
F.statusMessage(
|
F.statusMessage(
|
||||||
self.tr("Project {} loaded with some errors.").format(project), 5000, importance = 3)
|
self.tr("Project {} loaded with some errors.").format(project), 5000, importance = 3)
|
||||||
|
|
||||||
|
@ -1531,7 +1527,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
||||||
self.menuView.addMenu(self.menuMode)
|
self.menuView.addMenu(self.menuMode)
|
||||||
self.menuView.addSeparator()
|
self.menuView.addSeparator()
|
||||||
|
|
||||||
# print("Generating menus with", settings.viewSettings)
|
# LOGGER.debug("Generating menus with %s.", settings.viewSettings)
|
||||||
|
|
||||||
for mnu, mnud, icon in menus:
|
for mnu, mnud, icon in menus:
|
||||||
m = QMenu(mnu, self.menuView)
|
m = QMenu(mnu, self.menuView)
|
||||||
|
@ -1625,7 +1621,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
||||||
warning2 = self.tr("PyQt {} and Qt {} are in use.").format(qVersion(), PYQT_VERSION_STR)
|
warning2 = self.tr("PyQt {} and Qt {} are in use.").format(qVersion(), PYQT_VERSION_STR)
|
||||||
|
|
||||||
# Don't translate for debug log.
|
# Don't translate for debug log.
|
||||||
print("WARNING:", warning1, warning2)
|
LOGGER.warning(warning1)
|
||||||
|
LOGGER.warning(warning2)
|
||||||
|
|
||||||
msg = QMessageBox(QMessageBox.Warning,
|
msg = QMessageBox(QMessageBox.Warning,
|
||||||
self.tr("Proceed with import at your own risk"),
|
self.tr("Proceed with import at your own risk"),
|
||||||
|
|
|
@ -13,6 +13,8 @@ import re
|
||||||
|
|
||||||
from manuskript import enums
|
from manuskript import enums
|
||||||
|
|
||||||
|
import logging
|
||||||
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
class abstractItem():
|
class abstractItem():
|
||||||
|
|
||||||
|
@ -215,7 +217,7 @@ class abstractItem():
|
||||||
self.IDs = self.listAllIDs()
|
self.IDs = self.listAllIDs()
|
||||||
|
|
||||||
if max([self.IDs.count(i) for i in self.IDs if i]) != 1:
|
if max([self.IDs.count(i) for i in self.IDs if i]) != 1:
|
||||||
print("WARNING ! There are some items with same IDs:", [i for i in self.IDs if i and self.IDs.count(i) != 1])
|
LOGGER.warning("There are some items with overlapping IDs: %s", [i for i in self.IDs if i and self.IDs.count(i) != 1])
|
||||||
|
|
||||||
def checkChildren(item):
|
def checkChildren(item):
|
||||||
for c in item.children():
|
for c in item.children():
|
||||||
|
|
|
@ -26,6 +26,8 @@ except:
|
||||||
pass
|
pass
|
||||||
import time, os
|
import time, os
|
||||||
|
|
||||||
|
import logging
|
||||||
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
class abstractModel(QAbstractItemModel):
|
class abstractModel(QAbstractItemModel):
|
||||||
"""
|
"""
|
||||||
|
@ -83,7 +85,7 @@ class abstractModel(QAbstractItemModel):
|
||||||
if len(parent.children()) == 0:
|
if len(parent.children()) == 0:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# print(item.title(), [i.title() for i in parent.children()])
|
#LOGGER.debug("%s: %s", item.title(), [i.title() for i in parent.children()])
|
||||||
|
|
||||||
row = parent.children().index(item)
|
row = parent.children().index(item)
|
||||||
col = column
|
col = column
|
||||||
|
@ -177,7 +179,7 @@ class abstractModel(QAbstractItemModel):
|
||||||
|
|
||||||
# self.dataChanged.emit(index.sibling(index.row(), 0),
|
# self.dataChanged.emit(index.sibling(index.row(), 0),
|
||||||
# index.sibling(index.row(), max([i.value for i in Outline])))
|
# index.sibling(index.row(), max([i.value for i in Outline])))
|
||||||
# print("Model emit", index.row(), index.column())
|
# LOGGER.debug("Model dataChanged emit: %s, %s", index.row(), index.column())
|
||||||
self.dataChanged.emit(index, index)
|
self.dataChanged.emit(index, index)
|
||||||
|
|
||||||
if index.column() == Outline.type:
|
if index.column() == Outline.type:
|
||||||
|
|
|
@ -23,6 +23,8 @@ except:
|
||||||
# number formatting
|
# number formatting
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
import logging
|
||||||
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
class outlineItem(abstractItem, searchableItem):
|
class outlineItem(abstractItem, searchableItem):
|
||||||
|
|
||||||
|
@ -382,7 +384,7 @@ class outlineItem(abstractItem, searchableItem):
|
||||||
searchIn = character.name()
|
searchIn = character.name()
|
||||||
else:
|
else:
|
||||||
searchIn = ""
|
searchIn = ""
|
||||||
print("Character POV not found:", self.POV())
|
LOGGER.error("Character POV not found: %s", self.POV())
|
||||||
|
|
||||||
elif c == self.enum.status:
|
elif c == self.enum.status:
|
||||||
searchIn = mainWindow.mdlStatus.item(F.toInt(self.status()), 0).text()
|
searchIn = mainWindow.mdlStatus.item(F.toInt(self.status()), 0).text()
|
||||||
|
|
|
@ -3,6 +3,9 @@
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
import logging
|
||||||
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
# SHORT REFERENCES
|
# SHORT REFERENCES
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
@ -627,7 +630,7 @@ def open(ref):
|
||||||
mw.lstCharacters.setCurrentItem(item)
|
mw.lstCharacters.setCurrentItem(item)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
print("Error: Ref {} not found".format(ref))
|
LOGGER.error("Character reference {} not found.".format(ref))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
elif _type == TextLetter:
|
elif _type == TextLetter:
|
||||||
|
@ -639,7 +642,7 @@ def open(ref):
|
||||||
mw.mainEditor.setCurrentModelIndex(index, newTab=True)
|
mw.mainEditor.setCurrentModelIndex(index, newTab=True)
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
print("Ref not found")
|
LOGGER.error("Text reference {} not found.".format(ref))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
elif _type == PlotLetter:
|
elif _type == PlotLetter:
|
||||||
|
@ -651,7 +654,7 @@ def open(ref):
|
||||||
mw.lstPlots.setCurrentItem(item)
|
mw.lstPlots.setCurrentItem(item)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
print("Ref not found")
|
LOGGER.error("Plot reference {} not found.".format(ref))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
elif _type == WorldLetter:
|
elif _type == WorldLetter:
|
||||||
|
@ -664,8 +667,8 @@ def open(ref):
|
||||||
mw.mdlWorld.indexFromItem(item))
|
mw.mdlWorld.indexFromItem(item))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
print("Ref not found")
|
LOGGER.error("World reference {} not found.".format(ref))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
print("Ref not implemented")
|
LOGGER.error("Unable to identify reference type: {}.".format(ref))
|
||||||
return False
|
return False
|
||||||
|
|
|
@ -8,6 +8,9 @@ from PyQt5.QtWidgets import qApp
|
||||||
|
|
||||||
from manuskript.enums import Outline
|
from manuskript.enums import Outline
|
||||||
|
|
||||||
|
import logging
|
||||||
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
# TODO: move some/all of those settings to application settings and not project settings
|
# TODO: move some/all of those settings to application settings and not project settings
|
||||||
# in order to allow a shared project between several writers
|
# in order to allow a shared project between several writers
|
||||||
|
|
||||||
|
@ -187,7 +190,7 @@ def load(string, fromString=False, protocol=None):
|
||||||
allSettings = pickle.load(f)
|
allSettings = pickle.load(f)
|
||||||
|
|
||||||
except:
|
except:
|
||||||
print("{} doesn't exist, cannot load settings.".format(string))
|
LOGGER.error("Cannot load settings, {} does not exist.".format(string))
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
if protocol == 0:
|
if protocol == 0:
|
||||||
|
|
|
@ -13,7 +13,8 @@ QApplication([])
|
||||||
|
|
||||||
# Create app and mainWindow
|
# Create app and mainWindow
|
||||||
from manuskript import main
|
from manuskript import main
|
||||||
app, MW = main.prepare(tests=True)
|
arguments = main.process_commandline([])
|
||||||
|
app, MW = main.prepare(arguments, tests=True)
|
||||||
|
|
||||||
# FIXME: Again, don't know why, but when closing a project and then reopening
|
# FIXME: Again, don't know why, but when closing a project and then reopening
|
||||||
# one, we get a `TypeError: connection is not unique` in MainWindow:
|
# one, we get a `TypeError: connection is not unique` in MainWindow:
|
||||||
|
|
|
@ -6,6 +6,8 @@ from PyQt5.QtWidgets import QSizePolicy, QGroupBox, QWidget, QStylePainter, QSty
|
||||||
QStyle, QStyleOptionFrame, QStyleOptionFocusRect
|
QStyle, QStyleOptionFrame, QStyleOptionFocusRect
|
||||||
from manuskript.ui import style as S
|
from manuskript.ui import style as S
|
||||||
|
|
||||||
|
import logging
|
||||||
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
class collapsibleGroupBox(QGroupBox):
|
class collapsibleGroupBox(QGroupBox):
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
|
@ -25,7 +27,7 @@ class collapsibleGroupBox(QGroupBox):
|
||||||
self.tempWidget.setLayout(self.layout())
|
self.tempWidget.setLayout(self.layout())
|
||||||
# Set empty layout
|
# Set empty layout
|
||||||
l = QVBoxLayout()
|
l = QVBoxLayout()
|
||||||
# print(l.contentsMargins().left(), l.contentsMargins().bottom(), l.contentsMargins().top(), )
|
# LOGGER.debug("Bounds: %s, %s, %s, %s", l.contentsMargins().left(), l.contentsMargins().bottom(), l.contentsMargins().top(), l.contentsMargins().right())
|
||||||
l.setContentsMargins(0, 0, 0, 0)
|
l.setContentsMargins(0, 0, 0, 0)
|
||||||
self.setLayout(l)
|
self.setLayout(l)
|
||||||
self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Maximum)
|
self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Maximum)
|
||||||
|
|
|
@ -6,6 +6,8 @@ import re
|
||||||
from PyQt5.QtCore import QRegExp
|
from PyQt5.QtCore import QRegExp
|
||||||
from PyQt5.QtGui import QTextCursor
|
from PyQt5.QtGui import QTextCursor
|
||||||
|
|
||||||
|
import logging
|
||||||
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
def MDFormatSelection(editor, style):
|
def MDFormatSelection(editor, style):
|
||||||
"""
|
"""
|
||||||
|
@ -15,5 +17,5 @@ def MDFormatSelection(editor, style):
|
||||||
1: italic
|
1: italic
|
||||||
2: code
|
2: code
|
||||||
"""
|
"""
|
||||||
print("Formatting:", style, " (Unimplemented yet !)")
|
LOGGER.error("Formatting: %s (Not implemented!)", style)
|
||||||
# FIXME
|
# FIXME
|
|
@ -19,6 +19,8 @@ from manuskript.ui.editors.themes import loadThemeDatas
|
||||||
from manuskript.ui.views.MDEditView import MDEditView
|
from manuskript.ui.views.MDEditView import MDEditView
|
||||||
from manuskript.functions import Spellchecker
|
from manuskript.functions import Spellchecker
|
||||||
|
|
||||||
|
import logging
|
||||||
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
class fullScreenEditor(QWidget):
|
class fullScreenEditor(QWidget):
|
||||||
def __init__(self, index, parent=None, screenNumber=None):
|
def __init__(self, index, parent=None, screenNumber=None):
|
||||||
|
@ -177,7 +179,7 @@ class fullScreenEditor(QWidget):
|
||||||
# self.show()
|
# self.show()
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
# print("Leaving fullScreenEditor via Destructor event", flush=True)
|
LOGGER.debug("Leaving fullScreenEditor via Destructor event.")
|
||||||
self.showNormal()
|
self.showNormal()
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
|
@ -286,7 +288,7 @@ class fullScreenEditor(QWidget):
|
||||||
def keyPressEvent(self, event):
|
def keyPressEvent(self, event):
|
||||||
if event.key() in [Qt.Key_Escape, Qt.Key_F11] and \
|
if event.key() in [Qt.Key_Escape, Qt.Key_F11] and \
|
||||||
not self._locked:
|
not self._locked:
|
||||||
# print("Leaving fullScreenEditor via keyPressEvent", flush=True)
|
LOGGER.debug("Leaving fullScreenEditor via keyPressEvent.")
|
||||||
self.showNormal()
|
self.showNormal()
|
||||||
self.close()
|
self.close()
|
||||||
elif (event.modifiers() & Qt.AltModifier) and \
|
elif (event.modifiers() & Qt.AltModifier) and \
|
||||||
|
|
|
@ -20,6 +20,9 @@ try:
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
import logging
|
||||||
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
class mainEditor(QWidget, Ui_mainEditor):
|
class mainEditor(QWidget, Ui_mainEditor):
|
||||||
"""
|
"""
|
||||||
`mainEditor` is responsible for opening `outlineItem`s and offering information
|
`mainEditor` is responsible for opening `outlineItem`s and offering information
|
||||||
|
@ -389,7 +392,6 @@ class mainEditor(QWidget, Ui_mainEditor):
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
def setDict(self, dict):
|
def setDict(self, dict):
|
||||||
print(dict)
|
|
||||||
for w in self.allAllTabs():
|
for w in self.allAllTabs():
|
||||||
w.setDict(dict)
|
w.setDict(dict)
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,8 @@ from manuskript.functions import mainWindow, appPath
|
||||||
from manuskript.ui import style
|
from manuskript.ui import style
|
||||||
from manuskript.ui.editors.tabSplitter_ui import Ui_tabSplitter
|
from manuskript.ui.editors.tabSplitter_ui import Ui_tabSplitter
|
||||||
|
|
||||||
|
import logging
|
||||||
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
class tabSplitter(QWidget, Ui_tabSplitter):
|
class tabSplitter(QWidget, Ui_tabSplitter):
|
||||||
"""
|
"""
|
||||||
|
@ -39,7 +41,7 @@ class tabSplitter(QWidget, Ui_tabSplitter):
|
||||||
# try:
|
# try:
|
||||||
# self.tab.setTabBarAutoHide(True)
|
# self.tab.setTabBarAutoHide(True)
|
||||||
# except AttributeError:
|
# except AttributeError:
|
||||||
# print("Info: install Qt 5.4 or higher to use tab bar auto-hide in editor.")
|
# LOGGER.info("Install Qt 5.4 or higher to use tab bar auto-hide in editor.")
|
||||||
|
|
||||||
# Button to split
|
# Button to split
|
||||||
self.btnSplit = QPushButton(self)
|
self.btnSplit = QPushButton(self)
|
||||||
|
|
|
@ -12,6 +12,8 @@ import manuskript.ui.style as S
|
||||||
from manuskript import settings
|
from manuskript import settings
|
||||||
from manuskript import functions as F
|
from manuskript import functions as F
|
||||||
|
|
||||||
|
import logging
|
||||||
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
class BasicHighlighter(QSyntaxHighlighter):
|
class BasicHighlighter(QSyntaxHighlighter):
|
||||||
def __init__(self, editor):
|
def __init__(self, editor):
|
||||||
|
@ -130,14 +132,14 @@ class BasicHighlighter(QSyntaxHighlighter):
|
||||||
before you do any custom highlighting. Or implement doHighlightBlock.
|
before you do any custom highlighting. Or implement doHighlightBlock.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
#print(">", self.currentBlock().document().availableUndoSteps())
|
#LOGGER.debug("undoSteps before: %s", self.currentBlock().document().availableUndoSteps())
|
||||||
c = QTextCursor(self.currentBlock())
|
c = QTextCursor(self.currentBlock())
|
||||||
#c.joinPreviousEditBlock()
|
#c.joinPreviousEditBlock()
|
||||||
bf = QTextBlockFormat(self._defaultBlockFormat)
|
bf = QTextBlockFormat(self._defaultBlockFormat)
|
||||||
if bf != c.blockFormat():
|
if bf != c.blockFormat():
|
||||||
c.setBlockFormat(bf)
|
c.setBlockFormat(bf)
|
||||||
#c.endEditBlock()
|
#c.endEditBlock()
|
||||||
#print(" ", self.currentBlock().document().availableUndoSteps())
|
#LOGGER.debug("undoSteps after: %s", self.currentBlock().document().availableUndoSteps())
|
||||||
|
|
||||||
# self.setFormat(0, len(text), self._defaultCharFormat)
|
# self.setFormat(0, len(text), self._defaultCharFormat)
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,9 @@ from PyQt5.QtWidgets import *
|
||||||
from manuskript.ui.highlighters import MarkdownState as MS
|
from manuskript.ui.highlighters import MarkdownState as MS
|
||||||
from manuskript.ui.highlighters import MarkdownTokenType as MTT
|
from manuskript.ui.highlighters import MarkdownTokenType as MTT
|
||||||
|
|
||||||
|
import logging
|
||||||
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
# This file is simply a python translation of GhostWriter's Tokenizer.
|
# This file is simply a python translation of GhostWriter's Tokenizer.
|
||||||
# http://wereturtle.github.io/ghostwriter/
|
# http://wereturtle.github.io/ghostwriter/
|
||||||
# GPLV3+.
|
# GPLV3+.
|
||||||
|
@ -56,7 +59,7 @@ class HighlightTokenizer:
|
||||||
self.tokens.append(token)
|
self.tokens.append(token)
|
||||||
|
|
||||||
if token.type == -1:
|
if token.type == -1:
|
||||||
print("Error here", token.position, token.length)
|
LOGGER.error("Token type invalid: position %s, length %s.", token.position, token.length)
|
||||||
|
|
||||||
def setState(self, state):
|
def setState(self, state):
|
||||||
self.state = state
|
self.state = state
|
||||||
|
|
|
@ -14,6 +14,8 @@ from manuskript.ui.highlighters.markdownEnums import MarkdownState as MS
|
||||||
from manuskript.ui.highlighters.markdownTokenizer import MarkdownTokenizer as MT
|
from manuskript.ui.highlighters.markdownTokenizer import MarkdownTokenizer as MT
|
||||||
from manuskript import functions as F
|
from manuskript import functions as F
|
||||||
|
|
||||||
|
import logging
|
||||||
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
class MDEditView(textEditView):
|
class MDEditView(textEditView):
|
||||||
|
|
||||||
|
@ -660,10 +662,10 @@ class ImageTooltip:
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
# Somehow we lost track. Log what we can to hopefully figure it out.
|
# Somehow we lost track. Log what we can to hopefully figure it out.
|
||||||
print("Warning: unable to match fetched data for tooltip to original request.")
|
LOGGER.warning("Unable to match fetched data for tooltip to original request.")
|
||||||
print("- Completed request:", url_key)
|
LOGGER.warning("- Completed request: %s", url_key)
|
||||||
print("- Status upon finishing:", reply.error(), reply.errorString())
|
LOGGER.warning("- Status upon finishing: %s, %s", reply.error(), reply.errorString())
|
||||||
print("- Currently processing:", ImageTooltip.processing)
|
LOGGER.warning("- Currently processing: %s", ImageTooltip.processing)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Update cache with retrieved data.
|
# Update cache with retrieved data.
|
||||||
|
|
|
@ -13,7 +13,6 @@ class dndView(QAbstractItemView):
|
||||||
|
|
||||||
def dragMoveEvent(self, event):
|
def dragMoveEvent(self, event):
|
||||||
# return QAbstractItemView.dragMoveEvent(self, event)
|
# return QAbstractItemView.dragMoveEvent(self, event)
|
||||||
# print(a)
|
|
||||||
if event.keyboardModifiers() & Qt.ControlModifier:
|
if event.keyboardModifiers() & Qt.ControlModifier:
|
||||||
event.setDropAction(Qt.CopyAction)
|
event.setDropAction(Qt.CopyAction)
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -7,6 +7,8 @@ from manuskript.enums import Outline
|
||||||
from manuskript.ui.views.propertiesView_ui import Ui_propertiesView
|
from manuskript.ui.views.propertiesView_ui import Ui_propertiesView
|
||||||
from manuskript.models.characterPOVModel import characterPOVModel
|
from manuskript.models.characterPOVModel import characterPOVModel
|
||||||
|
|
||||||
|
import logging
|
||||||
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
class propertiesView(QWidget, Ui_propertiesView):
|
class propertiesView(QWidget, Ui_propertiesView):
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
|
@ -39,7 +41,7 @@ class propertiesView(QWidget, Ui_propertiesView):
|
||||||
def selectionChanged(self, sourceView):
|
def selectionChanged(self, sourceView):
|
||||||
|
|
||||||
indexes = self.getIndexes(sourceView)
|
indexes = self.getIndexes(sourceView)
|
||||||
# print(indexes)
|
# LOGGER.debug("selectionChanged indexes: %s", indexes)
|
||||||
if len(indexes) == 0:
|
if len(indexes) == 0:
|
||||||
self.setEnabled(False)
|
self.setEnabled(False)
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,9 @@ from manuskript.functions import Spellchecker
|
||||||
from manuskript.models.characterModel import Character, CharacterInfo
|
from manuskript.models.characterModel import Character, CharacterInfo
|
||||||
|
|
||||||
|
|
||||||
|
import logging
|
||||||
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
class textEditView(QTextEdit):
|
class textEditView(QTextEdit):
|
||||||
def __init__(self, parent=None, index=None, html=None, spellcheck=None,
|
def __init__(self, parent=None, index=None, html=None, spellcheck=None,
|
||||||
highlighting=False, dict="", autoResize=False):
|
highlighting=False, dict="", autoResize=False):
|
||||||
|
@ -58,13 +61,13 @@ class textEditView(QTextEdit):
|
||||||
self.updateTimer.setInterval(500)
|
self.updateTimer.setInterval(500)
|
||||||
self.updateTimer.setSingleShot(True)
|
self.updateTimer.setSingleShot(True)
|
||||||
self.updateTimer.timeout.connect(self.submit)
|
self.updateTimer.timeout.connect(self.submit)
|
||||||
# self.updateTimer.timeout.connect(lambda: print("Timeout"))
|
# self.updateTimer.timeout.connect(lambda: LOGGER.debug("Timeout."))
|
||||||
|
|
||||||
self.updateTimer.stop()
|
self.updateTimer.stop()
|
||||||
self.document().contentsChanged.connect(self.updateTimer.start, F.AUC)
|
self.document().contentsChanged.connect(self.updateTimer.start, F.AUC)
|
||||||
# self.document().contentsChanged.connect(lambda: print("Document changed"))
|
# self.document().contentsChanged.connect(lambda: LOGGER.debug("Document changed."))
|
||||||
|
|
||||||
# self.document().contentsChanged.connect(lambda: print(self.objectName(), "Contents changed"))
|
# self.document().contentsChanged.connect(lambda: LOGGER.debug("Contents changed: %s", self.objectName()))
|
||||||
|
|
||||||
self.setEnabled(False)
|
self.setEnabled(False)
|
||||||
|
|
||||||
|
@ -248,7 +251,7 @@ class textEditView(QTextEdit):
|
||||||
if topLeft.parent() != self._index.parent():
|
if topLeft.parent() != self._index.parent():
|
||||||
return
|
return
|
||||||
|
|
||||||
# print("Model changed: ({}:{}), ({}:{}/{}), ({}:{}) for {} of {}".format(
|
# LOGGER.debug("Model changed: ({}:{}), ({}:{}/{}), ({}:{}) for {} of {}".format(
|
||||||
# topLeft.row(), topLeft.column(),
|
# topLeft.row(), topLeft.column(),
|
||||||
# self._index.row(), self._index.row(), self._column,
|
# self._index.row(), self._index.row(), self._column,
|
||||||
# bottomRight.row(), bottomRight.column(),
|
# bottomRight.row(), bottomRight.column(),
|
||||||
|
@ -278,11 +281,11 @@ class textEditView(QTextEdit):
|
||||||
def updateText(self):
|
def updateText(self):
|
||||||
self._updating.lock()
|
self._updating.lock()
|
||||||
|
|
||||||
# print("Updating", self.objectName())
|
# LOGGER.debug("Updating %s", self.objectName())
|
||||||
if self._index:
|
if self._index:
|
||||||
self.disconnectDocument()
|
self.disconnectDocument()
|
||||||
if self.toPlainText() != F.toString(self._index.data()):
|
if self.toPlainText() != F.toString(self._index.data()):
|
||||||
# print(" Updating plaintext")
|
# LOGGER.debug(" Updating plaintext")
|
||||||
self.document().setPlainText(F.toString(self._index.data()))
|
self.document().setPlainText(F.toString(self._index.data()))
|
||||||
self.reconnectDocument()
|
self.reconnectDocument()
|
||||||
|
|
||||||
|
@ -319,18 +322,18 @@ class textEditView(QTextEdit):
|
||||||
text = self.toPlainText()
|
text = self.toPlainText()
|
||||||
self._updating.unlock()
|
self._updating.unlock()
|
||||||
|
|
||||||
# print("Submitting", self.objectName())
|
# LOGGER.debug("Submitting %s", self.objectName())
|
||||||
if self._index and self._index.isValid():
|
if self._index and self._index.isValid():
|
||||||
# item = self._index.internalPointer()
|
# item = self._index.internalPointer()
|
||||||
if text != self._index.data():
|
if text != self._index.data():
|
||||||
# print(" Submitting plain text")
|
# LOGGER.debug(" Submitting plain text")
|
||||||
self._model.setData(QModelIndex(self._index), text)
|
self._model.setData(QModelIndex(self._index), text)
|
||||||
|
|
||||||
elif self._indexes:
|
elif self._indexes:
|
||||||
for i in self._indexes:
|
for i in self._indexes:
|
||||||
item = i.internalPointer()
|
item = i.internalPointer()
|
||||||
if text != F.toString(item.data(self._column)):
|
if text != F.toString(item.data(self._column)):
|
||||||
print("Submitting many indexes")
|
LOGGER.debug("Submitting many indexes")
|
||||||
self._model.setData(i, text)
|
self._model.setData(i, text)
|
||||||
|
|
||||||
def keyPressEvent(self, event):
|
def keyPressEvent(self, event):
|
||||||
|
@ -484,7 +487,7 @@ class textEditView(QTextEdit):
|
||||||
|
|
||||||
def newCharacter(self):
|
def newCharacter(self):
|
||||||
text = self.sender().data()
|
text = self.sender().data()
|
||||||
print("new character!", text)
|
LOGGER.debug(f'New character: {text}')
|
||||||
# switch to character page
|
# switch to character page
|
||||||
mw = F.mainWindow()
|
mw = F.mainWindow()
|
||||||
mw.tabMain.setCurrentIndex(mw.TabPersos)
|
mw.tabMain.setCurrentIndex(mw.TabPersos)
|
||||||
|
@ -496,7 +499,7 @@ class textEditView(QTextEdit):
|
||||||
|
|
||||||
def newPlotItem(self):
|
def newPlotItem(self):
|
||||||
text = self.sender().data()
|
text = self.sender().data()
|
||||||
print("new plot item!", text)
|
LOGGER.debug(f'New plot item: {text}')
|
||||||
# switch to plot page
|
# switch to plot page
|
||||||
mw = F.mainWindow()
|
mw = F.mainWindow()
|
||||||
mw.tabMain.setCurrentIndex(mw.TabPlots)
|
mw.tabMain.setCurrentIndex(mw.TabPlots)
|
||||||
|
@ -509,7 +512,7 @@ class textEditView(QTextEdit):
|
||||||
|
|
||||||
def newWorldItem(self):
|
def newWorldItem(self):
|
||||||
text = self.sender().data()
|
text = self.sender().data()
|
||||||
print("new world item!", text)
|
LOGGER.debug(f'New world item: {text}')
|
||||||
mw = F.mainWindow()
|
mw = F.mainWindow()
|
||||||
mw.tabMain.setCurrentIndex(mw.TabWorld)
|
mw.tabMain.setCurrentIndex(mw.TabWorld)
|
||||||
item = mw.mdlWorld.addItem(title=text)
|
item = mw.mdlWorld.addItem(title=text)
|
||||||
|
|
|
@ -22,16 +22,13 @@ else:
|
||||||
|
|
||||||
if features['qtwebkit']:
|
if features['qtwebkit']:
|
||||||
from PyQt5.QtWebKitWidgets import QWebView
|
from PyQt5.QtWebKitWidgets import QWebView
|
||||||
print("Debug: Web rendering engine used: QWebView")
|
|
||||||
webEngine = "QtWebKit"
|
webEngine = "QtWebKit"
|
||||||
webView = QWebView
|
webView = QWebView
|
||||||
elif features['qtwebengine']:
|
elif features['qtwebengine']:
|
||||||
from PyQt5 import QtWebEngineWidgets
|
from PyQt5 import QtWebEngineWidgets
|
||||||
print("Debug: Web rendering engine used: QWebEngineView")
|
|
||||||
webEngine = "QtWebEngine"
|
webEngine = "QtWebEngine"
|
||||||
webView = QtWebEngineWidgets.QWebEngineView
|
webView = QtWebEngineWidgets.QWebEngineView
|
||||||
else:
|
else:
|
||||||
from PyQt5.QtWidgets import QTextEdit
|
from PyQt5.QtWidgets import QTextEdit
|
||||||
print("Debug: Web rendering engine used: QTextEdit")
|
|
||||||
webEngine = "QTextEdit"
|
webEngine = "QTextEdit"
|
||||||
webView = QTextEdit
|
webView = QTextEdit
|
||||||
|
|
|
@ -21,6 +21,9 @@ from manuskript.models.worldModel import worldModel
|
||||||
from manuskript.ui.welcome_ui import Ui_welcome
|
from manuskript.ui.welcome_ui import Ui_welcome
|
||||||
from manuskript.ui import style as S
|
from manuskript.ui import style as S
|
||||||
|
|
||||||
|
import logging
|
||||||
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
locale.setlocale(locale.LC_ALL, '')
|
locale.setlocale(locale.LC_ALL, '')
|
||||||
except:
|
except:
|
||||||
|
@ -57,8 +60,7 @@ class welcome(QWidget, Ui_welcome):
|
||||||
sttgs = QSettings()
|
sttgs = QSettings()
|
||||||
lastDirectory = sttgs.value("lastAccessedDirectory", defaultValue=".", type=str)
|
lastDirectory = sttgs.value("lastAccessedDirectory", defaultValue=".", type=str)
|
||||||
if lastDirectory != '.':
|
if lastDirectory != '.':
|
||||||
print(qApp.translate("lastAccessedDirectoryInfo", "Last accessed directory \"{}\" loaded.").format(
|
LOGGER.info("Last accessed directory \"{}\" loaded.".format(lastDirectory))
|
||||||
lastDirectory))
|
|
||||||
return lastDirectory
|
return lastDirectory
|
||||||
|
|
||||||
def setLastAccessedDirectory(self, dir):
|
def setLastAccessedDirectory(self, dir):
|
||||||
|
@ -422,10 +424,12 @@ class welcome(QWidget, Ui_welcome):
|
||||||
self.tree.expandAll()
|
self.tree.expandAll()
|
||||||
|
|
||||||
def loadDefaultDatas(self):
|
def loadDefaultDatas(self):
|
||||||
|
"""Initialize a basic Manuskript project."""
|
||||||
|
|
||||||
# Empty settings
|
# Empty settings
|
||||||
imp.reload(settings)
|
imp.reload(settings)
|
||||||
settings.initDefaultValues()
|
settings.initDefaultValues()
|
||||||
|
self.mw.loadEmptyDatas()
|
||||||
|
|
||||||
if self.template:
|
if self.template:
|
||||||
t = [i for i in self._templates if i[0] == self.template[0]]
|
t = [i for i in self._templates if i[0] == self.template[0]]
|
||||||
|
@ -433,20 +437,10 @@ class welcome(QWidget, Ui_welcome):
|
||||||
settings.viewMode = "simple"
|
settings.viewMode = "simple"
|
||||||
|
|
||||||
# Tasks
|
# Tasks
|
||||||
self.mw.mdlFlatData = QStandardItemModel(2, 8, self.mw)
|
self.mw.mdlFlatData.setRowCount(2) # data from: infos.txt, summary.txt
|
||||||
|
self.mw.mdlFlatData.setColumnCount(8) # version_1.py: len(infos.txt) == 8
|
||||||
# Persos
|
|
||||||
# self.mw.mdlPersos = QStandardItemModel(0, 0, self.mw)
|
|
||||||
self.mw.mdlCharacter = characterModel(self.mw)
|
|
||||||
# self.mdlPersosProxy = None # persosProxyModel() # None
|
|
||||||
# self.mw.mdlPersosProxy = persosProxyModel(self.mw)
|
|
||||||
|
|
||||||
# self.mw.mdlPersosInfos = QStandardItemModel(1, 0, self.mw)
|
|
||||||
# self.mw.mdlPersosInfos.insertColumn(0, [QStandardItem("ID")])
|
|
||||||
# self.mw.mdlPersosInfos.setHorizontalHeaderLabels(["Description"])
|
|
||||||
|
|
||||||
# Labels
|
# Labels
|
||||||
self.mw.mdlLabels = QStandardItemModel(self.mw)
|
|
||||||
for color, text in [
|
for color, text in [
|
||||||
(Qt.transparent, ""),
|
(Qt.transparent, ""),
|
||||||
(Qt.yellow, self.tr("Idea")),
|
(Qt.yellow, self.tr("Idea")),
|
||||||
|
@ -458,7 +452,6 @@ class welcome(QWidget, Ui_welcome):
|
||||||
self.mw.mdlLabels.appendRow(QStandardItem(iconFromColor(color), text))
|
self.mw.mdlLabels.appendRow(QStandardItem(iconFromColor(color), text))
|
||||||
|
|
||||||
# Status
|
# Status
|
||||||
self.mw.mdlStatus = QStandardItemModel(self.mw)
|
|
||||||
for text in [
|
for text in [
|
||||||
"",
|
"",
|
||||||
self.tr("TODO"),
|
self.tr("TODO"),
|
||||||
|
@ -468,14 +461,9 @@ class welcome(QWidget, Ui_welcome):
|
||||||
]:
|
]:
|
||||||
self.mw.mdlStatus.appendRow(QStandardItem(text))
|
self.mw.mdlStatus.appendRow(QStandardItem(text))
|
||||||
|
|
||||||
# Plot
|
# Plot (nothing special needed)
|
||||||
self.mw.mdlPlots = plotModel(self.mw)
|
|
||||||
|
|
||||||
# Outline
|
# Outline
|
||||||
self.mw.mdlOutline = outlineModel(self.mw)
|
|
||||||
|
|
||||||
# World
|
|
||||||
self.mw.mdlWorld = worldModel(self.mw)
|
|
||||||
|
|
||||||
root = self.mw.mdlOutline.rootItem
|
root = self.mw.mdlOutline.rootItem
|
||||||
_type = "md"
|
_type = "md"
|
||||||
|
@ -509,3 +497,5 @@ class welcome(QWidget, Ui_welcome):
|
||||||
|
|
||||||
if self.template and self.template[1]:
|
if self.template and self.template[1]:
|
||||||
addElement(root, self.template[1])
|
addElement(root, self.template[1])
|
||||||
|
|
||||||
|
# World (nothing special needed)
|
||||||
|
|
Loading…
Reference in a new issue