Merge branch 'develop' into develop

This commit is contained in:
Tobias Frisch 2021-04-08 18:33:27 +02:00 committed by GitHub
commit 7c93567279
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
107 changed files with 16579 additions and 13364 deletions

1
.gitignore vendored
View file

@ -20,3 +20,4 @@ icons/Numix
manuskript/pycallgraph.txt manuskript/pycallgraph.txt
snowflake* snowflake*
test-projects test-projects
main.pyproject.user

File diff suppressed because it is too large Load diff

Binary file not shown.

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

Binary file not shown.

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

Binary file not shown.

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

Binary file not shown.

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

3
main.pyproject Normal file

File diff suppressed because one or more lines are too long

View file

@ -26,7 +26,7 @@ class markdownConverter(abstractConverter):
@classmethod @classmethod
def isValid(self): def isValid(self):
return MD is not None return MD != None
@classmethod @classmethod
def convert(self, markdown): def convert(self, markdown):

View file

@ -1,5 +1,5 @@
#!/usr/bin/env python #!/usr/bin/env python
#--!-- coding: utf8 --!-- # --!-- coding: utf8 --!--
from enum import IntEnum from enum import IntEnum
@ -16,6 +16,8 @@ class Character(IntEnum):
summaryPara = 8 summaryPara = 8
summaryFull = 9 summaryFull = 9
notes = 10 notes = 10
pov = 11
infos = 12
class Plot(IntEnum): class Plot(IntEnum):
name = 0 name = 0
@ -60,8 +62,24 @@ class Outline(IntEnum):
textFormat = 15 textFormat = 15
revisions = 16 revisions = 16
customIcon = 17 customIcon = 17
charCount = 18
class Abstract(IntEnum): class Abstract(IntEnum):
title = 0 title = 0
ID = 1 ID = 1
type = 2 type = 2
class FlatData(IntEnum):
summarySituation = 0,
summarySentence = 1,
summaryPara = 2,
summaryPage = 3,
summaryFull = 4
class Model(IntEnum):
Character = 0
Plot = 1
PlotStep = 2
World = 3
Outline = 4
FlatData = 5

View file

@ -24,7 +24,7 @@ class HTML(markdown):
exportDefaultSuffix = ".html" exportDefaultSuffix = ".html"
def isValid(self): def isValid(self):
return MD is not None return MD != None
def settingsWidget(self): def settingsWidget(self):
w = markdownSettings(self) w = markdownSettings(self)

View file

@ -51,10 +51,10 @@ class plainText(basicFormat):
def getExportFilename(self, settingsWidget, varName=None, filter=None): def getExportFilename(self, settingsWidget, varName=None, filter=None):
if varName is None: if varName == None:
varName = self.exportVarName varName = self.exportVarName
if filter is None: if filter == None:
filter = self.exportFilter filter = self.exportFilter
settings = settingsWidget.getSettings() settings = settingsWidget.getSettings()

View file

@ -31,7 +31,7 @@ class PDF(abstractOutput):
def isValid(self): def isValid(self):
path = shutil.which("pdflatex") or shutil.which("xelatex") path = shutil.which("pdflatex") or shutil.which("xelatex")
return path is not None return path != None
def output(self, settingsWidget, outputfile=None): def output(self, settingsWidget, outputfile=None):
args = settingsWidget.runnableSettings() args = settingsWidget.runnableSettings()

View file

@ -75,7 +75,7 @@ class pandocSetting:
"""Return whether the specific setting is active with the given format.""" """Return whether the specific setting is active with the given format."""
# Empty formats means all # Empty formats means all
if self.formats is "": if self.formats == "":
return True return True
# "html" in "html markdown latex" # "html" in "html markdown latex"

View file

@ -9,7 +9,7 @@ from PyQt5.QtCore import Qt, QRect, QStandardPaths, QObject, QRegExp, QDir
from PyQt5.QtCore import QUrl, QTimer from PyQt5.QtCore import QUrl, QTimer
from PyQt5.QtGui import QBrush, QIcon, QPainter, QColor, QImage, QPixmap from PyQt5.QtGui import QBrush, QIcon, QPainter, QColor, QImage, QPixmap
from PyQt5.QtGui import QDesktopServices from PyQt5.QtGui import QDesktopServices
from PyQt5.QtWidgets import qApp, QFileDialog, QTextEdit from PyQt5.QtWidgets import qApp, QFileDialog
from manuskript.enums import Outline from manuskript.enums import Outline
@ -23,6 +23,14 @@ def wordCount(text):
t = [l for l in t if l] t = [l for l in t if l]
return len(t) return len(t)
def charCount(text, use_spaces = True):
t = text.strip()
if not use_spaces:
t = t.replace(" ", "")
return len(t)
validate_ok = lambda *args, **kwargs: True validate_ok = lambda *args, **kwargs: True
def uiParse(input, default, converter, validator=validate_ok): def uiParse(input, default, converter, validator=validate_ok):
""" """
@ -442,5 +450,48 @@ def inspect():
s.function)) s.function))
print(" " + "".join(s.code_context)) print(" " + "".join(s.code_context))
def search(searchRegex, text):
"""
Search all occurrences of a regex in a text.
:param searchRegex: a regex object with the search to perform
:param text: text to search on
:return: list of tuples (startPos, endPos)
"""
if text is not None:
return [(m.start(), m.end(), getSearchResultContext(text, m.start(), m.end())) for m in searchRegex.finditer(text)]
else:
return []
def getSearchResultContext(text, startPos, endPos):
matchSize = endPos - startPos
maxContextSize = max(matchSize, 600)
extraContextSize = int((maxContextSize - matchSize) / 2)
separator = "[...]"
context = ""
i = startPos - 1
while i > 0 and (startPos - i) < extraContextSize and text[i] != '\n':
i -= 1
contextStartPos = i
if i > 0:
context += separator + " "
context += text[contextStartPos:startPos].replace('\n', '')
context += '<b>' + text[startPos:endPos].replace('\n', '') + '</b>'
i = endPos
while i < len(text) and (i - endPos) < extraContextSize and text[i] != '\n':
i += 1
contextEndPos = i
context += text[endPos:contextEndPos].replace('\n', '')
if i < len(text):
context += " " + separator
return context
# Spellchecker loads writablePath from this file, so we need to load it after they get defined # Spellchecker loads writablePath from this file, so we need to load it after they get defined
from manuskript.functions.spellchecker import Spellchecker from manuskript.functions.spellchecker import Spellchecker

View file

@ -1,7 +1,7 @@
#!/usr/bin/env python #!/usr/bin/env python
# --!-- coding: utf8 --!-- # --!-- coding: utf8 --!--
import os, gzip, json, glob import os, gzip, json, glob, re
from PyQt5.QtCore import QLocale from PyQt5.QtCore import QLocale
from collections import OrderedDict from collections import OrderedDict
from manuskript.functions import writablePath from manuskript.functions import writablePath
@ -28,6 +28,17 @@ except ImportError:
symspellpy = None symspellpy = None
use_language_check = False
try:
try:
import language_tool_python as languagetool
except:
import language_check as languagetool
use_language_check = True
except:
languagetool = None
class Spellchecker: class Spellchecker:
dictionaries = {} dictionaries = {}
# In order of priority # In order of priority
@ -106,7 +117,7 @@ class Spellchecker:
(lib, name) = values (lib, name) = values
try: try:
d = Spellchecker.dictionaries.get(dictionary, None) d = Spellchecker.dictionaries.get(dictionary, None)
if d is None: if d == None:
for impl in Spellchecker.implementations: for impl in Spellchecker.implementations:
if impl.isInstalled() and lib == impl.getLibraryName(): if impl.isInstalled() and lib == impl.getLibraryName():
d = impl(name) d = impl(name)
@ -117,6 +128,17 @@ class Spellchecker:
pass pass
return None return None
class BasicMatch:
def __init__(self, startIndex, endIndex):
self.start = startIndex
self.end = endIndex
self.locqualityissuetype = 'misspelling'
self.replacements = []
self.msg = ''
def getWord(self, text):
return text[self.start:self.end]
class BasicDictionary: class BasicDictionary:
def __init__(self, name): def __init__(self, name):
self._lang = name self._lang = name
@ -162,12 +184,45 @@ class BasicDictionary:
def availableDictionaries(): def availableDictionaries():
raise NotImplemented raise NotImplemented
def checkText(self, 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
matches = []
for word_object in re.finditer(WORDS, text):
word = word_object.group(1)
if (self.isMisspelled(word) and not self.isCustomWord(word)):
matches.append(BasicMatch(
word_object.start(1), word_object.end(1)
))
return matches
def isMisspelled(self, word): def isMisspelled(self, word):
raise NotImplemented raise NotImplemented
def getSuggestions(self, word): def getSuggestions(self, word):
raise NotImplemented raise NotImplemented
def findSuggestions(self, text, start, end):
if start < end:
word = text[start:end]
if (self.isMisspelled(word) and not self.isCustomWord(word)):
match = BasicMatch(start, end)
match.replacements = self.getSuggestions(word)
return [ match ]
return []
def isCustomWord(self, word): def isCustomWord(self, word):
return word.lower() in self._customDict return word.lower() in self._customDict
@ -218,7 +273,7 @@ class EnchantDictionary(BasicDictionary):
@staticmethod @staticmethod
def isInstalled(): def isInstalled():
return enchant is not None return enchant != None
@staticmethod @staticmethod
def availableDictionaries(): def availableDictionaries():
@ -235,9 +290,9 @@ class EnchantDictionary(BasicDictionary):
if default_locale and not enchant.dict_exists(default_locale): if default_locale and not enchant.dict_exists(default_locale):
default_locale = None default_locale = None
if default_locale is None: if default_locale == None:
default_locale = QLocale.system().name() default_locale = QLocale.system().name()
if default_locale is None: if default_locale == None:
default_locale = self.availableDictionaries()[0] default_locale = self.availableDictionaries()[0]
return default_locale return default_locale
@ -278,7 +333,7 @@ class PySpellcheckerDictionary(BasicDictionary):
@staticmethod @staticmethod
def isInstalled(): def isInstalled():
return pyspellchecker is not None return pyspellchecker != None
@staticmethod @staticmethod
def availableDictionaries(): def availableDictionaries():
@ -298,7 +353,7 @@ class PySpellcheckerDictionary(BasicDictionary):
default_locale = QLocale.system().name() default_locale = QLocale.system().name()
if default_locale: if default_locale:
default_locale = default_locale[0:2] default_locale = default_locale[0:2]
if default_locale is None: if default_locale == None:
default_locale = "en" default_locale = "en"
return default_locale return default_locale
@ -363,7 +418,7 @@ class SymSpellDictionary(BasicDictionary):
@staticmethod @staticmethod
def isInstalled(): def isInstalled():
return symspellpy is not None return symspellpy != None
@classmethod @classmethod
def availableDictionaries(cls): def availableDictionaries(cls):
@ -422,8 +477,181 @@ class SymSpellDictionary(BasicDictionary):
# Since 6.3.8 # Since 6.3.8
self._dict.delete_dictionary_entry(word) self._dict.delete_dictionary_entry(word)
def get_languagetool_match_errorLength(match):
if use_language_check:
return match.errorlength
else:
return match.errorLength
def get_languagetool_match_ruleIssueType(match):
if use_language_check:
return match.locqualityissuetype
else:
return match.ruleIssueType
def get_languagetool_match_message(match):
if use_language_check:
return match.msg
else:
return match.message
class LanguageToolCache:
def __init__(self, tool, text):
self._length = len(text)
self._matches = self._buildMatches(tool, text)
def getMatches(self):
return self._matches
def _buildMatches(self, tool, text):
matches = []
for match in tool.check(text):
start = match.offset
end = start + get_languagetool_match_errorLength(match)
basic_match = BasicMatch(start, end)
basic_match.locqualityissuetype = get_languagetool_match_ruleIssueType(match)
basic_match.replacements = match.replacements
basic_match.msg = get_languagetool_match_message(match)
matches.append(basic_match)
return matches
def update(self, tool, text):
if len(text) != self._length:
self._matches = self._buildMatches(tool, text)
def get_languagetool_languages():
if use_language_check:
return languagetool.get_languages()
else:
return languagetool.LanguageTool()._get_languages()
class LanguageToolDictionary(BasicDictionary):
def __init__(self, name):
BasicDictionary.__init__(self, name)
if not (self._lang and self._lang in get_languagetool_languages()):
self._lang = self.getDefaultDictionary()
self._tool = languagetool.LanguageTool(self._lang)
self._cache = {}
@staticmethod
def getLibraryName():
return "LanguageTool"
@staticmethod
def getLibraryURL():
if use_language_check:
return "https://pypi.org/project/language-check/"
else:
return "https://pypi.org/project/language-tool-python/"
@staticmethod
def isInstalled():
if languagetool != None:
# This check, if Java is installed, is necessary to
# make sure LanguageTool can be run without problems.
#
return (os.system('java -version') == 0)
return False
@staticmethod
def availableDictionaries():
if LanguageToolDictionary.isInstalled():
languages = list(get_languagetool_languages())
languages.sort()
return languages
return []
@staticmethod
def getDefaultDictionary():
if not LanguageToolDictionary.isInstalled():
return None
default_locale = languagetool.get_locale_language()
if default_locale and not default_locale in get_languagetool_languages():
default_locale = None
if default_locale == None:
default_locale = QLocale.system().name()
if default_locale == None:
default_locale = self.availableDictionaries()[0]
return default_locale
def checkText(self, text):
matches = []
if len(text) == 0:
return matches
textId = hash(text)
cacheEntry = None
if not textId in self._cache:
cacheEntry = LanguageToolCache(self._tool, text)
self._cache[textId] = cacheEntry
else:
cacheEntry = self._cache[textId]
cacheEntry.update(self._tool, text)
for match in cacheEntry.getMatches():
word = match.getWord(text)
if not (match.locqualityissuetype == 'misspelling' and self.isCustomWord(word)):
matches.append(match)
return matches
def isMisspelled(self, word):
if self.isCustomWord(word):
return False
for match in self.checkText(word):
if match.locqualityissuetype == 'misspelling':
return True
return False
def getSuggestions(self, word):
suggestions = []
for match in self.checkText(word):
suggestions += match.replacements
return suggestions
def findSuggestions(self, text, start, end):
matches = []
checked = self.checkText(text)
if start == end:
# Check for containing area:
for match in checked:
if (start >= match.start and start <= match.end):
matches.append(match)
else:
# Check for overlapping area:
for match in checked:
if (match.end > start and match.start < end):
matches.append(match)
return matches
# Register the implementations in order of priority # Register the implementations in order of priority
Spellchecker.implementations.append(EnchantDictionary) Spellchecker.registerImplementation(EnchantDictionary)
Spellchecker.registerImplementation(SymSpellDictionary) Spellchecker.registerImplementation(SymSpellDictionary)
Spellchecker.registerImplementation(PySpellcheckerDictionary) Spellchecker.registerImplementation(PySpellcheckerDictionary)
Spellchecker.registerImplementation(LanguageToolDictionary)

View file

@ -47,7 +47,7 @@ class mindMapImporter(abstractImporter):
node = root.find("node") node = root.find("node")
items = [] items = []
if node is not None: if node != None:
items.extend(self.parseItems(node, parentItem)) items.extend(self.parseItems(node, parentItem))
ret = True ret = True
@ -97,7 +97,7 @@ class mindMapImporter(abstractImporter):
# Rich text content # Rich text content
content = "" content = ""
content = underElement.find("richcontent") content = underElement.find("richcontent")
if content is not None: if content != None:
# In Freemind, can be note or node # In Freemind, can be note or node
# Note: it's a note # Note: it's a note
# Node: it's the title of the node, in rich text # Node: it's the title of the node, in rich text
@ -130,7 +130,7 @@ class mindMapImporter(abstractImporter):
children = underElement.findall('node') children = underElement.findall('node')
# Process children # Process children
if children is not None and len(children) > 0: if children != None and len(children) > 0:
for c in children: for c in children:
items.extend(self.parseItems(c, item)) items.extend(self.parseItems(c, item))

View file

@ -52,10 +52,10 @@ class opmlImporter(abstractImporter):
bodyNode = opmlNode.find("body") bodyNode = opmlNode.find("body")
items = [] items = []
if bodyNode is not None: if bodyNode != None:
outlineEls = bodyNode.findall("outline") outlineEls = bodyNode.findall("outline")
if outlineEls is not None: if outlineEls != None:
for element in outlineEls: for element in outlineEls:
items.extend(cls.parseItems(element, parentItem)) items.extend(cls.parseItems(element, parentItem))
ret = True ret = True
@ -74,19 +74,20 @@ class opmlImporter(abstractImporter):
def parseItems(cls, underElement, parentItem=None): def parseItems(cls, underElement, parentItem=None):
items = [] items = []
title = underElement.get('text') title = underElement.get('text')
if title is not None: if title != None:
card = outlineItem(parent=parentItem, title=title) card = outlineItem(parent=parentItem, title=title)
items.append(card) items.append(card)
body = "" body = ""
note = underElement.get('_note') note = underElement.get('_note')
if note is not None and not cls.isWhitespaceOnly(note):
if note != None and not cls.isWhitespaceOnly(note):
#body = cls.restoreNewLines(note) #body = cls.restoreNewLines(note)
body = note body = note
children = underElement.findall('outline') children = underElement.findall('outline')
if children is not None and len(children) > 0:
if children != None and len(children) > 0:
for el in children: for el in children:
items.extend(cls.parseItems(el, card)) items.extend(cls.parseItems(el, card))
else: else:
@ -121,4 +122,4 @@ class opmlImporter(abstractImporter):
s = cls.restoreNewLines(inString) s = cls.restoreNewLines(inString)
s = ''.join(s.split()) s = ''.join(s.split())
return len(s) is 0 return len(s) == 0

View file

@ -38,7 +38,7 @@ class pandocImporter(abstractImporter):
r = pandocExporter().run(args) r = pandocExporter().run(args)
if r is None: if r == None:
return None return None
if formatTo == "opml": if formatTo == "opml":

View file

@ -40,6 +40,7 @@ characterMap = OrderedDict([
(Character.name, "Name"), (Character.name, "Name"),
(Character.ID, "ID"), (Character.ID, "ID"),
(Character.importance, "Importance"), (Character.importance, "Importance"),
(Character.pov, "POV"),
(Character.motivation, "Motivation"), (Character.motivation, "Motivation"),
(Character.goal, "Goal"), (Character.goal, "Goal"),
(Character.conflict, "Conflict"), (Character.conflict, "Conflict"),
@ -47,7 +48,7 @@ characterMap = OrderedDict([
(Character.summarySentence, "Phrase Summary"), (Character.summarySentence, "Phrase Summary"),
(Character.summaryPara, "Paragraph Summary"), (Character.summaryPara, "Paragraph Summary"),
(Character.summaryFull, "Full Summary"), (Character.summaryFull, "Full Summary"),
(Character.notes, "Notes"), (Character.notes, "Notes")
]) ])
# If true, logs infos while saving and loading. # If true, logs infos while saving and loading.
@ -106,7 +107,7 @@ def saveProject(zip=None):
settings. settings.
@return: True if successful, False otherwise. @return: True if successful, False otherwise.
""" """
if zip is None: if zip == None:
zip = settings.saveToZip zip = settings.saveToZip
log("\n\nSaving to:", "zip" if zip else "folder") log("\n\nSaving to:", "zip" if zip else "folder")
@ -123,7 +124,7 @@ def saveProject(zip=None):
project = mw.currentProject project = mw.currentProject
# Sanity check (see PR-583): make sure we actually have a current project. # Sanity check (see PR-583): make sure we actually have a current project.
if project is None: if project == None:
print("Error: cannot save project because there is no current project in the UI.") print("Error: cannot save project because there is no current project in the UI.")
return False return False
@ -197,7 +198,7 @@ def saveProject(zip=None):
# We skip the first row, which is empty and transparent # We skip the first row, which is empty and transparent
for i in range(1, mdl.rowCount()): for i in range(1, mdl.rowCount()):
color = "" color = ""
if mdl.data(mdl.index(i, 0), Qt.DecorationRole) is not None: if mdl.data(mdl.index(i, 0), Qt.DecorationRole) != None:
color = iconColor(mdl.data(mdl.index(i, 0), Qt.DecorationRole)).name(QColor.HexRgb) color = iconColor(mdl.data(mdl.index(i, 0), Qt.DecorationRole)).name(QColor.HexRgb)
color = color if color != "#ff000000" else "#00000000" color = color if color != "#ff000000" else "#00000000"
@ -900,7 +901,7 @@ def addTextItems(mdl, odict, parent=None):
@param odict: OrderedDict @param odict: OrderedDict
@return: nothing @return: nothing
""" """
if parent is None: if parent == None:
parent = mdl.rootItem parent = mdl.rootItem
for k in odict: for k in odict:

View file

@ -4,6 +4,7 @@ import faulthandler
import os import os
import platform import platform
import sys import sys
import signal
import manuskript.ui.views.webView import manuskript.ui.views.webView
from PyQt5.QtCore import QLocale, QTranslator, QSettings, Qt from PyQt5.QtCore import QLocale, QTranslator, QSettings, Qt
@ -15,11 +16,12 @@ from manuskript.version import getVersion
faulthandler.enable() faulthandler.enable()
def prepare(tests=False): def prepare(tests=False):
app = QApplication(sys.argv) app = QApplication(sys.argv)
app.setOrganizationName("manuskript"+("_tests" if tests else "")) app.setOrganizationName("manuskript" + ("_tests" if tests else ""))
app.setOrganizationDomain("www.theologeek.ch") app.setOrganizationDomain("www.theologeek.ch")
app.setApplicationName("manuskript"+("_tests" if tests else "")) app.setApplicationName("manuskript" + ("_tests" if tests else ""))
app.setApplicationVersion(getVersion()) app.setApplicationVersion(getVersion())
print("Running manuskript version {}.".format(getVersion())) print("Running manuskript version {}.".format(getVersion()))
@ -38,6 +40,7 @@ def prepare(tests=False):
# Translation process # Translation process
appTranslator = QTranslator(app) appTranslator = QTranslator(app)
# By default: locale # By default: locale
def tryLoadTranslation(translation, source): def tryLoadTranslation(translation, source):
@ -101,19 +104,21 @@ def prepare(tests=False):
def respectSystemDarkThemeSetting(): def respectSystemDarkThemeSetting():
"""Adjusts the Qt theme to match the OS 'dark theme' setting configured by the user.""" """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 return
# Basic Windows 10 Dark Theme support. # Basic Windows 10 Dark Theme support.
# Source: https://forum.qt.io/topic/101391/windows-10-dark-theme/4 # 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: if themeSettings.value("AppsUseLightTheme") == 0:
darkPalette = QPalette() darkPalette = QPalette()
darkColor = QColor(45,45,45) darkColor = QColor(45, 45, 45)
disabledColor = QColor(127,127,127) disabledColor = QColor(127, 127, 127)
darkPalette.setColor(QPalette.Window, darkColor) darkPalette.setColor(QPalette.Window, darkColor)
darkPalette.setColor(QPalette.WindowText, Qt.white) 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.AlternateBase, darkColor)
darkPalette.setColor(QPalette.ToolTipBase, Qt.white) darkPalette.setColor(QPalette.ToolTipBase, Qt.white)
darkPalette.setColor(QPalette.ToolTipText, Qt.white) darkPalette.setColor(QPalette.ToolTipText, Qt.white)
@ -137,7 +142,7 @@ def prepare(tests=False):
# This broke the Settings Dialog at one point... and then it stopped breaking it. # 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. # 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() respectSystemDarkThemeSetting()
@ -166,9 +171,9 @@ def prepare(tests=False):
return app, MW return app, MW
def launch(app, MW = None):
if MW is None: def launch(app, MW=None):
if MW == None:
from manuskript.functions import mainWindow from manuskript.functions import mainWindow
MW = mainWindow() MW = mainWindow()
@ -176,7 +181,7 @@ def launch(app, MW = None):
# Support for IPython Jupyter QT Console as a debugging aid. # Support for IPython Jupyter QT Console as a debugging aid.
# Last argument must be --console to enable it # 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/ipkernel_qtapp.py
# https://github.com/ipython/ipykernel/blob/master/examples/embedding/internal_ipkernel.py # https://github.com/ipython/ipykernel/blob/master/examples/embedding/internal_ipkernel.py
if len(sys.argv) > 1 and sys.argv[-1] == "--console": if len(sys.argv) > 1 and sys.argv[-1] == "--console":
@ -188,7 +193,7 @@ def launch(app, MW = None):
# Create IPython kernel within our application # Create IPython kernel within our application
kernel = IPKernelApp.instance() kernel = IPKernelApp.instance()
# Initialize it and use matplotlib for main event loop integration with QT # Initialize it and use matplotlib for main event loop integration with QT
kernel.initialize(['python', '--matplotlib=qt']) kernel.initialize(['python', '--matplotlib=qt'])
@ -207,6 +212,7 @@ def launch(app, MW = None):
app.quit() app.quit()
console.kill() console.kill()
kernel.io_loop.stop() kernel.io_loop.stop()
app.lastWindowClosed.connect(console_cleanup) app.lastWindowClosed.connect(console_cleanup)
# Very important, IPython-specific step: this gets GUI event loop # Very important, IPython-specific step: this gets GUI event loop
@ -221,6 +227,20 @@ def launch(app, MW = None):
qApp.exec_() qApp.exec_()
qApp.deleteLater() qApp.deleteLater()
def sigint_handler(sig, MW):
def handler(*args):
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 run(): def run():
""" """
Run separates prepare and launch for two reasons: Run separates prepare and launch for two reasons:
@ -229,9 +249,11 @@ def run():
""" """
# Need to return and keep `app` otherwise it gets deleted. # Need to return and keep `app` otherwise it gets deleted.
app, MW = prepare() app, MW = prepare()
setup_signal_handlers(MW)
# Separating launch to avoid segfault, so it seem. # Separating launch to avoid segfault, so it seem.
# Cf. http://stackoverflow.com/questions/12433491/is-this-pyqt-4-python-bug-or-wrongly-behaving-code # Cf. http://stackoverflow.com/questions/12433491/is-this-pyqt-4-python-bug-or-wrongly-behaving-code
launch(app, MW) launch(app, MW)
if __name__ == "__main__": if __name__ == "__main__":
run() run()

View file

@ -9,7 +9,7 @@ from PyQt5.QtCore import (pyqtSignal, QSignalMapper, QTimer, QSettings, Qt, QPoi
QRegExp, QUrl, QSize, QModelIndex) QRegExp, QUrl, QSize, QModelIndex)
from PyQt5.QtGui import QStandardItemModel, QIcon, QColor from PyQt5.QtGui import QStandardItemModel, QIcon, QColor
from PyQt5.QtWidgets import QMainWindow, QHeaderView, qApp, QMenu, QActionGroup, QAction, QStyle, QListWidgetItem, \ 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 import settings
from manuskript.enums import Character, PlotStep, Plot, World, Outline from manuskript.enums import Character, PlotStep, Plot, World, Outline
@ -129,6 +129,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.actCopy.triggered.connect(self.documentsCopy) self.actCopy.triggered.connect(self.documentsCopy)
self.actCut.triggered.connect(self.documentsCut) self.actCut.triggered.connect(self.documentsCut)
self.actPaste.triggered.connect(self.documentsPaste) self.actPaste.triggered.connect(self.documentsPaste)
self.actSearch.triggered.connect(self.doSearch)
self.actRename.triggered.connect(self.documentsRename) self.actRename.triggered.connect(self.documentsRename)
self.actDuplicate.triggered.connect(self.documentsDuplicate) self.actDuplicate.triggered.connect(self.documentsDuplicate)
self.actDelete.triggered.connect(self.documentsDelete) self.actDelete.triggered.connect(self.documentsDelete)
@ -270,7 +271,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.mainEditor self.mainEditor
] ]
while new is not None: while new != None:
if new in targets: if new in targets:
self._lastFocus = new self._lastFocus = new
break break
@ -346,6 +347,9 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# Slider importance # Slider importance
self.updateCharacterImportance(c.ID()) self.updateCharacterImportance(c.ID())
# POV state
self.updateCharacterPOVState(c.ID())
# Character Infos # Character Infos
self.tblPersoInfos.setRootIndex(index) self.tblPersoInfos.setRootIndex(index)
@ -366,6 +370,22 @@ class MainWindow(QMainWindow, Ui_MainWindow):
c = self.mdlCharacter.getCharacterByID(ID) c = self.mdlCharacter.getCharacterByID(ID)
self.sldPersoImportance.setValue(int(c.importance())) 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 # PLOTS
############################################################################### ###############################################################################
@ -480,6 +500,13 @@ class MainWindow(QMainWindow, Ui_MainWindow):
def documentsPaste(self): def documentsPaste(self):
"Paste clipboard item(s) into selected item." "Paste clipboard item(s) into selected item."
if self._lastFocus: self._lastFocus.paste() 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): def documentsRename(self):
"Rename selected item." "Rename selected item."
if self._lastFocus: self._lastFocus.rename() if self._lastFocus: self._lastFocus.rename()
@ -823,7 +850,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# risk a scenario where the timer somehow triggers a new save while saving. # risk a scenario where the timer somehow triggers a new save while saving.
self.saveTimerNoChanges.stop() 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 # No UI feedback here as this code path indicates a race condition that happens
# after the user has already closed the project through some way. But in that # after the user has already closed the project through some way. But in that
# scenario, this code should not be reachable to begin with. # scenario, this code should not be reachable to begin with.
@ -933,11 +960,13 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# Characters # Characters
self.lstCharacters.setCharactersModel(self.mdlCharacter) self.lstCharacters.setCharactersModel(self.mdlCharacter)
self.tblPersoInfos.setModel(self.mdlCharacter) self.tblPersoInfos.setModel(self.mdlCharacter)
self.btnAddPerso.clicked.connect(self.mdlCharacter.addCharacter, F.AUC)
try: try:
self.btnAddPerso.clicked.connect(self.lstCharacters.addCharacter, F.AUC)
self.btnRmPerso.clicked.connect(self.lstCharacters.removeCharacter, F.AUC) self.btnRmPerso.clicked.connect(self.lstCharacters.removeCharacter, F.AUC)
self.btnPersoColor.clicked.connect(self.lstCharacters.choseCharacterColor, 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.btnPersoAddInfo.clicked.connect(self.lstCharacters.addCharacterInfo, F.AUC)
self.btnPersoRmInfo.clicked.connect(self.lstCharacters.removeCharacterInfo, F.AUC) self.btnPersoRmInfo.clicked.connect(self.lstCharacters.removeCharacterInfo, F.AUC)
except TypeError: except TypeError:
@ -1078,7 +1107,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# disconnect only removes one connection at a time. # disconnect only removes one connection at a time.
while True: while True:
try: try:
if oldHandler is not None: if oldHandler != None:
signal.disconnect(oldHandler) signal.disconnect(oldHandler)
else: else:
signal.disconnect() signal.disconnect()
@ -1089,9 +1118,12 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# Break connections for UI elements that were connected in makeConnections() # Break connections for UI elements that were connected in makeConnections()
# Characters # 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.btnRmPerso.clicked, self.lstCharacters.removeCharacter)
self.disconnectAll(self.btnPersoColor.clicked, self.lstCharacters.choseCharacterColor) 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.btnPersoAddInfo.clicked, self.lstCharacters.addCharacterInfo)
self.disconnectAll(self.btnPersoRmInfo.clicked, self.lstCharacters.removeCharacterInfo) self.disconnectAll(self.btnPersoRmInfo.clicked, self.lstCharacters.removeCharacterInfo)
@ -1355,7 +1387,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
dictionaries = Spellchecker.availableDictionaries() dictionaries = Spellchecker.availableDictionaries()
# Set first run dictionary # Set first run dictionary
if settings.dict is None: if settings.dict == None:
settings.dict = Spellchecker.getDefaultDictionary() settings.dict = Spellchecker.getDefaultDictionary()
# Check if project dict is unavailable on this machine # Check if project dict is unavailable on this machine
@ -1566,7 +1598,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
w.cmbPOV.setVisible(val) w.cmbPOV.setVisible(val)
# POV in outline view # 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) settings.outlineViewColumns.remove(Outline.POV)
from manuskript.ui.views.outlineView import outlineView from manuskript.ui.views.outlineView import outlineView

View file

@ -39,7 +39,7 @@ class abstractItem():
self._data[self.enum.title] = title self._data[self.enum.title] = title
self._data[self.enum.type] = _type self._data[self.enum.type] = _type
if xml is not None: if xml != None:
self.setFromXML(xml) self.setFromXML(xml)
if parent: if parent:

View file

@ -140,7 +140,7 @@ class abstractModel(QAbstractItemModel):
# Check whether the parent is the root, or is otherwise invalid. # Check whether the parent is the root, or is otherwise invalid.
# That is to say: no parent or the parent lacks a parent. # That is to say: no parent or the parent lacks a parent.
if (parentItem == self.rootItem) or \ if (parentItem == self.rootItem) or \
(parentItem is None) or (parentItem.parent() is None): (parentItem == None) or (parentItem.parent() == None):
return QModelIndex() return QModelIndex()
return self.createIndex(parentItem.row(), 0, parentItem) return self.createIndex(parentItem.row(), 0, parentItem)
@ -292,7 +292,7 @@ class abstractModel(QAbstractItemModel):
# # Gets encoded mime data to retrieve the item # # Gets encoded mime data to retrieve the item
items = self.decodeMimeData(data) items = self.decodeMimeData(data)
if items is None: if items == None:
return False return False
# We check if parent is not a child of one of the items # We check if parent is not a child of one of the items
@ -330,7 +330,7 @@ class abstractModel(QAbstractItemModel):
return None return None
encodedData = bytes(data.data("application/xml")).decode() encodedData = bytes(data.data("application/xml")).decode()
root = ET.XML(encodedData) root = ET.XML(encodedData)
if root is None: if root == None:
return None return None
if root.tag != "outlineItems": if root.tag != "outlineItems":
@ -390,7 +390,7 @@ class abstractModel(QAbstractItemModel):
items = self.decodeMimeData(data) items = self.decodeMimeData(data)
if items is None: if items == None:
return False return False
if column > 0: if column > 0:

View file

@ -3,11 +3,14 @@
from PyQt5.QtCore import QModelIndex, Qt, QAbstractItemModel, QVariant from PyQt5.QtCore import QModelIndex, Qt, QAbstractItemModel, QVariant
from PyQt5.QtGui import QIcon, QPixmap, QColor from PyQt5.QtGui import QIcon, QPixmap, QColor
from manuskript.functions import randomColor, iconColor, mainWindow from manuskript.functions import randomColor, iconColor, mainWindow, search
from manuskript.enums import Character as C 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): def __init__(self, parent):
QAbstractItemModel.__init__(self, parent) QAbstractItemModel.__init__(self, parent)
@ -132,6 +135,9 @@ class characterModel(QAbstractItemModel):
def importance(self, row): def importance(self, row):
return self.character(row).importance() return self.character(row).importance()
def pov(self, row):
return self.character(row).pov()
############################################################################### ###############################################################################
# MODEL QUERIES # MODEL QUERIES
############################################################################### ###############################################################################
@ -143,29 +149,36 @@ class characterModel(QAbstractItemModel):
@return: array of array of ´character´, by importance. @return: array of array of ´character´, by importance.
""" """
r = [[], [], []] r = [[], [], []]
for c in self.characters: for c in self.characters:
r[2-int(c.importance())].append(c) r[2-int(c.importance())].append(c)
return r return r
def getCharacterByID(self, ID): def getCharacterByID(self, ID):
if ID is not None: if ID != None:
ID = str(ID) ID = str(ID)
for c in self.characters: for c in self.characters:
if c.ID() == ID: if c.ID() == ID:
return c return c
return None return None
############################################################################### ###############################################################################
# ADDING / REMOVING # ADDING / REMOVING
############################################################################### ###############################################################################
def addCharacter(self): def addCharacter(self, importance = 0, name="New character"):
""" """
Creates a new character Creates a new character
@param importance: the importance level of the character
@return: the character @return: the character
""" """
c = Character(model=self, name=self.tr("New character")) if not name:
self.beginInsertRows(QModelIndex(), len(self.characters), len(self.characters)) 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.characters.append(c)
self.endInsertRows() self.endInsertRows()
return c return c
@ -177,7 +190,8 @@ class characterModel(QAbstractItemModel):
@return: nothing @return: nothing
""" """
c = self.getCharacterByID(ID) 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.characters.remove(c)
self.endRemoveRows() self.endRemoveRows()
@ -197,7 +211,8 @@ class characterModel(QAbstractItemModel):
def addCharacterInfo(self, ID): def addCharacterInfo(self, ID):
c = self.getCharacterByID(ID) c = self.getCharacterByID(ID)
self.beginInsertRows(c.index(), len(c.infos), len(c.infos)) 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() self.endInsertRows()
mainWindow().updatePersoInfoView() mainWindow().updatePersoInfoView()
@ -217,12 +232,15 @@ class characterModel(QAbstractItemModel):
c.infos.pop(r) c.infos.pop(r)
self.endRemoveRows() self.endRemoveRows()
def searchableItems(self):
return self.characters
############################################################################### ###############################################################################
# CHARACTER # CHARACTER
############################################################################### ###############################################################################
class Character(): class Character(searchableItem):
def __init__(self, model, name="No name"): def __init__(self, model, name="No name", importance = 0):
self._model = model self._model = model
self.lastPath = "" self.lastPath = ""
@ -230,13 +248,19 @@ class Character():
self._data[C.name.value] = name self._data[C.name.value] = name
self.assignUniqueID() self.assignUniqueID()
self.assignRandomColor() self.assignRandomColor()
self._data[C.importance.value] = "0" self._data[C.importance.value] = str(importance)
self._data[C.pov.value] = "True"
self.infos = [] self.infos = []
super().__init__(CharacterSearchLabels)
def name(self): def name(self):
return self._data[C.name.value] return self._data[C.name.value]
def setName(self, value):
self._data[C.name.value] = value
def importance(self): def importance(self):
return self._data[C.importance.value] return self._data[C.importance.value]
@ -246,6 +270,12 @@ class Character():
def index(self, column=0): def index(self, column=0):
return self._model.indexFromItem(self, column) 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): def assignRandomColor(self):
""" """
Assigns a random color the the character. Assigns a random color the the character.
@ -274,6 +304,22 @@ class Character():
""" """
return iconColor(self.icon) 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()): def assignUniqueID(self, parent=QModelIndex()):
"""Assigns an unused character ID.""" """Assigns an unused character ID."""
vals = [] vals = []
@ -292,6 +338,42 @@ class Character():
r.append((i.description, i.value)) r.append((i.description, i.value))
return r 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(): class CharacterInfo():
def __init__(self, character, description="", value=""): def __init__(self, character, description="", value=""):
self.description = description self.description = description

View file

@ -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))

View file

@ -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]

View file

@ -8,10 +8,13 @@ from PyQt5.QtGui import QFont, QIcon
from PyQt5.QtWidgets import qApp from PyQt5.QtWidgets import qApp
from lxml import etree as ET from lxml import etree as ET
from manuskript.models.abstractItem import abstractItem from manuskript.models.abstractItem import abstractItem
from manuskript.models.searchableItem import searchableItem
from manuskript import enums from manuskript import enums
from manuskript import functions as F from manuskript import functions as F
from manuskript import settings from manuskript import settings
from manuskript.converters import HTML2PlainText from manuskript.converters import HTML2PlainText
from manuskript.searchLabels import OutlineSearchLabels
from manuskript.enums import Outline, Model
try: try:
locale.setlocale(locale.LC_ALL, '') locale.setlocale(locale.LC_ALL, '')
@ -21,7 +24,7 @@ except:
pass pass
class outlineItem(abstractItem): class outlineItem(abstractItem, searchableItem):
enum = enums.Outline enum = enums.Outline
@ -30,6 +33,7 @@ class outlineItem(abstractItem):
def __init__(self, model=None, title="", _type="folder", xml=None, parent=None, ID=None): def __init__(self, model=None, title="", _type="folder", xml=None, parent=None, ID=None):
abstractItem.__init__(self, model, title, _type, xml, parent, ID) abstractItem.__init__(self, model, title, _type, xml, parent, ID)
searchableItem.__init__(self, OutlineSearchLabels)
self.defaultTextType = None self.defaultTextType = None
if not self._data.get(self.enum.compile): if not self._data.get(self.enum.compile):
@ -80,6 +84,9 @@ class outlineItem(abstractItem):
def wordCount(self): def wordCount(self):
return self._data.get(self.enum.wordCount, 0) return self._data.get(self.enum.wordCount, 0)
def charCount(self):
return self._data.get(self.enum.charCount, 0)
def __str__(self): def __str__(self):
return "{id}: {folder}{title}{children}".format( return "{id}: {folder}{title}{children}".format(
id=self.ID(), id=self.ID(),
@ -89,6 +96,9 @@ class outlineItem(abstractItem):
) )
__repr__ = __str__ __repr__ = __str__
def charCount(self):
return self._data.get(self.enum.charCount, 0)
####################################################################### #######################################################################
# Data # Data
@ -119,7 +129,7 @@ class outlineItem(abstractItem):
elif role == Qt.FontRole: elif role == Qt.FontRole:
f = QFont() f = QFont()
if column == E.wordCount and self.isFolder(): if (column == E.wordCount or column == E.charCount) and self.isFolder():
f.setItalic(True) f.setItalic(True)
elif column == E.goal and self.isFolder() and not self.data(E.setGoal): elif column == E.goal and self.isFolder() and not self.data(E.setGoal):
f.setItalic(True) f.setItalic(True)
@ -140,7 +150,7 @@ class outlineItem(abstractItem):
# Checking if we will have to recount words # Checking if we will have to recount words
updateWordCount = False 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 updateWordCount = not column in self._data or self._data[column] != data
# Stuff to do before # Stuff to do before
@ -153,7 +163,9 @@ class outlineItem(abstractItem):
# Stuff to do afterwards # Stuff to do afterwards
if column == E.text: if column == E.text:
wc = F.wordCount(data) wc = F.wordCount(data)
cc = F.charCount(data, settings.countSpaces)
self.setData(E.wordCount, wc) self.setData(E.wordCount, wc)
self.setData(E.charCount, cc)
if column == E.compile: if column == E.compile:
# Title changes when compile changes # Title changes when compile changes
@ -195,9 +207,12 @@ class outlineItem(abstractItem):
else: else:
wc = 0 wc = 0
cc = 0
for c in self.children(): for c in self.children():
wc += F.toInt(c.data(self.enum.wordCount)) 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.wordCount] = wc
self._data[self.enum.charCount] = cc
setGoal = F.toInt(self.data(self.enum.setGoal)) setGoal = F.toInt(self.data(self.enum.setGoal))
goal = F.toInt(self.data(self.enum.goal)) goal = F.toInt(self.data(self.enum.goal))
@ -218,7 +233,8 @@ class outlineItem(abstractItem):
self.setData(self.enum.goalPercentage, "") self.setData(self.enum.goalPercentage, "")
self.emitDataChanged([self.enum.goal, self.enum.setGoal, 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(): if self.parent():
self.parent().updateWordCount() self.parent().updateWordCount()
@ -343,8 +359,7 @@ class outlineItem(abstractItem):
return lst return lst
def findItemsContaining(self, text, columns, mainWindow=F.mainWindow(), def findItemsContaining(self, text, columns, mainWindow=F.mainWindow(), caseSensitive=False, recursive=True):
caseSensitive=False, recursive=True):
"""Returns a list if IDs of all subitems """Returns a list if IDs of all subitems
containing ``text`` in columns ``columns`` containing ``text`` in columns ``columns``
(being a list of int). (being a list of int).
@ -357,16 +372,14 @@ class outlineItem(abstractItem):
return lst return lst
def itemContains(self, text, columns, mainWindow=F.mainWindow(), def itemContains(self, text, columns, mainWindow=F.mainWindow(), caseSensitive=False):
caseSensitive=False):
lst = [] lst = []
text = text.lower() if not caseSensitive else text text = text.lower() if not caseSensitive else text
for c in columns: for c in columns:
if c == self.enum.POV and self.POV(): if c == self.enum.POV and self.POV():
c = mainWindow.mdlCharacter.getCharacterByID(self.POV()) character = mainWindow.mdlCharacter.getCharacterByID(self.POV())
if c: if character:
searchIn = c.name() searchIn = character.name()
else: else:
searchIn = "" searchIn = ""
print("Character POV not found:", self.POV()) print("Character POV not found:", self.POV())
@ -381,7 +394,6 @@ class outlineItem(abstractItem):
searchIn = self.data(c) searchIn = self.data(c)
searchIn = searchIn.lower() if not caseSensitive else searchIn searchIn = searchIn.lower() if not caseSensitive else searchIn
if text in searchIn: if text in searchIn:
if not self.ID() in lst: if not self.ID() in lst:
lst.append(self.ID()) lst.append(self.ID())
@ -467,6 +479,7 @@ class outlineItem(abstractItem):
# We don't want to write some datas (computed) # We don't want to write some datas (computed)
XMLExclude = [enums.Outline.wordCount, XMLExclude = [enums.Outline.wordCount,
enums.Outline.charCount,
enums.Outline.goal, enums.Outline.goal,
enums.Outline.goalPercentage, enums.Outline.goalPercentage,
enums.Outline.revisions] enums.Outline.revisions]
@ -502,3 +515,39 @@ class outlineItem(abstractItem):
for child in root: for child in root:
if child.tag == "revision": if child.tag == "revision":
self.appendRevision(child.attrib["timestamp"], child.attrib["text"]) 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

View file

@ -2,10 +2,10 @@
# --!-- coding: utf8 --!-- # --!-- coding: utf8 --!--
from manuskript.models.abstractModel import abstractModel from manuskript.models.abstractModel import abstractModel
from manuskript.models import outlineItem from manuskript.models.searchableModel import searchableModel
from manuskript.models.outlineItem import outlineItem
class outlineModel(abstractModel, searchableModel):
class outlineModel(abstractModel):
def __init__(self, parent): def __init__(self, parent):
abstractModel.__init__(self, parent) abstractModel.__init__(self, parent)
self.rootItem = outlineItem(model=self, title="Root", ID="0") self.rootItem = outlineItem(model=self, title="Root", ID="0")
@ -14,3 +14,19 @@ class outlineModel(abstractModel):
def findItemsByPOV(self, POV): def findItemsByPOV(self, POV):
"Returns a list of IDs of all items whose POV is ``POV``." "Returns a list of IDs of all items whose POV is ``POV``."
return self.rootItem.findItemsByPOV(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

View file

@ -8,12 +8,15 @@ from PyQt5.QtGui import QStandardItem
from PyQt5.QtGui import QStandardItemModel from PyQt5.QtGui import QStandardItemModel
from PyQt5.QtWidgets import QAction, QMenu from PyQt5.QtWidgets import QAction, QMenu
from manuskript.enums import Plot from manuskript.enums import Plot, PlotStep, Model
from manuskript.enums import PlotStep
from manuskript.functions import toInt, mainWindow 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, searchableModel):
class plotModel(QStandardItemModel):
def __init__(self, parent): def __init__(self, parent):
QStandardItemModel.__init__(self, 0, 3, parent) QStandardItemModel.__init__(self, 0, 3, parent)
self.setHorizontalHeaderLabels([i.name for i in Plot]) self.setHorizontalHeaderLabels([i.name for i in Plot])
@ -73,7 +76,7 @@ class plotModel(QStandardItemModel):
if i == row: if i == row:
importance = self.item(i, Plot.importance).text() importance = self.item(i, Plot.importance).text()
return importance return importance
return "0" # Default to "Minor" return "0" # Default to "Minor"
def getSubPlotTextsByID(self, plotID, subplotRaw): def getSubPlotTextsByID(self, plotID, subplotRaw):
"""Returns a tuple (name, summary) for the subplot whose raw in the model """Returns a tuple (name, summary) for the subplot whose raw in the model
@ -102,12 +105,15 @@ class plotModel(QStandardItemModel):
# ADDING / REMOVING # ADDING / REMOVING
############################################################################### ###############################################################################
def addPlot(self): def addPlot(self, name="New plot"):
p = QStandardItem(self.tr("New plot")) if not name:
name="New Plot"
p = QStandardItem(self.tr(name))
_id = QStandardItem(self.getUniqueID()) _id = QStandardItem(self.getUniqueID())
importance = QStandardItem(str(0)) importance = QStandardItem(str(0))
self.appendRow([p, _id, importance, QStandardItem("Characters"), self.appendRow([p, _id, importance, QStandardItem("Characters"),
QStandardItem(), QStandardItem(), QStandardItem("Resolution steps")]) QStandardItem(), QStandardItem(), QStandardItem("Resolution steps")])
return p, _id
def getUniqueID(self, parent=QModelIndex()): def getUniqueID(self, parent=QModelIndex()):
"""Returns an unused ID""" """Returns an unused ID"""
@ -147,8 +153,8 @@ class plotModel(QStandardItemModel):
def data(self, index, role=Qt.DisplayRole): def data(self, index, role=Qt.DisplayRole):
if index.parent().isValid() and \ if index.parent().isValid() and \
index.parent().column() == Plot.steps and \ index.parent().column() == Plot.steps and \
index.column() == PlotStep.meta: index.column() == PlotStep.meta:
if role == Qt.TextAlignmentRole: if role == Qt.TextAlignmentRole:
return Qt.AlignRight | Qt.AlignVCenter return Qt.AlignRight | Qt.AlignVCenter
elif role == Qt.ForegroundRole: 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... # Don't know why, if summary is in third position, then drag/drop deletes it...
parentItem.appendRow([p, _id, QStandardItem(), summary]) parentItem.appendRow([p, _id, QStandardItem(), summary])
# Select last index # 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): def removeSubPlot(self):
""" """
@ -262,3 +269,118 @@ class plotModel(QStandardItemModel):
mpr.mapped.connect(self.addPlotPerso) mpr.mapped.connect(self.addPlotPerso)
self.mw.btnAddPlotPerso.setMenu(menu) 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

View file

@ -187,7 +187,7 @@ def infos(ref):
elif _type == CharacterLetter: elif _type == CharacterLetter:
m = mainWindow().mdlCharacter m = mainWindow().mdlCharacter
c = m.getCharacterByID(int(_ref)) c = m.getCharacterByID(int(_ref))
if c is None: if c == None:
return qApp.translate("references", "Unknown reference: {}.").format(ref) return qApp.translate("references", "Unknown reference: {}.").format(ref)
index = c.index() index = c.index()

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -1,18 +1,20 @@
#!/usr/bin/env python #!/usr/bin/env python
# --!-- coding: utf8 --!-- # --!-- coding: utf8 --!--
from PyQt5.QtCore import QModelIndex from PyQt5.QtCore import QModelIndex, QSize
from PyQt5.QtCore import QSize
from PyQt5.QtCore import Qt, QMimeData, QByteArray from PyQt5.QtCore import Qt, QMimeData, QByteArray
from PyQt5.QtGui import QStandardItem, QBrush, QFontMetrics from PyQt5.QtGui import QStandardItem, QBrush, QFontMetrics
from PyQt5.QtGui import QStandardItemModel, QColor from PyQt5.QtGui import QStandardItemModel, QColor
from PyQt5.QtWidgets import QMenu, QAction, qApp 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.functions import mainWindow
from manuskript.ui import style as S 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): def __init__(self, parent):
QStandardItemModel.__init__(self, 0, len(World), parent) QStandardItemModel.__init__(self, 0, len(World), parent)
self.mw = mainWindow() self.mw = mainWindow()
@ -136,6 +138,9 @@ class worldModel(QStandardItemModel):
_id = QStandardItem(self.getUniqueID()) _id = QStandardItem(self.getUniqueID())
row = [name, _id] + [QStandardItem() for i in range(2, len(World))] row = [name, _id] + [QStandardItem() for i in range(2, len(World))]
parent.appendRow(row) parent.appendRow(row)
self.mw.treeWorld.setExpanded(self.selectedIndex(), True)
self.mw.treeWorld.setCurrentIndex(self.indexFromItem(name))
return name return name
def getUniqueID(self): def getUniqueID(self):
@ -186,7 +191,7 @@ class worldModel(QStandardItemModel):
for index in indexes: for index in indexes:
item = self.itemFromIndex(index) item = self.itemFromIndex(index)
parent = item.parent() parent = item.parent()
if parent is None: if parent == None:
parent = self.invisibleRootItem() parent = self.invisibleRootItem()
row_indexes.append((parent, item.row())) row_indexes.append((parent, item.row()))
@ -353,3 +358,51 @@ class worldModel(QStandardItemModel):
return QSize(0, h + 6) return QSize(0, h + 6)
return QStandardItemModel.data(self, index, role) 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))

View file

@ -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"
}

View file

@ -47,6 +47,8 @@ corkSizeFactor = 100
folderView = "cork" folderView = "cork"
lastTab = 0 lastTab = 0
openIndexes = [""] openIndexes = [""]
progressChars = False
countSpaces = True
autoSave = False autoSave = False
autoSaveDelay = 5 autoSaveDelay = 5
autoSaveNoChanges = True autoSaveNoChanges = True
@ -123,7 +125,7 @@ def initDefaultValues():
def save(filename=None, protocol=None): def save(filename=None, protocol=None):
global spellcheck, dict, corkSliderFactor, viewSettings, corkSizeFactor, folderView, lastTab, openIndexes, \ 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, \ corkBackground, corkStyle, fullScreenTheme, defaultTextType, textEditor, revisions, frequencyAnalyzer, viewMode, \
saveToZip, dontShowDeleteWarning, fullscreenSettings saveToZip, dontShowDeleteWarning, fullscreenSettings
@ -136,6 +138,8 @@ def save(filename=None, protocol=None):
"folderView": folderView, "folderView": folderView,
"lastTab": lastTab, "lastTab": lastTab,
"openIndexes": openIndexes, "openIndexes": openIndexes,
"progressChars": progressChars,
"countSpaces": countSpaces,
"autoSave":autoSave, "autoSave":autoSave,
"autoSaveDelay":autoSaveDelay, "autoSaveDelay":autoSaveDelay,
# TODO: Settings Cleanup Task -- Rename saveOnQuit to saveOnProjectClose -- see PR #615 # TODO: Settings Cleanup Task -- Rename saveOnQuit to saveOnProjectClose -- see PR #615
@ -235,6 +239,14 @@ def load(string, fromString=False, protocol=None):
global openIndexes global openIndexes
openIndexes = allSettings["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: if "autoSave" in allSettings:
global autoSave global autoSave
autoSave = allSettings["autoSave"] autoSave = allSettings["autoSave"]

View file

@ -59,11 +59,16 @@ class settingsWindow(QWidget, Ui_Settings):
self.lstMenu.setMaximumWidth(140) self.lstMenu.setMaximumWidth(140)
self.lstMenu.setMinimumWidth(140) self.lstMenu.setMinimumWidth(140)
lowerKeys = [i.lower() for i in list(QStyleFactory.keys())]
# General # General
self.cmbStyle.addItems(list(QStyleFactory.keys())) self.cmbStyle.addItems(list(QStyleFactory.keys()))
self.cmbStyle.setCurrentIndex(
[i.lower() for i in list(QStyleFactory.keys())] try:
.index(qApp.style().objectName())) self.cmbStyle.setCurrentIndex(lowerKeys.index(qApp.style().objectName()))
except ValueError:
self.cmbStyle.setCurrentIndex(0)
self.cmbStyle.currentIndexChanged[str].connect(self.setStyle) self.cmbStyle.currentIndexChanged[str].connect(self.setStyle)
self.cmbTranslation.clear() self.cmbTranslation.clear()
@ -111,6 +116,9 @@ class settingsWindow(QWidget, Ui_Settings):
self.spnGeneralFontSize.setValue(f.pointSize()) self.spnGeneralFontSize.setValue(f.pointSize())
self.spnGeneralFontSize.valueChanged.connect(self.setAppFontSize) 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.txtAutoSave.setValidator(QIntValidator(0, 999, self))
self.txtAutoSaveNoChanges.setValidator(QIntValidator(0, 999, self)) self.txtAutoSaveNoChanges.setValidator(QIntValidator(0, 999, self))
self.chkAutoSave.setChecked(settings.autoSave) self.chkAutoSave.setChecked(settings.autoSave)
@ -164,10 +172,12 @@ class settingsWindow(QWidget, Ui_Settings):
for item, what, value in [ for item, what, value in [
(self.rdoTreeItemCount, "InfoFolder", "Count"), (self.rdoTreeItemCount, "InfoFolder", "Count"),
(self.rdoTreeWC, "InfoFolder", "WC"), (self.rdoTreeWC, "InfoFolder", "WC"),
(self.rdoTreeCC, "InfoFolder", "CC"),
(self.rdoTreeProgress, "InfoFolder", "Progress"), (self.rdoTreeProgress, "InfoFolder", "Progress"),
(self.rdoTreeSummary, "InfoFolder", "Summary"), (self.rdoTreeSummary, "InfoFolder", "Summary"),
(self.rdoTreeNothing, "InfoFolder", "Nothing"), (self.rdoTreeNothing, "InfoFolder", "Nothing"),
(self.rdoTreeTextWC, "InfoText", "WC"), (self.rdoTreeTextWC, "InfoText", "WC"),
(self.rdoTreeTextCC, "InfoText", "CC"),
(self.rdoTreeTextProgress, "InfoText", "Progress"), (self.rdoTreeTextProgress, "InfoText", "Progress"),
(self.rdoTreeTextSummary, "InfoText", "Summary"), (self.rdoTreeTextSummary, "InfoText", "Summary"),
(self.rdoTreeTextNothing, "InfoText", "Nothing"), (self.rdoTreeTextNothing, "InfoText", "Nothing"),
@ -180,6 +190,9 @@ class settingsWindow(QWidget, Ui_Settings):
lambda v: self.lblTreeIconSize.setText("{}x{}".format(v, v))) lambda v: self.lblTreeIconSize.setText("{}x{}".format(v, v)))
self.sldTreeIconSize.setValue(settings.viewSettings["Tree"]["iconSize"]) 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.rdoCorkOldStyle.setChecked(settings.corkStyle == "old")
self.rdoCorkNewStyle.setChecked(settings.corkStyle == "new") self.rdoCorkNewStyle.setChecked(settings.corkStyle == "new")
self.rdoCorkNewStyle.toggled.connect(self.setCorkStyle) self.rdoCorkNewStyle.toggled.connect(self.setCorkStyle)
@ -338,6 +351,11 @@ class settingsWindow(QWidget, Ui_Settings):
sttgs = QSettings(qApp.organizationName(), qApp.applicationName()) sttgs = QSettings(qApp.organizationName(), qApp.applicationName())
sttgs.setValue("appFontSize", val) sttgs.setValue("appFontSize", val)
def charSettingsChanged(self):
settings.progressChars = True if self.chkProgressChars.checkState() else False
self.mw.mainEditor.updateStats()
def saveSettingsChanged(self): def saveSettingsChanged(self):
if self.txtAutoSave.text() in ["", "0"]: if self.txtAutoSave.text() in ["", "0"]:
self.txtAutoSave.setText("1") self.txtAutoSave.setText("1")
@ -427,10 +445,12 @@ class settingsWindow(QWidget, Ui_Settings):
for item, what, value in [ for item, what, value in [
(self.rdoTreeItemCount, "InfoFolder", "Count"), (self.rdoTreeItemCount, "InfoFolder", "Count"),
(self.rdoTreeWC, "InfoFolder", "WC"), (self.rdoTreeWC, "InfoFolder", "WC"),
(self.rdoTreeCC, "InfoFolder", "CC"),
(self.rdoTreeProgress, "InfoFolder", "Progress"), (self.rdoTreeProgress, "InfoFolder", "Progress"),
(self.rdoTreeSummary, "InfoFolder", "Summary"), (self.rdoTreeSummary, "InfoFolder", "Summary"),
(self.rdoTreeNothing, "InfoFolder", "Nothing"), (self.rdoTreeNothing, "InfoFolder", "Nothing"),
(self.rdoTreeTextWC, "InfoText", "WC"), (self.rdoTreeTextWC, "InfoText", "WC"),
(self.rdoTreeTextCC, "InfoText", "CC"),
(self.rdoTreeTextProgress, "InfoText", "Progress"), (self.rdoTreeTextProgress, "InfoText", "Progress"),
(self.rdoTreeTextSummary, "InfoText", "Summary"), (self.rdoTreeTextSummary, "InfoText", "Summary"),
(self.rdoTreeTextNothing, "InfoText", "Nothing"), (self.rdoTreeTextNothing, "InfoText", "Nothing"),
@ -445,6 +465,11 @@ class settingsWindow(QWidget, Ui_Settings):
self.mw.treeRedacOutline.viewport().update() self.mw.treeRedacOutline.viewport().update()
def countSpacesChanged(self):
settings.countSpaces = True if self.chkCountSpaces.checkState() else False
self.mw.mainEditor.updateStats()
def setCorkColor(self): def setCorkColor(self):
color = QColor(settings.corkBackground["color"]) color = QColor(settings.corkBackground["color"])
self.colorDialog = QColorDialog(color, self) self.colorDialog = QColorDialog(color, self)

View file

@ -12,7 +12,7 @@ def MW():
""" """
from manuskript import functions as F from manuskript import functions as F
MW = F.mainWindow() MW = F.mainWindow()
assert MW is not None assert MW != None
assert MW == F.MW assert MW == F.MW
return MW return MW
@ -23,7 +23,7 @@ def MWNoProject(MW):
Take the MainWindow and close andy possibly open project. Take the MainWindow and close andy possibly open project.
""" """
MW.closeProject() MW.closeProject()
assert MW.currentProject is None assert MW.currentProject == None
return MW return MW
@pytest.fixture @pytest.fixture
@ -35,9 +35,9 @@ def MWEmptyProject(MW):
tf = tempfile.NamedTemporaryFile(suffix=".msk") tf = tempfile.NamedTemporaryFile(suffix=".msk")
MW.closeProject() MW.closeProject()
assert MW.currentProject is None assert MW.currentProject == None
MW.welcome.createFile(tf.name, overwrite=True) MW.welcome.createFile(tf.name, overwrite=True)
assert MW.currentProject is not None assert MW.currentProject != None
return MW return MW
# If using with: @pytest.fixture(scope='session', autouse=True) # If using with: @pytest.fixture(scope='session', autouse=True)
@ -67,6 +67,6 @@ def MWSampleProject(MW):
shutil.copyfile(src, tf.name) shutil.copyfile(src, tf.name)
shutil.copytree(src[:-4], tf.name[:-4]) shutil.copytree(src[:-4], tf.name[:-4])
MW.loadProject(tf.name) MW.loadProject(tf.name)
assert MW.currentProject is not None assert MW.currentProject != None
return MW return MW

View file

@ -130,9 +130,9 @@ def test_modelStuff(outlineModelBasic):
folder.setModel(k) folder.setModel(k)
assert folder.columnCount() == len(folder.enum) assert folder.columnCount() == len(folder.enum)
text1 = text2.copy() text1 = text2.copy()
assert text1.ID() is None assert text1.ID() == None
folder.appendChild(text1) folder.appendChild(text1)
assert text1.ID() is not None assert text1.ID() != None
assert folder.childCountRecursive() == 2 assert folder.childCountRecursive() == 2
assert text1.path() == "Folder > Text" assert text1.path() == "Folder > Text"
assert len(text1.pathID()) == 2 assert len(text1.pathID()) == 2

View file

@ -39,7 +39,7 @@ def test_references(MWSampleProject):
assert "\n" in Ref.infos(Ref.plotReference(plotID)) assert "\n" in Ref.infos(Ref.plotReference(plotID))
assert "Not a ref" in Ref.infos("<invalid>") assert "Not a ref" in Ref.infos("<invalid>")
assert "Unknown" in Ref.infos(Ref.plotReference("999")) 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(Ref.plotReference("999")) == None
assert Ref.shortInfos("<invalidref>") == -1 assert Ref.shortInfos("<invalidref>") == -1
@ -50,7 +50,7 @@ def test_references(MWSampleProject):
charID = IDs[0] charID = IDs[0]
assert "\n" in Ref.infos(Ref.characterReference(charID)) assert "\n" in Ref.infos(Ref.characterReference(charID))
assert "Unknown" in Ref.infos(Ref.characterReference("999")) 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(Ref.characterReference("999")) == None
assert Ref.shortInfos("<invalidref>") == -1 assert Ref.shortInfos("<invalidref>") == -1
@ -62,7 +62,7 @@ def test_references(MWSampleProject):
assert "\n" in Ref.infos(Ref.textReference(textID)) assert "\n" in Ref.infos(Ref.textReference(textID))
assert "Unknown" in Ref.infos(Ref.textReference("999")) 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(Ref.textReference("999")) == None
assert Ref.shortInfos("<invalidref>") == -1 assert Ref.shortInfos("<invalidref>") == -1
@ -73,7 +73,7 @@ def test_references(MWSampleProject):
assert "\n" in Ref.infos(Ref.worldReference(worldID)) assert "\n" in Ref.infos(Ref.worldReference(worldID))
assert "Unknown" in Ref.infos(Ref.worldReference("999")) 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(Ref.worldReference("999")) == None
assert Ref.shortInfos("<invalidref>") == -1 assert Ref.shortInfos("<invalidref>") == -1
@ -84,9 +84,9 @@ def test_references(MWSampleProject):
# Titles # Titles
for ref in refs: for ref in refs:
assert Ref.title(ref) is not None assert Ref.title(ref) != None
assert Ref.title("<invalid>") is None assert Ref.title("<invalid>") == None
assert Ref.title(Ref.plotReference("999")) is None assert Ref.title(Ref.plotReference("999")) == None
# Other stuff # Other stuff
assert Ref.type(Ref.plotReference(plotID)) == Ref.PlotLetter 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 "Unknown" in Ref.tooltip(Ref.worldReference("999"))
assert "Not a ref" in Ref.tooltip("<invalid>") assert "Not a ref" in Ref.tooltip("<invalid>")
for ref in refs: for ref in refs:
assert Ref.tooltip(ref) is not None assert Ref.tooltip(ref) != None
# Links # Links
assert Ref.refToLink("<invalid>") is None assert Ref.refToLink("<invalid>") == None
assert Ref.refToLink(Ref.plotReference("999")) == Ref.plotReference("999") assert Ref.refToLink(Ref.plotReference("999")) == Ref.plotReference("999")
assert Ref.refToLink(Ref.characterReference("999")) == Ref.characterReference("999") assert Ref.refToLink(Ref.characterReference("999")) == Ref.characterReference("999")
assert Ref.refToLink(Ref.textReference("999")) == Ref.textReference("999") assert Ref.refToLink(Ref.textReference("999")) == Ref.textReference("999")
@ -106,7 +106,7 @@ def test_references(MWSampleProject):
assert "<a href" in Ref.refToLink(ref) assert "<a href" in Ref.refToLink(ref)
# Open # Open
assert Ref.open("<invalid>") is None assert Ref.open("<invalid>") == None
assert Ref.open(Ref.plotReference("999")) == False assert Ref.open(Ref.plotReference("999")) == False
assert Ref.open(Ref.characterReference("999")) == False assert Ref.open(Ref.characterReference("999")) == False
assert Ref.open(Ref.textReference("999")) == False assert Ref.open(Ref.textReference("999")) == False

View file

@ -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

View file

@ -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 <b>Lucas</b>")
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 <b>Lucas</b>"

View file

@ -3,6 +3,7 @@
"""Tests for functions""" """Tests for functions"""
import re
from manuskript import functions as F from manuskript import functions as F
def test_wordCount(): def test_wordCount():
@ -46,8 +47,8 @@ def test_several():
assert F.iconColor(icon).name().lower() == "#ff0000" assert F.iconColor(icon).name().lower() == "#ff0000"
# themeIcon # themeIcon
assert F.themeIcon("text") is not None assert F.themeIcon("text") != None
assert F.themeIcon("nonexistingname") is not None assert F.themeIcon("nonexistingname") != None
# randomColor # randomColor
c1 = F.randomColor() c1 = F.randomColor()
@ -75,10 +76,10 @@ def test_outlineItemColors():
def test_paths(): def test_paths():
assert F.appPath() is not None assert F.appPath() != None
assert F.writablePath is not None assert F.writablePath != None
assert len(F.allPaths("suffix")) == 2 assert len(F.allPaths("suffix")) == 2
assert F.tempFile("yop") is not None assert F.tempFile("yop") != None
f = F.findBackground("spacedreams.jpg") f = F.findBackground("spacedreams.jpg")
assert "resources/backgrounds/spacedreams.jpg" in f assert "resources/backgrounds/spacedreams.jpg" in f
assert len(F.customIcons()) > 1 assert len(F.customIcons()) > 1
@ -87,10 +88,59 @@ def test_mainWindow():
from PyQt5.QtWidgets import QWidget, QLCDNumber from PyQt5.QtWidgets import QWidget, QLCDNumber
assert F.mainWindow() is not None assert F.mainWindow() != None
assert F.MW is not None assert F.MW != None
F.statusMessage("Test") F.statusMessage("Test")
F.printObjects() F.printObjects()
assert len(F.findWidgetsOfClass(QWidget)) > 0 assert len(F.findWidgetsOfClass(QWidget)) > 0
assert len(F.findWidgetsOfClass(QLCDNumber)) == 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, "<b>text</b>")]
def test_search_singleLine_start():
assert F.search(re.compile("text"), "text is this") == [(0, 4, "<b>text</b> is this")]
def test_search_singleLine_end():
assert F.search(re.compile("text"), "This is text") == [(8, 12, "This is <b>text</b>")]
def test_search_multipleLines_fullMatch():
assert F.search(re.compile("text"), "This is\ntext\nOK") == [(8, 12, "[...] <b>text</b> [...]")]
def test_search_multipleLines_start():
assert F.search(re.compile("text"), "This is\ntext oh yeah\nOK") == [(8, 12, "[...] <b>text</b> oh yeah [...]")]
def test_search_multipleLines_end():
assert F.search(re.compile("text"), "This is\nsome text\nOK") == [(13, 17, "[...] some <b>text</b> [...]")]
def test_search_multipleLines_full():
assert F.search(re.compile("text"), "This is\ntext\nOK") == [(8, 12, "[...] <b>text</b> [...]")]
def test_search_multiple_strMatches():
assert F.search(re.compile("text"), "text, text and more text") == [
(0, 4, "<b>text</b>, text and more text"),
(6, 10, "text, <b>text</b> and more text"),
(20, 24, "text, text and more <b>text</b>")
]
def test_search_multiple_strMatches_caseSensitive():
assert F.search(re.compile("text"), "TeXt, TEXT and more text") == [(20, 24, "TeXt, TEXT and more <b>text</b>")]
assert F.search(re.compile("text", re.IGNORECASE), "TeXt, TEXT and more text") == [
(0, 4, "<b>TeXt</b>, TEXT and more text"),
(6, 10, "TeXt, <b>TEXT</b> and more text"),
(20, 24, "TeXt, TEXT and more <b>text</b>")
]

View file

@ -55,7 +55,7 @@ def test_general(MWSampleProject):
state = settings() state = settings()
assert chk.isChecked() == state assert chk.isChecked() == state
chk.setChecked(not state) chk.setChecked(not state)
assert chk.isChecked() is not state assert chk.isChecked() != state
# Loading and Saving # Loading and Saving
SW.txtAutoSave.setText("0") SW.txtAutoSave.setText("0")
@ -86,7 +86,7 @@ def test_general(MWSampleProject):
SW.chkOutlineTitle.setChecked(Qt.Unchecked) SW.chkOutlineTitle.setChecked(Qt.Unchecked)
SW.chkOutlineTitle.setChecked(Qt.Checked) SW.chkOutlineTitle.setChecked(Qt.Checked)
# Can't test because of the dialog # Can't test because of the dialog
# assert SW.setCorkColor() is None # assert SW.setCorkColor() == None
SW.sldTreeIconSize.setValue(SW.sldTreeIconSize.value() + 1) SW.sldTreeIconSize.setValue(SW.sldTreeIconSize.value() + 1)
SW.rdoCorkNewStyle.toggled.emit(True) SW.rdoCorkNewStyle.toggled.emit(True)
SW.cmbCorkImage.currentIndexChanged.emit(0) SW.cmbCorkImage.currentIndexChanged.emit(0)
@ -98,7 +98,7 @@ def test_general(MWSampleProject):
# Test editor # Test editor
switchCheckBoxAndAssert(SW.chkEditorBackgroundTransparent, switchCheckBoxAndAssert(SW.chkEditorBackgroundTransparent,
lambda: S.textEditor["backgroundTransparent"]) lambda: S.textEditor["backgroundTransparent"])
assert SW.restoreEditorColors() is None assert SW.restoreEditorColors() == None
switchCheckBoxAndAssert(SW.chkEditorNoBlinking, switchCheckBoxAndAssert(SW.chkEditorNoBlinking,
lambda: S.textEditor["cursorNotBlinking"]) lambda: S.textEditor["cursorNotBlinking"])
# Twice on purpose: set and restore # Twice on purpose: set and restore
@ -108,7 +108,7 @@ def test_general(MWSampleProject):
SW.updateAllWidgets() SW.updateAllWidgets()
# Labels # Labels
assert SW.updateLabelColor(MW.mdlLabels.item(1).index()) is None assert SW.updateLabelColor(MW.mdlLabels.item(1).index()) == None
rc = MW.mdlLabels.rowCount() rc = MW.mdlLabels.rowCount()
SW.addLabel() SW.addLabel()
SW.lstLabels.setCurrentIndex( SW.lstLabels.setCurrentIndex(
@ -150,7 +150,7 @@ def test_general(MWSampleProject):
for i in range(4): for i in range(4):
SW.updateLineSpacing(i) SW.updateLineSpacing(i)
SW.updateUIFromTheme() # No time to wait on timer SW.updateUIFromTheme() # No time to wait on timer
assert SW._editingTheme is not None assert SW._editingTheme != None
SW.resize(SW.geometry().size()) # resizeEvent SW.resize(SW.geometry().size()) # resizeEvent
#TODO: other edit test (see SW.loadTheme #TODO: other edit test (see SW.loadTheme
SW.saveTheme() SW.saveTheme()

View file

@ -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()

View file

@ -96,7 +96,7 @@ class collapsibleDockWidgets(QToolBar):
def setCurrentGroup(self, group): def setCurrentGroup(self, group):
self.currentGroup = group self.currentGroup = group
for btn, action, widget, grp in self.otherWidgets: 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) action.setVisible(False)
else: else:
action.setVisible(True) action.setVisible(True)

View file

@ -8,7 +8,7 @@ class blockUserData(QTextBlockUserData):
def getUserData(block): def getUserData(block):
"""Returns userData if it exists, or a blank one.""" """Returns userData if it exists, or a blank one."""
data = block.userData() data = block.userData()
if data is None: if data == None:
data = blockUserData() data = blockUserData()
return data return data

View file

@ -6,7 +6,7 @@ from PyQt5.QtCore import Qt, QSize, QPoint, QRect, QEvent, QTime, QTimer
from PyQt5.QtGui import QFontMetrics, QColor, QBrush, QPalette, QPainter, QPixmap, QCursor from PyQt5.QtGui import QFontMetrics, QColor, QBrush, QPalette, QPainter, QPixmap, QCursor
from PyQt5.QtGui import QIcon from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QFrame, QWidget, QPushButton, qApp, QStyle, QComboBox, QLabel, QScrollBar, \ 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 # Spell checker support
from manuskript import settings from manuskript import settings
@ -21,7 +21,7 @@ from manuskript.functions import Spellchecker
class fullScreenEditor(QWidget): class fullScreenEditor(QWidget):
def __init__(self, index, parent=None): def __init__(self, index, parent=None, screenNumber=None):
QWidget.__init__(self, parent) QWidget.__init__(self, parent)
self.setAttribute(Qt.WA_DeleteOnClose, True) self.setAttribute(Qt.WA_DeleteOnClose, True)
self._background = None self._background = None
@ -162,6 +162,12 @@ class fullScreenEditor(QWidget):
self.topPanel.setAutoHideVariable('autohide-top') self.topPanel.setAutoHideVariable('autohide-top')
self.leftPanel.setAutoHideVariable('autohide-left') 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 # Connection
self._index.model().dataChanged.connect(self.dataChanged) self._index.model().dataChanged.connect(self.dataChanged)
@ -338,8 +344,8 @@ class fullScreenEditor(QWidget):
item = self._index.internalPointer() item = self._index.internalPointer()
previousItem = self.previousTextItem(item) previousItem = self.previousTextItem(item)
nextItem = self.nextTextItem(item) nextItem = self.nextTextItem(item)
self.btnPrevious.setEnabled(previousItem is not None) self.btnPrevious.setEnabled(previousItem != None)
self.btnNext.setEnabled(nextItem is not None) self.btnNext.setEnabled(nextItem != None)
self.wPath.setItem(item) self.wPath.setItem(item)
def updateStatusBar(self): def updateStatusBar(self):
@ -572,11 +578,11 @@ class myPanel(QWidget):
def addWidgetSetting(self, label, config_name, widgets): def addWidgetSetting(self, label, config_name, widgets):
setting = (label, config_name, widgets) setting = (label, config_name, widgets)
self._settings.append(setting) 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]) self._setSettingValue(setting, settings.fullscreenSettings[config_name])
def addSetting(self, label, config_name, default=True): 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._setConfig(config_name, default)
self.addWidgetSetting(label, config_name, None) self.addWidgetSetting(label, config_name, None)
@ -651,7 +657,7 @@ class myPath(QWidget):
if i == item: if i == item:
a.setIcon(QIcon.fromTheme("stock_yes")) a.setIcon(QIcon.fromTheme("stock_yes"))
a.setEnabled(False) a.setEnabled(False)
elif self.editor.firstTextItem(i) is None: elif self.editor.firstTextItem(i) == None:
a.setEnabled(False) a.setEnabled(False)
else: else:
a.triggered.connect(gen_cb(i)) a.triggered.connect(gen_cb(i))

View file

@ -105,6 +105,6 @@ class locker(QWidget, Ui_locker):
text)) text))
# Word locked # Word locked
elif self._target is not None: elif self._target != None:
self.btnLock.setText(self.tr("{} words remaining").format( self.btnLock.setText(self.tr("{} words remaining").format(
self._target - self._words)) self._target - self._words))

View file

@ -5,7 +5,7 @@ import locale
from PyQt5.QtCore import QModelIndex, QRect, QPoint from PyQt5.QtCore import QModelIndex, QRect, QPoint
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPixmap, QPainter, QIcon 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 import settings
from manuskript.enums import Outline from manuskript.enums import Outline
@ -120,7 +120,7 @@ class mainEditor(QWidget, Ui_mainEditor):
return self.tabSplitter.tab return self.tabSplitter.tab
def currentEditor(self, tabWidget=None): def currentEditor(self, tabWidget=None):
if tabWidget is None: if tabWidget == None:
tabWidget = self.currentTabWidget() tabWidget = self.currentTabWidget()
return tabWidget.currentWidget() return tabWidget.currentWidget()
# return self.tab.currentWidget() # return self.tab.currentWidget()
@ -153,7 +153,7 @@ class mainEditor(QWidget, Ui_mainEditor):
def allTabs(self, tabWidget=None): def allTabs(self, tabWidget=None):
"""Returns all the tabs from the given tabWidget. If tabWidget is None, from the current tabWidget.""" """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() tabWidget = self.currentTabWidget()
return [tabWidget.widget(i) for i in range(tabWidget.count())] return [tabWidget.widget(i) for i in range(tabWidget.count())]
@ -205,7 +205,7 @@ class mainEditor(QWidget, Ui_mainEditor):
title = self.getIndexTitle(index) title = self.getIndexTitle(index)
if tabWidget is None: if tabWidget == None:
tabWidget = self.currentTabWidget() tabWidget = self.currentTabWidget()
# Checking if tab is already opened # Checking if tab is already opened
@ -292,6 +292,7 @@ class mainEditor(QWidget, Ui_mainEditor):
return return
index = self.currentEditor().currentIndex index = self.currentEditor().currentIndex
if index.isValid(): if index.isValid():
item = index.internalPointer() item = index.internalPointer()
else: else:
@ -300,15 +301,21 @@ class mainEditor(QWidget, Ui_mainEditor):
if not item: if not item:
item = self.mw.mdlOutline.rootItem item = self.mw.mdlOutline.rootItem
cc = item.data(Outline.charCount)
wc = item.data(Outline.wordCount) wc = item.data(Outline.wordCount)
goal = item.data(Outline.goal) goal = item.data(Outline.goal)
chars = item.data(Outline.charCount) # len(item.data(Outline.text))
progress = item.data(Outline.goalPercentage) progress = item.data(Outline.goalPercentage)
goal = uiParse(goal, None, int, lambda x: x>=0) goal = uiParse(goal, None, int, lambda x: x>=0)
progress = uiParse(progress, 0.0, float) progress = uiParse(progress, 0.0, float)
if not cc:
cc = 0
if not wc: if not wc:
wc = 0 wc = 0
if goal: if goal:
self.lblRedacProgress.show() self.lblRedacProgress.show()
rect = self.lblRedacProgress.geometry() rect = self.lblRedacProgress.geometry()
@ -319,13 +326,31 @@ class mainEditor(QWidget, Ui_mainEditor):
drawProgress(p, rect, progress, 2) drawProgress(p, rect, progress, 2)
del p del p
self.lblRedacProgress.setPixmap(self.px) self.lblRedacProgress.setPixmap(self.px)
self.lblRedacWC.setText(self.tr("{} words / {} ").format(
locale.format_string("%d", wc, grouping=True), if settings.progressChars:
locale.format_string("%d", goal, grouping=True))) 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: else:
self.lblRedacProgress.hide() 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 # VIEWS
@ -354,7 +379,10 @@ class mainEditor(QWidget, Ui_mainEditor):
def showFullScreen(self): def showFullScreen(self):
if self.currentEditor(): if self.currentEditor():
self._fullScreen = fullScreenEditor(self.currentEditor().currentIndex) currentScreenNumber = QDesktopWidget().screenNumber(widget=self)
self._fullScreen = fullScreenEditor(
self.currentEditor().currentIndex,
screenNumber=currentScreenNumber)
############################################################################### ###############################################################################
# DICT AND STUFF LIKE THAT # DICT AND STUFF LIKE THAT

View file

@ -145,8 +145,8 @@ class tabSplitter(QWidget, Ui_tabSplitter):
def split(self, toggled=None, state=None): def split(self, toggled=None, state=None):
if state is None and self.splitState == 0 or state == 1: if state == None and self.splitState == 0 or state == 1:
if self.secondTab is None: if self.secondTab == None:
self.addSecondTab() self.addSecondTab()
self.splitState = 1 self.splitState = 1
@ -155,8 +155,8 @@ class tabSplitter(QWidget, Ui_tabSplitter):
self.btnSplit.setIcon(QIcon.fromTheme("split-vertical")) self.btnSplit.setIcon(QIcon.fromTheme("split-vertical"))
self.btnSplit.setToolTip(self.tr("Split horizontally")) self.btnSplit.setToolTip(self.tr("Split horizontally"))
elif state is None and self.splitState == 1 or state == 2: elif state == None and self.splitState == 1 or state == 2:
if self.secondTab is None: if self.secondTab == None:
self.addSecondTab() self.addSecondTab()
self.splitter.setOrientation(Qt.Vertical) self.splitter.setOrientation(Qt.Vertical)
@ -212,7 +212,7 @@ class tabSplitter(QWidget, Ui_tabSplitter):
# self.btnSplit.setGeometry(QRect(0, 0, 24, 24)) # self.btnSplit.setGeometry(QRect(0, 0, 24, 24))
def focusChanged(self, old, new): def focusChanged(self, old, new):
if self.secondTab is None or new is None: if self.secondTab == None or new == None:
return return
oldFT = self.focusTab oldFT = self.focusTab

View file

@ -18,7 +18,6 @@ class BasicHighlighter(QSyntaxHighlighter):
QSyntaxHighlighter.__init__(self, editor.document()) QSyntaxHighlighter.__init__(self, editor.document())
self.editor = editor self.editor = editor
self._misspelledColor = Qt.red
self._defaultBlockFormat = QTextBlockFormat() self._defaultBlockFormat = QTextBlockFormat()
self._defaultCharFormat = QTextCharFormat() self._defaultCharFormat = QTextCharFormat()
self.defaultTextColor = QColor(S.text) self.defaultTextColor = QColor(S.text)
@ -27,6 +26,40 @@ class BasicHighlighter(QSyntaxHighlighter):
self.linkColor = QColor(S.link) self.linkColor = QColor(S.link)
self.spellingErrorColor = QColor(Qt.red) 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): def setDefaultBlockFormat(self, bf):
self._defaultBlockFormat = bf self._defaultBlockFormat = bf
self.rehighlight() self.rehighlight()
@ -36,7 +69,7 @@ class BasicHighlighter(QSyntaxHighlighter):
self.rehighlight() self.rehighlight()
def setMisspelledColor(self, color): def setMisspelledColor(self, color):
self._misspelledColor = color self._errorColors['misspelled'] = color
def updateColorScheme(self, rehighlight=True): def updateColorScheme(self, rehighlight=True):
""" """
@ -134,32 +167,25 @@ class BasicHighlighter(QSyntaxHighlighter):
txt.end() - txt.start(), txt.end() - txt.start(),
fmt) 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. # 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 # 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 # So that it doesn't spellcheck while typing, but still spellchecks at
# end of lines. See github's issue #166. # end of lines. See github's issue #166.
textedText = text textedText = text
if self.currentBlock().position() + len(text) != \ if self.currentBlock().position() + len(text) != \
self.editor.textCursor().position(): self.editor.textCursor().position():
textedText = text + " " textedText = text + " "
# Based on http://john.nachtimwald.com/2009/08/22/qplaintextedit-with-in-line-spell-check/ # The text should only be checked once as a whole
WORDS = r'(?iu)((?:[^_\W]|\')+)[^A-Za-z0-9\']' for match in self.editor._dict.checkText(textedText):
# (?iu) means case insensitive and Unicode if match.locqualityissuetype in self._errorColors:
# ((?:[^_\W]|\')+) means words exclude underscores but include apostrophes highlight_color = self._errorColors[match.locqualityissuetype]
# [^A-Za-z0-9\'] used with above hack to prevent spellcheck while typing word
# format = self.format(match.start)
# See also https://stackoverflow.com/questions/2062169/regex-w-in-utf-8 format.setUnderlineColor(highlight_color)
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)
# SpellCheckUnderline fails with some fonts # SpellCheckUnderline fails with some fonts
format.setUnderlineStyle(QTextCharFormat.WaveUnderline) format.setUnderlineStyle(QTextCharFormat.WaveUnderline)
self.setFormat(word_object.start(1), self.setFormat(match.start, match.end - match.start, format)
word_object.end(1) - word_object.start(1),
format)

View file

@ -713,7 +713,7 @@ class MarkdownHighlighter(BasicHighlighter):
# FIXME: TypeError: could not convert 'TextBlockData' to 'QTextBlockUserData' # FIXME: TypeError: could not convert 'TextBlockData' to 'QTextBlockUserData'
# blockData = self.currentBlockUserData() # blockData = self.currentBlockUserData()
# if blockData is None: # if blockData == None:
# blockData = TextBlockData(self.document(), self.currentBlock()) # blockData = TextBlockData(self.document(), self.currentBlock())
# #
# self.setCurrentBlockUserData(blockData) # self.setCurrentBlockUserData(blockData)

View file

@ -0,0 +1,2 @@
#!/usr/bin/env python
# --!-- coding: utf8 --!--

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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")

View file

@ -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)

View file

@ -2,12 +2,14 @@
# Form implementation generated from reading ui file 'manuskript/ui/mainWindow.ui' # 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.14.1
# #
# WARNING! All changes made in this file will be lost! # WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object): class Ui_MainWindow(object):
def setupUi(self, MainWindow): def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow") MainWindow.setObjectName("MainWindow")
@ -378,69 +380,11 @@ class Ui_MainWindow(object):
self.scrollAreaPersoInfos.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop) self.scrollAreaPersoInfos.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop)
self.scrollAreaPersoInfos.setObjectName("scrollAreaPersoInfos") self.scrollAreaPersoInfos.setObjectName("scrollAreaPersoInfos")
self.scrollAreaPersoInfosWidget = QtWidgets.QWidget() self.scrollAreaPersoInfosWidget = QtWidgets.QWidget()
self.scrollAreaPersoInfosWidget.setGeometry(QtCore.QRect(0, 0, 204, 606)) self.scrollAreaPersoInfosWidget.setGeometry(QtCore.QRect(0, 0, 453, 695))
self.scrollAreaPersoInfosWidget.setObjectName("scrollAreaPersoInfosWidget") self.scrollAreaPersoInfosWidget.setObjectName("scrollAreaPersoInfosWidget")
self.formLayout_8 = QtWidgets.QFormLayout(self.scrollAreaPersoInfosWidget) self.formLayout_8 = QtWidgets.QFormLayout(self.scrollAreaPersoInfosWidget)
self.formLayout_8.setFieldGrowthPolicy(QtWidgets.QFormLayout.AllNonFixedFieldsGrow) self.formLayout_8.setFieldGrowthPolicy(QtWidgets.QFormLayout.AllNonFixedFieldsGrow)
self.formLayout_8.setObjectName("formLayout_8") 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 = QtWidgets.QLabel(self.scrollAreaPersoInfosWidget)
self.label_3.setObjectName("label_3") self.label_3.setObjectName("label_3")
self.formLayout_8.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.label_3) self.formLayout_8.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.label_3)
@ -454,6 +398,73 @@ class Ui_MainWindow(object):
self.btnPersoColor.setObjectName("btnPersoColor") self.btnPersoColor.setObjectName("btnPersoColor")
self.horizontalLayout_3.addWidget(self.btnPersoColor) self.horizontalLayout_3.addWidget(self.btnPersoColor)
self.formLayout_8.setLayout(2, QtWidgets.QFormLayout.FieldRole, self.horizontalLayout_3) 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.scrollAreaPersoInfos.setWidget(self.scrollAreaPersoInfosWidget)
self.verticalLayout_20.addWidget(self.scrollAreaPersoInfos) self.verticalLayout_20.addWidget(self.scrollAreaPersoInfos)
self.tabPersos.addTab(self.info, "") self.tabPersos.addTab(self.info, "")
@ -745,7 +756,7 @@ class Ui_MainWindow(object):
self.treeWorld.setRootIsDecorated(False) self.treeWorld.setRootIsDecorated(False)
self.treeWorld.setObjectName("treeWorld") self.treeWorld.setObjectName("treeWorld")
self.treeWorld.header().setVisible(False) self.treeWorld.header().setVisible(False)
self.treeWorld.header().setDefaultSectionSize(0) self.treeWorld.header().setDefaultSectionSize(25)
self.verticalLayout_32.addWidget(self.treeWorld) self.verticalLayout_32.addWidget(self.treeWorld)
self.horizontalLayout_19 = QtWidgets.QHBoxLayout() self.horizontalLayout_19 = QtWidgets.QHBoxLayout()
self.horizontalLayout_19.setObjectName("horizontalLayout_19") self.horizontalLayout_19.setObjectName("horizontalLayout_19")
@ -833,6 +844,7 @@ class Ui_MainWindow(object):
self.layoutWidget = QtWidgets.QWidget(self.splitterOutlineH) self.layoutWidget = QtWidgets.QWidget(self.splitterOutlineH)
self.layoutWidget.setObjectName("layoutWidget") self.layoutWidget.setObjectName("layoutWidget")
self.verticalLayout_14 = QtWidgets.QVBoxLayout(self.layoutWidget) self.verticalLayout_14 = QtWidgets.QVBoxLayout(self.layoutWidget)
self.verticalLayout_14.setContentsMargins(0, 0, 0, 0)
self.verticalLayout_14.setObjectName("verticalLayout_14") self.verticalLayout_14.setObjectName("verticalLayout_14")
self.splitterOutlineV = QtWidgets.QSplitter(self.layoutWidget) self.splitterOutlineV = QtWidgets.QSplitter(self.layoutWidget)
self.splitterOutlineV.setOrientation(QtCore.Qt.Vertical) self.splitterOutlineV.setOrientation(QtCore.Qt.Vertical)
@ -1029,7 +1041,7 @@ class Ui_MainWindow(object):
self.horizontalLayout_2.addWidget(self.stack) self.horizontalLayout_2.addWidget(self.stack)
MainWindow.setCentralWidget(self.centralwidget) MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow) self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 1112, 30)) self.menubar.setGeometry(QtCore.QRect(0, 0, 1112, 24))
self.menubar.setObjectName("menubar") self.menubar.setObjectName("menubar")
self.menuFile = QtWidgets.QMenu(self.menubar) self.menuFile = QtWidgets.QMenu(self.menubar)
self.menuFile.setObjectName("menuFile") self.menuFile.setObjectName("menuFile")
@ -1270,6 +1282,10 @@ class Ui_MainWindow(object):
self.actFormatList.setObjectName("actFormatList") self.actFormatList.setObjectName("actFormatList")
self.actFormatBlockquote = QtWidgets.QAction(MainWindow) self.actFormatBlockquote = QtWidgets.QAction(MainWindow)
self.actFormatBlockquote.setObjectName("actFormatBlockquote") self.actFormatBlockquote.setObjectName("actFormatBlockquote")
self.actSearch = QtWidgets.QAction(MainWindow)
icon = QtGui.QIcon.fromTheme("edit-find")
self.actSearch.setIcon(icon)
self.actSearch.setObjectName("actSearch")
self.menuFile.addAction(self.actOpen) self.menuFile.addAction(self.actOpen)
self.menuFile.addAction(self.menuRecents.menuAction()) self.menuFile.addAction(self.menuRecents.menuAction())
self.menuFile.addAction(self.actSave) self.menuFile.addAction(self.actSave)
@ -1313,6 +1329,7 @@ class Ui_MainWindow(object):
self.menuEdit.addAction(self.actCopy) self.menuEdit.addAction(self.actCopy)
self.menuEdit.addAction(self.actPaste) self.menuEdit.addAction(self.actPaste)
self.menuEdit.addAction(self.actDelete) self.menuEdit.addAction(self.actDelete)
self.menuEdit.addAction(self.actSearch)
self.menuEdit.addAction(self.actRename) self.menuEdit.addAction(self.actRename)
self.menuEdit.addSeparator() self.menuEdit.addSeparator()
self.menuEdit.addAction(self.mnuFormat.menuAction()) self.menuEdit.addAction(self.mnuFormat.menuAction())
@ -1339,7 +1356,7 @@ class Ui_MainWindow(object):
self.retranslateUi(MainWindow) self.retranslateUi(MainWindow)
self.stack.setCurrentIndex(1) self.stack.setCurrentIndex(1)
self.tabMain.setCurrentIndex(0) self.tabMain.setCurrentIndex(2)
self.tabSummary.setCurrentIndex(0) self.tabSummary.setCurrentIndex(0)
self.tabPersos.setCurrentIndex(0) self.tabPersos.setCurrentIndex(0)
self.tabPlot.setCurrentIndex(0) self.tabPlot.setCurrentIndex(0)
@ -1484,6 +1501,8 @@ class Ui_MainWindow(object):
self.tabMain.setTabText(self.tabMain.indexOf(self.lytTabSummary), _translate("MainWindow", "Summary")) self.tabMain.setTabText(self.tabMain.indexOf(self.lytTabSummary), _translate("MainWindow", "Summary"))
self.groupBox.setTitle(_translate("MainWindow", "Names")) self.groupBox.setTitle(_translate("MainWindow", "Names"))
self.txtPersosFilter.setPlaceholderText(_translate("MainWindow", "Filter")) 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_4.setText(_translate("MainWindow", "Motivation"))
self.label_5.setText(_translate("MainWindow", "Goal")) self.label_5.setText(_translate("MainWindow", "Goal"))
self.label_6.setText(_translate("MainWindow", "Conflict")) self.label_6.setText(_translate("MainWindow", "Conflict"))
@ -1492,7 +1511,6 @@ class Ui_MainWindow(object):
self.label_8.setText(_translate("MainWindow", "<html><head/><body><p align=\"right\">One paragraph<br/>summary</p></body></html>")) self.label_8.setText(_translate("MainWindow", "<html><head/><body><p align=\"right\">One paragraph<br/>summary</p></body></html>"))
self.btnStepFour.setText(_translate("MainWindow", "Next")) self.btnStepFour.setText(_translate("MainWindow", "Next"))
self.label_18.setText(_translate("MainWindow", "Importance")) 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.tabPersos.setTabText(self.tabPersos.indexOf(self.info), _translate("MainWindow", "Basic info"))
self.btnStepSix.setText(_translate("MainWindow", "Next")) self.btnStepSix.setText(_translate("MainWindow", "Next"))
self.tabPersos.setTabText(self.tabPersos.indexOf(self.tab_11), _translate("MainWindow", "Summary")) self.tabPersos.setTabText(self.tabPersos.indexOf(self.tab_11), _translate("MainWindow", "Summary"))
@ -1635,6 +1653,8 @@ class Ui_MainWindow(object):
self.actFormatOrderedList.setText(_translate("MainWindow", "&Ordered list")) self.actFormatOrderedList.setText(_translate("MainWindow", "&Ordered list"))
self.actFormatList.setText(_translate("MainWindow", "&Unordered list")) self.actFormatList.setText(_translate("MainWindow", "&Unordered list"))
self.actFormatBlockquote.setText(_translate("MainWindow", "B&lockquote")) self.actFormatBlockquote.setText(_translate("MainWindow", "B&lockquote"))
self.actSearch.setText(_translate("MainWindow", "Search"))
self.actSearch.setShortcut(_translate("MainWindow", "Ctrl+F"))
from manuskript.ui.cheatSheet import cheatSheet from manuskript.ui.cheatSheet import cheatSheet
from manuskript.ui.editors.mainEditor import mainEditor from manuskript.ui.editors.mainEditor import mainEditor

View file

@ -124,7 +124,7 @@
<enum>QTabWidget::Rounded</enum> <enum>QTabWidget::Rounded</enum>
</property> </property>
<property name="currentIndex"> <property name="currentIndex">
<number>0</number> <number>2</number>
</property> </property>
<property name="documentMode"> <property name="documentMode">
<bool>true</bool> <bool>true</bool>
@ -815,75 +815,126 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>204</width> <width>453</width>
<height>606</height> <height>695</height>
</rect> </rect>
</property> </property>
<layout class="QFormLayout" name="formLayout_8"> <layout class="QFormLayout" name="formLayout_8">
<property name="fieldGrowthPolicy"> <property name="fieldGrowthPolicy">
<enum>QFormLayout::AllNonFixedFieldsGrow</enum> <enum>QFormLayout::AllNonFixedFieldsGrow</enum>
</property> </property>
<item row="4" column="0"> <item row="2" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Name</string>
</property>
</widget>
</item>
<item row="2" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="lineEditView" name="txtPersoName"/>
</item>
<item>
<widget class="QPushButton" name="btnPersoColor">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item row="4" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_20">
<item>
<widget class="sldImportance" name="sldPersoImportance" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="chkPersoPOV">
<property name="text">
<string>Allow POV</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
<property name="autoRepeat">
<bool>false</bool>
</property>
<property name="tristate">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_4"> <widget class="QLabel" name="label_4">
<property name="text"> <property name="text">
<string>Motivation</string> <string>Motivation</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="1"> <item row="6" column="1">
<widget class="MDEditCompleter" name="txtPersoMotivation"/> <widget class="MDEditCompleter" name="txtPersoMotivation"/>
</item> </item>
<item row="5" column="0"> <item row="7" column="0">
<widget class="QLabel" name="label_5"> <widget class="QLabel" name="label_5">
<property name="text"> <property name="text">
<string>Goal</string> <string>Goal</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="5" column="1"> <item row="7" column="1">
<widget class="MDEditCompleter" name="txtPersoGoal"/> <widget class="MDEditCompleter" name="txtPersoGoal"/>
</item> </item>
<item row="6" column="0"> <item row="8" column="0">
<widget class="QLabel" name="label_6"> <widget class="QLabel" name="label_6">
<property name="text"> <property name="text">
<string>Conflict</string> <string>Conflict</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="6" column="1"> <item row="8" column="1">
<widget class="MDEditCompleter" name="txtPersoConflict"/> <widget class="MDEditCompleter" name="txtPersoConflict"/>
</item> </item>
<item row="7" column="0"> <item row="9" column="0">
<widget class="QLabel" name="label_7"> <widget class="QLabel" name="label_7">
<property name="text"> <property name="text">
<string>Epiphany</string> <string>Epiphany</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="7" column="1"> <item row="9" column="1">
<widget class="MDEditCompleter" name="txtPersoEpiphany"/> <widget class="MDEditCompleter" name="txtPersoEpiphany"/>
</item> </item>
<item row="8" column="0"> <item row="10" column="0">
<widget class="QLabel" name="label_24"> <widget class="QLabel" name="label_24">
<property name="text"> <property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p align=&quot;right&quot;&gt;One sentence&lt;br/&gt;summary&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p align=&quot;right&quot;&gt;One sentence&lt;br/&gt;summary&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="8" column="1"> <item row="10" column="1">
<widget class="MDEditCompleter" name="txtPersoSummarySentence"/> <widget class="MDEditCompleter" name="txtPersoSummarySentence"/>
</item> </item>
<item row="9" column="0"> <item row="11" column="0">
<widget class="QLabel" name="label_8"> <widget class="QLabel" name="label_8">
<property name="text"> <property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p align=&quot;right&quot;&gt;One paragraph&lt;br/&gt;summary&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p align=&quot;right&quot;&gt;One paragraph&lt;br/&gt;summary&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="9" column="1"> <item row="11" column="1">
<widget class="MDEditCompleter" name="txtPersoSummaryPara"/> <widget class="MDEditCompleter" name="txtPersoSummaryPara"/>
</item> </item>
<item row="10" column="1"> <item row="12" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_21"> <layout class="QHBoxLayout" name="horizontalLayout_21">
<item> <item>
<spacer name="horizontalSpacer_3"> <spacer name="horizontalSpacer_3">
@ -914,44 +965,13 @@
</item> </item>
</layout> </layout>
</item> </item>
<item row="3" column="0"> <item row="4" column="0">
<widget class="QLabel" name="label_18"> <widget class="QLabel" name="label_18">
<property name="text"> <property name="text">
<string>Importance</string> <string>Importance</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="1">
<widget class="sldImportance" name="sldPersoImportance" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Name</string>
</property>
</widget>
</item>
<item row="2" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="lineEditView" name="txtPersoName"/>
</item>
<item>
<widget class="QPushButton" name="btnPersoColor">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
</layout> </layout>
</widget> </widget>
</widget> </widget>
@ -1547,7 +1567,7 @@
<bool>false</bool> <bool>false</bool>
</attribute> </attribute>
<attribute name="headerDefaultSectionSize"> <attribute name="headerDefaultSectionSize">
<number>0</number> <number>25</number>
</attribute> </attribute>
</widget> </widget>
</item> </item>
@ -2095,7 +2115,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>1112</width> <width>1112</width>
<height>30</height> <height>24</height>
</rect> </rect>
</property> </property>
<widget class="QMenu" name="menuFile"> <widget class="QMenu" name="menuFile">
@ -2183,6 +2203,7 @@
<addaction name="actCopy"/> <addaction name="actCopy"/>
<addaction name="actPaste"/> <addaction name="actPaste"/>
<addaction name="actDelete"/> <addaction name="actDelete"/>
<addaction name="actSearch"/>
<addaction name="actRename"/> <addaction name="actRename"/>
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="mnuFormat"/> <addaction name="mnuFormat"/>
@ -2818,6 +2839,17 @@
<string>B&amp;lockquote</string> <string>B&amp;lockquote</string>
</property> </property>
</action> </action>
<action name="actSearch">
<property name="icon">
<iconset theme="edit-find"/>
</property>
<property name="text">
<string>Search</string>
</property>
<property name="shortcut">
<string>Ctrl+F</string>
</property>
</action>
</widget> </widget>
<customwidgets> <customwidgets>
<customwidget> <customwidget>

View file

@ -1,147 +1,151 @@
#!/usr/bin/env python #!/usr/bin/env python
# --!-- coding: utf8 --!-- # --!-- coding: utf8 --!--
from PyQt5.QtCore import Qt, QRect from PyQt5.QtCore import Qt, QRect, QEvent, QCoreApplication
from PyQt5.QtGui import QPalette, QFontMetrics from PyQt5.QtGui import QPalette, QFontMetrics, QKeySequence
from PyQt5.QtWidgets import QWidget, QMenu, QAction, qApp, QListWidgetItem, QStyledItemDelegate, QStyle from PyQt5.QtWidgets import QWidget, qApp, QListWidgetItem, QStyledItemDelegate, QStyle, QLabel, QToolTip, QShortcut
from manuskript.enums import Outline
from manuskript.functions import mainWindow from manuskript.functions import mainWindow
from manuskript.ui import style from manuskript.ui import style
from manuskript.ui.search_ui import Ui_search 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): class search(QWidget, Ui_search):
def __init__(self, parent=None): def __init__(self, parent=None):
_translate = QCoreApplication.translate
QWidget.__init__(self, parent) QWidget.__init__(self, parent)
self.setupUi(self) self.setupUi(self)
self.options = { self.searchTextInput.returnPressed.connect(self.search)
"All": True,
"Title": True,
"Text": True,
"Summary": False,
"Notes": False,
"POV": False,
"Status": False,
"Label": False,
"CS": True
}
self.text.returnPressed.connect(self.search) self.searchMenu = searchMenu()
self.generateOptionMenu() self.btnOptions.setMenu(self.searchMenu)
self.delegate = listResultDelegate(self) self.delegate = listResultDelegate(self)
self.result.setItemDelegate(self.delegate) self.result.setItemDelegate(self.delegate)
self.result.setMouseTracking(True)
self.result.itemClicked.connect(self.openItem) self.result.itemClicked.connect(self.openItem)
self.result.setStyleSheet(style.searchResultSS()) self.result.setStyleSheet(style.searchResultSS())
self.text.setStyleSheet(style.lineEditSS()) self.searchTextInput.setStyleSheet(style.lineEditSS())
def generateOptionMenu(self): self.searchResultHighlighter = searchResultHighlighter()
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()
a = QAction(self.tr("Options:"), self.menu) self.noResultsLabel = QLabel(_translate("Search", "No results found"), self.result)
a.setEnabled(False) self.noResultsLabel.setVisible(False)
self.menu.addAction(a) self.noResultsLabel.setStyleSheet("QLabel {color: gray;}")
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.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): # These texts are already included in translation files but including ":" at the end. We force here the
a = self.sender() # translation for them without ":"
self.options[a.data()] = a.isChecked() _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): 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() self.result.clear()
for r in results: self.result.setCurrentRow(0)
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)
# Removing override cursor searchText = self.searchTextInput.text()
qApp.restoreOverrideCursor() 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): def openItem(self, item):
r = Ref.textReference(item.data(Qt.UserRole)) self.searchResultHighlighter.highlightSearchResult(item.data(Qt.UserRole))
Ref.open(r)
# mw = mainWindow()
# index = mw.mdlOutline.getIndexByID(item.data(Qt.UserRole))
# mw.mainEditor.setCurrentModelIndex(index, newTab=True)
def leaveEvent(self, event):
self.delegate.mouseLeave()
class listResultDelegate(QStyledItemDelegate): class listResultDelegate(QStyledItemDelegate):
def __init__(self, parent=None): def __init__(self, parent=None):
QStyledItemDelegate.__init__(self, parent) QStyledItemDelegate.__init__(self, parent)
self._tooltipRowIndex = -1
def paint(self, painter, option, index): def paint(self, painter, option, index):
extra = index.data(Qt.UserRole + 1) extra = index.data(Qt.UserRole + 1)
if not extra: if not extra:
return QStyledItemDelegate.paint(self, painter, option, index) return QStyledItemDelegate.paint(self, painter, option, index)
else: else:
if option.state & QStyle.State_Selected: if option.state & QStyle.State_Selected:
painter.fillRect(option.rect, option.palette.color(QPalette.Highlight)) painter.fillRect(option.rect, option.palette.color(QPalette.Highlight))
title = index.data() title = index.data()
extra = " - {}".format(extra)
painter.drawText(option.rect.adjusted(2, 1, 0, 0), Qt.AlignLeft, title) painter.drawText(option.rect.adjusted(2, 1, 0, 0), Qt.AlignLeft, title)
fm = QFontMetrics(option.font) fm = QFontMetrics(option.font)
@ -153,5 +157,18 @@ class listResultDelegate(QStyledItemDelegate):
painter.setPen(Qt.white) painter.setPen(Qt.white)
else: else:
painter.setPen(Qt.gray) 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() 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(),
"<p>#" + str(index.row()) + " - " + extra + "</p><p>" + context + "</p>")
return True
return False
def mouseLeave(self):
self._tooltipRowIndex = -1

108
manuskript/ui/searchMenu.py Normal file
View file

@ -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)

View file

@ -19,12 +19,12 @@ class Ui_search(object):
self.horizontalLayout = QtWidgets.QHBoxLayout() self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setSpacing(0) self.horizontalLayout.setSpacing(0)
self.horizontalLayout.setObjectName("horizontalLayout") self.horizontalLayout.setObjectName("horizontalLayout")
self.text = QtWidgets.QLineEdit(search) self.searchTextInput = QtWidgets.QLineEdit(search)
self.text.setInputMask("") self.searchTextInput.setInputMask("")
self.text.setFrame(False) self.searchTextInput.setFrame(False)
self.text.setClearButtonEnabled(True) self.searchTextInput.setClearButtonEnabled(True)
self.text.setObjectName("text") self.searchTextInput.setObjectName("searchTextInput")
self.horizontalLayout.addWidget(self.text) self.horizontalLayout.addWidget(self.searchTextInput)
self.btnOptions = QtWidgets.QPushButton(search) self.btnOptions = QtWidgets.QPushButton(search)
self.btnOptions.setText("") self.btnOptions.setText("")
icon = QtGui.QIcon.fromTheme("edit-find") icon = QtGui.QIcon.fromTheme("edit-find")
@ -45,5 +45,5 @@ class Ui_search(object):
def retranslateUi(self, search): def retranslateUi(self, search):
_translate = QtCore.QCoreApplication.translate _translate = QtCore.QCoreApplication.translate
search.setWindowTitle(_translate("search", "Form")) search.setWindowTitle(_translate("search", "Form"))
self.text.setPlaceholderText(_translate("search", "Search for...")) self.searchTextInput.setPlaceholderText(_translate("search", "Search for..."))

View file

@ -35,7 +35,7 @@
<number>0</number> <number>0</number>
</property> </property>
<item> <item>
<widget class="QLineEdit" name="text"> <widget class="QLineEdit" name="searchTextInput">
<property name="inputMask"> <property name="inputMask">
<string/> <string/>
</property> </property>

View file

@ -1,10 +1,11 @@
# -*- coding: utf-8 -*- # -*- 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 from PyQt5 import QtCore, QtGui, QtWidgets
@ -13,7 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Settings(object): class Ui_Settings(object):
def setupUi(self, Settings): def setupUi(self, Settings):
Settings.setObjectName("Settings") Settings.setObjectName("Settings")
Settings.resize(658, 598) Settings.resize(681, 598)
self.horizontalLayout_8 = QtWidgets.QHBoxLayout(Settings) self.horizontalLayout_8 = QtWidgets.QHBoxLayout(Settings)
self.horizontalLayout_8.setObjectName("horizontalLayout_8") self.horizontalLayout_8.setObjectName("horizontalLayout_8")
self.lstMenu = QtWidgets.QListWidget(Settings) self.lstMenu = QtWidgets.QListWidget(Settings)
@ -55,50 +56,9 @@ class Ui_Settings(object):
self.groupBox_2.setFont(font) self.groupBox_2.setFont(font)
self.groupBox_2.setObjectName("groupBox_2") self.groupBox_2.setObjectName("groupBox_2")
self.formLayout_13 = QtWidgets.QFormLayout(self.groupBox_2) self.formLayout_13 = QtWidgets.QFormLayout(self.groupBox_2)
self.formLayout_13.setFieldGrowthPolicy(QtWidgets.QFormLayout.FieldsStayAtSizeHint)
self.formLayout_13.setObjectName("formLayout_13") self.formLayout_13.setObjectName("formLayout_13")
self.label_56 = QtWidgets.QLabel(self.groupBox_2) self.gridLayout_4 = QtWidgets.QGridLayout()
font = QtGui.QFont() self.gridLayout_4.setObjectName("gridLayout_4")
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.label_2 = QtWidgets.QLabel(self.groupBox_2) self.label_2 = QtWidgets.QLabel(self.groupBox_2)
font = QtGui.QFont() font = QtGui.QFont()
font.setBold(False) font.setBold(False)
@ -106,7 +66,70 @@ class Ui_Settings(object):
self.label_2.setFont(font) self.label_2.setFont(font)
self.label_2.setWordWrap(True) self.label_2.setWordWrap(True)
self.label_2.setObjectName("label_2") 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.verticalLayout_7.addWidget(self.groupBox_2)
self.groupBox_10 = QtWidgets.QGroupBox(self.stackedWidgetPage1) self.groupBox_10 = QtWidgets.QGroupBox(self.stackedWidgetPage1)
font = QtGui.QFont() font = QtGui.QFont()
@ -166,8 +189,8 @@ class Ui_Settings(object):
self.label.setFont(font) self.label.setFont(font)
self.label.setObjectName("label") self.label.setObjectName("label")
self.horizontalLayout_5.addWidget(self.label) self.horizontalLayout_5.addWidget(self.label)
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_5.addItem(spacerItem) self.horizontalLayout_5.addItem(spacerItem1)
self.verticalLayout_6.addLayout(self.horizontalLayout_5) self.verticalLayout_6.addLayout(self.horizontalLayout_5)
self.horizontalLayout_7 = QtWidgets.QHBoxLayout() self.horizontalLayout_7 = QtWidgets.QHBoxLayout()
self.horizontalLayout_7.setObjectName("horizontalLayout_7") self.horizontalLayout_7.setObjectName("horizontalLayout_7")
@ -202,8 +225,8 @@ class Ui_Settings(object):
self.label_14.setFont(font) self.label_14.setFont(font)
self.label_14.setObjectName("label_14") self.label_14.setObjectName("label_14")
self.horizontalLayout_7.addWidget(self.label_14) self.horizontalLayout_7.addWidget(self.label_14)
spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_7.addItem(spacerItem1) self.horizontalLayout_7.addItem(spacerItem2)
self.verticalLayout_6.addLayout(self.horizontalLayout_7) self.verticalLayout_6.addLayout(self.horizontalLayout_7)
self.chkSaveOnQuit = QtWidgets.QCheckBox(self.groupBox) self.chkSaveOnQuit = QtWidgets.QCheckBox(self.groupBox)
font = QtGui.QFont() font = QtGui.QFont()
@ -223,8 +246,8 @@ class Ui_Settings(object):
self.chkSaveToZip.setObjectName("chkSaveToZip") self.chkSaveToZip.setObjectName("chkSaveToZip")
self.verticalLayout_6.addWidget(self.chkSaveToZip) self.verticalLayout_6.addWidget(self.chkSaveToZip)
self.verticalLayout_7.addWidget(self.groupBox) self.verticalLayout_7.addWidget(self.groupBox)
spacerItem2 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) spacerItem3 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.verticalLayout_7.addItem(spacerItem2) self.verticalLayout_7.addItem(spacerItem3)
self.stack.addWidget(self.stackedWidgetPage1) self.stack.addWidget(self.stackedWidgetPage1)
self.page_3 = QtWidgets.QWidget() self.page_3 = QtWidgets.QWidget()
self.page_3.setObjectName("page_3") self.page_3.setObjectName("page_3")
@ -388,8 +411,8 @@ class Ui_Settings(object):
self.label_51.setObjectName("label_51") self.label_51.setObjectName("label_51")
self.gridLayout_2.addWidget(self.label_51, 6, 1, 1, 1) self.gridLayout_2.addWidget(self.label_51, 6, 1, 1, 1)
self.verticalLayout.addWidget(self.chkRevisionRemove) self.verticalLayout.addWidget(self.chkRevisionRemove)
spacerItem3 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) spacerItem4 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.verticalLayout.addItem(spacerItem3) self.verticalLayout.addItem(spacerItem4)
self.label_revisionDeprecation = QtWidgets.QLabel(self.page_3) self.label_revisionDeprecation = QtWidgets.QLabel(self.page_3)
self.label_revisionDeprecation.setWordWrap(True) self.label_revisionDeprecation.setWordWrap(True)
self.label_revisionDeprecation.setOpenExternalLinks(True) self.label_revisionDeprecation.setOpenExternalLinks(True)
@ -524,6 +547,25 @@ class Ui_Settings(object):
self.sldTreeIconSize.setObjectName("sldTreeIconSize") self.sldTreeIconSize.setObjectName("sldTreeIconSize")
self.horizontalLayout_11.addWidget(self.sldTreeIconSize) self.horizontalLayout_11.addWidget(self.sldTreeIconSize)
self.verticalLayout_17.addWidget(self.groupBox_16) 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 = QtWidgets.QHBoxLayout()
self.horizontalLayout_9.setObjectName("horizontalLayout_9") self.horizontalLayout_9.setObjectName("horizontalLayout_9")
self.groupBox_8 = QtWidgets.QGroupBox(self.tab) self.groupBox_8 = QtWidgets.QGroupBox(self.tab)
@ -548,6 +590,13 @@ class Ui_Settings(object):
self.rdoTreeWC.setFont(font) self.rdoTreeWC.setFont(font)
self.rdoTreeWC.setObjectName("rdoTreeWC") self.rdoTreeWC.setObjectName("rdoTreeWC")
self.verticalLayout_15.addWidget(self.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) self.rdoTreeProgress = QtWidgets.QRadioButton(self.groupBox_8)
font = QtGui.QFont() font = QtGui.QFont()
font.setBold(False) font.setBold(False)
@ -586,6 +635,13 @@ class Ui_Settings(object):
self.rdoTreeTextWC.setFont(font) self.rdoTreeTextWC.setFont(font)
self.rdoTreeTextWC.setObjectName("rdoTreeTextWC") self.rdoTreeTextWC.setObjectName("rdoTreeTextWC")
self.verticalLayout_16.addWidget(self.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) self.rdoTreeTextProgress = QtWidgets.QRadioButton(self.groupBox_9)
font = QtGui.QFont() font = QtGui.QFont()
font.setBold(False) font.setBold(False)
@ -607,12 +663,17 @@ class Ui_Settings(object):
self.rdoTreeTextNothing.setFont(font) self.rdoTreeTextNothing.setFont(font)
self.rdoTreeTextNothing.setObjectName("rdoTreeTextNothing") self.rdoTreeTextNothing.setObjectName("rdoTreeTextNothing")
self.verticalLayout_16.addWidget(self.rdoTreeTextNothing) self.verticalLayout_16.addWidget(self.rdoTreeTextNothing)
spacerItem4 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) spacerItem6 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.verticalLayout_16.addItem(spacerItem4) 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.horizontalLayout_9.addWidget(self.groupBox_9)
self.verticalLayout_17.addLayout(self.horizontalLayout_9) self.verticalLayout_17.addLayout(self.horizontalLayout_9)
spacerItem5 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) spacerItem7 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.verticalLayout_17.addItem(spacerItem5) self.verticalLayout_17.addItem(spacerItem7)
icon = QtGui.QIcon.fromTheme("view-list-tree") icon = QtGui.QIcon.fromTheme("view-list-tree")
self.tabViews.addTab(self.tab, icon, "") self.tabViews.addTab(self.tab, icon, "")
self.tab_2 = QtWidgets.QWidget() self.tab_2 = QtWidgets.QWidget()
@ -774,8 +835,8 @@ class Ui_Settings(object):
self.chkOutlineTitle.setObjectName("chkOutlineTitle") self.chkOutlineTitle.setObjectName("chkOutlineTitle")
self.gridLayout.addWidget(self.chkOutlineTitle, 3, 0, 1, 1) self.gridLayout.addWidget(self.chkOutlineTitle, 3, 0, 1, 1)
self.verticalLayout_11.addWidget(self.groupBox_6) self.verticalLayout_11.addWidget(self.groupBox_6)
spacerItem6 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) spacerItem8 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.verticalLayout_11.addItem(spacerItem6) self.verticalLayout_11.addItem(spacerItem8)
icon = QtGui.QIcon.fromTheme("view-outline") icon = QtGui.QIcon.fromTheme("view-outline")
self.tabViews.addTab(self.tab_2, icon, "") self.tabViews.addTab(self.tab_2, icon, "")
self.tab_3 = QtWidgets.QWidget() self.tab_3 = QtWidgets.QWidget()
@ -821,8 +882,8 @@ class Ui_Settings(object):
self.cmbCorkImage.setFont(font) self.cmbCorkImage.setFont(font)
self.cmbCorkImage.setObjectName("cmbCorkImage") self.cmbCorkImage.setObjectName("cmbCorkImage")
self.verticalLayout_8.addWidget(self.cmbCorkImage) self.verticalLayout_8.addWidget(self.cmbCorkImage)
spacerItem7 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) spacerItem9 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.verticalLayout_8.addItem(spacerItem7) self.verticalLayout_8.addItem(spacerItem9)
self.gridLayout_3.addWidget(self.groupBox_7, 1, 1, 1, 1) self.gridLayout_3.addWidget(self.groupBox_7, 1, 1, 1, 1)
self.groupBox_11 = QtWidgets.QGroupBox(self.tab_3) self.groupBox_11 = QtWidgets.QGroupBox(self.tab_3)
font = QtGui.QFont() font = QtGui.QFont()
@ -1380,8 +1441,8 @@ class Ui_Settings(object):
self.btnLabelColor.setIconSize(QtCore.QSize(64, 64)) self.btnLabelColor.setIconSize(QtCore.QSize(64, 64))
self.btnLabelColor.setObjectName("btnLabelColor") self.btnLabelColor.setObjectName("btnLabelColor")
self.verticalLayout_2.addWidget(self.btnLabelColor) self.verticalLayout_2.addWidget(self.btnLabelColor)
spacerItem8 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) spacerItem10 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.verticalLayout_2.addItem(spacerItem8) self.verticalLayout_2.addItem(spacerItem10)
self.horizontalLayout_2.addLayout(self.verticalLayout_2) self.horizontalLayout_2.addLayout(self.verticalLayout_2)
self.verticalLayout_3.addLayout(self.horizontalLayout_2) self.verticalLayout_3.addLayout(self.horizontalLayout_2)
self.horizontalLayout = QtWidgets.QHBoxLayout() self.horizontalLayout = QtWidgets.QHBoxLayout()
@ -1398,8 +1459,8 @@ class Ui_Settings(object):
self.btnLabelRemove.setIcon(icon) self.btnLabelRemove.setIcon(icon)
self.btnLabelRemove.setObjectName("btnLabelRemove") self.btnLabelRemove.setObjectName("btnLabelRemove")
self.horizontalLayout.addWidget(self.btnLabelRemove) self.horizontalLayout.addWidget(self.btnLabelRemove)
spacerItem9 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) spacerItem11 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout.addItem(spacerItem9) self.horizontalLayout.addItem(spacerItem11)
self.verticalLayout_3.addLayout(self.horizontalLayout) self.verticalLayout_3.addLayout(self.horizontalLayout)
self.stack.addWidget(self.stackedWidgetPage3) self.stack.addWidget(self.stackedWidgetPage3)
self.stackedWidgetPage4 = QtWidgets.QWidget() self.stackedWidgetPage4 = QtWidgets.QWidget()
@ -1433,8 +1494,8 @@ class Ui_Settings(object):
self.btnStatusRemove.setIcon(icon) self.btnStatusRemove.setIcon(icon)
self.btnStatusRemove.setObjectName("btnStatusRemove") self.btnStatusRemove.setObjectName("btnStatusRemove")
self.horizontalLayout_3.addWidget(self.btnStatusRemove) self.horizontalLayout_3.addWidget(self.btnStatusRemove)
spacerItem10 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) spacerItem12 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_3.addItem(spacerItem10) self.horizontalLayout_3.addItem(spacerItem12)
self.verticalLayout_4.addLayout(self.horizontalLayout_3) self.verticalLayout_4.addLayout(self.horizontalLayout_3)
self.stack.addWidget(self.stackedWidgetPage4) self.stack.addWidget(self.stackedWidgetPage4)
self.page = QtWidgets.QWidget() self.page = QtWidgets.QWidget()
@ -1482,8 +1543,8 @@ class Ui_Settings(object):
self.btnThemeRemove.setIcon(icon) self.btnThemeRemove.setIcon(icon)
self.btnThemeRemove.setObjectName("btnThemeRemove") self.btnThemeRemove.setObjectName("btnThemeRemove")
self.horizontalLayout_6.addWidget(self.btnThemeRemove) self.horizontalLayout_6.addWidget(self.btnThemeRemove)
spacerItem11 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) spacerItem13 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_6.addItem(spacerItem11) self.horizontalLayout_6.addItem(spacerItem13)
self.verticalLayout_12.addLayout(self.horizontalLayout_6) self.verticalLayout_12.addLayout(self.horizontalLayout_6)
self.themeStack.addWidget(self.stackedWidgetPage1_3) self.themeStack.addWidget(self.stackedWidgetPage1_3)
self.stackedWidgetPage2_3 = QtWidgets.QWidget() self.stackedWidgetPage2_3 = QtWidgets.QWidget()
@ -1823,9 +1884,9 @@ class Ui_Settings(object):
self.horizontalLayout_8.addWidget(self.stack) self.horizontalLayout_8.addWidget(self.stack)
self.retranslateUi(Settings) self.retranslateUi(Settings)
self.stack.setCurrentIndex(2) self.stack.setCurrentIndex(0)
self.tabViews.setCurrentIndex(3) self.tabViews.setCurrentIndex(0)
self.themeStack.setCurrentIndex(1) self.themeStack.setCurrentIndex(0)
self.themeEditStack.setCurrentIndex(3) self.themeEditStack.setCurrentIndex(3)
self.lstMenu.currentRowChanged['int'].connect(self.stack.setCurrentIndex) self.lstMenu.currentRowChanged['int'].connect(self.stack.setCurrentIndex)
self.chkRevisionsKeep.toggled['bool'].connect(self.chkRevisionRemove.setEnabled) self.chkRevisionsKeep.toggled['bool'].connect(self.chkRevisionRemove.setEnabled)
@ -1851,10 +1912,12 @@ class Ui_Settings(object):
self.lstMenu.setSortingEnabled(__sortingEnabled) self.lstMenu.setSortingEnabled(__sortingEnabled)
self.lblTitleGeneral.setText(_translate("Settings", "General settings")) self.lblTitleGeneral.setText(_translate("Settings", "General settings"))
self.groupBox_2.setTitle(_translate("Settings", "Application 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_56.setText(_translate("Settings", "Style:"))
self.label_57.setText(_translate("Settings", "Language:")) self.label_57.setText(_translate("Settings", "Language:"))
self.label_58.setText(_translate("Settings", "Font size:")) 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.groupBox_10.setTitle(_translate("Settings", "Loading"))
self.chkAutoLoad.setText(_translate("Settings", "Automatically load last project on startup")) self.chkAutoLoad.setText(_translate("Settings", "Automatically load last project on startup"))
self.groupBox.setTitle(_translate("Settings", "Saving")) self.groupBox.setTitle(_translate("Settings", "Saving"))
@ -1899,14 +1962,18 @@ class Ui_Settings(object):
self.cmbTreeBackground.setItemText(4, _translate("Settings", "Compile")) self.cmbTreeBackground.setItemText(4, _translate("Settings", "Compile"))
self.groupBox_16.setTitle(_translate("Settings", "Icon Size")) self.groupBox_16.setTitle(_translate("Settings", "Icon Size"))
self.lblTreeIconSize.setText(_translate("Settings", "TextLabel")) 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.groupBox_8.setTitle(_translate("Settings", "Folders"))
self.rdoTreeItemCount.setText(_translate("Settings", "Show ite&m count")) self.rdoTreeItemCount.setText(_translate("Settings", "Show ite&m count"))
self.rdoTreeWC.setText(_translate("Settings", "Show &word 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.rdoTreeProgress.setText(_translate("Settings", "S&how progress"))
self.rdoTreeSummary.setText(_translate("Settings", "Show summar&y")) self.rdoTreeSummary.setText(_translate("Settings", "Show summar&y"))
self.rdoTreeNothing.setText(_translate("Settings", "&Nothing")) self.rdoTreeNothing.setText(_translate("Settings", "&Nothing"))
self.groupBox_9.setTitle(_translate("Settings", "Text")) self.groupBox_9.setTitle(_translate("Settings", "Text"))
self.rdoTreeTextWC.setText(_translate("Settings", "&Show word count")) 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.rdoTreeTextProgress.setText(_translate("Settings", "Show p&rogress"))
self.rdoTreeTextSummary.setText(_translate("Settings", "Show summary")) self.rdoTreeTextSummary.setText(_translate("Settings", "Show summary"))
self.rdoTreeTextNothing.setText(_translate("Settings", "Nothing")) self.rdoTreeTextNothing.setText(_translate("Settings", "Nothing"))

View file

@ -6,7 +6,7 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>658</width> <width>681</width>
<height>598</height> <height>598</height>
</rect> </rect>
</property> </property>
@ -54,7 +54,7 @@
<item> <item>
<widget class="QStackedWidget" name="stack"> <widget class="QStackedWidget" name="stack">
<property name="currentIndex"> <property name="currentIndex">
<number>2</number> <number>0</number>
</property> </property>
<widget class="QWidget" name="stackedWidgetPage1"> <widget class="QWidget" name="stackedWidgetPage1">
<layout class="QVBoxLayout" name="verticalLayout_7"> <layout class="QVBoxLayout" name="verticalLayout_7">
@ -98,93 +98,139 @@
<string>Application settings</string> <string>Application settings</string>
</property> </property>
<layout class="QFormLayout" name="formLayout_13"> <layout class="QFormLayout" name="formLayout_13">
<property name="fieldGrowthPolicy"> <item row="0" column="0" colspan="2">
<enum>QFormLayout::FieldsStayAtSizeHint</enum> <layout class="QGridLayout" name="gridLayout_4">
</property> <item row="0" column="0">
<item row="3" column="0"> <widget class="QLabel" name="label_2">
<widget class="QLabel" name="label_56"> <property name="font">
<property name="font"> <font>
<font> <weight>50</weight>
<weight>50</weight> <bold>false</bold>
<bold>false</bold> </font>
</font> </property>
</property> <property name="text">
<property name="text"> <string>Restarting Manuskript ensures all settings take effect.</string>
<string>Style:</string> </property>
</property> <property name="wordWrap">
</widget> <bool>true</bool>
</item> </property>
<item row="3" column="1"> </widget>
<widget class="QComboBox" name="cmbStyle"> </item>
<property name="font"> <item row="1" column="0">
<font> <layout class="QHBoxLayout" name="horizontalLayout_12">
<weight>50</weight> <property name="sizeConstraint">
<bold>false</bold> <enum>QLayout::SetDefaultConstraint</enum>
</font> </property>
</property> <item>
</widget> <layout class="QFormLayout" name="formLayout_14">
</item> <property name="fieldGrowthPolicy">
<item row="6" column="0"> <enum>QFormLayout::AllNonFixedFieldsGrow</enum>
<widget class="QLabel" name="label_57"> </property>
<property name="font"> <item row="0" column="0">
<font> <widget class="QLabel" name="label_56">
<weight>50</weight> <property name="font">
<bold>false</bold> <font>
</font> <weight>50</weight>
</property> <bold>false</bold>
<property name="text"> </font>
<string>Language:</string> </property>
</property> <property name="text">
</widget> <string>Style:</string>
</item> </property>
<item row="6" column="1"> </widget>
<widget class="QComboBox" name="cmbTranslation"> </item>
<property name="font"> <item row="0" column="1">
<font> <widget class="QComboBox" name="cmbStyle">
<weight>50</weight> <property name="font">
<bold>false</bold> <font>
</font> <weight>50</weight>
</property> <bold>false</bold>
</widget> </font>
</item> </property>
<item row="7" column="0"> </widget>
<widget class="QLabel" name="label_58"> </item>
<property name="font"> <item row="1" column="0">
<font> <widget class="QLabel" name="label_57">
<weight>50</weight> <property name="font">
<bold>false</bold> <font>
</font> <weight>50</weight>
</property> <bold>false</bold>
<property name="text"> </font>
<string>Font size:</string> </property>
</property> <property name="text">
</widget> <string>Language:</string>
</item> </property>
<item row="7" column="1"> </widget>
<widget class="QSpinBox" name="spnGeneralFontSize"> </item>
<property name="font"> <item row="1" column="1">
<font> <widget class="QComboBox" name="cmbTranslation">
<weight>50</weight> <property name="font">
<bold>false</bold> <font>
</font> <weight>50</weight>
</property> <bold>false</bold>
</widget> </font>
</item> </property>
<item row="2" column="0" colspan="2"> </widget>
<widget class="QLabel" name="label_2"> </item>
<property name="font"> <item row="2" column="0">
<font> <widget class="QLabel" name="label_58">
<weight>50</weight> <property name="font">
<bold>false</bold> <font>
</font> <weight>50</weight>
</property> <bold>false</bold>
<property name="text"> </font>
<string>Restarting Manuskript ensures all settings take effect.</string> </property>
</property> <property name="text">
<property name="wordWrap"> <string>Font size:</string>
<bool>true</bool> </property>
</property> </widget>
</widget> </item>
<item row="2" column="1">
<widget class="QSpinBox" name="spnGeneralFontSize">
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QFormLayout" name="formLayout_15">
<item row="0" column="1">
<widget class="QCheckBox" name="chkProgressChars">
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>Show progress in chars next
to words</string>
</property>
</widget>
</item>
<item row="0" column="0">
<spacer name="horizontalSpacer_6">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</item> </item>
</layout> </layout>
</widget> </widget>
@ -817,7 +863,7 @@
<item> <item>
<widget class="QTabWidget" name="tabViews"> <widget class="QTabWidget" name="tabViews">
<property name="currentIndex"> <property name="currentIndex">
<number>3</number> <number>0</number>
</property> </property>
<widget class="QWidget" name="tab"> <widget class="QWidget" name="tab">
<attribute name="icon"> <attribute name="icon">
@ -1055,6 +1101,59 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item>
<widget class="QGroupBox" name="horizontalGroupBox">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="title">
<string>Char/Word Counter</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_13">
<property name="leftMargin">
<number>9</number>
</property>
<property name="topMargin">
<number>9</number>
</property>
<property name="rightMargin">
<number>9</number>
</property>
<property name="bottomMargin">
<number>9</number>
</property>
<item>
<widget class="QCheckBox" name="chkCountSpaces">
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>Count spaces as chars</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_7">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout_9"> <layout class="QHBoxLayout" name="horizontalLayout_9">
<item> <item>
@ -1095,6 +1194,19 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QRadioButton" name="rdoTreeCC">
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>Show char c&amp;ount</string>
</property>
</widget>
</item>
<item> <item>
<widget class="QRadioButton" name="rdoTreeProgress"> <widget class="QRadioButton" name="rdoTreeProgress">
<property name="font"> <property name="font">
@ -1165,6 +1277,19 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QRadioButton" name="rdoTreeTextCC">
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>Sho&amp;w char count</string>
</property>
</widget>
</item>
<item> <item>
<widget class="QRadioButton" name="rdoTreeTextProgress"> <widget class="QRadioButton" name="rdoTreeTextProgress">
<property name="font"> <property name="font">
@ -1224,6 +1349,11 @@
</spacer> </spacer>
</item> </item>
</layout> </layout>
<zorder>rdoTreeTextCC</zorder>
<zorder>rdoTreeTextWC</zorder>
<zorder>rdoTreeTextProgress</zorder>
<zorder>rdoTreeTextSummary</zorder>
<zorder>rdoTreeTextNothing</zorder>
</widget> </widget>
</item> </item>
</layout> </layout>
@ -2974,7 +3104,7 @@
<item> <item>
<widget class="QStackedWidget" name="themeStack"> <widget class="QStackedWidget" name="themeStack">
<property name="currentIndex"> <property name="currentIndex">
<number>1</number> <number>0</number>
</property> </property>
<widget class="QWidget" name="stackedWidgetPage1_3"> <widget class="QWidget" name="stackedWidgetPage1_3">
<layout class="QVBoxLayout" name="verticalLayout_12"> <layout class="QVBoxLayout" name="verticalLayout_12">

View file

@ -106,13 +106,18 @@ class MDEditCompleter(MDEditView):
self.completer.popup(self.textUnderCursor(select=True)) self.completer.popup(self.textUnderCursor(select=True))
def mouseMoveEvent(self, event): def mouseMoveEvent(self, event):
"""
When mouse moves, we show tooltip when appropriate.
"""
self.beginTooltipMoveEvent()
MDEditView.mouseMoveEvent(self, event) MDEditView.mouseMoveEvent(self, event)
self.endTooltipMoveEvent()
onRef = [r for r in self.refRects if r.contains(event.pos())] onRef = [r for r in self.refRects if r.contains(event.pos())]
if not onRef: if not onRef:
qApp.restoreOverrideCursor() qApp.restoreOverrideCursor()
QToolTip.hideText() self.hideTooltip()
return return
cursor = self.cursorForPosition(event.pos()) cursor = self.cursorForPosition(event.pos())
@ -120,7 +125,8 @@ class MDEditCompleter(MDEditView):
if ref: if ref:
if not qApp.overrideCursor(): if not qApp.overrideCursor():
qApp.setOverrideCursor(Qt.PointingHandCursor) 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): def mouseReleaseEvent(self, event):
MDEditView.mouseReleaseEvent(self, event) MDEditView.mouseReleaseEvent(self, event)

View file

@ -506,13 +506,15 @@ class MDEditView(textEditView):
""" """
When mouse moves, we show tooltip when appropriate. When mouse moves, we show tooltip when appropriate.
""" """
self.beginTooltipMoveEvent()
textEditView.mouseMoveEvent(self, event) textEditView.mouseMoveEvent(self, event)
self.endTooltipMoveEvent()
onRect = [r for r in self.clickRects if r.rect.contains(event.pos())] onRect = [r for r in self.clickRects if r.rect.contains(event.pos())]
if not onRect: if not onRect:
qApp.restoreOverrideCursor() qApp.restoreOverrideCursor()
QToolTip.hideText() self.hideTooltip()
return return
ct = onRect[0] ct = onRect[0]
@ -534,7 +536,7 @@ class MDEditView(textEditView):
if tooltip: if tooltip:
tooltip = self.tr("{} (CTRL+Click to open)").format(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): def mouseReleaseEvent(self, event):
textEditView.mouseReleaseEvent(self, event) textEditView.mouseReleaseEvent(self, event)

View file

@ -29,6 +29,8 @@ class characterTreeView(QTreeWidget):
self._rootItem = QTreeWidgetItem() self._rootItem = QTreeWidgetItem()
self.insertTopLevelItem(0, self._rootItem) self.insertTopLevelItem(0, self._rootItem)
self.importanceMap = {self.tr("Main"):2, self.tr("Secondary"):1, self.tr("Minor"):0}
def setCharactersModel(self, model): def setCharactersModel(self, model):
self._model = model self._model = model
self._model.dataChanged.connect(self.updateMaybe) self._model.dataChanged.connect(self.updateMaybe)
@ -64,7 +66,7 @@ class characterTreeView(QTreeWidget):
for child in range(item.childCount()): for child in range(item.childCount()):
sub = item.child(child) sub = item.child(child)
ID = sub.data(0, Qt.UserRole) ID = sub.data(0, Qt.UserRole)
if ID is not None: if ID != None:
# Update name # Update name
c = self._model.getCharacterByID(ID) c = self._model.getCharacterByID(ID)
name = c.name() name = c.name()
@ -86,11 +88,9 @@ class characterTreeView(QTreeWidget):
self.clear() self.clear()
characters = self._model.getCharactersByImportance() characters = self._model.getCharactersByImportance()
h = [self.tr("Main"), self.tr("Secondary"), self.tr("Minor")] for i, importanceLevel in enumerate(self.importanceMap):
for i in range(3):
# Create category item # Create category item
cat = QTreeWidgetItem(self, [h[i]]) cat = QTreeWidgetItem(self, [importanceLevel])
cat.setBackground(0, QBrush(QColor(S.highlightLight))) cat.setBackground(0, QBrush(QColor(S.highlightLight)))
cat.setForeground(0, QBrush(QColor(S.highlightedTextDark))) cat.setForeground(0, QBrush(QColor(S.highlightedTextDark)))
cat.setTextAlignment(0, Qt.AlignCenter) cat.setTextAlignment(0, Qt.AlignCenter)
@ -119,6 +119,24 @@ class characterTreeView(QTreeWidget):
self.expandAll() self.expandAll()
self._updating = False 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): def removeCharacter(self):
""" """
Removes selected character. Removes selected character.
@ -130,22 +148,30 @@ class characterTreeView(QTreeWidget):
def choseCharacterColor(self): def choseCharacterColor(self):
ID = self.currentCharacterID() ID = self.currentCharacterID()
c = self._model.getCharacterByID(ID) c = self._model.getCharacterByID(ID)
if c: if c:
color = iconColor(c.icon) color = iconColor(c.icon)
else: else:
color = Qt.white color = Qt.white
self.colorDialog = QColorDialog(color, mainWindow()) self.colorDialog = QColorDialog(color, mainWindow())
color = self.colorDialog.getColor(color) color = self.colorDialog.getColor(color)
if color.isValid(): if color.isValid():
c.setColor(color) c.setColor(color)
mainWindow().updateCharacterColor(ID) 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): def addCharacterInfo(self):
self._model.addCharacterInfo(self.currentCharacterID()) self._model.addCharacterInfo(self.currentCharacterID())
def removeCharacterInfo(self): def removeCharacterInfo(self):
self._model.removeCharacterInfo(self.currentCharacterID(), self._model.removeCharacterInfo(self.currentCharacterID())
)
def currentCharacterID(self): def currentCharacterID(self):
ID = None ID = None

View file

@ -43,11 +43,19 @@ class corkDelegate(QStyledItemDelegate):
return QStyledItemDelegate.editorEvent(self, event, model, option, index) return QStyledItemDelegate.editorEvent(self, event, model, option, index)
def createEditor(self, parent, 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) self.updateRects(option, index)
bgColor = self.bgColors.get(index, "white") 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 # One line summary
self.editing = Outline.summarySentence self.editing = Outline.summarySentence
edt = QLineEdit(parent) edt = QLineEdit(parent)
@ -64,7 +72,7 @@ class corkDelegate(QStyledItemDelegate):
edt.setStyleSheet("background: {}; color: black;".format(bgColor)) edt.setStyleSheet("background: {}; color: black;".format(bgColor))
return edt 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 # Title
self.editing = Outline.title self.editing = Outline.title
edt = QLineEdit(parent) edt = QLineEdit(parent)

View file

@ -27,6 +27,8 @@ class corkView(QListView, dndView, outlineBasics):
def updateBackground(self): def updateBackground(self):
if settings.corkBackground["image"] != "": if settings.corkBackground["image"] != "":
img = findBackground(settings.corkBackground["image"]) img = findBackground(settings.corkBackground["image"])
if img == None:
img = ""
else: else:
# No background image # No background image
img = "" img = ""

Some files were not shown because too many files have changed in this diff Show more