"))
+ )
+ 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.
diff --git a/manuskript/main.py b/manuskript/main.py
index 63bc9cf..c4d5bb6 100644
--- a/manuskript/main.py
+++ b/manuskript/main.py
@@ -4,8 +4,9 @@ import faulthandler
import os
import platform
import sys
+import signal
-import manuskript.ui.views.webView
+import manuskript.logging
from PyQt5.QtCore import QLocale, QTranslator, QSettings, Qt
from PyQt5.QtGui import QIcon, QColor, QPalette
from PyQt5.QtWidgets import QApplication, qApp, QStyleFactory
@@ -15,14 +16,26 @@ from manuskript.version import getVersion
faulthandler.enable()
-def prepare(tests=False):
+import logging
+LOGGER = logging.getLogger(__name__)
+
+def prepare(arguments, tests=False):
app = QApplication(sys.argv)
- app.setOrganizationName("manuskript"+("_tests" if tests else ""))
+ app.setOrganizationName("manuskript" + ("_tests" if tests else ""))
app.setOrganizationDomain("www.theologeek.ch")
- app.setApplicationName("manuskript"+("_tests" if tests else ""))
+ app.setApplicationName("manuskript" + ("_tests" if tests else ""))
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()
for i in [16, 32, 64, 128, 256, 512]:
icon.addFile(appPath("icons/Manuskript/icon-{}px.png".format(i)))
@@ -38,13 +51,14 @@ def prepare(tests=False):
# Translation process
appTranslator = QTranslator(app)
+
# By default: locale
def tryLoadTranslation(translation, source):
"""Tries to load and activate a given translation for use."""
if appTranslator.load(translation, appPath("i18n")):
app.installTranslator(appTranslator)
- print("Loaded translation: {}".format(translation))
+ LOGGER.info("Loaded translation: {}".format(translation))
# Note: QTranslator.load() does some fancy heuristics where it simplifies
# 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:
@@ -59,7 +73,7 @@ def prepare(tests=False):
# filenames when you observe strange behaviour with the loaded translations.
return True
else:
- print("No translation found or loaded. ({})".format(translation))
+ LOGGER.info("No translation found or loaded. ({})".format(translation))
return False
def activateTranslation(translation, source):
@@ -82,7 +96,7 @@ def prepare(tests=False):
break
if using_builtin_translation:
- print("Using the builtin translation.")
+ LOGGER.info("Using the builtin translation. (U.S. English)")
# Load application translation
translation = ""
@@ -96,24 +110,26 @@ def prepare(tests=False):
translation = QLocale().uiLanguages()
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)
def respectSystemDarkThemeSetting():
"""Adjusts the Qt theme to match the OS 'dark theme' setting configured by the user."""
- if platform.system() is not 'Windows':
+ if platform.system() != 'Windows':
return
# Basic Windows 10 Dark Theme support.
# Source: https://forum.qt.io/topic/101391/windows-10-dark-theme/4
- themeSettings = QSettings("HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", QSettings.NativeFormat)
+ themeSettings = QSettings(
+ "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
+ QSettings.NativeFormat)
if themeSettings.value("AppsUseLightTheme") == 0:
darkPalette = QPalette()
- darkColor = QColor(45,45,45)
- disabledColor = QColor(127,127,127)
+ darkColor = QColor(45, 45, 45)
+ disabledColor = QColor(127, 127, 127)
darkPalette.setColor(QPalette.Window, darkColor)
darkPalette.setColor(QPalette.WindowText, Qt.white)
- darkPalette.setColor(QPalette.Base, QColor(18,18,18))
+ darkPalette.setColor(QPalette.Base, QColor(18, 18, 18))
darkPalette.setColor(QPalette.AlternateBase, darkColor)
darkPalette.setColor(QPalette.ToolTipBase, Qt.white)
darkPalette.setColor(QPalette.ToolTipText, Qt.white)
@@ -137,7 +153,7 @@ def prepare(tests=False):
# This broke the Settings Dialog at one point... and then it stopped breaking it.
# TODO: Why'd it break? Check if tooltips look OK... and if not, make them look OK.
- #app.setStyleSheet("QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }")
+ # app.setStyleSheet("QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }")
respectSystemDarkThemeSetting()
@@ -159,16 +175,17 @@ def prepare(tests=False):
MW._defaultCursorFlashTime = qApp.cursorFlashTime()
# 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]):
path = os.path.abspath(sys.argv[1])
MW._autoLoadProject = path
return app, MW
-def launch(app, MW = None):
-
- if MW is None:
+def launch(arguments, app, MW = None):
+ if MW == None:
from manuskript.functions import mainWindow
MW = mainWindow()
@@ -176,10 +193,10 @@ def launch(app, MW = None):
# Support for IPython Jupyter QT Console as a debugging aid.
# Last argument must be --console to enable it
- # 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/internal_ipkernel.py
- if len(sys.argv) > 1 and sys.argv[-1] == "--console":
+ if arguments.console:
try:
from IPython.lib.kernel import connect_qtconsole
from ipykernel.kernelapp import IPKernelApp
@@ -188,7 +205,7 @@ def launch(app, MW = None):
# Create IPython kernel within our application
kernel = IPKernelApp.instance()
-
+
# Initialize it and use matplotlib for main event loop integration with QT
kernel.initialize(['python', '--matplotlib=qt'])
@@ -207,6 +224,7 @@ def launch(app, MW = None):
app.quit()
console.kill()
kernel.io_loop.stop()
+
app.lastWindowClosed.connect(console_cleanup)
# Very important, IPython-specific step: this gets GUI event loop
@@ -221,17 +239,61 @@ def launch(app, MW = None):
qApp.exec_()
qApp.deleteLater()
+
+def sigint_handler(sig, MW):
+ def handler(*args):
+ # Log before winding down to preserve order of cause and effect.
+ LOGGER.info(f'{sig} received. Quitting...')
+ MW.close()
+ print(f'{sig} received, quit.')
+
+ return handler
+
+
+def setup_signal_handlers(MW):
+ signal.signal(signal.SIGINT, sigint_handler("SIGINT", 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():
"""
Run separates prepare and launch for two reasons:
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
"""
+ # 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.
- app, MW = prepare()
+ app, MW = prepare(arguments)
+ setup_signal_handlers(MW)
# Separating launch to avoid segfault, so it seem.
# 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__":
run()
diff --git a/manuskript/mainWindow.py b/manuskript/mainWindow.py
index 5afb0fa..4778490 100644
--- a/manuskript/mainWindow.py
+++ b/manuskript/mainWindow.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python
# --!-- coding: utf8 --!--
-import imp
+import importlib
import os
import re
@@ -9,13 +9,14 @@ from PyQt5.QtCore import (pyqtSignal, QSignalMapper, QTimer, QSettings, Qt, QPoi
QRegExp, QUrl, QSize, QModelIndex)
from PyQt5.QtGui import QStandardItemModel, QIcon, QColor
from PyQt5.QtWidgets import QMainWindow, QHeaderView, qApp, QMenu, QActionGroup, QAction, QStyle, QListWidgetItem, \
- QLabel, QDockWidget, QWidget, QMessageBox
+ QLabel, QDockWidget, QWidget, QMessageBox, QLineEdit
from manuskript import settings
from manuskript.enums import Character, PlotStep, Plot, World, Outline
-from manuskript.functions import wordCount, appPath, findWidgetsOfClass
+from manuskript.functions import wordCount, appPath, findWidgetsOfClass, openURL, showInFolder
import manuskript.functions as F
from manuskript import loadSave
+from manuskript.logging import getLogFilePath
from manuskript.models.characterModel import characterModel
from manuskript.models import outlineModel
from manuskript.models.plotModel import plotModel
@@ -38,6 +39,9 @@ from manuskript.ui.statusLabel import statusLabel
from manuskript.ui.views.textEditView import textEditView
from manuskript.functions import Spellchecker
+import logging
+LOGGER = logging.getLogger(__name__)
+
class MainWindow(QMainWindow, Ui_MainWindow):
# dictChanged = pyqtSignal(str)
@@ -129,6 +133,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.actCopy.triggered.connect(self.documentsCopy)
self.actCut.triggered.connect(self.documentsCut)
self.actPaste.triggered.connect(self.documentsPaste)
+ self.actSearch.triggered.connect(self.doSearch)
self.actRename.triggered.connect(self.documentsRename)
self.actDuplicate.triggered.connect(self.documentsDuplicate)
self.actDelete.triggered.connect(self.documentsDelete)
@@ -175,6 +180,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# Main Menu:: Tool
self.actToolFrequency.triggered.connect(self.frequencyAnalyzer)
+ self.actSupport.triggered.connect(self.support)
+ self.actLocateLog.triggered.connect(self.locateLogFile)
self.actAbout.triggered.connect(self.about)
self.makeUIConnections()
@@ -270,7 +277,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.mainEditor
]
- while new is not None:
+ while new != None:
if new in targets:
self._lastFocus = new
break
@@ -346,6 +353,9 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# Slider importance
self.updateCharacterImportance(c.ID())
+ # POV state
+ self.updateCharacterPOVState(c.ID())
+
# Character Infos
self.tblPersoInfos.setRootIndex(index)
@@ -366,6 +376,22 @@ class MainWindow(QMainWindow, Ui_MainWindow):
c = self.mdlCharacter.getCharacterByID(ID)
self.sldPersoImportance.setValue(int(c.importance()))
+ def updateCharacterPOVState(self, ID):
+ c = self.mdlCharacter.getCharacterByID(ID)
+ self.disconnectAll(self.chkPersoPOV.stateChanged, self.lstCharacters.changeCharacterPOVState)
+
+ if c.pov():
+ self.chkPersoPOV.setCheckState(Qt.Checked)
+ else:
+ self.chkPersoPOV.setCheckState(Qt.Unchecked)
+
+ try:
+ self.chkPersoPOV.stateChanged.connect(self.lstCharacters.changeCharacterPOVState, F.AUC)
+ self.chkPersoPOV.setEnabled(len(self.mdlOutline.findItemsByPOV(ID)) == 0)
+ except TypeError:
+ #don't know what's up with this
+ pass
+
###############################################################################
# PLOTS
###############################################################################
@@ -480,6 +506,13 @@ class MainWindow(QMainWindow, Ui_MainWindow):
def documentsPaste(self):
"Paste clipboard item(s) into selected item."
if self._lastFocus: self._lastFocus.paste()
+ def doSearch(self):
+ "Do a global search."
+ self.dckSearch.show()
+ self.dckSearch.activateWindow()
+ searchTextInput = self.dckSearch.findChild(QLineEdit, 'searchTextInput')
+ searchTextInput.setFocus()
+ searchTextInput.selectAll()
def documentsRename(self):
"Rename selected item."
if self._lastFocus: self._lastFocus.rename()
@@ -557,14 +590,14 @@ class MainWindow(QMainWindow, Ui_MainWindow):
If ``loadFromFile`` is False, then it does not load datas from file.
It assumes that the datas have been populated in a different way."""
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(
self.tr("The file {} does not exist. Has it been moved or deleted?").format(project), importance=3)
return
if loadFromFile:
# Load empty settings
- imp.reload(settings)
+ importlib.reload(settings)
settings.initDefaultValues()
# Load data
@@ -612,7 +645,6 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.mdlCharacter.dataChanged.connect(self.startTimerNoChanges)
self.mdlPlots.dataChanged.connect(self.startTimerNoChanges)
self.mdlWorld.dataChanged.connect(self.startTimerNoChanges)
- # self.mdlPersosInfos.dataChanged.connect(self.startTimerNoChanges)
self.mdlStatus.dataChanged.connect(self.startTimerNoChanges)
self.mdlLabels.dataChanged.connect(self.startTimerNoChanges)
@@ -638,9 +670,6 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# Add project name to Window's name
self.setWindowTitle(self.projectName() + " - " + self.tr("Manuskript"))
- # Stuff
- # self.checkPersosID() # Shouldn't be necessary any longer
-
# Show main Window
self.switchToProject()
@@ -768,6 +797,10 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# Remembering the current items (stores outlineItem's ID)
settings.openIndexes = self.mainEditor.tabSplitter.openIndexes()
+ # Call close on the main window to clean children widgets
+ if self.mainEditor:
+ self.mainEditor.close()
+
# Save data from models
if settings.saveOnQuit:
self.saveDatas()
@@ -823,11 +856,11 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# risk a scenario where the timer somehow triggers a new save while saving.
self.saveTimerNoChanges.stop()
- if self.currentProject is None:
+ if self.currentProject == None:
# 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
# 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
r = loadSave.saveProject() # version=0
@@ -838,18 +871,15 @@ class MainWindow(QMainWindow, Ui_MainWindow):
feedback = self.tr("Project {} saved.").format(projectName)
F.statusMessage(feedback, importance=0)
+ LOGGER.info("Project {} saved.".format(projectName))
else:
feedback = self.tr("WARNING: Project {} not saved.").format(projectName)
F.statusMessage(feedback, importance=3)
-
- # Giving some feedback in console
- print(feedback)
+ LOGGER.warning("Project {} not saved.".format(projectName))
def loadEmptyDatas(self):
self.mdlFlatData = QStandardItemModel(self)
self.mdlCharacter = characterModel(self)
- # self.mdlPersosProxy = persosProxyModel(self)
- # self.mdlPersosInfos = QStandardItemModel(self)
self.mdlLabels = QStandardItemModel(self)
self.mdlStatus = QStandardItemModel(self)
self.mdlPlots = plotModel(self)
@@ -862,13 +892,13 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# Giving some feedback
if not errors:
- print(self.tr("Project {} loaded.").format(project))
+ LOGGER.info("Project {} loaded.".format(project))
F.statusMessage(
self.tr("Project {} loaded.").format(project), 2000)
else:
- print(self.tr("Project {} loaded with some errors:").format(project))
+ LOGGER.error("Project {} loaded with some errors:".format(project))
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(
self.tr("Project {} loaded with some errors.").format(project), 5000, importance = 3)
@@ -933,11 +963,13 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# Characters
self.lstCharacters.setCharactersModel(self.mdlCharacter)
self.tblPersoInfos.setModel(self.mdlCharacter)
-
- self.btnAddPerso.clicked.connect(self.mdlCharacter.addCharacter, F.AUC)
try:
+ self.btnAddPerso.clicked.connect(self.lstCharacters.addCharacter, F.AUC)
self.btnRmPerso.clicked.connect(self.lstCharacters.removeCharacter, F.AUC)
+
self.btnPersoColor.clicked.connect(self.lstCharacters.choseCharacterColor, F.AUC)
+ self.chkPersoPOV.stateChanged.connect(self.lstCharacters.changeCharacterPOVState, F.AUC)
+
self.btnPersoAddInfo.clicked.connect(self.lstCharacters.addCharacterInfo, F.AUC)
self.btnPersoRmInfo.clicked.connect(self.lstCharacters.removeCharacterInfo, F.AUC)
except TypeError:
@@ -1078,7 +1110,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# disconnect only removes one connection at a time.
while True:
try:
- if oldHandler is not None:
+ if oldHandler != None:
signal.disconnect(oldHandler)
else:
signal.disconnect()
@@ -1089,9 +1121,12 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# Break connections for UI elements that were connected in makeConnections()
# Characters
- self.disconnectAll(self.btnAddPerso.clicked, self.mdlCharacter.addCharacter)
+ self.disconnectAll(self.btnAddPerso.clicked, self.lstCharacters.addCharacter)
self.disconnectAll(self.btnRmPerso.clicked, self.lstCharacters.removeCharacter)
+
self.disconnectAll(self.btnPersoColor.clicked, self.lstCharacters.choseCharacterColor)
+ self.disconnectAll(self.chkPersoPOV.stateChanged, self.lstCharacters.changeCharacterPOVState)
+
self.disconnectAll(self.btnPersoAddInfo.clicked, self.lstCharacters.addCharacterInfo)
self.disconnectAll(self.btnPersoRmInfo.clicked, self.lstCharacters.removeCharacterInfo)
@@ -1147,6 +1182,50 @@ class MainWindow(QMainWindow, Ui_MainWindow):
r2 = self.geometry()
win.move(r2.center() - QPoint(r.width()/2, r.height()/2))
+ def support(self):
+ openURL("https://github.com/olivierkes/manuskript/wiki/Technical-Support")
+
+ def locateLogFile(self):
+ logfile = getLogFilePath()
+
+ # Make sure we are even logging to a file.
+ if not logfile:
+ QMessageBox(QMessageBox.Information,
+ self.tr("Sorry!"),
+ "" +
+ self.tr("This session is not being logged.") +
+ "
",
+ QMessageBox.Ok).exec()
+ return
+
+ # Remind user that log files are at their best once they are complete.
+ msg = QMessageBox(QMessageBox.Information,
+ self.tr("A log file is a Work in Progress!"),
+ "" +
+ self.tr("The log file \"{}\" will continue to be written to until Manuskript is closed.").format(os.path.basename(logfile)) +
+ "
" +
+ "" +
+ self.tr("It will now be displayed in your file manager, but is of limited use until you close Manuskript.") +
+ "
",
+ QMessageBox.Ok)
+
+ ret = msg.exec()
+
+ # Open the filemanager.
+ if ret == QMessageBox.Ok:
+ if not showInFolder(logfile):
+ # If everything convenient fails, at least make sure the user can browse to its location manually.
+ QMessageBox(QMessageBox.Critical,
+ self.tr("Error!"),
+ "" +
+ self.tr("An error was encountered while trying to show the log file below in your file manager.") +
+ "
" +
+ "" +
+ logfile +
+ "
",
+ QMessageBox.Ok).exec()
+
+
def about(self):
self.dialog = aboutDialog(mw=self)
self.dialog.setFixedSize(self.dialog.size())
@@ -1355,7 +1434,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
dictionaries = Spellchecker.availableDictionaries()
# Set first run dictionary
- if settings.dict is None:
+ if settings.dict == None:
settings.dict = Spellchecker.getDefaultDictionary()
# Check if project dict is unavailable on this machine
@@ -1499,7 +1578,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.menuView.addMenu(self.menuMode)
self.menuView.addSeparator()
- # print("Generating menus with", settings.viewSettings)
+ # LOGGER.debug("Generating menus with %s.", settings.viewSettings)
for mnu, mnud, icon in menus:
m = QMenu(mnu, self.menuView)
@@ -1566,7 +1645,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
w.cmbPOV.setVisible(val)
# POV in outline view
- if val is None and Outline.POV in settings.outlineViewColumns:
+ if val == None and Outline.POV in settings.outlineViewColumns:
settings.outlineViewColumns.remove(Outline.POV)
from manuskript.ui.views.outlineView import outlineView
@@ -1593,7 +1672,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
warning2 = self.tr("PyQt {} and Qt {} are in use.").format(qVersion(), PYQT_VERSION_STR)
# Don't translate for debug log.
- print("WARNING:", warning1, warning2)
+ LOGGER.warning(warning1)
+ LOGGER.warning(warning2)
msg = QMessageBox(QMessageBox.Warning,
self.tr("Proceed with import at your own risk"),
diff --git a/manuskript/models/abstractItem.py b/manuskript/models/abstractItem.py
index f3fa049..fb38691 100644
--- a/manuskript/models/abstractItem.py
+++ b/manuskript/models/abstractItem.py
@@ -13,6 +13,8 @@ import re
from manuskript import enums
+import logging
+LOGGER = logging.getLogger(__name__)
class abstractItem():
@@ -39,14 +41,17 @@ class abstractItem():
self._data[self.enum.title] = title
self._data[self.enum.type] = _type
- if xml is not None:
+ if xml != None:
self.setFromXML(xml)
+ if parent:
+ # add this as a child to the parent, and link to the outlineModel of the parent
+ parent.appendChild(self)
+
if ID:
self._data[self.enum.ID] = ID
- if parent:
- parent.appendChild(self)
+
#######################################################################
# Model
@@ -54,6 +59,11 @@ class abstractItem():
def setModel(self, model):
self._model = model
+ if not self.ID():
+ self.getUniqueID()
+ elif model:
+ # if we are setting a model update it's ID
+ self._model.updateAvailableIDs(self.ID())
for c in self.children():
c.setModel(model)
@@ -135,8 +145,6 @@ class abstractItem():
self.childItems.insert(row, child)
child._parent = self
child.setModel(self._model)
- if not child.ID():
- child.getUniqueID()
def removeChild(self, row):
"""
@@ -195,7 +203,7 @@ class abstractItem():
###############################################################################
def getUniqueID(self, recursive=False):
- self.setData(self.enum.ID, self._model.rootItem.findUniqueID())
+ self.setData(self.enum.ID, self._model.requestNewID())
if recursive:
for c in self.children():
@@ -209,7 +217,7 @@ class abstractItem():
self.IDs = self.listAllIDs()
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):
for c in item.children():
@@ -226,14 +234,6 @@ class abstractItem():
IDs.extend(c.listAllIDs())
return IDs
- def findUniqueID(self):
- IDs = [int(i) for i in self.IDs]
- k = 1
- while k in IDs:
- k += 1
- self.IDs.append(str(k))
- return str(k)
-
#######################################################################
# Data
#######################################################################
@@ -250,6 +250,10 @@ class abstractItem():
# Setting data
self._data[column] = data
+ # The _model will be none during splitting
+ if self._model and column == self.enum.ID:
+ self._model.updateAvailableIDs(data)
+
# Emit signal
self.emitDataChanged(cols=[column]) # new in 0.5.0
diff --git a/manuskript/models/abstractModel.py b/manuskript/models/abstractModel.py
index a536084..2ec3bfc 100644
--- a/manuskript/models/abstractModel.py
+++ b/manuskript/models/abstractModel.py
@@ -26,6 +26,8 @@ except:
pass
import time, os
+import logging
+LOGGER = logging.getLogger(__name__)
class abstractModel(QAbstractItemModel):
"""
@@ -36,17 +38,29 @@ class abstractModel(QAbstractItemModel):
- Interface with QModelIndex and stuff
- XML Import / Export
- Drag'n'drop
+
+ Row => item/abstractModel/etc.
+ Col => data sub-element. Col 1 (second counting) is ID for all model types.
"""
def __init__(self, parent):
QAbstractItemModel.__init__(self, parent)
-
- self.rootItem = outlineItem(self, title="Root", ID="0")
+ self.nextAvailableID = 1
# Stores removed item, in order to remove them on disk when saving, depending on the file format.
self.removed = []
self._removingRows = False
+ def requestNewID(self):
+ newID = self.nextAvailableID
+ self.nextAvailableID += 1
+ return str(newID)
+
+ # Call this if loading an ID from file rather than assigning a new one.
+ def updateAvailableIDs(self, addedID):
+ if int(addedID) >= self.nextAvailableID:
+ self.nextAvailableID = int(addedID) + 1
+
def index(self, row, column, parent):
if not self.hasIndex(row, column, parent):
@@ -74,7 +88,7 @@ class abstractModel(QAbstractItemModel):
if len(parent.children()) == 0:
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)
col = column
@@ -131,7 +145,7 @@ class abstractModel(QAbstractItemModel):
# Check whether the parent is the root, or is otherwise invalid.
# That is to say: no parent or the parent lacks a parent.
if (parentItem == self.rootItem) or \
- (parentItem is None) or (parentItem.parent() is None):
+ (parentItem == None) or (parentItem.parent() == None):
return QModelIndex()
return self.createIndex(parentItem.row(), 0, parentItem)
@@ -168,7 +182,7 @@ class abstractModel(QAbstractItemModel):
# self.dataChanged.emit(index.sibling(index.row(), 0),
# 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)
if index.column() == Outline.type:
@@ -283,7 +297,7 @@ class abstractModel(QAbstractItemModel):
# # Gets encoded mime data to retrieve the item
items = self.decodeMimeData(data)
- if items is None:
+ if items == None:
return False
# We check if parent is not a child of one of the items
@@ -321,7 +335,7 @@ class abstractModel(QAbstractItemModel):
return None
encodedData = bytes(data.data("application/xml")).decode()
root = ET.XML(encodedData)
- if root is None:
+ if root == None:
return None
if root.tag != "outlineItems":
@@ -381,7 +395,7 @@ class abstractModel(QAbstractItemModel):
items = self.decodeMimeData(data)
- if items is None:
+ if items == None:
return False
if column > 0:
@@ -415,21 +429,19 @@ class abstractModel(QAbstractItemModel):
# In case of copy actions, items might be duplicates, so we need new IDs.
# But they might not be, if we cut, then paste. Paste is a Copy Action.
# The first paste would not need new IDs. But subsequent ones will.
+
+ # Recursively change the existing IDs to new, unique values. No need to strip out the old
+ # even if they are not duplicated in pasting. There is no practical need for ID conservation.
+
if action == Qt.CopyAction:
IDs = self.rootItem.listAllIDs()
-
+
for item in items:
if item.ID() in IDs:
- # Recursively remove ID. So will get a new one when inserted.
- def stripID(item):
- item.setData(Outline.ID, None)
- for c in item.children():
- stripID(c)
-
- stripID(item)
+ item.getUniqueID(recursive=true)
r = self.insertItems(items, beginRow, parent)
-
+
return r
################# ADDING AND REMOVING #################
@@ -448,13 +460,13 @@ class abstractModel(QAbstractItemModel):
# Insert only if parent is folder
if parentItem.isFolder():
- self.beginInsertRows(parent, row, row + len(items) - 1)
-
+ self.beginInsertRows(parent, row, row + len(items) - 1) # Create space.
+
for i in items:
parentItem.insertChild(row + items.index(i), i)
-
+
self.endInsertRows()
-
+
return True
else:
@@ -507,8 +519,9 @@ class abstractModel(QAbstractItemModel):
else:
parentItem = parent.internalPointer()
- self._removingRows = True # Views that are updating can easily know
- # if this is due to row removal.
+ self._removingRows = True
+ # Views that are updating can easily know
+ # if this is due to row removal.
self.beginRemoveRows(parent, row, row + count - 1)
for i in range(count):
item = parentItem.removeChild(row)
diff --git a/manuskript/models/characterModel.py b/manuskript/models/characterModel.py
index a42f8f0..ae0eab9 100644
--- a/manuskript/models/characterModel.py
+++ b/manuskript/models/characterModel.py
@@ -3,11 +3,14 @@
from PyQt5.QtCore import QModelIndex, Qt, QAbstractItemModel, QVariant
from PyQt5.QtGui import QIcon, QPixmap, QColor
-from manuskript.functions import randomColor, iconColor, mainWindow
-from manuskript.enums import Character as C
+from manuskript.functions import randomColor, iconColor, mainWindow, search
+from manuskript.enums import Character as C, Model
+from manuskript.searchLabels import CharacterSearchLabels
+from manuskript.models.searchableModel import searchableModel
+from manuskript.models.searchableItem import searchableItem
-class characterModel(QAbstractItemModel):
+class characterModel(QAbstractItemModel, searchableModel):
def __init__(self, parent):
QAbstractItemModel.__init__(self, parent)
@@ -132,6 +135,9 @@ class characterModel(QAbstractItemModel):
def importance(self, row):
return self.character(row).importance()
+ def pov(self, row):
+ return self.character(row).pov()
+
###############################################################################
# MODEL QUERIES
###############################################################################
@@ -143,29 +149,36 @@ class characterModel(QAbstractItemModel):
@return: array of array of ´character´, by importance.
"""
r = [[], [], []]
+
for c in self.characters:
r[2-int(c.importance())].append(c)
+
return r
def getCharacterByID(self, ID):
- if ID is not None:
+ if ID != None:
ID = str(ID)
for c in self.characters:
if c.ID() == ID:
return c
+
return None
###############################################################################
# ADDING / REMOVING
###############################################################################
- def addCharacter(self):
+ def addCharacter(self, importance = 0, name="New character"):
"""
Creates a new character
+ @param importance: the importance level of the character
@return: the character
"""
- c = Character(model=self, name=self.tr("New character"))
- self.beginInsertRows(QModelIndex(), len(self.characters), len(self.characters))
+ if not name:
+ name="New Character"
+ c = Character(model=self, name=self.tr(name), importance = importance)
+ self.beginInsertRows(QModelIndex(), len(
+ self.characters), len(self.characters))
self.characters.append(c)
self.endInsertRows()
return c
@@ -177,7 +190,8 @@ class characterModel(QAbstractItemModel):
@return: nothing
"""
c = self.getCharacterByID(ID)
- self.beginRemoveRows(QModelIndex(), self.characters.index(c), self.characters.index(c))
+ self.beginRemoveRows(QModelIndex(), self.characters.index(
+ c), self.characters.index(c))
self.characters.remove(c)
self.endRemoveRows()
@@ -197,7 +211,8 @@ class characterModel(QAbstractItemModel):
def addCharacterInfo(self, ID):
c = self.getCharacterByID(ID)
self.beginInsertRows(c.index(), len(c.infos), len(c.infos))
- c.infos.append(CharacterInfo(c, description="Description", value="Value"))
+ c.infos.append(CharacterInfo(
+ c, description="Description", value="Value"))
self.endInsertRows()
mainWindow().updatePersoInfoView()
@@ -217,12 +232,15 @@ class characterModel(QAbstractItemModel):
c.infos.pop(r)
self.endRemoveRows()
+ def searchableItems(self):
+ return self.characters
+
###############################################################################
# CHARACTER
###############################################################################
-class Character():
- def __init__(self, model, name="No name"):
+class Character(searchableItem):
+ def __init__(self, model, name="No name", importance = 0):
self._model = model
self.lastPath = ""
@@ -230,13 +248,19 @@ class Character():
self._data[C.name.value] = name
self.assignUniqueID()
self.assignRandomColor()
- self._data[C.importance.value] = "0"
+ self._data[C.importance.value] = str(importance)
+ self._data[C.pov.value] = "True"
self.infos = []
+ super().__init__(CharacterSearchLabels)
+
def name(self):
return self._data[C.name.value]
+ def setName(self, value):
+ self._data[C.name.value] = value
+
def importance(self):
return self._data[C.importance.value]
@@ -246,6 +270,12 @@ class Character():
def index(self, column=0):
return self._model.indexFromItem(self, column)
+ def data(self, column):
+ if column == "Info":
+ return self.infos
+ else:
+ return self._data.get(column, None)
+
def assignRandomColor(self):
"""
Assigns a random color the the character.
@@ -274,6 +304,22 @@ class Character():
"""
return iconColor(self.icon)
+ def setPOVEnabled(self, enabled):
+ if enabled != self.pov():
+ if enabled:
+ self._data[C.pov.value] = 'True'
+ else:
+ self._data[C.pov.value] = 'False'
+
+ try:
+ self._model.dataChanged.emit(self.index(), self.index())
+ except:
+ # If it is the initialisation, won't be able to emit
+ pass
+
+ def pov(self):
+ return self._data[C.pov.value] == 'True'
+
def assignUniqueID(self, parent=QModelIndex()):
"""Assigns an unused character ID."""
vals = []
@@ -292,6 +338,42 @@ class Character():
r.append((i.description, i.value))
return r
+ def searchTitle(self, column):
+ return self.name()
+
+ def searchOccurrences(self, searchRegex, column):
+ results = []
+
+ data = self.searchData(column)
+ if isinstance(data, list):
+ for i in range(0, len(data)):
+ # For detailed info we will highlight the full row, so we pass the row index
+ # to the highlighter instead of the (startPos, endPos) of the match itself.
+ results += [self.wrapSearchOccurrence(column, i, 0, context) for
+ (startPos, endPos, context) in search(searchRegex, data[i].description)]
+ results += [self.wrapSearchOccurrence(column, i, 0, context) for
+ (startPos, endPos, context) in search(searchRegex, data[i].value)]
+ else:
+ results += super().searchOccurrences(searchRegex, column)
+
+ return results
+
+ def searchID(self):
+ return self.ID()
+
+ def searchPath(self, column):
+ return [self.translate("Characters"), self.name(), self.translate(self.searchColumnLabel(column))]
+
+ def searchData(self, column):
+ if column == C.infos:
+ return self.infos
+ else:
+ return self.data(column)
+
+ def searchModel(self):
+ return Model.Character
+
+
class CharacterInfo():
def __init__(self, character, description="", value=""):
self.description = description
diff --git a/manuskript/models/characterPOVModel.py b/manuskript/models/characterPOVModel.py
new file mode 100644
index 0000000..fbbe419
--- /dev/null
+++ b/manuskript/models/characterPOVModel.py
@@ -0,0 +1,47 @@
+#!/usr/bin/env python
+# --!-- coding: utf8 --!--
+from PyQt5.QtCore import QModelIndex, QSortFilterProxyModel
+
+
+class characterPOVModel(QSortFilterProxyModel):
+
+ def __init__(self, sourceModel, parent=None):
+ QSortFilterProxyModel.__init__(self, parent)
+
+ self.setSourceModel(sourceModel)
+
+ if sourceModel:
+ sourceModel.dataChanged.connect(self.sourceDataChanged)
+
+ def filterAcceptsRow(self, sourceRow, sourceParent):
+ return self.sourceModel().pov(sourceRow)
+
+ def rowToSource(self, row):
+ index = self.index(row, 0)
+ sourceIndex = self.mapToSource(index)
+ return sourceIndex.row()
+
+ def sourceDataChanged(self, topLeft, bottomRight):
+ self.invalidateFilter()
+
+ ###############################################################################
+ # CHARACTER QUERIES
+ ###############################################################################
+
+ def character(self, row):
+ return self.sourceModel().character(self.rowToSource(row))
+
+ def name(self, row):
+ return self.sourceModel().name(self.rowToSource(row))
+
+ def icon(self, row):
+ return self.sourceModel().icon(self.rowToSource(row))
+
+ def ID(self, row):
+ return self.sourceModel().ID(self.rowToSource(row))
+
+ def importance(self, row):
+ return self.sourceModel().importance(self.rowToSource(row))
+
+ def pov(self, row):
+ return self.sourceModel().pov(self.rowToSource(row))
diff --git a/manuskript/models/flatDataModelWrapper.py b/manuskript/models/flatDataModelWrapper.py
new file mode 100644
index 0000000..57ac262
--- /dev/null
+++ b/manuskript/models/flatDataModelWrapper.py
@@ -0,0 +1,53 @@
+#!/usr/bin/env python
+# --!-- coding: utf8 --!--
+
+from manuskript.enums import FlatData, Model
+from manuskript.searchLabels import FlatDataSearchLabels
+
+from manuskript.models.searchableModel import searchableModel
+from manuskript.models.searchableItem import searchableItem
+
+"""
+All searches are performed on models inheriting from searchableModel, but special metadata such as book summaries
+are stored directly on a GUI element (QStandardItemModel). We wrap this GUI element inside this wrapper class
+so it exposes the same interface for searches.
+"""
+class flatDataModelWrapper(searchableModel, searchableItem):
+ def __init__(self, qstandardItemModel):
+ self.qstandardItemModel = qstandardItemModel
+
+ def searchableItems(self):
+ return [flatDataItemWrapper(self.qstandardItemModel)]
+
+
+class flatDataItemWrapper(searchableItem):
+ def __init__(self, qstandardItemModel):
+ super().__init__(FlatDataSearchLabels)
+ self.qstandardItemModel = qstandardItemModel
+
+ def searchModel(self):
+ return Model.FlatData
+
+ def searchID(self):
+ return None
+
+ def searchTitle(self, column):
+ return self.translate(self.searchColumnLabel(column))
+
+ def searchPath(self, column):
+ return [self.translate("Summary"), self.translate(self.searchColumnLabel(column))]
+
+ def searchData(self, column):
+ return self.qstandardItemModel.item(1, self.searchDataIndex(column)).text()
+
+ @staticmethod
+ def searchDataIndex(column):
+ columnIndices = {
+ FlatData.summarySituation: 0,
+ FlatData.summarySentence: 1,
+ FlatData.summaryPara: 2,
+ FlatData.summaryPage: 3,
+ FlatData.summaryFull: 4
+ }
+
+ return columnIndices[column]
\ No newline at end of file
diff --git a/manuskript/models/outlineItem.py b/manuskript/models/outlineItem.py
index 56b6464..97160aa 100644
--- a/manuskript/models/outlineItem.py
+++ b/manuskript/models/outlineItem.py
@@ -8,10 +8,13 @@ from PyQt5.QtGui import QFont, QIcon
from PyQt5.QtWidgets import qApp
from lxml import etree as ET
from manuskript.models.abstractItem import abstractItem
+from manuskript.models.searchableItem import searchableItem
from manuskript import enums
from manuskript import functions as F
from manuskript import settings
from manuskript.converters import HTML2PlainText
+from manuskript.searchLabels import OutlineSearchLabels
+from manuskript.enums import Outline, Model
try:
locale.setlocale(locale.LC_ALL, '')
@@ -20,8 +23,10 @@ except:
# number formatting
pass
+import logging
+LOGGER = logging.getLogger(__name__)
-class outlineItem(abstractItem):
+class outlineItem(abstractItem, searchableItem):
enum = enums.Outline
@@ -30,6 +35,7 @@ class outlineItem(abstractItem):
def __init__(self, model=None, title="", _type="folder", xml=None, parent=None, ID=None):
abstractItem.__init__(self, model, title, _type, xml, parent, ID)
+ searchableItem.__init__(self, OutlineSearchLabels)
self.defaultTextType = None
if not self._data.get(self.enum.compile):
@@ -80,6 +86,9 @@ class outlineItem(abstractItem):
def wordCount(self):
return self._data.get(self.enum.wordCount, 0)
+ def charCount(self):
+ return self._data.get(self.enum.charCount, 0)
+
def __str__(self):
return "{id}: {folder}{title}{children}".format(
id=self.ID(),
@@ -89,6 +98,9 @@ class outlineItem(abstractItem):
)
__repr__ = __str__
+
+ def charCount(self):
+ return self._data.get(self.enum.charCount, 0)
#######################################################################
# Data
@@ -119,7 +131,7 @@ class outlineItem(abstractItem):
elif role == Qt.FontRole:
f = QFont()
- if column == E.wordCount and self.isFolder():
+ if (column == E.wordCount or column == E.charCount) and self.isFolder():
f.setItalic(True)
elif column == E.goal and self.isFolder() and not self.data(E.setGoal):
f.setItalic(True)
@@ -140,7 +152,7 @@ class outlineItem(abstractItem):
# Checking if we will have to recount words
updateWordCount = False
- if column in [E.wordCount, E.goal, E.setGoal]:
+ if column in [E.wordCount, E.charCount, E.goal, E.setGoal]:
updateWordCount = not column in self._data or self._data[column] != data
# Stuff to do before
@@ -153,7 +165,9 @@ class outlineItem(abstractItem):
# Stuff to do afterwards
if column == E.text:
wc = F.wordCount(data)
+ cc = F.charCount(data, settings.countSpaces)
self.setData(E.wordCount, wc)
+ self.setData(E.charCount, cc)
if column == E.compile:
# Title changes when compile changes
@@ -195,9 +209,12 @@ class outlineItem(abstractItem):
else:
wc = 0
+ cc = 0
for c in self.children():
wc += F.toInt(c.data(self.enum.wordCount))
+ cc += F.toInt(c.data(self.enum.charCount))
self._data[self.enum.wordCount] = wc
+ self._data[self.enum.charCount] = cc
setGoal = F.toInt(self.data(self.enum.setGoal))
goal = F.toInt(self.data(self.enum.goal))
@@ -218,7 +235,8 @@ class outlineItem(abstractItem):
self.setData(self.enum.goalPercentage, "")
self.emitDataChanged([self.enum.goal, self.enum.setGoal,
- self.enum.wordCount, self.enum.goalPercentage])
+ self.enum.wordCount, self.enum.charCount,
+ self.enum.goalPercentage])
if self.parent():
self.parent().updateWordCount()
@@ -343,8 +361,7 @@ class outlineItem(abstractItem):
return lst
- def findItemsContaining(self, text, columns, mainWindow=F.mainWindow(),
- caseSensitive=False, recursive=True):
+ def findItemsContaining(self, text, columns, mainWindow=F.mainWindow(), caseSensitive=False, recursive=True):
"""Returns a list if IDs of all subitems
containing ``text`` in columns ``columns``
(being a list of int).
@@ -357,19 +374,17 @@ class outlineItem(abstractItem):
return lst
- def itemContains(self, text, columns, mainWindow=F.mainWindow(),
- caseSensitive=False):
+ def itemContains(self, text, columns, mainWindow=F.mainWindow(), caseSensitive=False):
lst = []
text = text.lower() if not caseSensitive else text
for c in columns:
-
if c == self.enum.POV and self.POV():
- c = mainWindow.mdlCharacter.getCharacterByID(self.POV())
- if c:
- searchIn = c.name()
+ character = mainWindow.mdlCharacter.getCharacterByID(self.POV())
+ if character:
+ searchIn = character.name()
else:
searchIn = ""
- print("Character POV not found:", self.POV())
+ LOGGER.error("Character POV not found: %s", self.POV())
elif c == self.enum.status:
searchIn = mainWindow.mdlStatus.item(F.toInt(self.status()), 0).text()
@@ -381,7 +396,6 @@ class outlineItem(abstractItem):
searchIn = self.data(c)
searchIn = searchIn.lower() if not caseSensitive else searchIn
-
if text in searchIn:
if not self.ID() in lst:
lst.append(self.ID())
@@ -467,6 +481,7 @@ class outlineItem(abstractItem):
# We don't want to write some datas (computed)
XMLExclude = [enums.Outline.wordCount,
+ enums.Outline.charCount,
enums.Outline.goal,
enums.Outline.goalPercentage,
enums.Outline.revisions]
@@ -502,3 +517,39 @@ class outlineItem(abstractItem):
for child in root:
if child.tag == "revision":
self.appendRevision(child.attrib["timestamp"], child.attrib["text"])
+
+ #######################################################################
+ # Search
+ #######################################################################
+ def searchModel(self):
+ return Model.Outline
+
+ def searchID(self):
+ return self.data(Outline.ID)
+
+ def searchTitle(self, column):
+ return self.title()
+
+ def searchPath(self, column):
+ return [self.translate("Outline")] + self.path().split(' > ') + [self.translate(self.searchColumnLabel(column))]
+
+ def searchData(self, column):
+ mainWindow = F.mainWindow()
+
+ searchData = None
+
+ if column == self.enum.POV and self.POV():
+ character = mainWindow.mdlCharacter.getCharacterByID(self.POV())
+ if character:
+ searchData = character.name()
+
+ elif column == self.enum.status:
+ searchData = mainWindow.mdlStatus.item(F.toInt(self.status()), 0).text()
+
+ elif column == self.enum.label:
+ searchData = mainWindow.mdlLabels.item(F.toInt(self.label()), 0).text()
+
+ else:
+ searchData = self.data(column)
+
+ return searchData
diff --git a/manuskript/models/outlineModel.py b/manuskript/models/outlineModel.py
index 63494e2..162ce80 100644
--- a/manuskript/models/outlineModel.py
+++ b/manuskript/models/outlineModel.py
@@ -2,12 +2,31 @@
# --!-- coding: utf8 --!--
from manuskript.models.abstractModel import abstractModel
+from manuskript.models.searchableModel import searchableModel
+from manuskript.models.outlineItem import outlineItem
-
-class outlineModel(abstractModel):
+class outlineModel(abstractModel, searchableModel):
def __init__(self, parent):
abstractModel.__init__(self, parent)
+ self.rootItem = outlineItem(model=self, title="Root", ID="0")
+
def findItemsByPOV(self, POV):
"Returns a list of IDs of all items whose POV is ``POV``."
return self.rootItem.findItemsByPOV(POV)
+
+ def searchableItems(self):
+ result = []
+
+ for child in self.rootItem.children():
+ result += self._searchableItems(child)
+
+ return result
+
+ def _searchableItems(self, item):
+ result = [item]
+
+ for child in item.children():
+ result += self._searchableItems(child)
+
+ return result
diff --git a/manuskript/models/plotModel.py b/manuskript/models/plotModel.py
index fe01bd0..113b75e 100644
--- a/manuskript/models/plotModel.py
+++ b/manuskript/models/plotModel.py
@@ -8,12 +8,15 @@ from PyQt5.QtGui import QStandardItem
from PyQt5.QtGui import QStandardItemModel
from PyQt5.QtWidgets import QAction, QMenu
-from manuskript.enums import Plot
-from manuskript.enums import PlotStep
+from manuskript.enums import Plot, PlotStep, Model
from manuskript.functions import toInt, mainWindow
+from manuskript.models.searchResultModel import searchResultModel
+from manuskript.searchLabels import PlotSearchLabels, PLOT_STEP_COLUMNS_OFFSET
+from manuskript.functions import search
+from manuskript.models.searchableModel import searchableModel
+from manuskript.models.searchableItem import searchableItem
-
-class plotModel(QStandardItemModel):
+class plotModel(QStandardItemModel, searchableModel):
def __init__(self, parent):
QStandardItemModel.__init__(self, 0, 3, parent)
self.setHorizontalHeaderLabels([i.name for i in Plot])
@@ -73,7 +76,7 @@ class plotModel(QStandardItemModel):
if i == row:
importance = self.item(i, Plot.importance).text()
return importance
- return "0" # Default to "Minor"
+ return "0" # Default to "Minor"
def getSubPlotTextsByID(self, plotID, subplotRaw):
"""Returns a tuple (name, summary) for the subplot whose raw in the model
@@ -102,12 +105,15 @@ class plotModel(QStandardItemModel):
# ADDING / REMOVING
###############################################################################
- def addPlot(self):
- p = QStandardItem(self.tr("New plot"))
+ def addPlot(self, name="New plot"):
+ if not name:
+ name="New Plot"
+ p = QStandardItem(self.tr(name))
_id = QStandardItem(self.getUniqueID())
importance = QStandardItem(str(0))
self.appendRow([p, _id, importance, QStandardItem("Characters"),
QStandardItem(), QStandardItem(), QStandardItem("Resolution steps")])
+ return p, _id
def getUniqueID(self, parent=QModelIndex()):
"""Returns an unused ID"""
@@ -147,8 +153,8 @@ class plotModel(QStandardItemModel):
def data(self, index, role=Qt.DisplayRole):
if index.parent().isValid() and \
- index.parent().column() == Plot.steps and \
- index.column() == PlotStep.meta:
+ index.parent().column() == Plot.steps and \
+ index.column() == PlotStep.meta:
if role == Qt.TextAlignmentRole:
return Qt.AlignRight | Qt.AlignVCenter
elif role == Qt.ForegroundRole:
@@ -186,7 +192,8 @@ class plotModel(QStandardItemModel):
# Don't know why, if summary is in third position, then drag/drop deletes it...
parentItem.appendRow([p, _id, QStandardItem(), summary])
# Select last index
- self.mw.lstSubPlots.setCurrentIndex(parent.child(self.rowCount(parent) - 1, 0))
+ self.mw.lstSubPlots.setCurrentIndex(
+ parent.child(self.rowCount(parent) - 1, 0))
def removeSubPlot(self):
"""
@@ -262,3 +269,118 @@ class plotModel(QStandardItemModel):
mpr.mapped.connect(self.addPlotPerso)
self.mw.btnAddPlotPerso.setMenu(menu)
+
+ #######################################################################
+ # Search
+ #######################################################################
+ def searchableItems(self):
+ items = []
+
+ for i in range(self.rowCount()):
+ items.append(plotItemSearchWrapper(i, self.item, self.mw.mdlCharacter.getCharacterByID))
+
+ return items
+
+
+class plotItemSearchWrapper(searchableItem):
+ def __init__(self, rowIndex, getItem, getCharacterByID):
+ self.rowIndex = rowIndex
+ self.getItem = getItem
+ self.getCharacterByID = getCharacterByID
+ super().__init__(PlotSearchLabels)
+
+ def searchOccurrences(self, searchRegex, column):
+ results = []
+
+ plotName = self.getItem(self.rowIndex, Plot.name).text()
+ if column >= PLOT_STEP_COLUMNS_OFFSET:
+ results += self.searchInPlotSteps(self.rowIndex, plotName, column, column - PLOT_STEP_COLUMNS_OFFSET, searchRegex, False)
+ else:
+ item_name = self.getItem(self.rowIndex, Plot.name).text()
+ if column == Plot.characters:
+ charactersList = self.getItem(self.rowIndex, Plot.characters)
+
+ for i in range(charactersList.rowCount()):
+ characterID = charactersList.child(i).text()
+
+ character = self.getCharacterByID(characterID)
+ if character:
+ columnText = character.name()
+
+ characterResults = search(searchRegex, columnText)
+ if len(characterResults):
+ # We will highlight the full character row in the plot characters list, so we
+ # return the row index instead of the match start and end positions.
+ results += [
+ searchResultModel(Model.Plot, self.getItem(self.rowIndex, Plot.ID).text(), column,
+ self.translate(item_name),
+ self.searchPath(column),
+ [(i, 0)], context) for start, end, context in
+ search(searchRegex, columnText)]
+ else:
+ results += super().searchOccurrences(searchRegex, column)
+ if column == Plot.name:
+ results += self.searchInPlotSteps(self.rowIndex, plotName, Plot.name, PlotStep.name,
+ searchRegex, False)
+ elif column == Plot.summary:
+ results += self.searchInPlotSteps(self.rowIndex, plotName, Plot.summary, PlotStep.summary,
+ searchRegex, True)
+
+ return results
+
+ def searchModel(self):
+ return Model.Plot
+
+ def searchID(self):
+ return self.getItem(self.rowIndex, Plot.ID).text()
+
+ def searchTitle(self, column):
+ return self.getItem(self.rowIndex, Plot.name).text()
+
+ def searchPath(self, column):
+ def _path(item):
+ path = []
+
+ if item.parent():
+ path += _path(item.parent())
+ path.append(item.text())
+
+ return path
+
+ return [self.translate("Plot")] + _path(self.getItem(self.rowIndex, Plot.name)) + [self.translate(self.searchColumnLabel(column))]
+
+ def searchData(self, column):
+ return self.getItem(self.rowIndex, column).text()
+
+ def plotStepPath(self, plotName, plotStepName, column):
+ return [self.translate("Plot"), plotName, plotStepName, self.translate(self.searchColumnLabel(column))]
+
+ def searchInPlotSteps(self, plotIndex, plotName, plotColumn, plotStepColumn, searchRegex, searchInsidePlotStep):
+ results = []
+
+ # Plot step info can be found in two places: the own list of plot steps (this is the case for ie. name and meta
+ # fields) and "inside" the plot step once it is selected in the list (as it's the case for the summary).
+ if searchInsidePlotStep:
+ # We are searching *inside* the plot step, so we return both the row index (for selecting the right plot
+ # step in the list), and (start, end) positions of the match inside the text field for highlighting it.
+ getSearchData = lambda rowIndex, start, end, context: ([(rowIndex, 0), (start, end)], context)
+ else:
+ # We are searching *in the plot step row*, so we only return the row index for selecting the right plot
+ # step in the list when highlighting search results.
+ getSearchData = lambda rowIndex, start, end, context: ([(rowIndex, 0)], context)
+
+ item = self.getItem(plotIndex, Plot.steps)
+ for i in range(item.rowCount()):
+ if item.child(i, PlotStep.ID):
+ plotStepName = item.child(i, PlotStep.name).text()
+ plotStepText = item.child(i, plotStepColumn).text()
+
+ # We will highlight the full plot step row in the plot steps list, so we
+ # return the row index instead of the match start and end positions.
+ results += [searchResultModel(Model.PlotStep, self.getItem(plotIndex, Plot.ID).text(), plotStepColumn,
+ self.translate(plotStepName),
+ self.plotStepPath(plotName, plotStepName, plotColumn),
+ *getSearchData(i, start, end, context)) for start, end, context in
+ search(searchRegex, plotStepText)]
+
+ return results
\ No newline at end of file
diff --git a/manuskript/models/references.py b/manuskript/models/references.py
index 17696c8..9baa8db 100644
--- a/manuskript/models/references.py
+++ b/manuskript/models/references.py
@@ -3,6 +3,9 @@
import re
+import logging
+LOGGER = logging.getLogger(__name__)
+
###############################################################################
# SHORT REFERENCES
###############################################################################
@@ -187,7 +190,7 @@ def infos(ref):
elif _type == CharacterLetter:
m = mainWindow().mdlCharacter
c = m.getCharacterByID(int(_ref))
- if c is None:
+ if c == None:
return qApp.translate("references", "Unknown reference: {}.").format(ref)
index = c.index()
@@ -627,7 +630,7 @@ def open(ref):
mw.lstCharacters.setCurrentItem(item)
return True
- print("Error: Ref {} not found".format(ref))
+ LOGGER.error("Character reference {} not found.".format(ref))
return False
elif _type == TextLetter:
@@ -639,7 +642,7 @@ def open(ref):
mw.mainEditor.setCurrentModelIndex(index, newTab=True)
return True
else:
- print("Ref not found")
+ LOGGER.error("Text reference {} not found.".format(ref))
return False
elif _type == PlotLetter:
@@ -651,7 +654,7 @@ def open(ref):
mw.lstPlots.setCurrentItem(item)
return True
- print("Ref not found")
+ LOGGER.error("Plot reference {} not found.".format(ref))
return False
elif _type == WorldLetter:
@@ -664,8 +667,8 @@ def open(ref):
mw.mdlWorld.indexFromItem(item))
return True
- print("Ref not found")
+ LOGGER.error("World reference {} not found.".format(ref))
return False
- print("Ref not implemented")
+ LOGGER.error("Unable to identify reference type: {}.".format(ref))
return False
diff --git a/manuskript/models/searchFilter.py b/manuskript/models/searchFilter.py
new file mode 100644
index 0000000..ae2096d
--- /dev/null
+++ b/manuskript/models/searchFilter.py
@@ -0,0 +1,32 @@
+#!/usr/bin/env python
+# --!-- coding: utf8 --!--
+
+
+class searchFilter:
+ def __init__(self, label, enabled, modelColumns = None):
+ if not isinstance(label, str):
+ raise TypeError("label must be a str")
+
+ if not isinstance(enabled, bool):
+ raise TypeError("enabled must be a bool")
+
+ if modelColumns is not None and (not isinstance(modelColumns, list)):
+ raise TypeError("modelColumns must be a list or None")
+
+ self._label = label
+ self._enabled = enabled
+ self._modelColumns = modelColumns
+ if self._modelColumns is None:
+ self._modelColumns = []
+
+ def label(self):
+ return self._label
+
+ def enabled(self):
+ return self._enabled
+
+ def modelColumns(self):
+ return self._modelColumns
+
+ def setEnabled(self, enabled):
+ self._enabled = enabled
diff --git a/manuskript/models/searchResultModel.py b/manuskript/models/searchResultModel.py
new file mode 100644
index 0000000..07ad038
--- /dev/null
+++ b/manuskript/models/searchResultModel.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python
+# --!-- coding: utf8 --!--
+
+
+class searchResultModel():
+ def __init__(self, model_type, model_id, column, title, path, pos, context):
+ self._type = model_type
+ self._id = model_id
+ self._column = column
+ self._title = title
+ self._path = path
+ self._pos = pos
+ self._context = context
+
+ def type(self):
+ return self._type
+
+ def id(self):
+ return self._id
+
+ def column(self):
+ return self._column
+
+ def title(self):
+ return self._title
+
+ def path(self):
+ return self._path
+
+ def pos(self):
+ return self._pos
+
+ def context(self):
+ return self._context
+
+ def __repr__(self):
+ return "(%s, %s, %s, %s, %s, %s, %s)" % (self._type, self._id, self._column, self._title, self._path, self._pos, self._context)
+
+ def __eq__(self, other):
+ return self.type() == other.type() and \
+ self.id() == other.id() and \
+ self.column == other.column and \
+ self.pos() == other.pos() and \
+ self.context == other.context
diff --git a/manuskript/models/searchableItem.py b/manuskript/models/searchableItem.py
new file mode 100644
index 0000000..25ca23b
--- /dev/null
+++ b/manuskript/models/searchableItem.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+# --!-- coding: utf8 --!--
+
+
+from manuskript.models.searchResultModel import searchResultModel
+from manuskript.functions import search
+from PyQt5.QtCore import QCoreApplication
+
+class searchableItem():
+ def __init__(self, searchColumnLabels):
+ self._searchColumnLabels = searchColumnLabels
+
+ def searchOccurrences(self, searchRegex, column):
+ return [self.wrapSearchOccurrence(column, startPos, endPos, context) for (startPos, endPos, context) in search(searchRegex, self.searchData(column))]
+
+ def wrapSearchOccurrence(self, column, startPos, endPos, context):
+ return searchResultModel(self.searchModel(), self.searchID(), column, self.searchTitle(column), self.searchPath(column), [(startPos, endPos)], context)
+
+ def searchModel(self):
+ raise NotImplementedError
+
+ def searchID(self):
+ raise NotImplementedError
+
+ def searchTitle(self, column):
+ raise NotImplementedError
+
+ def searchPath(self, column):
+ return []
+
+ def searchData(self, column):
+ raise NotImplementedError
+
+ def searchColumnLabel(self, column):
+ return self._searchColumnLabels.get(column, "")
+
+ def translate(self, text):
+ return QCoreApplication.translate("MainWindow", text)
diff --git a/manuskript/models/searchableModel.py b/manuskript/models/searchableModel.py
new file mode 100644
index 0000000..c7246b9
--- /dev/null
+++ b/manuskript/models/searchableModel.py
@@ -0,0 +1,15 @@
+#!/usr/bin/env python
+# --!-- coding: utf8 --!--
+
+
+class searchableModel():
+
+ def searchOccurrences(self, searchRegex, columns):
+ results = []
+ for item in self.searchableItems():
+ for column in columns:
+ results += item.searchOccurrences(searchRegex, column)
+ return results
+
+ def searchableItems(self):
+ raise NotImplementedError
diff --git a/manuskript/models/worldModel.py b/manuskript/models/worldModel.py
index 61254cd..475736a 100644
--- a/manuskript/models/worldModel.py
+++ b/manuskript/models/worldModel.py
@@ -1,18 +1,20 @@
#!/usr/bin/env python
# --!-- coding: utf8 --!--
-from PyQt5.QtCore import QModelIndex
-from PyQt5.QtCore import QSize
+from PyQt5.QtCore import QModelIndex, QSize
from PyQt5.QtCore import Qt, QMimeData, QByteArray
from PyQt5.QtGui import QStandardItem, QBrush, QFontMetrics
from PyQt5.QtGui import QStandardItemModel, QColor
from PyQt5.QtWidgets import QMenu, QAction, qApp
-from manuskript.enums import World
+from manuskript.enums import World, Model
from manuskript.functions import mainWindow
from manuskript.ui import style as S
+from manuskript.models.searchableModel import searchableModel
+from manuskript.models.searchableItem import searchableItem
+from manuskript.searchLabels import WorldSearchLabels
-class worldModel(QStandardItemModel):
+class worldModel(QStandardItemModel, searchableModel):
def __init__(self, parent):
QStandardItemModel.__init__(self, 0, len(World), parent)
self.mw = mainWindow()
@@ -136,6 +138,9 @@ class worldModel(QStandardItemModel):
_id = QStandardItem(self.getUniqueID())
row = [name, _id] + [QStandardItem() for i in range(2, len(World))]
parent.appendRow(row)
+
+ self.mw.treeWorld.setExpanded(self.selectedIndex(), True)
+ self.mw.treeWorld.setCurrentIndex(self.indexFromItem(name))
return name
def getUniqueID(self):
@@ -186,7 +191,7 @@ class worldModel(QStandardItemModel):
for index in indexes:
item = self.itemFromIndex(index)
parent = item.parent()
- if parent is None:
+ if parent == None:
parent = self.invisibleRootItem()
row_indexes.append((parent, item.row()))
@@ -353,3 +358,51 @@ class worldModel(QStandardItemModel):
return QSize(0, h + 6)
return QStandardItemModel.data(self, index, role)
+
+ #######################################################################
+ # Search
+ #######################################################################
+ def searchableItems(self):
+ def readAll(item):
+ items = [WorldItemSearchWrapper(item, self.itemID(item), self.indexFromItem(item), self.data)]
+
+ for c in self.children(item):
+ items += readAll(c)
+
+ return items
+
+ return readAll(self.invisibleRootItem())
+
+class WorldItemSearchWrapper(searchableItem):
+ def __init__(self, item, itemID, itemIndex, getColumnData):
+ super().__init__(WorldSearchLabels)
+ self.item = item
+ self.itemID = itemID
+ self.itemIndex = itemIndex
+ self.getColumnData = getColumnData
+
+ def searchModel(self):
+ return Model.World
+
+ def searchID(self):
+ return self.itemID
+
+ def searchTitle(self, column):
+ return self.item.text()
+
+ def searchPath(self, column):
+
+ def _path(item):
+ path = []
+
+ if item.parent():
+ path += _path(item.parent())
+ path.append(item.text())
+
+ return path
+
+ return [self.translate("World")] + _path(self.item) + [self.translate(self.searchColumnLabel(column))]
+
+ def searchData(self, column):
+ return self.getColumnData(self.itemIndex.sibling(self.itemIndex.row(), column))
+
diff --git a/manuskript/searchLabels.py b/manuskript/searchLabels.py
new file mode 100644
index 0000000..587e468
--- /dev/null
+++ b/manuskript/searchLabels.py
@@ -0,0 +1,56 @@
+#!/usr/bin/env python
+# --!-- coding: utf8 --!--
+
+from manuskript.enums import Outline, Character, FlatData, World, Plot, PlotStep
+
+OutlineSearchLabels = {
+ Outline.title: "Title",
+ Outline.text: "Text",
+ Outline.summarySentence: "One sentence summary",
+ Outline.summaryFull: "Summary",
+ Outline.POV: "POV",
+ Outline.notes: "Notes",
+ Outline.status: "Status",
+ Outline.label: "Label"
+}
+
+CharacterSearchLabels = {
+ Character.name: "Name",
+ Character.motivation: "Motivation",
+ Character.goal: "Goal",
+ Character.conflict: "Conflict",
+ Character.epiphany: "Epiphany",
+ Character.summarySentence: "One sentence summary",
+ Character.summaryPara: "One paragraph summary",
+ Character.summaryFull: "Summary",
+ Character.notes: "Notes",
+ Character.infos: "Detailed info"
+}
+
+FlatDataSearchLabels = {
+ FlatData.summarySituation: "Situation",
+ FlatData.summarySentence: "One sentence summary",
+ FlatData.summaryPara: "One paragraph summary",
+ FlatData.summaryPage: "One page summary",
+ FlatData.summaryFull: "Full summary"
+}
+
+WorldSearchLabels = {
+ World.name: "Name",
+ World.description: "Description",
+ World.passion: "Passion",
+ World.conflict: "Conflict"
+}
+
+# Search menu includes one single option for both plot and plotStep models. For plotStep related fields
+# (like PlotStep.meta) we add an offset so it is not confused with the Plot enum value mapping to the same integer.
+PLOT_STEP_COLUMNS_OFFSET = 30
+
+PlotSearchLabels = {
+ Plot.name: "Name",
+ Plot.description: "Description",
+ Plot.characters: "Characters",
+ Plot.result: "Result",
+ Plot.summary: "Summary",
+ PLOT_STEP_COLUMNS_OFFSET + PlotStep.meta: "Meta"
+}
diff --git a/manuskript/settings.py b/manuskript/settings.py
index 96a658e..56eb0ec 100644
--- a/manuskript/settings.py
+++ b/manuskript/settings.py
@@ -8,6 +8,9 @@ from PyQt5.QtWidgets import qApp
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
# in order to allow a shared project between several writers
@@ -47,6 +50,8 @@ corkSizeFactor = 100
folderView = "cork"
lastTab = 0
openIndexes = [""]
+progressChars = False
+countSpaces = True
autoSave = False
autoSaveDelay = 5
autoSaveNoChanges = True
@@ -123,7 +128,7 @@ def initDefaultValues():
def save(filename=None, protocol=None):
global spellcheck, dict, corkSliderFactor, viewSettings, corkSizeFactor, folderView, lastTab, openIndexes, \
- autoSave, autoSaveDelay, saveOnQuit, autoSaveNoChanges, autoSaveNoChangesDelay, outlineViewColumns, \
+ progressChars, autoSave, autoSaveDelay, saveOnQuit, autoSaveNoChanges, autoSaveNoChangesDelay, outlineViewColumns, \
corkBackground, corkStyle, fullScreenTheme, defaultTextType, textEditor, revisions, frequencyAnalyzer, viewMode, \
saveToZip, dontShowDeleteWarning, fullscreenSettings
@@ -136,6 +141,8 @@ def save(filename=None, protocol=None):
"folderView": folderView,
"lastTab": lastTab,
"openIndexes": openIndexes,
+ "progressChars": progressChars,
+ "countSpaces": countSpaces,
"autoSave":autoSave,
"autoSaveDelay":autoSaveDelay,
# TODO: Settings Cleanup Task -- Rename saveOnQuit to saveOnProjectClose -- see PR #615
@@ -183,7 +190,7 @@ def load(string, fromString=False, protocol=None):
allSettings = pickle.load(f)
except:
- print("{} doesn't exist, cannot load settings.".format(string))
+ LOGGER.error("Cannot load settings, {} does not exist.".format(string))
return
else:
if protocol == 0:
@@ -235,6 +242,14 @@ def load(string, fromString=False, protocol=None):
global openIndexes
openIndexes = allSettings["openIndexes"]
+ if "progressChars" in allSettings:
+ global progressChars
+ progressChars = allSettings["progressChars"]
+
+ if "countSpaces" in allSettings:
+ global countSpaces
+ countSpaces = allSettings["countSpaces"]
+
if "autoSave" in allSettings:
global autoSave
autoSave = allSettings["autoSave"]
diff --git a/manuskript/settingsWindow.py b/manuskript/settingsWindow.py
index 4ae34bf..4802410 100644
--- a/manuskript/settingsWindow.py
+++ b/manuskript/settingsWindow.py
@@ -59,11 +59,16 @@ class settingsWindow(QWidget, Ui_Settings):
self.lstMenu.setMaximumWidth(140)
self.lstMenu.setMinimumWidth(140)
+ lowerKeys = [i.lower() for i in list(QStyleFactory.keys())]
+
# General
self.cmbStyle.addItems(list(QStyleFactory.keys()))
- self.cmbStyle.setCurrentIndex(
- [i.lower() for i in list(QStyleFactory.keys())]
- .index(qApp.style().objectName()))
+
+ try:
+ self.cmbStyle.setCurrentIndex(lowerKeys.index(qApp.style().objectName()))
+ except ValueError:
+ self.cmbStyle.setCurrentIndex(0)
+
self.cmbStyle.currentIndexChanged[str].connect(self.setStyle)
self.cmbTranslation.clear()
@@ -111,6 +116,9 @@ class settingsWindow(QWidget, Ui_Settings):
self.spnGeneralFontSize.setValue(f.pointSize())
self.spnGeneralFontSize.valueChanged.connect(self.setAppFontSize)
+ self.chkProgressChars.setChecked(settings.progressChars);
+ self.chkProgressChars.stateChanged.connect(self.charSettingsChanged)
+
self.txtAutoSave.setValidator(QIntValidator(0, 999, self))
self.txtAutoSaveNoChanges.setValidator(QIntValidator(0, 999, self))
self.chkAutoSave.setChecked(settings.autoSave)
@@ -164,10 +172,12 @@ class settingsWindow(QWidget, Ui_Settings):
for item, what, value in [
(self.rdoTreeItemCount, "InfoFolder", "Count"),
(self.rdoTreeWC, "InfoFolder", "WC"),
+ (self.rdoTreeCC, "InfoFolder", "CC"),
(self.rdoTreeProgress, "InfoFolder", "Progress"),
(self.rdoTreeSummary, "InfoFolder", "Summary"),
(self.rdoTreeNothing, "InfoFolder", "Nothing"),
(self.rdoTreeTextWC, "InfoText", "WC"),
+ (self.rdoTreeTextCC, "InfoText", "CC"),
(self.rdoTreeTextProgress, "InfoText", "Progress"),
(self.rdoTreeTextSummary, "InfoText", "Summary"),
(self.rdoTreeTextNothing, "InfoText", "Nothing"),
@@ -180,6 +190,9 @@ class settingsWindow(QWidget, Ui_Settings):
lambda v: self.lblTreeIconSize.setText("{}x{}".format(v, v)))
self.sldTreeIconSize.setValue(settings.viewSettings["Tree"]["iconSize"])
+ self.chkCountSpaces.setChecked(settings.countSpaces);
+ self.chkCountSpaces.stateChanged.connect(self.countSpacesChanged)
+
self.rdoCorkOldStyle.setChecked(settings.corkStyle == "old")
self.rdoCorkNewStyle.setChecked(settings.corkStyle == "new")
self.rdoCorkNewStyle.toggled.connect(self.setCorkStyle)
@@ -338,6 +351,11 @@ class settingsWindow(QWidget, Ui_Settings):
sttgs = QSettings(qApp.organizationName(), qApp.applicationName())
sttgs.setValue("appFontSize", val)
+ def charSettingsChanged(self):
+ settings.progressChars = True if self.chkProgressChars.checkState() else False
+
+ self.mw.mainEditor.updateStats()
+
def saveSettingsChanged(self):
if self.txtAutoSave.text() in ["", "0"]:
self.txtAutoSave.setText("1")
@@ -427,10 +445,12 @@ class settingsWindow(QWidget, Ui_Settings):
for item, what, value in [
(self.rdoTreeItemCount, "InfoFolder", "Count"),
(self.rdoTreeWC, "InfoFolder", "WC"),
+ (self.rdoTreeCC, "InfoFolder", "CC"),
(self.rdoTreeProgress, "InfoFolder", "Progress"),
(self.rdoTreeSummary, "InfoFolder", "Summary"),
(self.rdoTreeNothing, "InfoFolder", "Nothing"),
(self.rdoTreeTextWC, "InfoText", "WC"),
+ (self.rdoTreeTextCC, "InfoText", "CC"),
(self.rdoTreeTextProgress, "InfoText", "Progress"),
(self.rdoTreeTextSummary, "InfoText", "Summary"),
(self.rdoTreeTextNothing, "InfoText", "Nothing"),
@@ -445,6 +465,11 @@ class settingsWindow(QWidget, Ui_Settings):
self.mw.treeRedacOutline.viewport().update()
+ def countSpacesChanged(self):
+ settings.countSpaces = True if self.chkCountSpaces.checkState() else False
+
+ self.mw.mainEditor.updateStats()
+
def setCorkColor(self):
color = QColor(settings.corkBackground["color"])
self.colorDialog = QColorDialog(color, self)
diff --git a/manuskript/tests/__init__.py b/manuskript/tests/__init__.py
index 19c56d6..54a0fe1 100644
--- a/manuskript/tests/__init__.py
+++ b/manuskript/tests/__init__.py
@@ -13,7 +13,8 @@ QApplication([])
# Create app and mainWindow
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
# one, we get a `TypeError: connection is not unique` in MainWindow:
diff --git a/manuskript/tests/conftest.py b/manuskript/tests/conftest.py
index 09629e4..ff212ba 100644
--- a/manuskript/tests/conftest.py
+++ b/manuskript/tests/conftest.py
@@ -12,7 +12,7 @@ def MW():
"""
from manuskript import functions as F
MW = F.mainWindow()
- assert MW is not None
+ assert MW != None
assert MW == F.MW
return MW
@@ -23,7 +23,7 @@ def MWNoProject(MW):
Take the MainWindow and close andy possibly open project.
"""
MW.closeProject()
- assert MW.currentProject is None
+ assert MW.currentProject == None
return MW
@pytest.fixture
@@ -35,9 +35,9 @@ def MWEmptyProject(MW):
tf = tempfile.NamedTemporaryFile(suffix=".msk")
MW.closeProject()
- assert MW.currentProject is None
+ assert MW.currentProject == None
MW.welcome.createFile(tf.name, overwrite=True)
- assert MW.currentProject is not None
+ assert MW.currentProject != None
return MW
# If using with: @pytest.fixture(scope='session', autouse=True)
@@ -67,6 +67,6 @@ def MWSampleProject(MW):
shutil.copyfile(src, tf.name)
shutil.copytree(src[:-4], tf.name[:-4])
MW.loadProject(tf.name)
- assert MW.currentProject is not None
+ assert MW.currentProject != None
return MW
diff --git a/manuskript/tests/models/test_outlineItem.py b/manuskript/tests/models/test_outlineItem.py
index cc681c0..e217309 100644
--- a/manuskript/tests/models/test_outlineItem.py
+++ b/manuskript/tests/models/test_outlineItem.py
@@ -123,16 +123,16 @@ def test_modelStuff(outlineModelBasic):
assert folder.findItemsContaining("VALUE", cols, MW, True) == []
assert folder.findItemsContaining("VALUE", cols, MW, False) == [text2.ID()]
- # Model, count and copy
+ # Model, count and copy
k = folder._model
- folder.setModel(14)
- assert text2._model == 14
+ folder.setModel(None)
+ assert text2._model is None
folder.setModel(k)
assert folder.columnCount() == len(folder.enum)
text1 = text2.copy()
- assert text1.ID() is None
+ assert text1.ID() == None
folder.appendChild(text1)
- assert text1.ID() is not None
+ assert text1.ID() != None
assert folder.childCountRecursive() == 2
assert text1.path() == "Folder > Text"
assert len(text1.pathID()) == 2
diff --git a/manuskript/tests/models/test_references.py b/manuskript/tests/models/test_references.py
index c49e5a5..b9fb2d0 100644
--- a/manuskript/tests/models/test_references.py
+++ b/manuskript/tests/models/test_references.py
@@ -39,7 +39,7 @@ def test_references(MWSampleProject):
assert "\n" in Ref.infos(Ref.plotReference(plotID))
assert "Not a ref" in Ref.infos("")
assert "Unknown" in Ref.infos(Ref.plotReference("999"))
- assert Ref.shortInfos(Ref.plotReference(plotID)) is not None
+ assert Ref.shortInfos(Ref.plotReference(plotID)) != None
assert Ref.shortInfos(Ref.plotReference("999")) == None
assert Ref.shortInfos("") == -1
@@ -50,7 +50,7 @@ def test_references(MWSampleProject):
charID = IDs[0]
assert "\n" in Ref.infos(Ref.characterReference(charID))
assert "Unknown" in Ref.infos(Ref.characterReference("999"))
- assert Ref.shortInfos(Ref.characterReference(charID)) is not None
+ assert Ref.shortInfos(Ref.characterReference(charID)) != None
assert Ref.shortInfos(Ref.characterReference("999")) == None
assert Ref.shortInfos("") == -1
@@ -62,7 +62,7 @@ def test_references(MWSampleProject):
assert "\n" in Ref.infos(Ref.textReference(textID))
assert "Unknown" in Ref.infos(Ref.textReference("999"))
- assert Ref.shortInfos(Ref.textReference(textID)) is not None
+ assert Ref.shortInfos(Ref.textReference(textID)) != None
assert Ref.shortInfos(Ref.textReference("999")) == None
assert Ref.shortInfos("") == -1
@@ -73,7 +73,7 @@ def test_references(MWSampleProject):
assert "\n" in Ref.infos(Ref.worldReference(worldID))
assert "Unknown" in Ref.infos(Ref.worldReference("999"))
- assert Ref.shortInfos(Ref.worldReference(worldID)) is not None
+ assert Ref.shortInfos(Ref.worldReference(worldID)) != None
assert Ref.shortInfos(Ref.worldReference("999")) == None
assert Ref.shortInfos("") == -1
@@ -84,9 +84,9 @@ def test_references(MWSampleProject):
# Titles
for ref in refs:
- assert Ref.title(ref) is not None
- assert Ref.title("") is None
- assert Ref.title(Ref.plotReference("999")) is None
+ assert Ref.title(ref) != None
+ assert Ref.title("") == None
+ assert Ref.title(Ref.plotReference("999")) == None
# Other stuff
assert Ref.type(Ref.plotReference(plotID)) == Ref.PlotLetter
@@ -94,10 +94,10 @@ def test_references(MWSampleProject):
assert "Unknown" in Ref.tooltip(Ref.worldReference("999"))
assert "Not a ref" in Ref.tooltip("")
for ref in refs:
- assert Ref.tooltip(ref) is not None
+ assert Ref.tooltip(ref) != None
# Links
- assert Ref.refToLink("") is None
+ assert Ref.refToLink("") == None
assert Ref.refToLink(Ref.plotReference("999")) == Ref.plotReference("999")
assert Ref.refToLink(Ref.characterReference("999")) == Ref.characterReference("999")
assert Ref.refToLink(Ref.textReference("999")) == Ref.textReference("999")
@@ -106,7 +106,7 @@ def test_references(MWSampleProject):
assert "") is None
+ assert Ref.open("") == None
assert Ref.open(Ref.plotReference("999")) == False
assert Ref.open(Ref.characterReference("999")) == False
assert Ref.open(Ref.textReference("999")) == False
diff --git a/manuskript/tests/models/test_searchFilter.py b/manuskript/tests/models/test_searchFilter.py
new file mode 100644
index 0000000..8c484d0
--- /dev/null
+++ b/manuskript/tests/models/test_searchFilter.py
@@ -0,0 +1,41 @@
+#!/usr/bin/env python
+# --!-- coding: utf8 --!--
+
+import pytest
+from manuskript.models.searchFilter import searchFilter
+
+
+def test_searchFilter_constructionOk():
+ filter = searchFilter("label", True, [3])
+ assert filter.label() == "label"
+ assert filter.enabled() is True
+ assert filter.modelColumns() == [3]
+
+
+def test_searchFilter_constructionOkWithNoneModelColumn():
+ filter = searchFilter("label", True)
+ assert filter.label() == "label"
+ assert filter.enabled() is True
+ assert filter.modelColumns() == []
+
+
+def test_searchFilter_constructionBadLabelType():
+ with pytest.raises(TypeError, match=r".*label must be a str.*"):
+ searchFilter(13, True, [3])
+
+
+def test_searchFilter_constructionBadEnabledType():
+ with pytest.raises(TypeError, match=r".*enabled must be a bool.*"):
+ searchFilter("label", 3, [3])
+
+
+def test_searchFilter_constructionBadModelColumnType():
+ with pytest.raises(TypeError, match=r".*modelColumns must be a list or None.*"):
+ searchFilter("label", False, True)
+
+
+def test_searchFilter_setEnabled():
+ filter = searchFilter("label", True, [3])
+ assert filter.enabled() is True
+ filter.setEnabled(False)
+ assert filter.enabled() is False
diff --git a/manuskript/tests/models/test_searchResultModel.py b/manuskript/tests/models/test_searchResultModel.py
new file mode 100644
index 0000000..62d71ca
--- /dev/null
+++ b/manuskript/tests/models/test_searchResultModel.py
@@ -0,0 +1,16 @@
+#!/usr/bin/env python
+# --!-- coding: utf8 --!--
+
+from manuskript.models.searchResultModel import searchResultModel
+from manuskript.enums import Character
+
+
+def test_searchResultModel_constructionOk():
+ searchResult = searchResultModel("Character", "3", Character.notes, "Lucas", "A > B > C", (15, 18), "This is Lucas")
+ assert searchResult.id() == "3"
+ assert searchResult.column() == Character.notes
+ assert searchResult.title() == "Lucas"
+ assert searchResult.path() == "A > B > C"
+ assert searchResult.pos() == (15, 18)
+ assert searchResult.context() == "This is Lucas"
+
diff --git a/manuskript/tests/test_functions.py b/manuskript/tests/test_functions.py
index fbea5ab..fc8dffc 100644
--- a/manuskript/tests/test_functions.py
+++ b/manuskript/tests/test_functions.py
@@ -3,6 +3,7 @@
"""Tests for functions"""
+import re
from manuskript import functions as F
def test_wordCount():
@@ -46,8 +47,8 @@ def test_several():
assert F.iconColor(icon).name().lower() == "#ff0000"
# themeIcon
- assert F.themeIcon("text") is not None
- assert F.themeIcon("nonexistingname") is not None
+ assert F.themeIcon("text") != None
+ assert F.themeIcon("nonexistingname") != None
# randomColor
c1 = F.randomColor()
@@ -75,10 +76,10 @@ def test_outlineItemColors():
def test_paths():
- assert F.appPath() is not None
- assert F.writablePath is not None
+ assert F.appPath() != None
+ assert F.writablePath != None
assert len(F.allPaths("suffix")) == 2
- assert F.tempFile("yop") is not None
+ assert F.tempFile("yop") != None
f = F.findBackground("spacedreams.jpg")
assert "resources/backgrounds/spacedreams.jpg" in f
assert len(F.customIcons()) > 1
@@ -87,10 +88,59 @@ def test_mainWindow():
from PyQt5.QtWidgets import QWidget, QLCDNumber
- assert F.mainWindow() is not None
- assert F.MW is not None
+ assert F.mainWindow() != None
+ assert F.MW != None
F.statusMessage("Test")
F.printObjects()
assert len(F.findWidgetsOfClass(QWidget)) > 0
assert len(F.findWidgetsOfClass(QLCDNumber)) == 0
+
+
+def test_search_noMatch():
+ assert F.search(re.compile("text"), "foo") == []
+
+
+def test_search_singleLine_fullMatch():
+ assert F.search(re.compile("text"), "text") == [(0, 4, "text")]
+
+
+def test_search_singleLine_start():
+ assert F.search(re.compile("text"), "text is this") == [(0, 4, "text is this")]
+
+
+def test_search_singleLine_end():
+ assert F.search(re.compile("text"), "This is text") == [(8, 12, "This is text")]
+
+
+def test_search_multipleLines_fullMatch():
+ assert F.search(re.compile("text"), "This is\ntext\nOK") == [(8, 12, "[...] text [...]")]
+
+
+def test_search_multipleLines_start():
+ assert F.search(re.compile("text"), "This is\ntext oh yeah\nOK") == [(8, 12, "[...] text oh yeah [...]")]
+
+
+def test_search_multipleLines_end():
+ assert F.search(re.compile("text"), "This is\nsome text\nOK") == [(13, 17, "[...] some text [...]")]
+
+def test_search_multipleLines_full():
+ assert F.search(re.compile("text"), "This is\ntext\nOK") == [(8, 12, "[...] text [...]")]
+
+
+def test_search_multiple_strMatches():
+ assert F.search(re.compile("text"), "text, text and more text") == [
+ (0, 4, "text, text and more text"),
+ (6, 10, "text, text and more text"),
+ (20, 24, "text, text and more text")
+ ]
+
+
+def test_search_multiple_strMatches_caseSensitive():
+ assert F.search(re.compile("text"), "TeXt, TEXT and more text") == [(20, 24, "TeXt, TEXT and more text")]
+
+ assert F.search(re.compile("text", re.IGNORECASE), "TeXt, TEXT and more text") == [
+ (0, 4, "TeXt, TEXT and more text"),
+ (6, 10, "TeXt, TEXT and more text"),
+ (20, 24, "TeXt, TEXT and more text")
+ ]
\ No newline at end of file
diff --git a/manuskript/tests/test_settingsWindow.py b/manuskript/tests/test_settingsWindow.py
index 8edc6f3..71ed09f 100644
--- a/manuskript/tests/test_settingsWindow.py
+++ b/manuskript/tests/test_settingsWindow.py
@@ -55,7 +55,7 @@ def test_general(MWSampleProject):
state = settings()
assert chk.isChecked() == state
chk.setChecked(not state)
- assert chk.isChecked() is not state
+ assert chk.isChecked() != state
# Loading and Saving
SW.txtAutoSave.setText("0")
@@ -86,7 +86,7 @@ def test_general(MWSampleProject):
SW.chkOutlineTitle.setChecked(Qt.Unchecked)
SW.chkOutlineTitle.setChecked(Qt.Checked)
# Can't test because of the dialog
- # assert SW.setCorkColor() is None
+ # assert SW.setCorkColor() == None
SW.sldTreeIconSize.setValue(SW.sldTreeIconSize.value() + 1)
SW.rdoCorkNewStyle.toggled.emit(True)
SW.cmbCorkImage.currentIndexChanged.emit(0)
@@ -98,7 +98,7 @@ def test_general(MWSampleProject):
# Test editor
switchCheckBoxAndAssert(SW.chkEditorBackgroundTransparent,
lambda: S.textEditor["backgroundTransparent"])
- assert SW.restoreEditorColors() is None
+ assert SW.restoreEditorColors() == None
switchCheckBoxAndAssert(SW.chkEditorNoBlinking,
lambda: S.textEditor["cursorNotBlinking"])
# Twice on purpose: set and restore
@@ -108,7 +108,7 @@ def test_general(MWSampleProject):
SW.updateAllWidgets()
# Labels
- assert SW.updateLabelColor(MW.mdlLabels.item(1).index()) is None
+ assert SW.updateLabelColor(MW.mdlLabels.item(1).index()) == None
rc = MW.mdlLabels.rowCount()
SW.addLabel()
SW.lstLabels.setCurrentIndex(
@@ -150,7 +150,7 @@ def test_general(MWSampleProject):
for i in range(4):
SW.updateLineSpacing(i)
SW.updateUIFromTheme() # No time to wait on timer
- assert SW._editingTheme is not None
+ assert SW._editingTheme != None
SW.resize(SW.geometry().size()) # resizeEvent
#TODO: other edit test (see SW.loadTheme
SW.saveTheme()
diff --git a/manuskript/tests/ui/test_searchMenu.py b/manuskript/tests/ui/test_searchMenu.py
new file mode 100644
index 0000000..659c471
--- /dev/null
+++ b/manuskript/tests/ui/test_searchMenu.py
@@ -0,0 +1,56 @@
+#!/usr/bin/env python
+# --!-- coding: utf8 --!--
+
+from manuskript.ui.searchMenu import searchMenu
+from manuskript.enums import Outline, Character, FlatData, World, Plot, PlotStep, Model
+from manuskript.searchLabels import PLOT_STEP_COLUMNS_OFFSET
+
+
+def triggerFilter(filterKey, actions):
+ list(filter(lambda action: action.data() == filterKey, actions))[0].trigger()
+
+
+def test_searchMenu_defaultColumns():
+ """
+ By default all model columns are selected.
+ """
+ search_menu = searchMenu()
+
+ assert set(search_menu.columns(Model.Outline)) == {
+ Outline.title, Outline.text, Outline.summaryFull,
+ Outline.summarySentence, Outline.notes, Outline.POV,
+ Outline.status, Outline.label
+ }
+
+ assert set(search_menu.columns(Model.Character)) == {
+ Character.name, Character.motivation, Character.goal, Character.conflict,
+ Character.epiphany, Character.summarySentence, Character.summaryPara,
+ Character.summaryFull, Character.notes, Character.infos
+ }
+
+ assert set(search_menu.columns(Model.FlatData)) == {
+ FlatData.summarySituation, FlatData.summarySentence, FlatData.summaryPara,
+ FlatData.summaryPage, FlatData.summaryFull
+ }
+
+ assert set(search_menu.columns(Model.World)) == {
+ World.name, World.description, World.passion, World.conflict
+ }
+
+ assert set(search_menu.columns(Model.Plot)) == {
+ Plot.name, Plot.description, Plot.characters, Plot.result,
+ Plot.summary, PLOT_STEP_COLUMNS_OFFSET + PlotStep.meta
+ }
+
+
+def test_searchMenu_someColumns():
+ """
+ When deselecting some filters the columns associated to those filters are not returned.
+ """
+ search_menu = searchMenu()
+
+ triggerFilter(Model.Outline, search_menu.actions())
+ triggerFilter(Model.Character, search_menu.actions())
+
+ assert set(search_menu.columns(Model.Outline)) == set()
+ assert set(search_menu.columns(Model.Character)) == set()
diff --git a/manuskript/ui/collapsibleDockWidgets.py b/manuskript/ui/collapsibleDockWidgets.py
index 08c7331..5d2f18a 100644
--- a/manuskript/ui/collapsibleDockWidgets.py
+++ b/manuskript/ui/collapsibleDockWidgets.py
@@ -96,7 +96,7 @@ class collapsibleDockWidgets(QToolBar):
def setCurrentGroup(self, group):
self.currentGroup = group
for btn, action, widget, grp in self.otherWidgets:
- if not grp == group or grp is None:
+ if not grp == group or grp == None:
action.setVisible(False)
else:
action.setVisible(True)
diff --git a/manuskript/ui/collapsibleGroupBox.py b/manuskript/ui/collapsibleGroupBox.py
index 2b3cc04..e6c1b44 100644
--- a/manuskript/ui/collapsibleGroupBox.py
+++ b/manuskript/ui/collapsibleGroupBox.py
@@ -6,6 +6,8 @@ from PyQt5.QtWidgets import QSizePolicy, QGroupBox, QWidget, QStylePainter, QSty
QStyle, QStyleOptionFrame, QStyleOptionFocusRect
from manuskript.ui import style as S
+import logging
+LOGGER = logging.getLogger(__name__)
class collapsibleGroupBox(QGroupBox):
def __init__(self, parent=None):
@@ -25,7 +27,7 @@ class collapsibleGroupBox(QGroupBox):
self.tempWidget.setLayout(self.layout())
# Set empty layout
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)
self.setLayout(l)
self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Maximum)
diff --git a/manuskript/ui/editors/MDFunctions.py b/manuskript/ui/editors/MDFunctions.py
index 0fbd8b8..058de34 100644
--- a/manuskript/ui/editors/MDFunctions.py
+++ b/manuskript/ui/editors/MDFunctions.py
@@ -6,6 +6,8 @@ import re
from PyQt5.QtCore import QRegExp
from PyQt5.QtGui import QTextCursor
+import logging
+LOGGER = logging.getLogger(__name__)
def MDFormatSelection(editor, style):
"""
@@ -15,5 +17,5 @@ def MDFormatSelection(editor, style):
1: italic
2: code
"""
- print("Formatting:", style, " (Unimplemented yet !)")
+ LOGGER.error("Formatting: %s (Not implemented!)", style)
# FIXME
\ No newline at end of file
diff --git a/manuskript/ui/editors/blockUserData.py b/manuskript/ui/editors/blockUserData.py
index 7f39c2c..44340f5 100644
--- a/manuskript/ui/editors/blockUserData.py
+++ b/manuskript/ui/editors/blockUserData.py
@@ -8,7 +8,7 @@ class blockUserData(QTextBlockUserData):
def getUserData(block):
"""Returns userData if it exists, or a blank one."""
data = block.userData()
- if data is None:
+ if data == None:
data = blockUserData()
return data
diff --git a/manuskript/ui/editors/fullScreenEditor.py b/manuskript/ui/editors/fullScreenEditor.py
index 712d241..10b8657 100644
--- a/manuskript/ui/editors/fullScreenEditor.py
+++ b/manuskript/ui/editors/fullScreenEditor.py
@@ -2,11 +2,11 @@
# --!-- coding: utf8 --!--
import os
-from PyQt5.QtCore import Qt, QSize, QPoint, QRect, QEvent, QTime, QTimer
+from PyQt5.QtCore import Qt, QSize, QPoint, QRect, QEvent, QTime, QTimer, pyqtSignal
from PyQt5.QtGui import QFontMetrics, QColor, QBrush, QPalette, QPainter, QPixmap, QCursor
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QFrame, QWidget, QPushButton, qApp, QStyle, QComboBox, QLabel, QScrollBar, \
- QStyleOptionSlider, QHBoxLayout, QVBoxLayout, QMenu, QAction
+ QStyleOptionSlider, QHBoxLayout, QVBoxLayout, QMenu, QAction, QDesktopWidget
# Spell checker support
from manuskript import settings
@@ -19,9 +19,13 @@ from manuskript.ui.editors.themes import loadThemeDatas
from manuskript.ui.views.MDEditView import MDEditView
from manuskript.functions import Spellchecker
+import logging
+LOGGER = logging.getLogger(__name__)
class fullScreenEditor(QWidget):
- def __init__(self, index, parent=None):
+ exited = pyqtSignal()
+
+ def __init__(self, index, parent=None, screenNumber=None):
QWidget.__init__(self, parent)
self.setAttribute(Qt.WA_DeleteOnClose, True)
self._background = None
@@ -162,6 +166,12 @@ class fullScreenEditor(QWidget):
self.topPanel.setAutoHideVariable('autohide-top')
self.leftPanel.setAutoHideVariable('autohide-left')
+ # Set the screen to the same screen as the main window
+ if screenNumber is not None:
+ screenres = QDesktopWidget().screenGeometry(screenNumber);
+ self.move(QPoint(screenres.x(), screenres.y()));
+ self.resize(screenres.width(), screenres.height());
+
# Connection
self._index.model().dataChanged.connect(self.dataChanged)
@@ -170,13 +180,13 @@ class fullScreenEditor(QWidget):
# self.showMaximized()
# self.show()
- def __del__(self):
- # print("Leaving fullScreenEditor via Destructor event", flush=True)
- self.showNormal()
- self.close()
-
def leaveFullscreen(self):
+ self.__exit__("Leaving fullScreenEditor via leaveFullScreen.")
+
+ def __exit__(self, message):
+ LOGGER.debug(message)
self.showNormal()
+ self.exited.emit()
self.close()
def setLocked(self, val):
@@ -280,9 +290,7 @@ class fullScreenEditor(QWidget):
def keyPressEvent(self, event):
if event.key() in [Qt.Key_Escape, Qt.Key_F11] and \
not self._locked:
- # print("Leaving fullScreenEditor via keyPressEvent", flush=True)
- self.showNormal()
- self.close()
+ self.__exit__("Leaving fullScreenEditor via keyPressEvent.")
elif (event.modifiers() & Qt.AltModifier) and \
event.key() in [Qt.Key_PageUp, Qt.Key_PageDown, Qt.Key_Left, Qt.Key_Right]:
if event.key() in [Qt.Key_PageUp, Qt.Key_Left]:
@@ -338,8 +346,8 @@ class fullScreenEditor(QWidget):
item = self._index.internalPointer()
previousItem = self.previousTextItem(item)
nextItem = self.nextTextItem(item)
- self.btnPrevious.setEnabled(previousItem is not None)
- self.btnNext.setEnabled(nextItem is not None)
+ self.btnPrevious.setEnabled(previousItem != None)
+ self.btnNext.setEnabled(nextItem != None)
self.wPath.setItem(item)
def updateStatusBar(self):
@@ -572,11 +580,11 @@ class myPanel(QWidget):
def addWidgetSetting(self, label, config_name, widgets):
setting = (label, config_name, widgets)
self._settings.append(setting)
- if settings.fullscreenSettings.get(config_name, None) is not None:
+ if settings.fullscreenSettings.get(config_name, None) != None:
self._setSettingValue(setting, settings.fullscreenSettings[config_name])
def addSetting(self, label, config_name, default=True):
- if settings.fullscreenSettings.get(config_name, None) is None:
+ if settings.fullscreenSettings.get(config_name, None) == None:
self._setConfig(config_name, default)
self.addWidgetSetting(label, config_name, None)
@@ -651,7 +659,7 @@ class myPath(QWidget):
if i == item:
a.setIcon(QIcon.fromTheme("stock_yes"))
a.setEnabled(False)
- elif self.editor.firstTextItem(i) is None:
+ elif self.editor.firstTextItem(i) == None:
a.setEnabled(False)
else:
a.triggered.connect(gen_cb(i))
diff --git a/manuskript/ui/editors/locker.py b/manuskript/ui/editors/locker.py
index d9a648e..2a007a3 100644
--- a/manuskript/ui/editors/locker.py
+++ b/manuskript/ui/editors/locker.py
@@ -105,6 +105,6 @@ class locker(QWidget, Ui_locker):
text))
# Word locked
- elif self._target is not None:
+ elif self._target != None:
self.btnLock.setText(self.tr("{} words remaining").format(
self._target - self._words))
diff --git a/manuskript/ui/editors/mainEditor.py b/manuskript/ui/editors/mainEditor.py
index 967f283..7ee4300 100644
--- a/manuskript/ui/editors/mainEditor.py
+++ b/manuskript/ui/editors/mainEditor.py
@@ -5,7 +5,7 @@ import locale
from PyQt5.QtCore import QModelIndex, QRect, QPoint
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPixmap, QPainter, QIcon
-from PyQt5.QtWidgets import QWidget, qApp
+from PyQt5.QtWidgets import QWidget, qApp, QDesktopWidget
from manuskript import settings
from manuskript.enums import Outline
@@ -20,6 +20,9 @@ try:
except:
pass
+import logging
+LOGGER = logging.getLogger(__name__)
+
class mainEditor(QWidget, Ui_mainEditor):
"""
`mainEditor` is responsible for opening `outlineItem`s and offering information
@@ -64,6 +67,7 @@ class mainEditor(QWidget, Ui_mainEditor):
QWidget.__init__(self, parent)
self.setupUi(self)
self._updating = False
+ self._fullScreen = None
self.mw = mainWindow()
@@ -120,7 +124,7 @@ class mainEditor(QWidget, Ui_mainEditor):
return self.tabSplitter.tab
def currentEditor(self, tabWidget=None):
- if tabWidget is None:
+ if tabWidget == None:
tabWidget = self.currentTabWidget()
return tabWidget.currentWidget()
# return self.tab.currentWidget()
@@ -151,9 +155,13 @@ class mainEditor(QWidget, Ui_mainEditor):
for ts in reversed(self.allTabSplitters()):
ts.closeSplit()
+ def close(self):
+ if self._fullScreen is not None:
+ self._fullScreen.leaveFullscreen()
+
def allTabs(self, tabWidget=None):
"""Returns all the tabs from the given tabWidget. If tabWidget is None, from the current tabWidget."""
- if tabWidget is None:
+ if tabWidget == None:
tabWidget = self.currentTabWidget()
return [tabWidget.widget(i) for i in range(tabWidget.count())]
@@ -205,7 +213,7 @@ class mainEditor(QWidget, Ui_mainEditor):
title = self.getIndexTitle(index)
- if tabWidget is None:
+ if tabWidget == None:
tabWidget = self.currentTabWidget()
# Checking if tab is already opened
@@ -292,6 +300,7 @@ class mainEditor(QWidget, Ui_mainEditor):
return
index = self.currentEditor().currentIndex
+
if index.isValid():
item = index.internalPointer()
else:
@@ -300,15 +309,21 @@ class mainEditor(QWidget, Ui_mainEditor):
if not item:
item = self.mw.mdlOutline.rootItem
+ cc = item.data(Outline.charCount)
wc = item.data(Outline.wordCount)
goal = item.data(Outline.goal)
+ chars = item.data(Outline.charCount) # len(item.data(Outline.text))
progress = item.data(Outline.goalPercentage)
goal = uiParse(goal, None, int, lambda x: x>=0)
progress = uiParse(progress, 0.0, float)
+ if not cc:
+ cc = 0
+
if not wc:
wc = 0
+
if goal:
self.lblRedacProgress.show()
rect = self.lblRedacProgress.geometry()
@@ -319,13 +334,31 @@ class mainEditor(QWidget, Ui_mainEditor):
drawProgress(p, rect, progress, 2)
del p
self.lblRedacProgress.setPixmap(self.px)
- self.lblRedacWC.setText(self.tr("{} words / {} ").format(
- locale.format_string("%d", wc, grouping=True),
- locale.format_string("%d", goal, grouping=True)))
+
+ if settings.progressChars:
+ self.lblRedacWC.setText(self.tr("({} chars) {} words / {} ").format(
+ locale.format("%d", cc, grouping=True),
+ locale.format("%d", wc, grouping=True),
+ locale.format("%d", goal, grouping=True)))
+ self.lblRedacWC.setToolTip("")
+ else:
+ self.lblRedacWC.setText(self.tr("{} words / {} ").format(
+ locale.format("%d", wc, grouping=True),
+ locale.format("%d", goal, grouping=True)))
+ self.lblRedacWC.setToolTip(self.tr("{} chars").format(
+ locale.format("%d", cc, grouping=True)))
else:
self.lblRedacProgress.hide()
- self.lblRedacWC.setText(self.tr("{} words ").format(
- locale.format_string("%d", wc, grouping=True)))
+
+ if settings.progressChars:
+ self.lblRedacWC.setText(self.tr("{} chars ").format(
+ locale.format("%d", cc, grouping=True)))
+ self.lblRedacWC.setToolTip("")
+ else:
+ self.lblRedacWC.setText(self.tr("{} words ").format(
+ locale.format("%d", wc, grouping=True)))
+ self.lblRedacWC.setToolTip(self.tr("{} chars").format(
+ locale.format("%d", cc, grouping=True)))
###############################################################################
# VIEWS
@@ -354,14 +387,21 @@ class mainEditor(QWidget, Ui_mainEditor):
def showFullScreen(self):
if self.currentEditor():
- self._fullScreen = fullScreenEditor(self.currentEditor().currentIndex)
+ currentScreenNumber = QDesktopWidget().screenNumber(widget=self)
+ self._fullScreen = fullScreenEditor(
+ self.currentEditor().currentIndex,
+ screenNumber=currentScreenNumber)
+ # Clean the variable when closing fullscreen prevent errors
+ self._fullScreen.exited.connect(self.clearFullScreen)
+
+ def clearFullScreen(self):
+ self._fullScreen = None
###############################################################################
# DICT AND STUFF LIKE THAT
###############################################################################
def setDict(self, dict):
- print(dict)
for w in self.allAllTabs():
w.setDict(dict)
diff --git a/manuskript/ui/editors/tabSplitter.py b/manuskript/ui/editors/tabSplitter.py
index 13adfaf..d6f4ff3 100644
--- a/manuskript/ui/editors/tabSplitter.py
+++ b/manuskript/ui/editors/tabSplitter.py
@@ -10,6 +10,8 @@ from manuskript.functions import mainWindow, appPath
from manuskript.ui import style
from manuskript.ui.editors.tabSplitter_ui import Ui_tabSplitter
+import logging
+LOGGER = logging.getLogger(__name__)
class tabSplitter(QWidget, Ui_tabSplitter):
"""
@@ -39,7 +41,7 @@ class tabSplitter(QWidget, Ui_tabSplitter):
# try:
# self.tab.setTabBarAutoHide(True)
# 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
self.btnSplit = QPushButton(self)
@@ -145,8 +147,8 @@ class tabSplitter(QWidget, Ui_tabSplitter):
def split(self, toggled=None, state=None):
- if state is None and self.splitState == 0 or state == 1:
- if self.secondTab is None:
+ if state == None and self.splitState == 0 or state == 1:
+ if self.secondTab == None:
self.addSecondTab()
self.splitState = 1
@@ -155,8 +157,8 @@ class tabSplitter(QWidget, Ui_tabSplitter):
self.btnSplit.setIcon(QIcon.fromTheme("split-vertical"))
self.btnSplit.setToolTip(self.tr("Split horizontally"))
- elif state is None and self.splitState == 1 or state == 2:
- if self.secondTab is None:
+ elif state == None and self.splitState == 1 or state == 2:
+ if self.secondTab == None:
self.addSecondTab()
self.splitter.setOrientation(Qt.Vertical)
@@ -212,7 +214,7 @@ class tabSplitter(QWidget, Ui_tabSplitter):
# self.btnSplit.setGeometry(QRect(0, 0, 24, 24))
def focusChanged(self, old, new):
- if self.secondTab is None or new is None:
+ if self.secondTab == None or new == None:
return
oldFT = self.focusTab
diff --git a/manuskript/ui/highlighters/basicHighlighter.py b/manuskript/ui/highlighters/basicHighlighter.py
index 362ee5a..2e7358e 100644
--- a/manuskript/ui/highlighters/basicHighlighter.py
+++ b/manuskript/ui/highlighters/basicHighlighter.py
@@ -12,13 +12,14 @@ import manuskript.ui.style as S
from manuskript import settings
from manuskript import functions as F
+import logging
+LOGGER = logging.getLogger(__name__)
class BasicHighlighter(QSyntaxHighlighter):
def __init__(self, editor):
QSyntaxHighlighter.__init__(self, editor.document())
self.editor = editor
- self._misspelledColor = Qt.red
self._defaultBlockFormat = QTextBlockFormat()
self._defaultCharFormat = QTextCharFormat()
self.defaultTextColor = QColor(S.text)
@@ -27,6 +28,40 @@ class BasicHighlighter(QSyntaxHighlighter):
self.linkColor = QColor(S.link)
self.spellingErrorColor = QColor(Qt.red)
+ # Matches during checking can be separated by their type (all of them listed here):
+ # https://languagetool.org/development/api/org/languagetool/rules/ITSIssueType.html
+ #
+ # These are the colors for actual spell-, grammar- and style-checking:
+ self._errorColors = {
+ 'addition' : QColor(255, 215, 0), # gold
+ 'characters' : QColor(135, 206, 235), # sky blue
+ 'duplication' : QColor(0, 255, 255), # cyan / aqua
+ 'formatting' : QColor(0, 128, 128), # teal
+ 'grammar' : QColor(0, 0, 255), # blue
+ 'inconsistency' : QColor(128, 128, 0), # olive
+ 'inconsistententities' : QColor(46, 139, 87), # sea green
+ 'internationalization' : QColor(255, 165, 0), # orange
+ 'legal' : QColor(255, 69, 0), # orange red
+ 'length' : QColor(47, 79, 79), # dark slate gray
+ 'localespecificcontent' : QColor(188, 143, 143),# rosy brown
+ 'localeviolation' : QColor(128, 0, 0), # maroon
+ 'markup' : QColor(128, 0, 128), # purple
+ 'misspelling' : QColor(255, 0, 0), # red
+ 'mistranslation' : QColor(255, 0, 255), # magenta / fuchsia
+ 'nonconformance' : QColor(255, 218, 185), # peach puff
+ 'numbers' : QColor(65, 105, 225), # royal blue
+ 'omission' : QColor(255, 20, 147), # deep pink
+ 'other' : QColor(138, 43, 226), # blue violet
+ 'patternproblem' : QColor(0, 128, 0), # green
+ 'register' : QColor(112,128,144), # slate gray
+ 'style' : QColor(0, 255, 0), # lime
+ 'terminology' : QColor(0, 0, 128), # navy
+ 'typographical' : QColor(255, 255, 0), # yellow
+ 'uncategorized' : QColor(128, 128, 128), # gray
+ 'untranslated' : QColor(210, 105, 30), # chocolate
+ 'whitespace' : QColor(192, 192, 192) # silver
+ }
+
def setDefaultBlockFormat(self, bf):
self._defaultBlockFormat = bf
self.rehighlight()
@@ -36,7 +71,7 @@ class BasicHighlighter(QSyntaxHighlighter):
self.rehighlight()
def setMisspelledColor(self, color):
- self._misspelledColor = color
+ self._errorColors['misspelled'] = color
def updateColorScheme(self, rehighlight=True):
"""
@@ -97,14 +132,14 @@ class BasicHighlighter(QSyntaxHighlighter):
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.joinPreviousEditBlock()
bf = QTextBlockFormat(self._defaultBlockFormat)
if bf != c.blockFormat():
c.setBlockFormat(bf)
#c.endEditBlock()
- #print(" ", self.currentBlock().document().availableUndoSteps())
+ #LOGGER.debug("undoSteps after: %s", self.currentBlock().document().availableUndoSteps())
# self.setFormat(0, len(text), self._defaultCharFormat)
@@ -134,32 +169,25 @@ class BasicHighlighter(QSyntaxHighlighter):
txt.end() - txt.start(),
fmt)
- # Spell checking
+ if hasattr(self.editor, "spellcheck") and self.editor.spellcheck and self.editor._dict:
+ # Spell checking
- # Following algorithm would not check words at the end of line.
- # This hacks adds a space to every line where the text cursor is not
- # So that it doesn't spellcheck while typing, but still spellchecks at
- # end of lines. See github's issue #166.
- textedText = text
- if self.currentBlock().position() + len(text) != \
- self.editor.textCursor().position():
- textedText = text + " "
+ # Following algorithm would not check words at the end of line.
+ # This hacks adds a space to every line where the text cursor is not
+ # So that it doesn't spellcheck while typing, but still spellchecks at
+ # end of lines. See github's issue #166.
+ textedText = text
+ if self.currentBlock().position() + len(text) != \
+ self.editor.textCursor().position():
+ textedText = text + " "
- # Based on http://john.nachtimwald.com/2009/08/22/qplaintextedit-with-in-line-spell-check/
- WORDS = r'(?iu)((?:[^_\W]|\')+)[^A-Za-z0-9\']'
- # (?iu) means case insensitive and Unicode
- # ((?:[^_\W]|\')+) means words exclude underscores but include apostrophes
- # [^A-Za-z0-9\'] used with above hack to prevent spellcheck while typing word
- #
- # See also https://stackoverflow.com/questions/2062169/regex-w-in-utf-8
- if hasattr(self.editor, "spellcheck") and self.editor.spellcheck:
- for word_object in re.finditer(WORDS, textedText):
- if (self.editor._dict
- and self.editor._dict.isMisspelled(word_object.group(1))):
- format = self.format(word_object.start(1))
- format.setUnderlineColor(self._misspelledColor)
+ # The text should only be checked once as a whole
+ for match in self.editor._dict.checkText(textedText):
+ if match.locqualityissuetype in self._errorColors:
+ highlight_color = self._errorColors[match.locqualityissuetype]
+
+ format = self.format(match.start)
+ format.setUnderlineColor(highlight_color)
# SpellCheckUnderline fails with some fonts
format.setUnderlineStyle(QTextCharFormat.WaveUnderline)
- self.setFormat(word_object.start(1),
- word_object.end(1) - word_object.start(1),
- format)
+ self.setFormat(match.start, match.end - match.start, format)
diff --git a/manuskript/ui/highlighters/markdownHighlighter.py b/manuskript/ui/highlighters/markdownHighlighter.py
index 4d873b4..3a1564e 100644
--- a/manuskript/ui/highlighters/markdownHighlighter.py
+++ b/manuskript/ui/highlighters/markdownHighlighter.py
@@ -713,7 +713,7 @@ class MarkdownHighlighter(BasicHighlighter):
# FIXME: TypeError: could not convert 'TextBlockData' to 'QTextBlockUserData'
# blockData = self.currentBlockUserData()
- # if blockData is None:
+ # if blockData == None:
# blockData = TextBlockData(self.document(), self.currentBlock())
#
# self.setCurrentBlockUserData(blockData)
diff --git a/manuskript/ui/highlighters/markdownTokenizer.py b/manuskript/ui/highlighters/markdownTokenizer.py
index 9bae886..237b18e 100644
--- a/manuskript/ui/highlighters/markdownTokenizer.py
+++ b/manuskript/ui/highlighters/markdownTokenizer.py
@@ -9,6 +9,9 @@ from PyQt5.QtWidgets import *
from manuskript.ui.highlighters import MarkdownState as MS
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.
# http://wereturtle.github.io/ghostwriter/
# GPLV3+.
@@ -56,7 +59,7 @@ class HighlightTokenizer:
self.tokens.append(token)
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):
self.state = state
diff --git a/manuskript/ui/highlighters/searchResultHighlighters/__init__.py b/manuskript/ui/highlighters/searchResultHighlighters/__init__.py
new file mode 100644
index 0000000..7af0224
--- /dev/null
+++ b/manuskript/ui/highlighters/searchResultHighlighters/__init__.py
@@ -0,0 +1,2 @@
+#!/usr/bin/env python
+# --!-- coding: utf8 --!--
diff --git a/manuskript/ui/highlighters/searchResultHighlighters/abstractSearchResultHighlighter.py b/manuskript/ui/highlighters/searchResultHighlighters/abstractSearchResultHighlighter.py
new file mode 100644
index 0000000..393c1bc
--- /dev/null
+++ b/manuskript/ui/highlighters/searchResultHighlighters/abstractSearchResultHighlighter.py
@@ -0,0 +1,13 @@
+#!/usr/bin/env python
+# --!-- coding: utf8 --!--
+
+
+class abstractSearchResultHighlighter():
+ """
+ Interface for all classes highlighting search results on widgets.
+ """
+ def __init__(self):
+ pass
+
+ def highlightSearchResult(self, searchResult):
+ raise NotImplementedError
diff --git a/manuskript/ui/highlighters/searchResultHighlighters/abstractSpecificSearchResultHighlighter.py b/manuskript/ui/highlighters/searchResultHighlighters/abstractSpecificSearchResultHighlighter.py
new file mode 100644
index 0000000..3a310e0
--- /dev/null
+++ b/manuskript/ui/highlighters/searchResultHighlighters/abstractSpecificSearchResultHighlighter.py
@@ -0,0 +1,24 @@
+#!/usr/bin/env python
+# --!-- coding: utf8 --!--
+
+
+from manuskript.ui.highlighters.searchResultHighlighters.widgetSelectionHighlighter import widgetSelectionHighlighter
+
+
+class abstractSearchResultHighlighter():
+ def __init__(self):
+ self._widgetSelectionHighlighter = widgetSelectionHighlighter()
+
+ def highlightSearchResult(self, searchResult):
+ self.openView(searchResult)
+ widgets = self.retrieveWidget(searchResult)
+ if not isinstance(widgets, list):
+ widgets = [widgets]
+ for i in range(len(widgets)):
+ self._widgetSelectionHighlighter.highlight_widget_selection(widgets[i], searchResult.pos()[i][0], searchResult.pos()[i][1], i == len(widgets) - 1)
+
+ def openView(self, searchResult):
+ raise RuntimeError
+
+ def retrieveWidget(self, searchResult):
+ raise RuntimeError
diff --git a/manuskript/ui/highlighters/searchResultHighlighters/characterSearchResultHighlighter.py b/manuskript/ui/highlighters/searchResultHighlighters/characterSearchResultHighlighter.py
new file mode 100644
index 0000000..16a200d
--- /dev/null
+++ b/manuskript/ui/highlighters/searchResultHighlighters/characterSearchResultHighlighter.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+# --!-- coding: utf8 --!--
+
+
+from manuskript.models import references as Ref
+from manuskript.functions import mainWindow
+from manuskript.enums import Character
+from PyQt5.QtWidgets import QTextEdit, QTableView, QLineEdit
+from manuskript.ui.highlighters.searchResultHighlighters.abstractSpecificSearchResultHighlighter import abstractSearchResultHighlighter
+
+
+class characterSearchResultHighlighter(abstractSearchResultHighlighter):
+ def __init__(self):
+ super().__init__()
+
+ def openView(self, searchResult):
+ r = Ref.characterReference(searchResult.id())
+ Ref.open(r)
+ mainWindow().tabPersos.setEnabled(True)
+
+ def retrieveWidget(self, searchResult):
+ textEditMap = {
+ Character.name: (0, "txtPersoName", QLineEdit),
+ Character.goal: (0, "txtPersoGoal", QTextEdit),
+ Character.motivation: (0, "txtPersoMotivation", QTextEdit),
+ Character.conflict: (0, "txtPersoConflict", QTextEdit),
+ Character.epiphany: (0, "txtPersoEpiphany", QTextEdit),
+ Character.summarySentence: (0, "txtPersoSummarySentence", QTextEdit),
+ Character.summaryPara: (0, "txtPersoSummaryPara", QTextEdit),
+ Character.summaryFull: (1, "txtPersoSummaryFull", QTextEdit),
+ Character.notes: (2, "txtPersoNotes", QTextEdit),
+ Character.infos: (3, "tblPersoInfos", QTableView)
+ }
+
+ characterTabIndex, characterWidgetName, characterWidgetClass = textEditMap[searchResult.column()]
+
+ mainWindow().tabPersos.setCurrentIndex(characterTabIndex)
+ return mainWindow().tabPersos.findChild(characterWidgetClass, characterWidgetName)
diff --git a/manuskript/ui/highlighters/searchResultHighlighters/flatDataSearchResultHighlighter.py b/manuskript/ui/highlighters/searchResultHighlighters/flatDataSearchResultHighlighter.py
new file mode 100644
index 0000000..4d68fc9
--- /dev/null
+++ b/manuskript/ui/highlighters/searchResultHighlighters/flatDataSearchResultHighlighter.py
@@ -0,0 +1,29 @@
+#!/usr/bin/env python
+# --!-- coding: utf8 --!--
+
+from manuskript.functions import mainWindow
+from manuskript.enums import FlatData
+from PyQt5.QtWidgets import QTextEdit, QLineEdit
+from manuskript.ui.highlighters.searchResultHighlighters.abstractSpecificSearchResultHighlighter import abstractSearchResultHighlighter
+
+
+class flatDataSearchResultHighlighter(abstractSearchResultHighlighter):
+ def __init__(self):
+ super().__init__()
+
+ def openView(self, searchResult):
+ mainWindow().tabMain.setCurrentIndex(mainWindow().TabSummary)
+
+ def retrieveWidget(self, searchResult):
+ editors = {
+ FlatData.summarySituation: (0, "txtSummarySituation", QLineEdit, mainWindow()),
+ FlatData.summarySentence: (0, "txtSummarySentence", QTextEdit, mainWindow().tabSummary),
+ FlatData.summaryPara: (1, "txtSummaryPara", QTextEdit, mainWindow().tabSummary),
+ FlatData.summaryPage: (2, "txtSummaryPage", QTextEdit, mainWindow().tabSummary),
+ FlatData.summaryFull: (3, "txtSummaryFull", QTextEdit, mainWindow().tabSummary)
+ }
+
+ stackIndex, editorName, editorClass, rootWidget = editors[searchResult.column()]
+
+ mainWindow().tabSummary.setCurrentIndex(stackIndex)
+ return rootWidget.findChild(editorClass, editorName)
diff --git a/manuskript/ui/highlighters/searchResultHighlighters/outlineSearchResultHighlighter.py b/manuskript/ui/highlighters/searchResultHighlighters/outlineSearchResultHighlighter.py
new file mode 100644
index 0000000..801f7cd
--- /dev/null
+++ b/manuskript/ui/highlighters/searchResultHighlighters/outlineSearchResultHighlighter.py
@@ -0,0 +1,47 @@
+#!/usr/bin/env python
+# --!-- coding: utf8 --!--
+
+from manuskript.models import references as Ref
+from manuskript.enums import Outline
+from manuskript.ui.highlighters.searchResultHighlighters.abstractSpecificSearchResultHighlighter import abstractSearchResultHighlighter
+from manuskript.functions import mainWindow
+from PyQt5.QtWidgets import QTextEdit, QLineEdit, QLabel
+from manuskript.ui.views.metadataView import metadataView
+from manuskript.ui.collapsibleGroupBox2 import collapsibleGroupBox2
+
+
+class outlineSearchResultHighlighter(abstractSearchResultHighlighter):
+ def __init__(self):
+ super().__init__()
+ self.outline_index = None
+
+ def openView(self, searchResult):
+ r = Ref.textReference(searchResult.id())
+ Ref.open(r)
+
+ def retrieveWidget(self, searchResult):
+ editors = {
+ Outline.text: ("txtRedacText", QTextEdit, None),
+ Outline.title: ("txtTitle", QLineEdit, "grpProperties"),
+ Outline.summarySentence: ("txtSummarySentence", QLineEdit, "grpSummary"),
+ Outline.summaryFull: ("txtSummaryFull", QTextEdit, "grpSummary"),
+ Outline.notes: ("txtNotes", QTextEdit, "grpNotes"),
+
+ # TODO: Tried to highlight the combo box themselves (ie. cmbPOV) but didn't succeed.
+ Outline.POV: ("lblPOV", QLabel, "grpProperties"),
+ Outline.status: ("lblStatus", QLabel, "grpProperties"),
+ Outline.label: ("lblLabel", QLabel, "grpProperties")
+ }
+
+ editorName, editorClass, parentName = editors[searchResult.column()]
+
+ # Metadata columns are inside a splitter widget that my be hidden, so we show them.
+ if parentName:
+ metadataViewWidget = mainWindow().findChild(metadataView, "redacMetadata")
+ metadataViewWidget.show()
+ metadataViewWidget.findChild(collapsibleGroupBox2, parentName).button.setChecked(True)
+ widget = metadataViewWidget.findChild(editorClass, editorName)
+ else:
+ widget = mainWindow().mainEditor.currentEditor().findChild(editorClass, editorName)
+
+ return widget
diff --git a/manuskript/ui/highlighters/searchResultHighlighters/plotSearchResultHighlighter.py b/manuskript/ui/highlighters/searchResultHighlighters/plotSearchResultHighlighter.py
new file mode 100644
index 0000000..94578a3
--- /dev/null
+++ b/manuskript/ui/highlighters/searchResultHighlighters/plotSearchResultHighlighter.py
@@ -0,0 +1,32 @@
+#!/usr/bin/env python
+# --!-- coding: utf8 --!--
+
+
+from manuskript.models import references as Ref
+from manuskript.functions import mainWindow
+from manuskript.enums import Plot
+from PyQt5.QtWidgets import QTextEdit, QLineEdit, QListView
+from manuskript.ui.highlighters.searchResultHighlighters.abstractSpecificSearchResultHighlighter import abstractSearchResultHighlighter
+
+
+class plotSearchResultHighlighter(abstractSearchResultHighlighter):
+ def __init__(self):
+ super().__init__()
+
+ def openView(self, searchResult):
+ r = Ref.plotReference(searchResult.id())
+ Ref.open(r)
+ mainWindow().tabPlot.setEnabled(True)
+
+ def retrieveWidget(self, searchResult):
+ textEditMap = {
+ Plot.name: (0, "txtPlotName", QLineEdit),
+ Plot.description: (0, "txtPlotDescription", QTextEdit),
+ Plot.characters: (0, "lstPlotPerso", QListView),
+ Plot.result: (0, "txtPlotResult", QTextEdit)
+ }
+
+ tabIndex, widgetName, widgetClass = textEditMap[searchResult.column()]
+
+ mainWindow().tabPlot.setCurrentIndex(tabIndex)
+ return mainWindow().tabPlot.findChild(widgetClass, widgetName)
diff --git a/manuskript/ui/highlighters/searchResultHighlighters/plotStepSearchResultHighlighter.py b/manuskript/ui/highlighters/searchResultHighlighters/plotStepSearchResultHighlighter.py
new file mode 100644
index 0000000..7b7b146
--- /dev/null
+++ b/manuskript/ui/highlighters/searchResultHighlighters/plotStepSearchResultHighlighter.py
@@ -0,0 +1,35 @@
+#!/usr/bin/env python
+# --!-- coding: utf8 --!--
+
+
+from manuskript.models import references as Ref
+from manuskript.functions import mainWindow
+from manuskript.enums import PlotStep
+from PyQt5.QtWidgets import QTableView, QTextEdit
+from manuskript.ui.highlighters.searchResultHighlighters.abstractSpecificSearchResultHighlighter import abstractSearchResultHighlighter
+
+
+class plotStepSearchResultHighlighter(abstractSearchResultHighlighter):
+ def __init__(self):
+ super().__init__()
+
+ def openView(self, searchResult):
+ r = Ref.plotReference(searchResult.id())
+ Ref.open(r)
+ mainWindow().tabPlot.setEnabled(True)
+
+ def retrieveWidget(self, searchResult):
+ textEditMap = {
+ PlotStep.name: [(1, "lstSubPlots", QTableView)],
+ PlotStep.meta: [(1, "lstSubPlots", QTableView)],
+ PlotStep.summary: [(1, "lstSubPlots", QTableView), (1, "txtSubPlotSummary", QTextEdit)]
+ }
+
+ map = textEditMap[searchResult.column()]
+ widgets = []
+ for tabIndex, widgetName, widgetClass in map:
+ mainWindow().tabPlot.setCurrentIndex(tabIndex)
+
+ widgets.append(mainWindow().tabPlot.findChild(widgetClass, widgetName))
+
+ return widgets
diff --git a/manuskript/ui/highlighters/searchResultHighlighters/searchResultHighlighter.py b/manuskript/ui/highlighters/searchResultHighlighters/searchResultHighlighter.py
new file mode 100644
index 0000000..eeb1aa8
--- /dev/null
+++ b/manuskript/ui/highlighters/searchResultHighlighters/searchResultHighlighter.py
@@ -0,0 +1,34 @@
+#!/usr/bin/env python
+# --!-- coding: utf8 --!--
+
+from manuskript.ui.highlighters.searchResultHighlighters.abstractSearchResultHighlighter import abstractSearchResultHighlighter
+from manuskript.ui.highlighters.searchResultHighlighters.characterSearchResultHighlighter import characterSearchResultHighlighter
+from manuskript.ui.highlighters.searchResultHighlighters.flatDataSearchResultHighlighter import flatDataSearchResultHighlighter
+from manuskript.ui.highlighters.searchResultHighlighters.outlineSearchResultHighlighter import outlineSearchResultHighlighter
+from manuskript.ui.highlighters.searchResultHighlighters.worldSearchResultHighlighter import worldSearchResultHighlighter
+from manuskript.ui.highlighters.searchResultHighlighters.plotSearchResultHighlighter import plotSearchResultHighlighter
+from manuskript.ui.highlighters.searchResultHighlighters.plotStepSearchResultHighlighter import plotStepSearchResultHighlighter
+from manuskript.enums import Model
+
+
+class searchResultHighlighter(abstractSearchResultHighlighter):
+ def __init__(self):
+ super().__init__()
+
+ def highlightSearchResult(self, searchResult):
+ if searchResult.type() == Model.Character:
+ highlighter = characterSearchResultHighlighter()
+ elif searchResult.type() == Model.FlatData:
+ highlighter = flatDataSearchResultHighlighter()
+ elif searchResult.type() == Model.Outline:
+ highlighter = outlineSearchResultHighlighter()
+ elif searchResult.type() == Model.World:
+ highlighter = worldSearchResultHighlighter()
+ elif searchResult.type() == Model.Plot:
+ highlighter = plotSearchResultHighlighter()
+ elif searchResult.type() == Model.PlotStep:
+ highlighter = plotStepSearchResultHighlighter()
+ else:
+ raise NotImplementedError
+
+ highlighter.highlightSearchResult(searchResult)
diff --git a/manuskript/ui/highlighters/searchResultHighlighters/widgetSelectionHighlighter.py b/manuskript/ui/highlighters/searchResultHighlighters/widgetSelectionHighlighter.py
new file mode 100644
index 0000000..1533387
--- /dev/null
+++ b/manuskript/ui/highlighters/searchResultHighlighters/widgetSelectionHighlighter.py
@@ -0,0 +1,91 @@
+#!/usr/bin/env python
+# --!-- coding: utf8 --!--
+
+from PyQt5.QtGui import QTextCursor
+from PyQt5.QtWidgets import QTextEdit, QTableView, QListView, QLineEdit, QPlainTextEdit, QLabel
+
+
+class widgetSelectionHighlighter():
+ """
+ Utility class for highlighting a search result on a widget.
+ """
+ def __init__(self):
+ pass
+
+ def highlight_widget_selection(self, widget, startPos, endPos, clearOnFocusOut=True):
+ if isinstance(widget, QTextEdit) or isinstance(widget, QPlainTextEdit):
+ self._highlightTextEditSearchResult(widget, startPos, endPos, clearOnFocusOut)
+ elif isinstance(widget, QLineEdit):
+ self._highlightLineEditSearchResult(widget, startPos, endPos, clearOnFocusOut)
+ elif isinstance(widget, QTableView):
+ self._highlightTableViewSearchResult(widget, startPos, clearOnFocusOut)
+ elif isinstance(widget, QListView):
+ self._highlightListViewSearchResult(widget, startPos, clearOnFocusOut)
+ elif isinstance(widget, QLabel):
+ self._highlightLabelSearchResult(widget, clearOnFocusOut)
+ else:
+ raise NotImplementedError
+
+ widget.setFocus(True)
+
+ @staticmethod
+ def generateClearHandler(widget, clearCallback):
+ """
+ Generates a clear handler to be run when the given widget loses focus.
+
+ :param widget: widget we want to attach the handler to
+ :param clearCallback: callback to be called when the given widget loses focus.
+ :return:
+ """
+ def clearHandler(_widget, previous_on_focus_out_event):
+ clearCallback(_widget)
+ _widget.focusOutEvent = previous_on_focus_out_event
+
+ widget.focusOutEvent = lambda e: clearHandler(widget, widget.focusOutEvent)
+
+ def _highlightTextEditSearchResult(self, textEdit, startPos, endPos, clearOnFocusOut):
+ # On focus out, clear text edit selection.
+ oldTextCursor = textEdit.textCursor()
+ if clearOnFocusOut:
+ self.generateClearHandler(textEdit, lambda widget: widget.setTextCursor(oldTextCursor))
+
+ # Highlight search result on the text edit.
+ c = textEdit.textCursor()
+ c.setPosition(startPos)
+ c.setPosition(endPos, QTextCursor.KeepAnchor)
+ textEdit.setTextCursor(c)
+
+ def _highlightLineEditSearchResult(self, lineEdit, startPos, endPos, clearOnFocusOut):
+ # On focus out, clear line edit selection.
+ if clearOnFocusOut:
+ self.generateClearHandler(lineEdit, lambda widget: widget.deselect())
+
+ # Highlight search result on line edit.
+ lineEdit.setCursorPosition(startPos)
+ lineEdit.cursorForward(True, endPos - startPos)
+
+ def _highlightTableViewSearchResult(self, tableView, startPos, clearOnFocusOut):
+ # On focus out, clear table selection.
+ if clearOnFocusOut:
+ self.generateClearHandler(tableView, lambda widget: widget.clearSelection())
+
+ # Highlight table row containing search result.
+ tableView.selectRow(startPos)
+
+ def _highlightListViewSearchResult(self, listView, startPos, clearOnFocusOut):
+ # On focus out, clear table selection.
+ if clearOnFocusOut:
+ self.generateClearHandler(listView, lambda widget: widget.selectionModel().clearSelection())
+
+ # Highlight list item containing search result.
+ listView.setCurrentIndex(listView.model().index(startPos, 0, listView.rootIndex()))
+
+ def _highlightLabelSearchResult(self, label, clearOnFocusOut):
+ # On focus out, clear label selection.
+ # FIXME: This would overwrite all styles!
+ oldStyle = label.styleSheet()
+ if clearOnFocusOut:
+ self.generateClearHandler(label, lambda widget: widget.setStyleSheet(oldStyle))
+
+ # Highlight search result on label.
+ label.setStyleSheet("background-color: steelblue")
diff --git a/manuskript/ui/highlighters/searchResultHighlighters/worldSearchResultHighlighter.py b/manuskript/ui/highlighters/searchResultHighlighters/worldSearchResultHighlighter.py
new file mode 100644
index 0000000..0556b0c
--- /dev/null
+++ b/manuskript/ui/highlighters/searchResultHighlighters/worldSearchResultHighlighter.py
@@ -0,0 +1,32 @@
+#!/usr/bin/env python
+# --!-- coding: utf8 --!--
+
+
+from manuskript.models import references as Ref
+from manuskript.functions import mainWindow
+from manuskript.enums import World
+from PyQt5.QtWidgets import QTextEdit, QLineEdit
+from manuskript.ui.highlighters.searchResultHighlighters.abstractSpecificSearchResultHighlighter import abstractSearchResultHighlighter
+
+
+class worldSearchResultHighlighter(abstractSearchResultHighlighter):
+ def __init__(self):
+ super().__init__()
+
+ def openView(self, searchResult):
+ r = Ref.worldReference(searchResult.id())
+ Ref.open(r)
+ mainWindow().tabWorld.setEnabled(True)
+
+ def retrieveWidget(self, searchResult):
+ textEditMap = {
+ World.name: (0, "txtWorldName", QLineEdit),
+ World.description: (0, "txtWorldDescription", QTextEdit),
+ World.passion: (1, "txtWorldPassion", QTextEdit),
+ World.conflict: (1, "txtWorldConflict", QTextEdit),
+ }
+
+ tabIndex, widgetName, widgetClass = textEditMap[searchResult.column()]
+
+ mainWindow().tabWorld.setCurrentIndex(tabIndex)
+ return mainWindow().tabWorld.findChild(widgetClass, widgetName)
diff --git a/manuskript/ui/mainWindow.py b/manuskript/ui/mainWindow.py
index 76f0822..eb8ce7f 100644
--- a/manuskript/ui/mainWindow.py
+++ b/manuskript/ui/mainWindow.py
@@ -2,12 +2,15 @@
# Form implementation generated from reading ui file 'manuskript/ui/mainWindow.ui'
#
-# Created by: PyQt5 UI code generator 5.5.1
+# Created by: PyQt5 UI code generator 5.15.4
#
-# WARNING! All changes made in this file will be lost!
+# WARNING: Any manual changes made to this file will be lost when pyuic5 is
+# run again. Do not edit this file unless you know what you are doing.
+
from PyQt5 import QtCore, QtGui, QtWidgets
+
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
@@ -378,69 +381,11 @@ class Ui_MainWindow(object):
self.scrollAreaPersoInfos.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop)
self.scrollAreaPersoInfos.setObjectName("scrollAreaPersoInfos")
self.scrollAreaPersoInfosWidget = QtWidgets.QWidget()
- self.scrollAreaPersoInfosWidget.setGeometry(QtCore.QRect(0, 0, 204, 606))
+ self.scrollAreaPersoInfosWidget.setGeometry(QtCore.QRect(0, 0, 429, 719))
self.scrollAreaPersoInfosWidget.setObjectName("scrollAreaPersoInfosWidget")
self.formLayout_8 = QtWidgets.QFormLayout(self.scrollAreaPersoInfosWidget)
self.formLayout_8.setFieldGrowthPolicy(QtWidgets.QFormLayout.AllNonFixedFieldsGrow)
self.formLayout_8.setObjectName("formLayout_8")
- self.label_4 = QtWidgets.QLabel(self.scrollAreaPersoInfosWidget)
- self.label_4.setObjectName("label_4")
- self.formLayout_8.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.label_4)
- self.txtPersoMotivation = MDEditCompleter(self.scrollAreaPersoInfosWidget)
- self.txtPersoMotivation.setObjectName("txtPersoMotivation")
- self.formLayout_8.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.txtPersoMotivation)
- self.label_5 = QtWidgets.QLabel(self.scrollAreaPersoInfosWidget)
- self.label_5.setObjectName("label_5")
- self.formLayout_8.setWidget(5, QtWidgets.QFormLayout.LabelRole, self.label_5)
- self.txtPersoGoal = MDEditCompleter(self.scrollAreaPersoInfosWidget)
- self.txtPersoGoal.setObjectName("txtPersoGoal")
- self.formLayout_8.setWidget(5, QtWidgets.QFormLayout.FieldRole, self.txtPersoGoal)
- self.label_6 = QtWidgets.QLabel(self.scrollAreaPersoInfosWidget)
- self.label_6.setObjectName("label_6")
- self.formLayout_8.setWidget(6, QtWidgets.QFormLayout.LabelRole, self.label_6)
- self.txtPersoConflict = MDEditCompleter(self.scrollAreaPersoInfosWidget)
- self.txtPersoConflict.setObjectName("txtPersoConflict")
- self.formLayout_8.setWidget(6, QtWidgets.QFormLayout.FieldRole, self.txtPersoConflict)
- self.label_7 = QtWidgets.QLabel(self.scrollAreaPersoInfosWidget)
- self.label_7.setObjectName("label_7")
- self.formLayout_8.setWidget(7, QtWidgets.QFormLayout.LabelRole, self.label_7)
- self.txtPersoEpiphany = MDEditCompleter(self.scrollAreaPersoInfosWidget)
- self.txtPersoEpiphany.setObjectName("txtPersoEpiphany")
- self.formLayout_8.setWidget(7, QtWidgets.QFormLayout.FieldRole, self.txtPersoEpiphany)
- self.label_24 = QtWidgets.QLabel(self.scrollAreaPersoInfosWidget)
- self.label_24.setObjectName("label_24")
- self.formLayout_8.setWidget(8, QtWidgets.QFormLayout.LabelRole, self.label_24)
- self.txtPersoSummarySentence = MDEditCompleter(self.scrollAreaPersoInfosWidget)
- self.txtPersoSummarySentence.setObjectName("txtPersoSummarySentence")
- self.formLayout_8.setWidget(8, QtWidgets.QFormLayout.FieldRole, self.txtPersoSummarySentence)
- self.label_8 = QtWidgets.QLabel(self.scrollAreaPersoInfosWidget)
- self.label_8.setObjectName("label_8")
- self.formLayout_8.setWidget(9, QtWidgets.QFormLayout.LabelRole, self.label_8)
- self.txtPersoSummaryPara = MDEditCompleter(self.scrollAreaPersoInfosWidget)
- self.txtPersoSummaryPara.setObjectName("txtPersoSummaryPara")
- self.formLayout_8.setWidget(9, QtWidgets.QFormLayout.FieldRole, self.txtPersoSummaryPara)
- self.horizontalLayout_21 = QtWidgets.QHBoxLayout()
- self.horizontalLayout_21.setObjectName("horizontalLayout_21")
- spacerItem10 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
- self.horizontalLayout_21.addItem(spacerItem10)
- self.btnStepFour = QtWidgets.QPushButton(self.scrollAreaPersoInfosWidget)
- icon = QtGui.QIcon.fromTheme("go-next")
- self.btnStepFour.setIcon(icon)
- self.btnStepFour.setFlat(True)
- self.btnStepFour.setObjectName("btnStepFour")
- self.horizontalLayout_21.addWidget(self.btnStepFour)
- self.formLayout_8.setLayout(10, QtWidgets.QFormLayout.FieldRole, self.horizontalLayout_21)
- self.label_18 = QtWidgets.QLabel(self.scrollAreaPersoInfosWidget)
- self.label_18.setObjectName("label_18")
- self.formLayout_8.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.label_18)
- self.sldPersoImportance = sldImportance(self.scrollAreaPersoInfosWidget)
- sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred)
- sizePolicy.setHorizontalStretch(0)
- sizePolicy.setVerticalStretch(0)
- sizePolicy.setHeightForWidth(self.sldPersoImportance.sizePolicy().hasHeightForWidth())
- self.sldPersoImportance.setSizePolicy(sizePolicy)
- self.sldPersoImportance.setObjectName("sldPersoImportance")
- self.formLayout_8.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.sldPersoImportance)
self.label_3 = QtWidgets.QLabel(self.scrollAreaPersoInfosWidget)
self.label_3.setObjectName("label_3")
self.formLayout_8.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.label_3)
@@ -454,6 +399,73 @@ class Ui_MainWindow(object):
self.btnPersoColor.setObjectName("btnPersoColor")
self.horizontalLayout_3.addWidget(self.btnPersoColor)
self.formLayout_8.setLayout(2, QtWidgets.QFormLayout.FieldRole, self.horizontalLayout_3)
+ self.horizontalLayout_20 = QtWidgets.QHBoxLayout()
+ self.horizontalLayout_20.setObjectName("horizontalLayout_20")
+ self.sldPersoImportance = sldImportance(self.scrollAreaPersoInfosWidget)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.sldPersoImportance.sizePolicy().hasHeightForWidth())
+ self.sldPersoImportance.setSizePolicy(sizePolicy)
+ self.sldPersoImportance.setObjectName("sldPersoImportance")
+ self.horizontalLayout_20.addWidget(self.sldPersoImportance)
+ self.chkPersoPOV = QtWidgets.QCheckBox(self.scrollAreaPersoInfosWidget)
+ self.chkPersoPOV.setChecked(False)
+ self.chkPersoPOV.setAutoRepeat(False)
+ self.chkPersoPOV.setTristate(False)
+ self.chkPersoPOV.setObjectName("chkPersoPOV")
+ self.horizontalLayout_20.addWidget(self.chkPersoPOV)
+ self.formLayout_8.setLayout(4, QtWidgets.QFormLayout.FieldRole, self.horizontalLayout_20)
+ self.label_4 = QtWidgets.QLabel(self.scrollAreaPersoInfosWidget)
+ self.label_4.setObjectName("label_4")
+ self.formLayout_8.setWidget(6, QtWidgets.QFormLayout.LabelRole, self.label_4)
+ self.txtPersoMotivation = MDEditCompleter(self.scrollAreaPersoInfosWidget)
+ self.txtPersoMotivation.setObjectName("txtPersoMotivation")
+ self.formLayout_8.setWidget(6, QtWidgets.QFormLayout.FieldRole, self.txtPersoMotivation)
+ self.label_5 = QtWidgets.QLabel(self.scrollAreaPersoInfosWidget)
+ self.label_5.setObjectName("label_5")
+ self.formLayout_8.setWidget(7, QtWidgets.QFormLayout.LabelRole, self.label_5)
+ self.txtPersoGoal = MDEditCompleter(self.scrollAreaPersoInfosWidget)
+ self.txtPersoGoal.setObjectName("txtPersoGoal")
+ self.formLayout_8.setWidget(7, QtWidgets.QFormLayout.FieldRole, self.txtPersoGoal)
+ self.label_6 = QtWidgets.QLabel(self.scrollAreaPersoInfosWidget)
+ self.label_6.setObjectName("label_6")
+ self.formLayout_8.setWidget(8, QtWidgets.QFormLayout.LabelRole, self.label_6)
+ self.txtPersoConflict = MDEditCompleter(self.scrollAreaPersoInfosWidget)
+ self.txtPersoConflict.setObjectName("txtPersoConflict")
+ self.formLayout_8.setWidget(8, QtWidgets.QFormLayout.FieldRole, self.txtPersoConflict)
+ self.label_7 = QtWidgets.QLabel(self.scrollAreaPersoInfosWidget)
+ self.label_7.setObjectName("label_7")
+ self.formLayout_8.setWidget(9, QtWidgets.QFormLayout.LabelRole, self.label_7)
+ self.txtPersoEpiphany = MDEditCompleter(self.scrollAreaPersoInfosWidget)
+ self.txtPersoEpiphany.setObjectName("txtPersoEpiphany")
+ self.formLayout_8.setWidget(9, QtWidgets.QFormLayout.FieldRole, self.txtPersoEpiphany)
+ self.label_24 = QtWidgets.QLabel(self.scrollAreaPersoInfosWidget)
+ self.label_24.setObjectName("label_24")
+ self.formLayout_8.setWidget(10, QtWidgets.QFormLayout.LabelRole, self.label_24)
+ self.txtPersoSummarySentence = MDEditCompleter(self.scrollAreaPersoInfosWidget)
+ self.txtPersoSummarySentence.setObjectName("txtPersoSummarySentence")
+ self.formLayout_8.setWidget(10, QtWidgets.QFormLayout.FieldRole, self.txtPersoSummarySentence)
+ self.label_8 = QtWidgets.QLabel(self.scrollAreaPersoInfosWidget)
+ self.label_8.setObjectName("label_8")
+ self.formLayout_8.setWidget(11, QtWidgets.QFormLayout.LabelRole, self.label_8)
+ self.txtPersoSummaryPara = MDEditCompleter(self.scrollAreaPersoInfosWidget)
+ self.txtPersoSummaryPara.setObjectName("txtPersoSummaryPara")
+ self.formLayout_8.setWidget(11, QtWidgets.QFormLayout.FieldRole, self.txtPersoSummaryPara)
+ self.horizontalLayout_21 = QtWidgets.QHBoxLayout()
+ self.horizontalLayout_21.setObjectName("horizontalLayout_21")
+ spacerItem10 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
+ self.horizontalLayout_21.addItem(spacerItem10)
+ self.btnStepFour = QtWidgets.QPushButton(self.scrollAreaPersoInfosWidget)
+ icon = QtGui.QIcon.fromTheme("go-next")
+ self.btnStepFour.setIcon(icon)
+ self.btnStepFour.setFlat(True)
+ self.btnStepFour.setObjectName("btnStepFour")
+ self.horizontalLayout_21.addWidget(self.btnStepFour)
+ self.formLayout_8.setLayout(12, QtWidgets.QFormLayout.FieldRole, self.horizontalLayout_21)
+ self.label_18 = QtWidgets.QLabel(self.scrollAreaPersoInfosWidget)
+ self.label_18.setObjectName("label_18")
+ self.formLayout_8.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.label_18)
self.scrollAreaPersoInfos.setWidget(self.scrollAreaPersoInfosWidget)
self.verticalLayout_20.addWidget(self.scrollAreaPersoInfos)
self.tabPersos.addTab(self.info, "")
@@ -745,7 +757,7 @@ class Ui_MainWindow(object):
self.treeWorld.setRootIsDecorated(False)
self.treeWorld.setObjectName("treeWorld")
self.treeWorld.header().setVisible(False)
- self.treeWorld.header().setDefaultSectionSize(0)
+ self.treeWorld.header().setDefaultSectionSize(35)
self.verticalLayout_32.addWidget(self.treeWorld)
self.horizontalLayout_19 = QtWidgets.QHBoxLayout()
self.horizontalLayout_19.setObjectName("horizontalLayout_19")
@@ -833,6 +845,7 @@ class Ui_MainWindow(object):
self.layoutWidget = QtWidgets.QWidget(self.splitterOutlineH)
self.layoutWidget.setObjectName("layoutWidget")
self.verticalLayout_14 = QtWidgets.QVBoxLayout(self.layoutWidget)
+ self.verticalLayout_14.setContentsMargins(0, 0, 0, 0)
self.verticalLayout_14.setObjectName("verticalLayout_14")
self.splitterOutlineV = QtWidgets.QSplitter(self.layoutWidget)
self.splitterOutlineV.setOrientation(QtCore.Qt.Vertical)
@@ -1029,7 +1042,7 @@ class Ui_MainWindow(object):
self.horizontalLayout_2.addWidget(self.stack)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
- self.menubar.setGeometry(QtCore.QRect(0, 0, 1112, 30))
+ self.menubar.setGeometry(QtCore.QRect(0, 0, 1112, 21))
self.menubar.setObjectName("menubar")
self.menuFile = QtWidgets.QMenu(self.menubar)
self.menuFile.setObjectName("menuFile")
@@ -1270,6 +1283,14 @@ class Ui_MainWindow(object):
self.actFormatList.setObjectName("actFormatList")
self.actFormatBlockquote = QtWidgets.QAction(MainWindow)
self.actFormatBlockquote.setObjectName("actFormatBlockquote")
+ self.actSearch = QtWidgets.QAction(MainWindow)
+ icon = QtGui.QIcon.fromTheme("edit-find")
+ self.actSearch.setIcon(icon)
+ self.actSearch.setObjectName("actSearch")
+ self.actSupport = QtWidgets.QAction(MainWindow)
+ self.actSupport.setObjectName("actSupport")
+ self.actLocateLog = QtWidgets.QAction(MainWindow)
+ self.actLocateLog.setObjectName("actLocateLog")
self.menuFile.addAction(self.actOpen)
self.menuFile.addAction(self.menuRecents.menuAction())
self.menuFile.addAction(self.actSave)
@@ -1281,6 +1302,10 @@ class Ui_MainWindow(object):
self.menuFile.addSeparator()
self.menuFile.addAction(self.actQuit)
self.menuHelp.addAction(self.actShowHelp)
+ self.menuHelp.addSeparator()
+ self.menuHelp.addAction(self.actSupport)
+ self.menuHelp.addAction(self.actLocateLog)
+ self.menuHelp.addSeparator()
self.menuHelp.addAction(self.actAbout)
self.menuTools.addAction(self.actSpellcheck)
self.menuTools.addAction(self.actToolFrequency)
@@ -1313,6 +1338,7 @@ class Ui_MainWindow(object):
self.menuEdit.addAction(self.actCopy)
self.menuEdit.addAction(self.actPaste)
self.menuEdit.addAction(self.actDelete)
+ self.menuEdit.addAction(self.actSearch)
self.menuEdit.addAction(self.actRename)
self.menuEdit.addSeparator()
self.menuEdit.addAction(self.mnuFormat.menuAction())
@@ -1339,7 +1365,7 @@ class Ui_MainWindow(object):
self.retranslateUi(MainWindow)
self.stack.setCurrentIndex(1)
- self.tabMain.setCurrentIndex(0)
+ self.tabMain.setCurrentIndex(2)
self.tabSummary.setCurrentIndex(0)
self.tabPersos.setCurrentIndex(0)
self.tabPlot.setCurrentIndex(0)
@@ -1484,6 +1510,8 @@ class Ui_MainWindow(object):
self.tabMain.setTabText(self.tabMain.indexOf(self.lytTabSummary), _translate("MainWindow", "Summary"))
self.groupBox.setTitle(_translate("MainWindow", "Names"))
self.txtPersosFilter.setPlaceholderText(_translate("MainWindow", "Filter"))
+ self.label_3.setText(_translate("MainWindow", "Name"))
+ self.chkPersoPOV.setText(_translate("MainWindow", "Allow POV"))
self.label_4.setText(_translate("MainWindow", "Motivation"))
self.label_5.setText(_translate("MainWindow", "Goal"))
self.label_6.setText(_translate("MainWindow", "Conflict"))
@@ -1492,7 +1520,6 @@ class Ui_MainWindow(object):
self.label_8.setText(_translate("MainWindow", "One paragraph
summary
"))
self.btnStepFour.setText(_translate("MainWindow", "Next"))
self.label_18.setText(_translate("MainWindow", "Importance"))
- self.label_3.setText(_translate("MainWindow", "Name"))
self.tabPersos.setTabText(self.tabPersos.indexOf(self.info), _translate("MainWindow", "Basic info"))
self.btnStepSix.setText(_translate("MainWindow", "Next"))
self.tabPersos.setTabText(self.tabPersos.indexOf(self.tab_11), _translate("MainWindow", "Summary"))
@@ -1635,7 +1662,15 @@ class Ui_MainWindow(object):
self.actFormatOrderedList.setText(_translate("MainWindow", "&Ordered list"))
self.actFormatList.setText(_translate("MainWindow", "&Unordered list"))
self.actFormatBlockquote.setText(_translate("MainWindow", "B&lockquote"))
-
+ self.actSearch.setText(_translate("MainWindow", "Search"))
+ self.actSearch.setShortcut(_translate("MainWindow", "Ctrl+F"))
+ self.actSupport.setText(_translate("MainWindow", "&Technical Support"))
+ self.actSupport.setToolTip(_translate("MainWindow", "How to obtain technical support for Manuskript."))
+ self.actSupport.setShortcut(_translate("MainWindow", "F1"))
+ self.actLocateLog.setText(_translate("MainWindow", "&Locate log file..."))
+ self.actLocateLog.setIconText(_translate("MainWindow", "Locate log file"))
+ self.actLocateLog.setToolTip(_translate("MainWindow", "Locate the diagnostic log file used for this session."))
+ self.actLocateLog.setShortcut(_translate("MainWindow", "Shift+F1"))
from manuskript.ui.cheatSheet import cheatSheet
from manuskript.ui.editors.mainEditor import mainEditor
from manuskript.ui.search import search
diff --git a/manuskript/ui/mainWindow.ui b/manuskript/ui/mainWindow.ui
index 3da6713..d036b5f 100644
--- a/manuskript/ui/mainWindow.ui
+++ b/manuskript/ui/mainWindow.ui
@@ -124,7 +124,7 @@
QTabWidget::Rounded
- 0
+ 2
true
@@ -815,75 +815,126 @@
0
0
- 204
- 606
+ 429
+ 719
QFormLayout::AllNonFixedFieldsGrow
- -
+
-
+
+
+ Name
+
+
+
+ -
+
+
-
+
+
+ -
+
+
+
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ -
+
+
+ Allow POV
+
+
+ false
+
+
+ false
+
+
+ false
+
+
+
+
+
+ -
Motivation
- -
+
-
- -
+
-
Goal
- -
+
-
- -
+
-
Conflict
- -
+
-
- -
+
-
Epiphany
- -
+
-
- -
+
-
<html><head/><body><p align="right">One sentence<br/>summary</p></body></html>
- -
+
-
- -
+
-
<html><head/><body><p align="right">One paragraph<br/>summary</p></body></html>
- -
+
-
- -
+
-
-
@@ -914,44 +965,13 @@
- -
+
-
Importance
- -
-
-
-
- 0
- 0
-
-
-
-
- -
-
-
- Name
-
-
-
- -
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
@@ -1547,7 +1567,7 @@
false
- 0
+ 35
@@ -2095,7 +2115,7 @@
0
0
1112
- 30
+ 21
diff --git a/manuskript/ui/search.py b/manuskript/ui/search.py
index 06441ae..f177c12 100644
--- a/manuskript/ui/search.py
+++ b/manuskript/ui/search.py
@@ -1,147 +1,151 @@
#!/usr/bin/env python
# --!-- coding: utf8 --!--
-from PyQt5.QtCore import Qt, QRect
-from PyQt5.QtGui import QPalette, QFontMetrics
-from PyQt5.QtWidgets import QWidget, QMenu, QAction, qApp, QListWidgetItem, QStyledItemDelegate, QStyle
+from PyQt5.QtCore import Qt, QRect, QEvent, QCoreApplication
+from PyQt5.QtGui import QPalette, QFontMetrics, QKeySequence
+from PyQt5.QtWidgets import QWidget, qApp, QListWidgetItem, QStyledItemDelegate, QStyle, QLabel, QToolTip, QShortcut
+
-from manuskript.enums import Outline
from manuskript.functions import mainWindow
from manuskript.ui import style
from manuskript.ui.search_ui import Ui_search
-from manuskript.models import references as Ref
+from manuskript.enums import Model
+
+from manuskript.models.flatDataModelWrapper import flatDataModelWrapper
+from manuskript.ui.searchMenu import searchMenu
+from manuskript.ui.highlighters.searchResultHighlighters.searchResultHighlighter import searchResultHighlighter
class search(QWidget, Ui_search):
def __init__(self, parent=None):
+ _translate = QCoreApplication.translate
+
QWidget.__init__(self, parent)
self.setupUi(self)
- self.options = {
- "All": True,
- "Title": True,
- "Text": True,
- "Summary": False,
- "Notes": False,
- "POV": False,
- "Status": False,
- "Label": False,
- "CS": True
- }
+ self.searchTextInput.returnPressed.connect(self.search)
- self.text.returnPressed.connect(self.search)
- self.generateOptionMenu()
+ self.searchMenu = searchMenu()
+ self.btnOptions.setMenu(self.searchMenu)
self.delegate = listResultDelegate(self)
self.result.setItemDelegate(self.delegate)
+ self.result.setMouseTracking(True)
self.result.itemClicked.connect(self.openItem)
self.result.setStyleSheet(style.searchResultSS())
- self.text.setStyleSheet(style.lineEditSS())
+ self.searchTextInput.setStyleSheet(style.lineEditSS())
- def generateOptionMenu(self):
- self.menu = QMenu(self)
- a = QAction(self.tr("Search in:"), self.menu)
- a.setEnabled(False)
- self.menu.addAction(a)
- for i, d in [
- (self.tr("All"), "All"),
- (self.tr("Title"), "Title"),
- (self.tr("Text"), "Text"),
- (self.tr("Summary"), "Summary"),
- (self.tr("Notes"), "Notes"),
- (self.tr("POV"), "POV"),
- (self.tr("Status"), "Status"),
- (self.tr("Label"), "Label"),
- ]:
- a = QAction(i, self.menu)
- a.setCheckable(True)
- a.setChecked(self.options[d])
- a.setData(d)
- a.triggered.connect(self.updateOptions)
- self.menu.addAction(a)
- self.menu.addSeparator()
+ self.searchResultHighlighter = searchResultHighlighter()
- a = QAction(self.tr("Options:"), self.menu)
- a.setEnabled(False)
- self.menu.addAction(a)
- for i, d in [
- (self.tr("Case sensitive"), "CS"),
- ]:
- a = QAction(i, self.menu)
- a.setCheckable(True)
- a.setChecked(self.options[d])
- a.setData(d)
- a.triggered.connect(self.updateOptions)
- self.menu.addAction(a)
- self.menu.addSeparator()
+ self.noResultsLabel = QLabel(_translate("Search", "No results found"), self.result)
+ self.noResultsLabel.setVisible(False)
+ self.noResultsLabel.setStyleSheet("QLabel {color: gray;}")
- self.btnOptions.setMenu(self.menu)
+ # Add shortcuts for navigating through search results
+ QShortcut(QKeySequence(_translate("MainWindow", "F3")), self.searchTextInput, self.nextSearchResult)
+ QShortcut(QKeySequence(_translate("MainWindow", "Shift+F3")), self.searchTextInput, self.previousSearchResult)
- def updateOptions(self):
- a = self.sender()
- self.options[a.data()] = a.isChecked()
+ # These texts are already included in translation files but including ":" at the end. We force here the
+ # translation for them without ":"
+ _translate("MainWindow", "Situation")
+ _translate("MainWindow", "Status")
+
+ def nextSearchResult(self):
+ if self.result.currentRow() < self.result.count() - 1:
+ self.result.setCurrentRow(self.result.currentRow() + 1)
+ else:
+ self.result.setCurrentRow(0)
+
+ if 0 < self.result.currentRow() < self.result.count():
+ self.openItem(self.result.currentItem())
+
+ def previousSearchResult(self):
+ if self.result.currentRow() > 0:
+ self.result.setCurrentRow(self.result.currentRow() - 1)
+ else:
+ self.result.setCurrentRow(self.result.count() - 1)
+
+ if 0 < self.result.currentRow() < self.result.count():
+ self.openItem(self.result.currentItem())
+
+ def prepareRegex(self, searchText):
+ import re
+
+ flags = re.UNICODE
+
+ if self.searchMenu.caseSensitive() is False:
+ flags |= re.IGNORECASE
+
+ if self.searchMenu.regex() is False:
+ searchText = re.escape(searchText)
+
+ if self.searchMenu.matchWords() is True:
+ # Source: https://stackoverflow.com/a/15863102
+ searchText = r'\b' + searchText + r'\b'
+
+ return re.compile(searchText, flags)
def search(self):
- text = self.text.text()
-
- # Choosing the right columns
- lstColumns = [
- ("Title", Outline.title),
- ("Text", Outline.text),
- ("Summary", Outline.summarySentence),
- ("Summary", Outline.summaryFull),
- ("Notes", Outline.notes),
- ("POV", Outline.POV),
- ("Status", Outline.status),
- ("Label", Outline.label),
- ]
- columns = [c[1] for c in lstColumns if self.options[c[0]] or self.options["All"]]
-
- # Setting override cursor
- qApp.setOverrideCursor(Qt.WaitCursor)
-
- # Searching
- model = mainWindow().mdlOutline
- results = model.findItemsContaining(text, columns, self.options["CS"])
-
- # Showing results
self.result.clear()
- for r in results:
- index = model.getIndexByID(r)
- if not index.isValid():
- continue
- item = index.internalPointer()
- i = QListWidgetItem(item.title(), self.result)
- i.setData(Qt.UserRole, r)
- i.setData(Qt.UserRole + 1, item.path())
- self.result.addItem(i)
+ self.result.setCurrentRow(0)
- # Removing override cursor
- qApp.restoreOverrideCursor()
+ searchText = self.searchTextInput.text()
+ if len(searchText) > 0:
+ searchRegex = self.prepareRegex(searchText)
+ results = []
+
+ # Set override cursor
+ qApp.setOverrideCursor(Qt.WaitCursor)
+
+ for model, modelName in [
+ (mainWindow().mdlOutline, Model.Outline),
+ (mainWindow().mdlCharacter, Model.Character),
+ (flatDataModelWrapper(mainWindow().mdlFlatData), Model.FlatData),
+ (mainWindow().mdlWorld, Model.World),
+ (mainWindow().mdlPlots, Model.Plot)
+ ]:
+ filteredColumns = self.searchMenu.columns(modelName)
+
+ # Searching
+ if len(filteredColumns):
+ results += model.searchOccurrences(searchRegex, filteredColumns)
+
+ # Showing results
+ self.generateResultsLists(results)
+
+ # Remove override cursor
+ qApp.restoreOverrideCursor()
+
+ def generateResultsLists(self, results):
+ self.noResultsLabel.setVisible(len(results) == 0)
+ for result in results:
+ item = QListWidgetItem(result.title(), self.result)
+ item.setData(Qt.UserRole, result)
+ item.setData(Qt.UserRole + 1, ' > '.join(result.path()))
+ item.setData(Qt.UserRole + 2, result.context())
+ self.result.addItem(item)
def openItem(self, item):
- r = Ref.textReference(item.data(Qt.UserRole))
- Ref.open(r)
- # mw = mainWindow()
- # index = mw.mdlOutline.getIndexByID(item.data(Qt.UserRole))
- # mw.mainEditor.setCurrentModelIndex(index, newTab=True)
+ self.searchResultHighlighter.highlightSearchResult(item.data(Qt.UserRole))
+ def leaveEvent(self, event):
+ self.delegate.mouseLeave()
class listResultDelegate(QStyledItemDelegate):
def __init__(self, parent=None):
QStyledItemDelegate.__init__(self, parent)
+ self._tooltipRowIndex = -1
def paint(self, painter, option, index):
extra = index.data(Qt.UserRole + 1)
+
if not extra:
return QStyledItemDelegate.paint(self, painter, option, index)
-
else:
if option.state & QStyle.State_Selected:
painter.fillRect(option.rect, option.palette.color(QPalette.Highlight))
title = index.data()
- extra = " - {}".format(extra)
painter.drawText(option.rect.adjusted(2, 1, 0, 0), Qt.AlignLeft, title)
fm = QFontMetrics(option.font)
@@ -153,5 +157,18 @@ class listResultDelegate(QStyledItemDelegate):
painter.setPen(Qt.white)
else:
painter.setPen(Qt.gray)
- painter.drawText(r.adjusted(2, 1, 0, 0), Qt.AlignLeft, extra)
+ painter.drawText(r.adjusted(2, 1, 0, 0), Qt.AlignLeft, " - {}".format(extra))
painter.restore()
+
+ def editorEvent(self, event, model, option, index):
+ if event.type() == QEvent.MouseMove and self._tooltipRowIndex != index.row():
+ self._tooltipRowIndex = index.row()
+ context = index.data(Qt.UserRole + 2)
+ extra = index.data(Qt.UserRole + 1)
+ QToolTip.showText(event.globalPos(),
+ "#" + str(index.row()) + " - " + extra + "
" + context + "
")
+ return True
+ return False
+
+ def mouseLeave(self):
+ self._tooltipRowIndex = -1
diff --git a/manuskript/ui/searchMenu.py b/manuskript/ui/searchMenu.py
new file mode 100644
index 0000000..59468f8
--- /dev/null
+++ b/manuskript/ui/searchMenu.py
@@ -0,0 +1,108 @@
+#!/usr/bin/env python
+# --!-- coding: utf8 --!--
+from PyQt5.QtWidgets import QMenu, QAction
+from PyQt5.QtCore import QCoreApplication
+from PyQt5 import QtCore
+
+from manuskript.searchLabels import OutlineSearchLabels, CharacterSearchLabels, FlatDataSearchLabels, WorldSearchLabels, PlotSearchLabels
+from manuskript.models.searchFilter import searchFilter
+from manuskript.enums import Model
+
+
+def filterKey(modelPreffix, column):
+ return modelPreffix + str(column)
+
+
+class searchMenu(QMenu):
+ def __init__(self, parent=None):
+ QMenu.__init__(self, parent)
+
+ _translate = QCoreApplication.translate
+ # Model keys must match the ones used in search widget class
+ self.filters = {
+ Model.Outline: searchFilter(_translate("MainWindow", "Outline"), True, list(OutlineSearchLabels.keys())),
+ Model.Character: searchFilter(_translate("MainWindow", "Characters"), True, list(CharacterSearchLabels.keys())),
+ Model.FlatData: searchFilter(_translate("MainWindow", "FlatData"), True, list(FlatDataSearchLabels.keys())),
+ Model.World: searchFilter(_translate("MainWindow", "World"), True, list(WorldSearchLabels.keys())),
+ Model.Plot: searchFilter(_translate("MainWindow", "Plot"), True, list(PlotSearchLabels.keys()))
+ }
+
+ self.options = {
+ "CS": [self.tr("Case sensitive"), True],
+ "MatchWords": [self.tr("Match words"), False],
+ "Regex": [self.tr("Regex"), False]
+ }
+
+ self._generateOptions()
+
+ def _generateOptions(self):
+ a = QAction(self.tr("Search in:"), self)
+ a.setEnabled(False)
+ self.addAction(a)
+ for filterKey in self.filters:
+ a = QAction(self.tr(self.filters[filterKey].label()), self)
+ a.setCheckable(True)
+ a.setChecked(self.filters[filterKey].enabled())
+ a.setData(filterKey)
+ a.triggered.connect(self._updateFilters)
+ self.addAction(a)
+ self.addSeparator()
+
+ a = QAction(self.tr("Options:"), self)
+ a.setEnabled(False)
+ self.addAction(a)
+ for optionKey in self.options:
+ a = QAction(self.options[optionKey][0], self)
+ a.setCheckable(True)
+ a.setChecked(self.options[optionKey][1])
+ a.setData(optionKey)
+ a.triggered.connect(self._updateOptions)
+ self.addAction(a)
+ self.addSeparator()
+
+ def _updateFilters(self):
+ a = self.sender()
+ self.filters[a.data()].setEnabled(a.isChecked())
+
+ def _updateOptions(self):
+ a = self.sender()
+ self.options[a.data()][1] = a.isChecked()
+
+ def columns(self, modelName):
+ if self.filters[modelName].enabled():
+ return self.filters[modelName].modelColumns()
+ else:
+ return []
+
+ def caseSensitive(self):
+ return self.options["CS"][1]
+
+ def matchWords(self):
+ return self.options["MatchWords"][1]
+
+ def regex(self):
+ return self.options["Regex"][1]
+
+ def mouseReleaseEvent(self, event):
+ # Workaround for enabling / disabling actions without closing the menu.
+ # Source: https://stackoverflow.com/a/14967212
+ action = self.activeAction()
+ if action:
+ action.setEnabled(False)
+ QMenu.mouseReleaseEvent(self, event)
+ action.setEnabled(True)
+ action.trigger()
+ else:
+ QMenu.mouseReleaseEvent(self, event)
+
+ def keyPressEvent(self, event):
+ # Workaround for enabling / disabling actions without closing the menu.
+ # Source: https://stackoverflow.com/a/14967212
+ action = self.activeAction()
+ if action and event.key() == QtCore.Qt.Key_Return:
+ action.setEnabled(False)
+ QMenu.keyPressEvent(self, event)
+ action.setEnabled(True)
+ action.trigger()
+ else:
+ QMenu.keyPressEvent(self, event)
diff --git a/manuskript/ui/search_ui.py b/manuskript/ui/search_ui.py
index d9f5c31..5052977 100644
--- a/manuskript/ui/search_ui.py
+++ b/manuskript/ui/search_ui.py
@@ -19,12 +19,12 @@ class Ui_search(object):
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setSpacing(0)
self.horizontalLayout.setObjectName("horizontalLayout")
- self.text = QtWidgets.QLineEdit(search)
- self.text.setInputMask("")
- self.text.setFrame(False)
- self.text.setClearButtonEnabled(True)
- self.text.setObjectName("text")
- self.horizontalLayout.addWidget(self.text)
+ self.searchTextInput = QtWidgets.QLineEdit(search)
+ self.searchTextInput.setInputMask("")
+ self.searchTextInput.setFrame(False)
+ self.searchTextInput.setClearButtonEnabled(True)
+ self.searchTextInput.setObjectName("searchTextInput")
+ self.horizontalLayout.addWidget(self.searchTextInput)
self.btnOptions = QtWidgets.QPushButton(search)
self.btnOptions.setText("")
icon = QtGui.QIcon.fromTheme("edit-find")
@@ -45,5 +45,5 @@ class Ui_search(object):
def retranslateUi(self, search):
_translate = QtCore.QCoreApplication.translate
search.setWindowTitle(_translate("search", "Form"))
- self.text.setPlaceholderText(_translate("search", "Search for..."))
+ self.searchTextInput.setPlaceholderText(_translate("search", "Search for..."))
diff --git a/manuskript/ui/search_ui.ui b/manuskript/ui/search_ui.ui
index 1b63fdc..89eb0a0 100644
--- a/manuskript/ui/search_ui.ui
+++ b/manuskript/ui/search_ui.ui
@@ -35,7 +35,7 @@
0
-
-
+
diff --git a/manuskript/ui/settings_ui.py b/manuskript/ui/settings_ui.py
index 0cd39f5..b34d982 100644
--- a/manuskript/ui/settings_ui.py
+++ b/manuskript/ui/settings_ui.py
@@ -1,10 +1,11 @@
# -*- coding: utf-8 -*-
-# Form implementation generated from reading ui file 'manuskript/ui/settings_ui.ui'
+# Form implementation generated from reading ui file 'settings_ui.ui'
#
-# Created by: PyQt5 UI code generator 5.13.0
+# Created by: PyQt5 UI code generator 5.15.0
#
-# WARNING! All changes made in this file will be lost!
+# WARNING: Any manual changes made to this file will be lost when pyuic5 is
+# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
@@ -13,7 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Settings(object):
def setupUi(self, Settings):
Settings.setObjectName("Settings")
- Settings.resize(658, 598)
+ Settings.resize(681, 598)
self.horizontalLayout_8 = QtWidgets.QHBoxLayout(Settings)
self.horizontalLayout_8.setObjectName("horizontalLayout_8")
self.lstMenu = QtWidgets.QListWidget(Settings)
@@ -55,50 +56,9 @@ class Ui_Settings(object):
self.groupBox_2.setFont(font)
self.groupBox_2.setObjectName("groupBox_2")
self.formLayout_13 = QtWidgets.QFormLayout(self.groupBox_2)
- self.formLayout_13.setFieldGrowthPolicy(QtWidgets.QFormLayout.FieldsStayAtSizeHint)
self.formLayout_13.setObjectName("formLayout_13")
- self.label_56 = QtWidgets.QLabel(self.groupBox_2)
- font = QtGui.QFont()
- font.setBold(False)
- font.setWeight(50)
- self.label_56.setFont(font)
- self.label_56.setObjectName("label_56")
- self.formLayout_13.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.label_56)
- self.cmbStyle = QtWidgets.QComboBox(self.groupBox_2)
- font = QtGui.QFont()
- font.setBold(False)
- font.setWeight(50)
- self.cmbStyle.setFont(font)
- self.cmbStyle.setObjectName("cmbStyle")
- self.formLayout_13.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.cmbStyle)
- self.label_57 = QtWidgets.QLabel(self.groupBox_2)
- font = QtGui.QFont()
- font.setBold(False)
- font.setWeight(50)
- self.label_57.setFont(font)
- self.label_57.setObjectName("label_57")
- self.formLayout_13.setWidget(6, QtWidgets.QFormLayout.LabelRole, self.label_57)
- self.cmbTranslation = QtWidgets.QComboBox(self.groupBox_2)
- font = QtGui.QFont()
- font.setBold(False)
- font.setWeight(50)
- self.cmbTranslation.setFont(font)
- self.cmbTranslation.setObjectName("cmbTranslation")
- self.formLayout_13.setWidget(6, QtWidgets.QFormLayout.FieldRole, self.cmbTranslation)
- self.label_58 = QtWidgets.QLabel(self.groupBox_2)
- font = QtGui.QFont()
- font.setBold(False)
- font.setWeight(50)
- self.label_58.setFont(font)
- self.label_58.setObjectName("label_58")
- self.formLayout_13.setWidget(7, QtWidgets.QFormLayout.LabelRole, self.label_58)
- self.spnGeneralFontSize = QtWidgets.QSpinBox(self.groupBox_2)
- font = QtGui.QFont()
- font.setBold(False)
- font.setWeight(50)
- self.spnGeneralFontSize.setFont(font)
- self.spnGeneralFontSize.setObjectName("spnGeneralFontSize")
- self.formLayout_13.setWidget(7, QtWidgets.QFormLayout.FieldRole, self.spnGeneralFontSize)
+ self.gridLayout_4 = QtWidgets.QGridLayout()
+ self.gridLayout_4.setObjectName("gridLayout_4")
self.label_2 = QtWidgets.QLabel(self.groupBox_2)
font = QtGui.QFont()
font.setBold(False)
@@ -106,7 +66,70 @@ class Ui_Settings(object):
self.label_2.setFont(font)
self.label_2.setWordWrap(True)
self.label_2.setObjectName("label_2")
- self.formLayout_13.setWidget(2, QtWidgets.QFormLayout.SpanningRole, self.label_2)
+ self.gridLayout_4.addWidget(self.label_2, 0, 0, 1, 1)
+ self.horizontalLayout_12 = QtWidgets.QHBoxLayout()
+ self.horizontalLayout_12.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint)
+ self.horizontalLayout_12.setObjectName("horizontalLayout_12")
+ self.formLayout_14 = QtWidgets.QFormLayout()
+ self.formLayout_14.setFieldGrowthPolicy(QtWidgets.QFormLayout.AllNonFixedFieldsGrow)
+ self.formLayout_14.setObjectName("formLayout_14")
+ self.label_56 = QtWidgets.QLabel(self.groupBox_2)
+ font = QtGui.QFont()
+ font.setBold(False)
+ font.setWeight(50)
+ self.label_56.setFont(font)
+ self.label_56.setObjectName("label_56")
+ self.formLayout_14.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.label_56)
+ self.cmbStyle = QtWidgets.QComboBox(self.groupBox_2)
+ font = QtGui.QFont()
+ font.setBold(False)
+ font.setWeight(50)
+ self.cmbStyle.setFont(font)
+ self.cmbStyle.setObjectName("cmbStyle")
+ self.formLayout_14.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.cmbStyle)
+ self.label_57 = QtWidgets.QLabel(self.groupBox_2)
+ font = QtGui.QFont()
+ font.setBold(False)
+ font.setWeight(50)
+ self.label_57.setFont(font)
+ self.label_57.setObjectName("label_57")
+ self.formLayout_14.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.label_57)
+ self.cmbTranslation = QtWidgets.QComboBox(self.groupBox_2)
+ font = QtGui.QFont()
+ font.setBold(False)
+ font.setWeight(50)
+ self.cmbTranslation.setFont(font)
+ self.cmbTranslation.setObjectName("cmbTranslation")
+ self.formLayout_14.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.cmbTranslation)
+ self.label_58 = QtWidgets.QLabel(self.groupBox_2)
+ font = QtGui.QFont()
+ font.setBold(False)
+ font.setWeight(50)
+ self.label_58.setFont(font)
+ self.label_58.setObjectName("label_58")
+ self.formLayout_14.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.label_58)
+ self.spnGeneralFontSize = QtWidgets.QSpinBox(self.groupBox_2)
+ font = QtGui.QFont()
+ font.setBold(False)
+ font.setWeight(50)
+ self.spnGeneralFontSize.setFont(font)
+ self.spnGeneralFontSize.setObjectName("spnGeneralFontSize")
+ self.formLayout_14.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.spnGeneralFontSize)
+ self.horizontalLayout_12.addLayout(self.formLayout_14)
+ self.formLayout_15 = QtWidgets.QFormLayout()
+ self.formLayout_15.setObjectName("formLayout_15")
+ self.chkProgressChars = QtWidgets.QCheckBox(self.groupBox_2)
+ font = QtGui.QFont()
+ font.setBold(False)
+ font.setWeight(50)
+ self.chkProgressChars.setFont(font)
+ self.chkProgressChars.setObjectName("chkProgressChars")
+ self.formLayout_15.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.chkProgressChars)
+ spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
+ self.formLayout_15.setItem(0, QtWidgets.QFormLayout.LabelRole, spacerItem)
+ self.horizontalLayout_12.addLayout(self.formLayout_15)
+ self.gridLayout_4.addLayout(self.horizontalLayout_12, 1, 0, 1, 1)
+ self.formLayout_13.setLayout(0, QtWidgets.QFormLayout.SpanningRole, self.gridLayout_4)
self.verticalLayout_7.addWidget(self.groupBox_2)
self.groupBox_10 = QtWidgets.QGroupBox(self.stackedWidgetPage1)
font = QtGui.QFont()
@@ -166,8 +189,8 @@ class Ui_Settings(object):
self.label.setFont(font)
self.label.setObjectName("label")
self.horizontalLayout_5.addWidget(self.label)
- spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
- self.horizontalLayout_5.addItem(spacerItem)
+ spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
+ self.horizontalLayout_5.addItem(spacerItem1)
self.verticalLayout_6.addLayout(self.horizontalLayout_5)
self.horizontalLayout_7 = QtWidgets.QHBoxLayout()
self.horizontalLayout_7.setObjectName("horizontalLayout_7")
@@ -202,8 +225,8 @@ class Ui_Settings(object):
self.label_14.setFont(font)
self.label_14.setObjectName("label_14")
self.horizontalLayout_7.addWidget(self.label_14)
- spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
- self.horizontalLayout_7.addItem(spacerItem1)
+ spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
+ self.horizontalLayout_7.addItem(spacerItem2)
self.verticalLayout_6.addLayout(self.horizontalLayout_7)
self.chkSaveOnQuit = QtWidgets.QCheckBox(self.groupBox)
font = QtGui.QFont()
@@ -223,8 +246,8 @@ class Ui_Settings(object):
self.chkSaveToZip.setObjectName("chkSaveToZip")
self.verticalLayout_6.addWidget(self.chkSaveToZip)
self.verticalLayout_7.addWidget(self.groupBox)
- spacerItem2 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
- self.verticalLayout_7.addItem(spacerItem2)
+ spacerItem3 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
+ self.verticalLayout_7.addItem(spacerItem3)
self.stack.addWidget(self.stackedWidgetPage1)
self.page_3 = QtWidgets.QWidget()
self.page_3.setObjectName("page_3")
@@ -388,8 +411,8 @@ class Ui_Settings(object):
self.label_51.setObjectName("label_51")
self.gridLayout_2.addWidget(self.label_51, 6, 1, 1, 1)
self.verticalLayout.addWidget(self.chkRevisionRemove)
- spacerItem3 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
- self.verticalLayout.addItem(spacerItem3)
+ spacerItem4 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
+ self.verticalLayout.addItem(spacerItem4)
self.label_revisionDeprecation = QtWidgets.QLabel(self.page_3)
self.label_revisionDeprecation.setWordWrap(True)
self.label_revisionDeprecation.setOpenExternalLinks(True)
@@ -524,6 +547,25 @@ class Ui_Settings(object):
self.sldTreeIconSize.setObjectName("sldTreeIconSize")
self.horizontalLayout_11.addWidget(self.sldTreeIconSize)
self.verticalLayout_17.addWidget(self.groupBox_16)
+ self.horizontalGroupBox = QtWidgets.QGroupBox(self.tab)
+ font = QtGui.QFont()
+ font.setBold(True)
+ font.setWeight(75)
+ self.horizontalGroupBox.setFont(font)
+ self.horizontalGroupBox.setObjectName("horizontalGroupBox")
+ self.horizontalLayout_13 = QtWidgets.QHBoxLayout(self.horizontalGroupBox)
+ self.horizontalLayout_13.setContentsMargins(9, 9, 9, 9)
+ self.horizontalLayout_13.setObjectName("horizontalLayout_13")
+ self.chkCountSpaces = QtWidgets.QCheckBox(self.horizontalGroupBox)
+ font = QtGui.QFont()
+ font.setBold(False)
+ font.setWeight(50)
+ self.chkCountSpaces.setFont(font)
+ self.chkCountSpaces.setObjectName("chkCountSpaces")
+ self.horizontalLayout_13.addWidget(self.chkCountSpaces)
+ spacerItem5 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
+ self.horizontalLayout_13.addItem(spacerItem5)
+ self.verticalLayout_17.addWidget(self.horizontalGroupBox)
self.horizontalLayout_9 = QtWidgets.QHBoxLayout()
self.horizontalLayout_9.setObjectName("horizontalLayout_9")
self.groupBox_8 = QtWidgets.QGroupBox(self.tab)
@@ -548,6 +590,13 @@ class Ui_Settings(object):
self.rdoTreeWC.setFont(font)
self.rdoTreeWC.setObjectName("rdoTreeWC")
self.verticalLayout_15.addWidget(self.rdoTreeWC)
+ self.rdoTreeCC = QtWidgets.QRadioButton(self.groupBox_8)
+ font = QtGui.QFont()
+ font.setBold(False)
+ font.setWeight(50)
+ self.rdoTreeCC.setFont(font)
+ self.rdoTreeCC.setObjectName("rdoTreeCC")
+ self.verticalLayout_15.addWidget(self.rdoTreeCC)
self.rdoTreeProgress = QtWidgets.QRadioButton(self.groupBox_8)
font = QtGui.QFont()
font.setBold(False)
@@ -586,6 +635,13 @@ class Ui_Settings(object):
self.rdoTreeTextWC.setFont(font)
self.rdoTreeTextWC.setObjectName("rdoTreeTextWC")
self.verticalLayout_16.addWidget(self.rdoTreeTextWC)
+ self.rdoTreeTextCC = QtWidgets.QRadioButton(self.groupBox_9)
+ font = QtGui.QFont()
+ font.setBold(False)
+ font.setWeight(50)
+ self.rdoTreeTextCC.setFont(font)
+ self.rdoTreeTextCC.setObjectName("rdoTreeTextCC")
+ self.verticalLayout_16.addWidget(self.rdoTreeTextCC)
self.rdoTreeTextProgress = QtWidgets.QRadioButton(self.groupBox_9)
font = QtGui.QFont()
font.setBold(False)
@@ -607,12 +663,17 @@ class Ui_Settings(object):
self.rdoTreeTextNothing.setFont(font)
self.rdoTreeTextNothing.setObjectName("rdoTreeTextNothing")
self.verticalLayout_16.addWidget(self.rdoTreeTextNothing)
- spacerItem4 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
- self.verticalLayout_16.addItem(spacerItem4)
+ spacerItem6 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
+ self.verticalLayout_16.addItem(spacerItem6)
+ self.rdoTreeTextCC.raise_()
+ self.rdoTreeTextWC.raise_()
+ self.rdoTreeTextProgress.raise_()
+ self.rdoTreeTextSummary.raise_()
+ self.rdoTreeTextNothing.raise_()
self.horizontalLayout_9.addWidget(self.groupBox_9)
self.verticalLayout_17.addLayout(self.horizontalLayout_9)
- spacerItem5 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
- self.verticalLayout_17.addItem(spacerItem5)
+ spacerItem7 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
+ self.verticalLayout_17.addItem(spacerItem7)
icon = QtGui.QIcon.fromTheme("view-list-tree")
self.tabViews.addTab(self.tab, icon, "")
self.tab_2 = QtWidgets.QWidget()
@@ -774,8 +835,8 @@ class Ui_Settings(object):
self.chkOutlineTitle.setObjectName("chkOutlineTitle")
self.gridLayout.addWidget(self.chkOutlineTitle, 3, 0, 1, 1)
self.verticalLayout_11.addWidget(self.groupBox_6)
- spacerItem6 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
- self.verticalLayout_11.addItem(spacerItem6)
+ spacerItem8 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
+ self.verticalLayout_11.addItem(spacerItem8)
icon = QtGui.QIcon.fromTheme("view-outline")
self.tabViews.addTab(self.tab_2, icon, "")
self.tab_3 = QtWidgets.QWidget()
@@ -821,8 +882,8 @@ class Ui_Settings(object):
self.cmbCorkImage.setFont(font)
self.cmbCorkImage.setObjectName("cmbCorkImage")
self.verticalLayout_8.addWidget(self.cmbCorkImage)
- spacerItem7 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
- self.verticalLayout_8.addItem(spacerItem7)
+ spacerItem9 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
+ self.verticalLayout_8.addItem(spacerItem9)
self.gridLayout_3.addWidget(self.groupBox_7, 1, 1, 1, 1)
self.groupBox_11 = QtWidgets.QGroupBox(self.tab_3)
font = QtGui.QFont()
@@ -1380,8 +1441,8 @@ class Ui_Settings(object):
self.btnLabelColor.setIconSize(QtCore.QSize(64, 64))
self.btnLabelColor.setObjectName("btnLabelColor")
self.verticalLayout_2.addWidget(self.btnLabelColor)
- spacerItem8 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
- self.verticalLayout_2.addItem(spacerItem8)
+ spacerItem10 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
+ self.verticalLayout_2.addItem(spacerItem10)
self.horizontalLayout_2.addLayout(self.verticalLayout_2)
self.verticalLayout_3.addLayout(self.horizontalLayout_2)
self.horizontalLayout = QtWidgets.QHBoxLayout()
@@ -1398,8 +1459,8 @@ class Ui_Settings(object):
self.btnLabelRemove.setIcon(icon)
self.btnLabelRemove.setObjectName("btnLabelRemove")
self.horizontalLayout.addWidget(self.btnLabelRemove)
- spacerItem9 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
- self.horizontalLayout.addItem(spacerItem9)
+ spacerItem11 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
+ self.horizontalLayout.addItem(spacerItem11)
self.verticalLayout_3.addLayout(self.horizontalLayout)
self.stack.addWidget(self.stackedWidgetPage3)
self.stackedWidgetPage4 = QtWidgets.QWidget()
@@ -1433,8 +1494,8 @@ class Ui_Settings(object):
self.btnStatusRemove.setIcon(icon)
self.btnStatusRemove.setObjectName("btnStatusRemove")
self.horizontalLayout_3.addWidget(self.btnStatusRemove)
- spacerItem10 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
- self.horizontalLayout_3.addItem(spacerItem10)
+ spacerItem12 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
+ self.horizontalLayout_3.addItem(spacerItem12)
self.verticalLayout_4.addLayout(self.horizontalLayout_3)
self.stack.addWidget(self.stackedWidgetPage4)
self.page = QtWidgets.QWidget()
@@ -1482,8 +1543,8 @@ class Ui_Settings(object):
self.btnThemeRemove.setIcon(icon)
self.btnThemeRemove.setObjectName("btnThemeRemove")
self.horizontalLayout_6.addWidget(self.btnThemeRemove)
- spacerItem11 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
- self.horizontalLayout_6.addItem(spacerItem11)
+ spacerItem13 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
+ self.horizontalLayout_6.addItem(spacerItem13)
self.verticalLayout_12.addLayout(self.horizontalLayout_6)
self.themeStack.addWidget(self.stackedWidgetPage1_3)
self.stackedWidgetPage2_3 = QtWidgets.QWidget()
@@ -1823,9 +1884,9 @@ class Ui_Settings(object):
self.horizontalLayout_8.addWidget(self.stack)
self.retranslateUi(Settings)
- self.stack.setCurrentIndex(2)
- self.tabViews.setCurrentIndex(3)
- self.themeStack.setCurrentIndex(1)
+ self.stack.setCurrentIndex(0)
+ self.tabViews.setCurrentIndex(0)
+ self.themeStack.setCurrentIndex(0)
self.themeEditStack.setCurrentIndex(3)
self.lstMenu.currentRowChanged['int'].connect(self.stack.setCurrentIndex)
self.chkRevisionsKeep.toggled['bool'].connect(self.chkRevisionRemove.setEnabled)
@@ -1851,10 +1912,12 @@ class Ui_Settings(object):
self.lstMenu.setSortingEnabled(__sortingEnabled)
self.lblTitleGeneral.setText(_translate("Settings", "General settings"))
self.groupBox_2.setTitle(_translate("Settings", "Application settings"))
+ self.label_2.setText(_translate("Settings", "Restarting Manuskript ensures all settings take effect."))
self.label_56.setText(_translate("Settings", "Style:"))
self.label_57.setText(_translate("Settings", "Language:"))
self.label_58.setText(_translate("Settings", "Font size:"))
- self.label_2.setText(_translate("Settings", "Restarting Manuskript ensures all settings take effect."))
+ self.chkProgressChars.setText(_translate("Settings", "Show progress in chars next\n"
+" to words"))
self.groupBox_10.setTitle(_translate("Settings", "Loading"))
self.chkAutoLoad.setText(_translate("Settings", "Automatically load last project on startup"))
self.groupBox.setTitle(_translate("Settings", "Saving"))
@@ -1899,14 +1962,18 @@ class Ui_Settings(object):
self.cmbTreeBackground.setItemText(4, _translate("Settings", "Compile"))
self.groupBox_16.setTitle(_translate("Settings", "Icon Size"))
self.lblTreeIconSize.setText(_translate("Settings", "TextLabel"))
+ self.horizontalGroupBox.setTitle(_translate("Settings", "Char/Word Counter"))
+ self.chkCountSpaces.setText(_translate("Settings", "Count spaces as chars"))
self.groupBox_8.setTitle(_translate("Settings", "Folders"))
self.rdoTreeItemCount.setText(_translate("Settings", "Show ite&m count"))
self.rdoTreeWC.setText(_translate("Settings", "Show &word count"))
+ self.rdoTreeCC.setText(_translate("Settings", "Show char c&ount"))
self.rdoTreeProgress.setText(_translate("Settings", "S&how progress"))
self.rdoTreeSummary.setText(_translate("Settings", "Show summar&y"))
self.rdoTreeNothing.setText(_translate("Settings", "&Nothing"))
self.groupBox_9.setTitle(_translate("Settings", "Text"))
self.rdoTreeTextWC.setText(_translate("Settings", "&Show word count"))
+ self.rdoTreeTextCC.setText(_translate("Settings", "Sho&w char count"))
self.rdoTreeTextProgress.setText(_translate("Settings", "Show p&rogress"))
self.rdoTreeTextSummary.setText(_translate("Settings", "Show summary"))
self.rdoTreeTextNothing.setText(_translate("Settings", "Nothing"))
diff --git a/manuskript/ui/settings_ui.ui b/manuskript/ui/settings_ui.ui
index 6563bcb..6b1a658 100644
--- a/manuskript/ui/settings_ui.ui
+++ b/manuskript/ui/settings_ui.ui
@@ -6,7 +6,7 @@
0
0
- 658
+ 681
598
@@ -54,7 +54,7 @@
-
- 2
+ 0
@@ -98,93 +98,139 @@
Application settings
-
- QFormLayout::FieldsStayAtSizeHint
-
-
-
-
-
-
- 50
- false
-
-
-
- Style:
-
-
-
- -
-
-
-
- 50
- false
-
-
-
-
- -
-
-
-
- 50
- false
-
-
-
- Language:
-
-
-
- -
-
-
-
- 50
- false
-
-
-
-
- -
-
-
-
- 50
- false
-
-
-
- Font size:
-
-
-
- -
-
-
-
- 50
- false
-
-
-
-
- -
-
-
-
- 50
- false
-
-
-
- Restarting Manuskript ensures all settings take effect.
-
-
- true
-
-
+
-
+
+
-
+
+
+
+ 50
+ false
+
+
+
+ Restarting Manuskript ensures all settings take effect.
+
+
+ true
+
+
+
+ -
+
+
+ QLayout::SetDefaultConstraint
+
+
-
+
+
+ QFormLayout::AllNonFixedFieldsGrow
+
+
-
+
+
+
+ 50
+ false
+
+
+
+ Style:
+
+
+
+ -
+
+
+
+ 50
+ false
+
+
+
+
+ -
+
+
+
+ 50
+ false
+
+
+
+ Language:
+
+
+
+ -
+
+
+
+ 50
+ false
+
+
+
+
+ -
+
+
+
+ 50
+ false
+
+
+
+ Font size:
+
+
+
+ -
+
+
+
+ 50
+ false
+
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 50
+ false
+
+
+
+ Show progress in chars next
+ to words
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+
+
+
@@ -817,7 +863,7 @@
-
- 3
+ 0
@@ -1055,6 +1101,59 @@
+ -
+
+
+
+ 75
+ true
+
+
+
+ Char/Word Counter
+
+
+
+ 9
+
+
+ 9
+
+
+ 9
+
+
+ 9
+
+
-
+
+
+
+ 50
+ false
+
+
+
+ Count spaces as chars
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+
-
-
@@ -1095,6 +1194,19 @@
+ -
+
+
+
+ 50
+ false
+
+
+
+ Show char c&ount
+
+
+
-
@@ -1165,6 +1277,19 @@
+ -
+
+
+
+ 50
+ false
+
+
+
+ Sho&w char count
+
+
+
-
@@ -1224,6 +1349,11 @@
+ rdoTreeTextCC
+ rdoTreeTextWC
+ rdoTreeTextProgress
+ rdoTreeTextSummary
+ rdoTreeTextNothing
@@ -2974,7 +3104,7 @@
-
- 1
+ 0
diff --git a/manuskript/ui/views/MDEditCompleter.py b/manuskript/ui/views/MDEditCompleter.py
index e0db680..0101238 100644
--- a/manuskript/ui/views/MDEditCompleter.py
+++ b/manuskript/ui/views/MDEditCompleter.py
@@ -106,13 +106,18 @@ class MDEditCompleter(MDEditView):
self.completer.popup(self.textUnderCursor(select=True))
def mouseMoveEvent(self, event):
+ """
+ When mouse moves, we show tooltip when appropriate.
+ """
+ self.beginTooltipMoveEvent()
MDEditView.mouseMoveEvent(self, event)
+ self.endTooltipMoveEvent()
onRef = [r for r in self.refRects if r.contains(event.pos())]
if not onRef:
qApp.restoreOverrideCursor()
- QToolTip.hideText()
+ self.hideTooltip()
return
cursor = self.cursorForPosition(event.pos())
@@ -120,7 +125,8 @@ class MDEditCompleter(MDEditView):
if ref:
if not qApp.overrideCursor():
qApp.setOverrideCursor(Qt.PointingHandCursor)
- QToolTip.showText(self.mapToGlobal(event.pos()), Ref.tooltip(ref))
+
+ self.showTooltip(self.mapToGlobal(event.pos()), Ref.tooltip(ref))
def mouseReleaseEvent(self, event):
MDEditView.mouseReleaseEvent(self, event)
diff --git a/manuskript/ui/views/MDEditView.py b/manuskript/ui/views/MDEditView.py
index e8eb564..dcd5e3a 100644
--- a/manuskript/ui/views/MDEditView.py
+++ b/manuskript/ui/views/MDEditView.py
@@ -14,6 +14,8 @@ from manuskript.ui.highlighters.markdownEnums import MarkdownState as MS
from manuskript.ui.highlighters.markdownTokenizer import MarkdownTokenizer as MT
from manuskript import functions as F
+import logging
+LOGGER = logging.getLogger(__name__)
class MDEditView(textEditView):
@@ -506,13 +508,15 @@ class MDEditView(textEditView):
"""
When mouse moves, we show tooltip when appropriate.
"""
+ self.beginTooltipMoveEvent()
textEditView.mouseMoveEvent(self, event)
+ self.endTooltipMoveEvent()
onRect = [r for r in self.clickRects if r.rect.contains(event.pos())]
if not onRect:
qApp.restoreOverrideCursor()
- QToolTip.hideText()
+ self.hideTooltip()
return
ct = onRect[0]
@@ -534,7 +538,7 @@ class MDEditView(textEditView):
if tooltip:
tooltip = self.tr("{} (CTRL+Click to open)").format(tooltip)
- QToolTip.showText(self.mapToGlobal(event.pos()), tooltip)
+ self.showTooltip(self.mapToGlobal(event.pos()), tooltip)
def mouseReleaseEvent(self, event):
textEditView.mouseReleaseEvent(self, event)
@@ -658,10 +662,10 @@ class ImageTooltip:
return
else:
# 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.")
- print("- Completed request:", url_key)
- print("- Status upon finishing:", reply.error(), reply.errorString())
- print("- Currently processing:", ImageTooltip.processing)
+ LOGGER.warning("Unable to match fetched data for tooltip to original request.")
+ LOGGER.warning("- Completed request: %s", url_key)
+ LOGGER.warning("- Status upon finishing: %s, %s", reply.error(), reply.errorString())
+ LOGGER.warning("- Currently processing: %s", ImageTooltip.processing)
return
# Update cache with retrieved data.
diff --git a/manuskript/ui/views/characterTreeView.py b/manuskript/ui/views/characterTreeView.py
index 6997f90..ea5798c 100644
--- a/manuskript/ui/views/characterTreeView.py
+++ b/manuskript/ui/views/characterTreeView.py
@@ -29,6 +29,8 @@ class characterTreeView(QTreeWidget):
self._rootItem = QTreeWidgetItem()
self.insertTopLevelItem(0, self._rootItem)
+ self.importanceMap = {self.tr("Main"):2, self.tr("Secondary"):1, self.tr("Minor"):0}
+
def setCharactersModel(self, model):
self._model = model
self._model.dataChanged.connect(self.updateMaybe)
@@ -64,7 +66,7 @@ class characterTreeView(QTreeWidget):
for child in range(item.childCount()):
sub = item.child(child)
ID = sub.data(0, Qt.UserRole)
- if ID is not None:
+ if ID != None:
# Update name
c = self._model.getCharacterByID(ID)
name = c.name()
@@ -86,11 +88,9 @@ class characterTreeView(QTreeWidget):
self.clear()
characters = self._model.getCharactersByImportance()
- h = [self.tr("Main"), self.tr("Secondary"), self.tr("Minor")]
-
- for i in range(3):
+ for i, importanceLevel in enumerate(self.importanceMap):
# Create category item
- cat = QTreeWidgetItem(self, [h[i]])
+ cat = QTreeWidgetItem(self, [importanceLevel])
cat.setBackground(0, QBrush(QColor(S.highlightLight)))
cat.setForeground(0, QBrush(QColor(S.highlightedTextDark)))
cat.setTextAlignment(0, Qt.AlignCenter)
@@ -119,6 +119,24 @@ class characterTreeView(QTreeWidget):
self.expandAll()
self._updating = False
+ def addCharacter(self):
+ curr_item = self.currentItem()
+ curr_importance = 0
+
+ # check if an item is selected
+ if curr_item != None:
+ if curr_item.parent() == None:
+ # this is a top-level category, so find its importance
+ # get the current text, then look up the importance level
+ text = curr_item.text(0)
+ curr_importance = self.importanceMap[text]
+ else:
+ # get the importance from the currently-highlighted character
+ curr_character = self.currentCharacter()
+ curr_importance = curr_character.importance()
+
+ self._model.addCharacter(importance=curr_importance)
+
def removeCharacter(self):
"""
Removes selected character.
@@ -130,22 +148,30 @@ class characterTreeView(QTreeWidget):
def choseCharacterColor(self):
ID = self.currentCharacterID()
c = self._model.getCharacterByID(ID)
+
if c:
color = iconColor(c.icon)
else:
color = Qt.white
+
self.colorDialog = QColorDialog(color, mainWindow())
color = self.colorDialog.getColor(color)
+
if color.isValid():
c.setColor(color)
mainWindow().updateCharacterColor(ID)
+ def changeCharacterPOVState(self, state):
+ ID = self.currentCharacterID()
+ c = self._model.getCharacterByID(ID)
+ c.setPOVEnabled(state == Qt.Checked)
+ mainWindow().updateCharacterPOVState(ID)
+
def addCharacterInfo(self):
self._model.addCharacterInfo(self.currentCharacterID())
def removeCharacterInfo(self):
- self._model.removeCharacterInfo(self.currentCharacterID(),
- )
+ self._model.removeCharacterInfo(self.currentCharacterID())
def currentCharacterID(self):
ID = None
diff --git a/manuskript/ui/views/corkDelegate.py b/manuskript/ui/views/corkDelegate.py
index 70ff19e..bd776c1 100644
--- a/manuskript/ui/views/corkDelegate.py
+++ b/manuskript/ui/views/corkDelegate.py
@@ -43,11 +43,19 @@ class corkDelegate(QStyledItemDelegate):
return QStyledItemDelegate.editorEvent(self, event, model, option, index)
def createEditor(self, parent, option, index):
+ # When the user performs a global search and selects an Outline result (title or summary), the
+ # associated chapter is selected in cork view, triggering a call to this method with the results
+ # list widget set in self.sender(). In this case we store the searched column so we know which
+ # editor should be created.
+ searchedColumn = None
+ if self.sender() is not None and self.sender().objectName() == 'result' and self.sender().currentItem():
+ searchedColumn = self.sender().currentItem().data(Qt.UserRole).column()
+
self.updateRects(option, index)
bgColor = self.bgColors.get(index, "white")
- if self.mainLineRect.contains(self.lastPos):
+ if searchedColumn == Outline.summarySentence or (self.lastPos is not None and self.mainLineRect.contains(self.lastPos)):
# One line summary
self.editing = Outline.summarySentence
edt = QLineEdit(parent)
@@ -64,7 +72,7 @@ class corkDelegate(QStyledItemDelegate):
edt.setStyleSheet("background: {}; color: black;".format(bgColor))
return edt
- elif self.titleRect.contains(self.lastPos):
+ elif searchedColumn == Outline.title or (self.lastPos is not None and self.titleRect.contains(self.lastPos)):
# Title
self.editing = Outline.title
edt = QLineEdit(parent)
diff --git a/manuskript/ui/views/corkView.py b/manuskript/ui/views/corkView.py
index e77e49d..864a02a 100644
--- a/manuskript/ui/views/corkView.py
+++ b/manuskript/ui/views/corkView.py
@@ -27,6 +27,8 @@ class corkView(QListView, dndView, outlineBasics):
def updateBackground(self):
if settings.corkBackground["image"] != "":
img = findBackground(settings.corkBackground["image"])
+ if img == None:
+ img = ""
else:
# No background image
img = ""
diff --git a/manuskript/ui/views/dndView.py b/manuskript/ui/views/dndView.py
index c028964..b2abf08 100644
--- a/manuskript/ui/views/dndView.py
+++ b/manuskript/ui/views/dndView.py
@@ -13,7 +13,6 @@ class dndView(QAbstractItemView):
def dragMoveEvent(self, event):
# return QAbstractItemView.dragMoveEvent(self, event)
- # print(a)
if event.keyboardModifiers() & Qt.ControlModifier:
event.setDropAction(Qt.CopyAction)
else:
diff --git a/manuskript/ui/views/lineEditView.py b/manuskript/ui/views/lineEditView.py
index 0366016..b61042f 100644
--- a/manuskript/ui/views/lineEditView.py
+++ b/manuskript/ui/views/lineEditView.py
@@ -31,7 +31,7 @@ class lineEditView(QLineEdit):
self._index = index
self._model = index.model()
# self.item = index.internalPointer()
- if self._placeholderText is not None:
+ if self._placeholderText != None:
self.setPlaceholderText(self._placeholderText)
self.textEdited.connect(self.submit)
self.updateText()
diff --git a/manuskript/ui/views/outlineDelegates.py b/manuskript/ui/views/outlineDelegates.py
index 48bba8e..4fdc0f2 100644
--- a/manuskript/ui/views/outlineDelegates.py
+++ b/manuskript/ui/views/outlineDelegates.py
@@ -313,7 +313,7 @@ class outlineLabelDelegate(QStyledItemDelegate):
idx = self.mdlLabels.indexFromItem(item)
opt = QStyleOptionViewItem(option)
self.initStyleOption(opt, idx)
- s = qApp.style().sizeFromContents(QStyle.CT_ItemViewItem, opt, QSize())
+ s = qApp.style().sizeFromContents(QStyle.CT_ItemViewItem, opt, QSize(), None)
if s.width() > 150:
s.setWidth(150)
elif s.width() < 50:
diff --git a/manuskript/ui/views/propertiesView.py b/manuskript/ui/views/propertiesView.py
index 861dc74..0c5f95b 100644
--- a/manuskript/ui/views/propertiesView.py
+++ b/manuskript/ui/views/propertiesView.py
@@ -5,7 +5,10 @@ from PyQt5.QtGui import QIntValidator
from manuskript.enums import Outline
from manuskript.ui.views.propertiesView_ui import Ui_propertiesView
+from manuskript.models.characterPOVModel import characterPOVModel
+import logging
+LOGGER = logging.getLogger(__name__)
class propertiesView(QWidget, Ui_propertiesView):
def __init__(self, parent=None):
@@ -14,7 +17,7 @@ class propertiesView(QWidget, Ui_propertiesView):
self.txtGoal.setColumn(Outline.setGoal)
def setModels(self, mdlOutline, mdlCharacter, mdlLabels, mdlStatus):
- self.cmbPOV.setModels(mdlCharacter, mdlOutline)
+ self.cmbPOV.setModels(characterPOVModel(mdlCharacter), mdlOutline)
self.cmbLabel.setModels(mdlLabels, mdlOutline)
self.cmbStatus.setModels(mdlStatus, mdlOutline)
self.chkCompile.setModel(mdlOutline)
@@ -38,7 +41,7 @@ class propertiesView(QWidget, Ui_propertiesView):
def selectionChanged(self, sourceView):
indexes = self.getIndexes(sourceView)
- # print(indexes)
+ # LOGGER.debug("selectionChanged indexes: %s", indexes)
if len(indexes) == 0:
self.setEnabled(False)
diff --git a/manuskript/ui/views/textEditView.py b/manuskript/ui/views/textEditView.py
index 41324eb..b4f07e9 100644
--- a/manuskript/ui/views/textEditView.py
+++ b/manuskript/ui/views/textEditView.py
@@ -1,11 +1,11 @@
#!/usr/bin/env python
# --!-- coding: utf8 --!--
-import re
+import re, textwrap
from PyQt5.Qt import QApplication
from PyQt5.QtCore import QTimer, QModelIndex, Qt, QEvent, pyqtSignal, QRegExp, QLocale, QPersistentModelIndex, QMutex
from PyQt5.QtGui import QTextBlockFormat, QTextCharFormat, QFont, QColor, QIcon, QMouseEvent, QTextCursor
-from PyQt5.QtWidgets import QWidget, QTextEdit, qApp, QAction, QMenu
+from PyQt5.QtWidgets import QWidget, QTextEdit, qApp, QAction, QMenu, QToolTip
from manuskript import settings
from manuskript.enums import Outline, World, Character, Plot
@@ -14,6 +14,11 @@ from manuskript.models import outlineModel, outlineItem
from manuskript.ui.highlighters import BasicHighlighter
from manuskript.ui import style as S
from manuskript.functions import Spellchecker
+from manuskript.models.characterModel import Character, CharacterInfo
+
+
+import logging
+LOGGER = logging.getLogger(__name__)
class textEditView(QTextEdit):
def __init__(self, parent=None, index=None, html=None, spellcheck=None,
@@ -34,7 +39,7 @@ class textEditView(QTextEdit):
self._themeData = None
self._highlighterClass = BasicHighlighter
- if spellcheck is None:
+ if spellcheck == None:
spellcheck = settings.spellcheck
self.spellcheck = spellcheck
@@ -47,6 +52,8 @@ class textEditView(QTextEdit):
self.highlightWord = ""
self.highligtCS = False
self._dict = None
+ self._tooltip = { 'depth' : 0, 'active' : 0 }
+
# self.document().contentsChanged.connect(self.submit, F.AUC)
# Submit text changed only after 500ms without modifications
@@ -54,13 +61,13 @@ class textEditView(QTextEdit):
self.updateTimer.setInterval(500)
self.updateTimer.setSingleShot(True)
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.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)
@@ -163,9 +170,9 @@ class textEditView(QTextEdit):
def loadFontSettings(self):
if self._fromTheme or \
- not self._index or \
- type(self._index.model()) != outlineModel or \
- self._column != Outline.text:
+ not self._index or \
+ type(self._index.model()) != outlineModel or \
+ self._column != Outline.text:
return
opt = settings.textEditor
@@ -173,7 +180,7 @@ class textEditView(QTextEdit):
f.fromString(opt["font"])
background = (opt["background"] if not opt["backgroundTransparent"]
else "transparent")
- foreground = opt["fontColor"] # if not opt["backgroundTransparent"]
+ foreground = opt["fontColor"] # if not opt["backgroundTransparent"]
# else S.text
# self.setFont(f)
self.setStyleSheet("""QTextEdit{{
@@ -185,15 +192,16 @@ class textEditView(QTextEdit):
{maxWidth}
}}
""".format(
- bg=background,
- foreground=foreground,
- ff=f.family(),
- fs="{}pt".format(str(f.pointSize())),
- mTB = opt["marginsTB"],
- mLR = opt["marginsLR"],
- maxWidth = "max-width: {}px;".format(opt["maxWidth"]) if opt["maxWidth"] else "",
- )
- )
+ bg=background,
+ foreground=foreground,
+ ff=f.family(),
+ fs="{}pt".format(str(f.pointSize())),
+ mTB=opt["marginsTB"],
+ mLR=opt["marginsLR"],
+ maxWidth="max-width: {}px;".format(
+ opt["maxWidth"]) if opt["maxWidth"] else "",
+ )
+ )
self._defaultFontSize = f.pointSize()
# We set the parent background to the editor's background in case
@@ -205,11 +213,11 @@ class textEditView(QTextEdit):
QWidget#{name}{{
background: {bg};
}}""".format(
- # We style by name, otherwise all inheriting widgets get the same
- # colored background, for example context menu.
- name=self.parent().objectName(),
- bg=background,
- ))
+ # We style by name, otherwise all inheriting widgets get the same
+ # colored background, for example context menu.
+ name=self.parent().objectName(),
+ bg=background,
+ ))
cf = QTextCharFormat()
# cf.setFont(f)
@@ -243,7 +251,7 @@ class textEditView(QTextEdit):
if topLeft.parent() != self._index.parent():
return
- # print("Model changed: ({}:{}), ({}:{}/{}), ({}:{}) for {} of {}".format(
+ # LOGGER.debug("Model changed: ({}:{}), ({}:{}/{}), ({}:{}) for {} of {}".format(
# topLeft.row(), topLeft.column(),
# self._index.row(), self._index.row(), self._column,
# bottomRight.row(), bottomRight.column(),
@@ -273,11 +281,11 @@ class textEditView(QTextEdit):
def updateText(self):
self._updating.lock()
- # print("Updating", self.objectName())
+ # LOGGER.debug("Updating %s", self.objectName())
if self._index:
self.disconnectDocument()
if self.toPlainText() != F.toString(self._index.data()):
- # print(" Updating plaintext")
+ # LOGGER.debug(" Updating plaintext")
self.document().setPlainText(F.toString(self._index.data()))
self.reconnectDocument()
@@ -314,18 +322,18 @@ class textEditView(QTextEdit):
text = self.toPlainText()
self._updating.unlock()
- # print("Submitting", self.objectName())
+ # LOGGER.debug("Submitting %s", self.objectName())
if self._index and self._index.isValid():
# item = self._index.internalPointer()
if text != self._index.data():
- # print(" Submitting plain text")
+ # LOGGER.debug(" Submitting plain text")
self._model.setData(QModelIndex(self._index), text)
elif self._indexes:
for i in self._indexes:
item = i.internalPointer()
if text != F.toString(item.data(self._column)):
- print("Submitting many indexes")
+ LOGGER.debug("Submitting many indexes")
self._model.setData(i, text)
def keyPressEvent(self, event):
@@ -393,6 +401,49 @@ class textEditView(QTextEdit):
Qt.LeftButton, Qt.LeftButton, Qt.NoModifier)
QTextEdit.mousePressEvent(self, event)
+ def beginTooltipMoveEvent(self):
+ self._tooltip['depth'] += 1
+
+ def endTooltipMoveEvent(self):
+ self._tooltip['depth'] -= 1
+
+ def showTooltip(self, pos, text):
+ QToolTip.showText(pos, text)
+ self._tooltip['active'] = self._tooltip['depth']
+
+ def hideTooltip(self):
+ if self._tooltip['active'] == self._tooltip['depth']:
+ QToolTip.hideText()
+
+ def mouseMoveEvent(self, event):
+ """
+ When mouse moves, we show tooltip when appropriate.
+ """
+ self.beginTooltipMoveEvent()
+ QTextEdit.mouseMoveEvent(self, event)
+ self.endTooltipMoveEvent()
+
+ match = None
+
+ # Check if the selected word has any suggestions for correction
+ if self.spellcheck and self._dict:
+ cursor = self.cursorForPosition(event.pos())
+
+ # Searches for correlating/overlapping matches
+ suggestions = self._dict.findSuggestions(self.toPlainText(), cursor.selectionStart(), cursor.selectionEnd())
+
+ if len(suggestions) > 0:
+ # I think it should focus on one type of error at a time.
+ match = suggestions[0]
+
+ if match:
+ # Wrap the message into a fitting width
+ msg_lines = textwrap.wrap(match.msg, 48)
+
+ self.showTooltip(event.globalPos(), "\n".join(msg_lines))
+ else:
+ self.hideTooltip()
+
def wheelEvent(self, event):
"""
We catch wheelEvent if key modifier is CTRL to change font size.
@@ -427,58 +478,198 @@ class textEditView(QTextEdit):
QAction.__init__(self, *args)
self.triggered.connect(lambda x: self.correct.emit(
- str(self.text())))
+ str(self.text())))
def contextMenuEvent(self, event):
# Based on http://john.nachtimwald.com/2009/08/22/qplaintextedit-with-in-line-spell-check/
popup_menu = self.createStandardContextMenu()
popup_menu.exec_(event.globalPos())
+ def newCharacter(self):
+ text = self.sender().data()
+ LOGGER.debug(f'New character: {text}')
+ # switch to character page
+ mw = F.mainWindow()
+ mw.tabMain.setCurrentIndex(mw.TabPersos)
+ # add character
+ c = mw.mdlCharacter.addCharacter(name=text)
+ # switch to character
+ item = mw.lstCharacters.getItemByID(c.ID())
+ mw.lstCharacters.setCurrentItem(item)
+
+ def newPlotItem(self):
+ text = self.sender().data()
+ LOGGER.debug(f'New plot item: {text}')
+ # switch to plot page
+ mw = F.mainWindow()
+ mw.tabMain.setCurrentIndex(mw.TabPlots)
+ # add character
+ p, ID = mw.mdlPlots.addPlot(text)
+ # switch to character
+ plotIndex = mw.mdlPlots.getIndexFromID(ID.text())
+ # segfaults for some reason
+ # mw.lstSubPlots.setCurrentIndex(plotIndex)
+
+ def newWorldItem(self):
+ text = self.sender().data()
+ LOGGER.debug(f'New world item: {text}')
+ mw = F.mainWindow()
+ mw.tabMain.setCurrentIndex(mw.TabWorld)
+ item = mw.mdlWorld.addItem(title=text)
+ mw.treeWorld.setCurrentIndex(
+ mw.mdlWorld.indexFromItem(item))
+
+
+ def appendContextMenuEntriesForWord(self, popup_menu, selectedWord):
+ # add "new " buttons at end
+ if selectedWord != None:
+ # new character
+ charAction = QAction(self.tr("&New Character"), popup_menu)
+ charAction.setIcon(F.themeIcon("characters"))
+ charAction.triggered.connect(self.newCharacter)
+ charAction.setData(selectedWord)
+ popup_menu.insertAction(None, charAction)
+
+ # new plot item
+ plotAction = QAction(self.tr("&New Plot Item"), popup_menu)
+ plotAction.setIcon(F.themeIcon("plots"))
+ plotAction.triggered.connect(self.newPlotItem)
+ plotAction.setData(selectedWord)
+ popup_menu.insertAction(None, plotAction)
+
+ # new world item
+ worldAction = QAction(self.tr("&New World Item"), popup_menu)
+ worldAction.setIcon(F.themeIcon("world"))
+ worldAction.triggered.connect(self.newWorldItem)
+ worldAction.setData(selectedWord)
+ popup_menu.insertAction(None, worldAction)
+
+ return popup_menu
+
def createStandardContextMenu(self):
popup_menu = QTextEdit.createStandardContextMenu(self)
- if not self.spellcheck:
- return popup_menu
-
- # Select the word under the cursor.
- # But only if there is no selection (otherwise it's impossible to select more text to copy/cut)
cursor = self.textCursor()
- if not cursor.hasSelection():
- cursor.select(QTextCursor.WordUnderCursor)
- self.setTextCursor(cursor)
+ selectedWord = cursor.selectedText() if cursor.hasSelection() else None
+
+ if not self.spellcheck:
+ return self.appendContextMenuEntriesForWord(popup_menu, selectedWord)
+
+ suggestions = []
+
+ # Check for any suggestions for corrections at the cursors position
+ if self._dict != None:
+ text = self.toPlainText()
+
+ suggestions = self._dict.findSuggestions(text, cursor.selectionStart(), cursor.selectionEnd())
+
+ # Select the word under the cursor if necessary.
+ # But only if there is no selection (otherwise it's impossible to select more text to copy/cut)
+ if not cursor.hasSelection() and len(suggestions) == 0:
+ old_position = cursor.position()
+
+ cursor.select(QTextCursor.WordUnderCursor)
+ self.setTextCursor(cursor)
+
+ if cursor.hasSelection():
+ selectedWord = cursor.selectedText()
+
+ # Check if the selected word is misspelled and offer spelling
+ # suggestions if it is.
+ suggestions = self._dict.findSuggestions(text, cursor.selectionStart(), cursor.selectionEnd())
+
+ if len(suggestions) == 0:
+ cursor.clearSelection()
+ cursor.setPosition(old_position, QTextCursor.MoveAnchor)
+ self.setTextCursor(cursor)
+
+ selectedWord = None
+
+ popup_menu = self.appendContextMenuEntriesForWord(popup_menu, selectedWord)
+
+ if len(suggestions) > 0 or selectedWord != None:
+ valid = len(suggestions) == 0
- # Check if the selected word is misspelled and offer spelling
- # suggestions if it is.
- if self._dict and cursor.hasSelection():
- text = str(cursor.selectedText())
- valid = not self._dict.isMisspelled(text)
- selectedWord = cursor.selectedText()
if not valid:
- spell_menu = QMenu(self.tr('Spelling Suggestions'), self)
- spell_menu.setIcon(F.themeIcon("spelling"))
- for word in self._dict.getSuggestions(text):
- action = self.SpellAction(word, spell_menu)
- action.correct.connect(self.correctWord)
- spell_menu.addAction(action)
+ # I think it should focus on one type of error at a time.
+ match = suggestions[0]
+
popup_menu.insertSeparator(popup_menu.actions()[0])
- # Adds: add to dictionary
- addAction = QAction(self.tr("&Add to dictionary"), popup_menu)
- addAction.setIcon(QIcon.fromTheme("list-add"))
- addAction.triggered.connect(self.addWordToDict)
- addAction.setData(selectedWord)
- popup_menu.insertAction(popup_menu.actions()[0], addAction)
- # Only add the spelling suggests to the menu if there are
- # suggestions.
- if len(spell_menu.actions()) != 0:
- # Adds: suggestions
- popup_menu.insertMenu(popup_menu.actions()[0], spell_menu)
- # popup_menu.insertSeparator(popup_menu.actions()[0])
+
+ if match.locqualityissuetype == 'misspelling':
+ spell_menu = QMenu(self.tr('Spelling Suggestions'), self)
+ spell_menu.setIcon(F.themeIcon("spelling"))
+
+ if (match.end > match.start and selectedWord == None):
+ # Select the actual area of the match
+ cursor = self.textCursor()
+ cursor.setPosition(match.start, QTextCursor.MoveAnchor);
+ cursor.setPosition(match.end, QTextCursor.KeepAnchor);
+ self.setTextCursor(cursor)
+
+ selectedWord = cursor.selectedText()
+
+ for word in match.replacements:
+ action = self.SpellAction(word, spell_menu)
+ action.correct.connect(self.correctWord)
+ spell_menu.addAction(action)
+
+ # Adds: add to dictionary
+ addAction = QAction(self.tr("&Add to dictionary"), popup_menu)
+ addAction.setIcon(QIcon.fromTheme("list-add"))
+ addAction.triggered.connect(self.addWordToDict)
+ addAction.setData(selectedWord)
+
+ popup_menu.insertAction(popup_menu.actions()[0], addAction)
+
+ # Only add the spelling suggests to the menu if there are
+ # suggestions.
+ if len(match.replacements) > 0:
+ # Adds: suggestions
+ popup_menu.insertMenu(popup_menu.actions()[0], spell_menu)
+ else:
+ correct_menu = None
+ correct_action = None
+
+ if (len(match.replacements) > 0 and match.end > match.start):
+ # Select the actual area of the match
+ cursor = self.textCursor()
+ cursor.setPosition(match.start, QTextCursor.MoveAnchor);
+ cursor.setPosition(match.end, QTextCursor.KeepAnchor);
+ self.setTextCursor(cursor)
+
+ if len(match.replacements) > 0:
+ correct_menu = QMenu(self.tr('&Correction Suggestions'), self)
+ correct_menu.setIcon(F.themeIcon("spelling"))
+
+ for word in match.replacements:
+ action = self.SpellAction(word, correct_menu)
+ action.correct.connect(self.correctWord)
+ correct_menu.addAction(action)
+
+ if correct_menu == None:
+ correct_action = QAction(self.tr('&Correction Suggestion'), popup_menu)
+ correct_action.setIcon(F.themeIcon("spelling"))
+ correct_action.setEnabled(False)
+
+ # Wrap the message into a fitting width
+ msg_lines = textwrap.wrap(match.msg, 48)
+
+ # Insert the lines of the message backwards
+ for i in range(0, len(msg_lines)):
+ popup_menu.insertSection(popup_menu.actions()[0], msg_lines[len(msg_lines) - (i + 1)])
+
+ if correct_menu != None:
+ popup_menu.insertMenu(popup_menu.actions()[0], correct_menu)
+ else:
+ popup_menu.insertAction(popup_menu.actions()[0], correct_action)
# If word was added to custom dict, give the possibility to remove it
- elif valid and self._dict.isCustomWord(selectedWord):
+ elif self._dict.isCustomWord(selectedWord):
popup_menu.insertSeparator(popup_menu.actions()[0])
# Adds: remove from dictionary
- rmAction = QAction(self.tr("&Remove from custom dictionary"), popup_menu)
+ rmAction = QAction(
+ self.tr("&Remove from custom dictionary"), popup_menu)
rmAction.setIcon(QIcon.fromTheme("list-remove"))
rmAction.triggered.connect(self.rmWordFromDict)
rmAction.setData(selectedWord)
diff --git a/manuskript/ui/views/treeDelegates.py b/manuskript/ui/views/treeDelegates.py
index fa59702..040b4f5 100644
--- a/manuskript/ui/views/treeDelegates.py
+++ b/manuskript/ui/views/treeDelegates.py
@@ -34,8 +34,8 @@ class treeTitleDelegate(QStyledItemDelegate):
opt = QStyleOptionViewItem(option)
self.initStyleOption(opt, index)
- iconRect = style.subElementRect(style.SE_ItemViewItemDecoration, opt)
- textRect = style.subElementRect(style.SE_ItemViewItemText, opt)
+ iconRect = style.subElementRect(style.SE_ItemViewItemDecoration, opt, None)
+ textRect = style.subElementRect(style.SE_ItemViewItemText, opt, None)
# Background
style.drawPrimitive(style.PE_PanelItemViewItem, opt, painter)
@@ -111,6 +111,9 @@ class treeTitleDelegate(QStyledItemDelegate):
elif settings.viewSettings["Tree"]["InfoFolder"] == "WC":
extraText = item.wordCount()
extraText = " ({})".format(extraText)
+ elif settings.viewSettings["Tree"]["InfoFolder"] == "CC":
+ extraText = item.charCount()
+ extraText = " ({})".format(extraText)
elif settings.viewSettings["Tree"]["InfoFolder"] == "Progress":
extraText = int(toFloat(item.data(Outline.goalPercentage)) * 100)
if extraText:
@@ -124,6 +127,9 @@ class treeTitleDelegate(QStyledItemDelegate):
if settings.viewSettings["Tree"]["InfoText"] == "WC":
extraText = item.wordCount()
extraText = " ({})".format(extraText)
+ elif settings.viewSettings["Tree"]["InfoText"] == "CC":
+ extraText = item.charCount()
+ extraText = " ({})".format(extraText)
elif settings.viewSettings["Tree"]["InfoText"] == "Progress":
extraText = int(toFloat(item.data(Outline.goalPercentage)) * 100)
if extraText:
diff --git a/manuskript/ui/views/treeView.py b/manuskript/ui/views/treeView.py
index 3fa7271..2655808 100644
--- a/manuskript/ui/views/treeView.py
+++ b/manuskript/ui/views/treeView.py
@@ -69,7 +69,7 @@ class treeView(QTreeView, dndView, outlineBasics):
return menu
def expandCurrentIndex(self, index=None):
- if index is None or type(index) == bool:
+ if index == None or type(index) == bool:
index = self._indexesToOpen[0] # self.currentIndex()
self.expand(index)
@@ -78,7 +78,7 @@ class treeView(QTreeView, dndView, outlineBasics):
self.expandCurrentIndex(index=idx)
def collapseCurrentIndex(self, index=None):
- if index is None or type(index) == bool:
+ if index == None or type(index) == bool:
index = self._indexesToOpen[0] # self.currentIndex()
self.collapse(index)
diff --git a/manuskript/ui/views/webView.py b/manuskript/ui/views/webView.py
index 4d6462c..23e09d1 100644
--- a/manuskript/ui/views/webView.py
+++ b/manuskript/ui/views/webView.py
@@ -22,16 +22,13 @@ else:
if features['qtwebkit']:
from PyQt5.QtWebKitWidgets import QWebView
- print("Debug: Web rendering engine used: QWebView")
webEngine = "QtWebKit"
webView = QWebView
elif features['qtwebengine']:
from PyQt5 import QtWebEngineWidgets
- print("Debug: Web rendering engine used: QWebEngineView")
webEngine = "QtWebEngine"
webView = QtWebEngineWidgets.QWebEngineView
else:
from PyQt5.QtWidgets import QTextEdit
- print("Debug: Web rendering engine used: QTextEdit")
webEngine = "QTextEdit"
webView = QTextEdit
diff --git a/manuskript/ui/welcome.py b/manuskript/ui/welcome.py
index 8055c3a..fce923b 100644
--- a/manuskript/ui/welcome.py
+++ b/manuskript/ui/welcome.py
@@ -2,7 +2,7 @@
# --!-- coding: utf8 --!--
import locale
-import imp
+import importlib
import os
from PyQt5.QtCore import QSettings, QRegExp, Qt, QDir
@@ -21,6 +21,9 @@ from manuskript.models.worldModel import worldModel
from manuskript.ui.welcome_ui import Ui_welcome
from manuskript.ui import style as S
+import logging
+LOGGER = logging.getLogger(__name__)
+
try:
locale.setlocale(locale.LC_ALL, '')
except:
@@ -57,8 +60,7 @@ class welcome(QWidget, Ui_welcome):
sttgs = QSettings()
lastDirectory = sttgs.value("lastAccessedDirectory", defaultValue=".", type=str)
if lastDirectory != '.':
- print(qApp.translate("lastAccessedDirectoryInfo", "Last accessed directory \"{}\" loaded.").format(
- lastDirectory))
+ LOGGER.info("Last accessed directory \"{}\" loaded.".format(lastDirectory))
return lastDirectory
def setLastAccessedDirectory(self, dir):
@@ -371,7 +373,7 @@ class welcome(QWidget, Ui_welcome):
Qt.FindChildrenRecursively):
# Update self.template to reflect the changed name values
templateIndex = t.property("templateIndex")
- if templateIndex is not None :
+ if templateIndex != None :
self.template[1][templateIndex] = (
self.template[1][templateIndex][0],
t.text())
@@ -422,10 +424,12 @@ class welcome(QWidget, Ui_welcome):
self.tree.expandAll()
def loadDefaultDatas(self):
+ """Initialize a basic Manuskript project."""
# Empty settings
- imp.reload(settings)
+ importlib.reload(settings)
settings.initDefaultValues()
+ self.mw.loadEmptyDatas()
if self.template:
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"
# Tasks
- self.mw.mdlFlatData = QStandardItemModel(2, 8, self.mw)
-
- # 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"])
+ self.mw.mdlFlatData.setRowCount(2) # data from: infos.txt, summary.txt
+ self.mw.mdlFlatData.setColumnCount(8) # version_1.py: len(infos.txt) == 8
# Labels
- self.mw.mdlLabels = QStandardItemModel(self.mw)
for color, text in [
(Qt.transparent, ""),
(Qt.yellow, self.tr("Idea")),
@@ -458,7 +452,6 @@ class welcome(QWidget, Ui_welcome):
self.mw.mdlLabels.appendRow(QStandardItem(iconFromColor(color), text))
# Status
- self.mw.mdlStatus = QStandardItemModel(self.mw)
for text in [
"",
self.tr("TODO"),
@@ -468,14 +461,9 @@ class welcome(QWidget, Ui_welcome):
]:
self.mw.mdlStatus.appendRow(QStandardItem(text))
- # Plot
- self.mw.mdlPlots = plotModel(self.mw)
+ # Plot (nothing special needed)
# Outline
- self.mw.mdlOutline = outlineModel(self.mw)
-
- # World
- self.mw.mdlWorld = worldModel(self.mw)
root = self.mw.mdlOutline.rootItem
_type = "md"
@@ -509,3 +497,5 @@ class welcome(QWidget, Ui_welcome):
if self.template and self.template[1]:
addElement(root, self.template[1])
+
+ # World (nothing special needed)
diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml
index 703d87e..76df07f 100644
--- a/snap/snapcraft.yaml
+++ b/snap/snapcraft.yaml
@@ -21,6 +21,9 @@ grade: stable
confinement: strict
base: core18
icon: icons/Manuskript/manuskript.svg
+layout:
+ /usr/share/pandoc/data/templates:
+ bind: $SNAP/usr/share/pandoc/data/templates
apps:
manuskript: