diff --git a/.gitignore b/.gitignore index 2e6ffc7e..abbe884a 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,5 @@ ExportTest icons/Numix .idea dist -build \ No newline at end of file +build +test-projects \ No newline at end of file diff --git a/i18n/manuskript_fr.ts b/i18n/manuskript_fr.ts index d4e27417..6a308d39 100644 --- a/i18n/manuskript_fr.ts +++ b/i18n/manuskript_fr.ts @@ -1669,7 +1669,7 @@ des lignes: - outlinePersoDelegate + outlineCharacterDelegate None diff --git a/makefile b/makefile index f003a312..46e7e6e3 100644 --- a/makefile +++ b/makefile @@ -10,7 +10,7 @@ run: $(UIs) bin/manuskript debug: $(UIs) - gdb --args python3 manuskript/main.py + gdb --args python3 bin/manuskript lineprof: kernprof -l -v manuskript/main.py diff --git a/manuskript/enums.py b/manuskript/enums.py index 8fa68a58..661915f0 100644 --- a/manuskript/enums.py +++ b/manuskript/enums.py @@ -9,7 +9,7 @@ from enum import Enum #def enum(**enums): #return type(str('Enum'), (), enums) -class Perso(Enum): +class Character(Enum): name = 0 ID = 1 importance = 2 @@ -21,20 +21,18 @@ class Perso(Enum): summaryPara = 8 summaryFull = 9 notes = 10 - infoName = 11 - infoData = 12 class Plot(Enum): name = 0 ID = 1 importance = 2 - persos = 3 + characters = 3 description = 4 result = 5 - subplots = 6 + steps = 6 summary = 7 -class Subplot(Enum): +class PlotStep(Enum): name = 0 ID = 1 meta = 2 @@ -66,4 +64,3 @@ class Outline(Enum): # (sum of all sub-items' goals) textFormat = 15 revisions = 16 - diff --git a/manuskript/functions.py b/manuskript/functions.py index 2e442fdb..fa8cffd8 100644 --- a/manuskript/functions.py +++ b/manuskript/functions.py @@ -121,9 +121,9 @@ def outlineItemColors(item): # POV colors["POV"] = QColor(Qt.transparent) POV = item.data(Outline.POV.value) - for i in range(mw.mdlPersos.rowCount()): - if mw.mdlPersos.ID(i) == POV: - colors["POV"] = iconColor(mw.mdlPersos.icon(i)) + for i in range(mw.mdlCharacter.rowCount()): + if mw.mdlCharacter.ID(i) == POV: + colors["POV"] = iconColor(mw.mdlCharacter.icon(i)) # Label lbl = item.data(Outline.label.value) @@ -179,6 +179,10 @@ def allPaths(suffix=None): return paths def lightBlue(): + """ + A light blue used in several places in manuskript. + @return: QColor + """ return QColor(Qt.blue).lighter(190) def totalObjects(): diff --git a/manuskript/loadSave.py b/manuskript/loadSave.py index af5dfc6d..69f2b229 100644 --- a/manuskript/loadSave.py +++ b/manuskript/loadSave.py @@ -1,149 +1,58 @@ #!/usr/bin/env python -#--!-- coding: utf8 --!-- +# --!-- coding: utf8 --!-- +# The loadSave file calls the propper functions to load and save file +# trying to detect the proper file format if it comes from an older version +import os import zipfile -from PyQt5.QtCore import QModelIndex, Qt -from PyQt5.QtGui import QColor, QStandardItem -from PyQt5.QtWidgets import qApp -from lxml import etree as ET +import manuskript.load_save.version_0 as v0 +import manuskript.load_save.version_1 as v1 -from manuskript.functions import iconColor, iconFromColorString -try: - import zlib # Used with zipfile for compression - compression = zipfile.ZIP_DEFLATED -except: - compression = zipfile.ZIP_STORED +def saveProject(version=None): -def saveFilesToZip(files, zipname): - """Saves given files to zipname. - files is actually a list of (content, filename).""" - - zf = zipfile.ZipFile(zipname, mode="w") - - for content, filename in files: - zf.writestr(filename, content, compress_type=compression) - - zf.close() - -def loadFilesFromZip(zipname): - """Returns the content of zipfile as a dict of filename:content.""" - print(zipname) - zf = zipfile.ZipFile(zipname) - files = {} - for f in zf.namelist(): - files[f] = zf.read(f) - return files - -def saveStandardItemModelXML(mdl, xml=None): - """Saves the given QStandardItemModel to XML. - If xml (filename) is given, saves to xml. Otherwise returns as string.""" - - root = ET.Element("model") - root.attrib["version"] = qApp.applicationVersion() - - # Header - header = ET.SubElement(root, "header") - vHeader = ET.SubElement(header, "vertical") - for x in range(mdl.rowCount()): - vH = ET.SubElement(vHeader, "label") - vH.attrib["row"] = str(x) - vH.attrib["text"] = str(mdl.headerData(x, Qt.Vertical)) - - hHeader = ET.SubElement(header, "horizontal") - for y in range(mdl.columnCount()): - hH = ET.SubElement(hHeader, "label") - hH.attrib["row"] = str(y) - hH.attrib["text"] = str(mdl.headerData(y, Qt.Horizontal)) - - # Data - data = ET.SubElement(root, "data") - saveItem(data, mdl) - - #print(qApp.tr("Saving to {}.").format(xml)) - if xml: - ET.ElementTree(root).write(xml, encoding="UTF-8", xml_declaration=True, pretty_print=True) + # While debugging, we don't save the project + # return + + if version == 0: + v0.saveProject() else: - return ET.tostring(root, encoding="UTF-8", xml_declaration=True, pretty_print=True) - -def saveItem(root, mdl, parent=QModelIndex()): - for x in range(mdl.rowCount(parent)): - row = ET.SubElement(root, "row") - row.attrib["row"] = str(x) - - for y in range(mdl.columnCount(parent)): - col = ET.SubElement(row, "col") - col.attrib["col"] = str(y) - if mdl.data(mdl.index(x, y, parent), Qt.DecorationRole) != None: - color = iconColor(mdl.data(mdl.index(x, y, parent), Qt.DecorationRole)).name(QColor.HexArgb) - col.attrib["color"] = color if color != "#ff000000" else "#00000000" - if mdl.data(mdl.index(x, y, parent)) != "": - col.text = mdl.data(mdl.index(x, y, parent)) - if mdl.hasChildren(mdl.index(x, y, parent)): - saveItem(col, mdl, mdl.index(x, y, parent)) - -def loadStandardItemModelXML(mdl, xml, fromString=False): - """Load data to a QStandardItemModel mdl from xml. - By default xml is a filename. If fromString=True, xml is a string containg the data.""" - - #print(qApp.tr("Loading {}... ").format(xml), end="") - - if not fromString: - try: - tree = ET.parse(xml) - except: - print("Failed.") - return + v1.saveProject() + + # FIXME: add settings to chose between saving as zip or not. + + +def loadProject(project): + + # Detect version + isZip = False + version = 0 + + # Is it a zip? + try: + zf = zipfile.ZipFile(project) + isZip = True + except zipfile.BadZipFile: + isZip = False + + # Does it have a VERSION in zip root? + if isZip and "VERSION" in zf.namelist(): + version = int(zf.read("VERSION")) + + # Zip but no VERSION: oldest file format + elif isZip: + version = 0 + + # Not a zip else: - root = ET.fromstring(xml) - - #root = tree.getroot() - - #Header - hLabels = [] - vLabels = [] - for l in root.find("header").find("horizontal").findall("label"): - hLabels.append(l.attrib["text"]) - for l in root.find("header").find("vertical").findall("label"): - vLabels.append(l.attrib["text"]) - - #print(root.find("header").find("vertical").text) - - #mdl.setVerticalHeaderLabels(vLabels) - #mdl.setHorizontalHeaderLabels(hLabels) - - # Populates with empty items - for i in enumerate(vLabels): - row = [] - for r in enumerate(hLabels): - row.append(QStandardItem()) - mdl.appendRow(row) - - #Data - data = root.find("data") - loadItem(data, mdl) - - return True - -def loadItem(root, mdl, parent=QModelIndex()): - for row in root: - r = int(row.attrib["row"]) - for col in row: - c = int(col.attrib["col"]) - item = mdl.itemFromIndex(mdl.index(r, c, parent)) - if not item: - item = QStandardItem() - mdl.itemFromIndex(parent).setChild(r, c, item) - - if col.text: - #mdl.setData(mdl.index(r, c, parent), col.text) - item.setText(col.text) - - if "color" in col.attrib: - #mdl.itemFromIndex(mdl.index(r, c, parent)).setIcon(iconFromColorString(col.attrib["color"])) - item.setIcon(iconFromColorString(col.attrib["color"])) - - if len(col) != 0: - #loadItem(col, mdl, mdl.index(r, c, parent)) - loadItem(col, mdl, mdl.indexFromItem(item)) \ No newline at end of file + with open(project, "r") as f: + version = int(f.read()) + + print("Loading:", project) + print("Detected file format version: {}. Zip: {}.".format(version, isZip)) + + if version == 0: + v0.loadProject(project) + else: + v1.loadProject(project, zip=isZip) diff --git a/manuskript/load_save/__init__.py b/manuskript/load_save/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/manuskript/load_save/version_0.py b/manuskript/load_save/version_0.py new file mode 100644 index 00000000..76cd886d --- /dev/null +++ b/manuskript/load_save/version_0.py @@ -0,0 +1,289 @@ +#!/usr/bin/env python +# --!-- coding: utf8 --!-- + +# Version 0 of file saving format. +# Was used at the begining and up util version XXX when +# it was superseded by Version 1, which is more open and flexible + +import zipfile + +from PyQt5.QtCore import QModelIndex, Qt +from PyQt5.QtGui import QColor, QStandardItem +from PyQt5.QtWidgets import qApp +from lxml import etree as ET + +from manuskript import settings +from manuskript.functions import iconColor, iconFromColorString, mainWindow +from manuskript.models.characterModel import Character, CharacterInfo + +try: + import zlib # Used with zipfile for compression + + compression = zipfile.ZIP_DEFLATED +except: + compression = zipfile.ZIP_STORED + +########################################################################################### +# SAVE +########################################################################################### + +def saveProject(): + """ + Saves the whole project. Call this function to save the project in Version 0 format. + """ + + files = [] + mw = mainWindow() + + files.append((saveStandardItemModelXML(mw.mdlFlatData), + "flatModel.xml")) + print("ERROR: file format 0 does not save characters !") + # files.append((saveStandardItemModelXML(mw.mdlCharacter), + # "perso.xml")) + files.append((saveStandardItemModelXML(mw.mdlWorld), + "world.xml")) + files.append((saveStandardItemModelXML(mw.mdlLabels), + "labels.xml")) + files.append((saveStandardItemModelXML(mw.mdlStatus), + "status.xml")) + files.append((saveStandardItemModelXML(mw.mdlPlots), + "plots.xml")) + files.append((mw.mdlOutline.saveToXML(), + "outline.xml")) + files.append((settings.save(), + "settings.pickle")) + + saveFilesToZip(files, mw.currentProject) + +def saveFilesToZip(files, zipname): + """Saves given files to zipname. + files is actually a list of (content, filename).""" + + zf = zipfile.ZipFile(zipname, mode="w") + + for content, filename in files: + zf.writestr(filename, content, compress_type=compression) + + zf.close() + +def saveStandardItemModelXML(mdl, xml=None): + """Saves the given QStandardItemModel to XML. + If xml (filename) is given, saves to xml. Otherwise returns as string.""" + + root = ET.Element("model") + root.attrib["version"] = qApp.applicationVersion() + + # Header + header = ET.SubElement(root, "header") + vHeader = ET.SubElement(header, "vertical") + for x in range(mdl.rowCount()): + vH = ET.SubElement(vHeader, "label") + vH.attrib["row"] = str(x) + vH.attrib["text"] = str(mdl.headerData(x, Qt.Vertical)) + + hHeader = ET.SubElement(header, "horizontal") + for y in range(mdl.columnCount()): + hH = ET.SubElement(hHeader, "label") + hH.attrib["row"] = str(y) + hH.attrib["text"] = str(mdl.headerData(y, Qt.Horizontal)) + + # Data + data = ET.SubElement(root, "data") + saveItem(data, mdl) + + # print(qApp.tr("Saving to {}.").format(xml)) + if xml: + ET.ElementTree(root).write(xml, encoding="UTF-8", xml_declaration=True, pretty_print=True) + else: + return ET.tostring(root, encoding="UTF-8", xml_declaration=True, pretty_print=True) + + +def saveItem(root, mdl, parent=QModelIndex()): + for x in range(mdl.rowCount(parent)): + row = ET.SubElement(root, "row") + row.attrib["row"] = str(x) + + for y in range(mdl.columnCount(parent)): + col = ET.SubElement(row, "col") + col.attrib["col"] = str(y) + if mdl.data(mdl.index(x, y, parent), Qt.DecorationRole) != None: + color = iconColor(mdl.data(mdl.index(x, y, parent), Qt.DecorationRole)).name(QColor.HexArgb) + col.attrib["color"] = color if color != "#ff000000" else "#00000000" + if mdl.data(mdl.index(x, y, parent)) != "": + col.text = mdl.data(mdl.index(x, y, parent)) + if mdl.hasChildren(mdl.index(x, y, parent)): + saveItem(col, mdl, mdl.index(x, y, parent)) + +########################################################################################### +# LOAD +########################################################################################### + +def loadProject(project): + + files = loadFilesFromZip(project) + mw = mainWindow() + + errors = [] + + if "flatModel.xml" in files: + loadStandardItemModelXML(mw.mdlFlatData, + files["flatModel.xml"], fromString=True) + else: + errors.append("flatModel.xml") + + if "perso.xml" in files: + loadStandardItemModelXMLForCharacters(mw.mdlCharacter, files["perso.xml"]) + else: + errors.append("perso.xml") + + if "world.xml" in files: + loadStandardItemModelXML(mw.mdlWorld, + files["world.xml"], fromString=True) + else: + errors.append("world.xml") + + if "labels.xml" in files: + loadStandardItemModelXML(mw.mdlLabels, + files["labels.xml"], fromString=True) + else: + errors.append("labels.xml") + + if "status.xml" in files: + loadStandardItemModelXML(mw.mdlStatus, + files["status.xml"], fromString=True) + else: + errors.append("status.xml") + + if "plots.xml" in files: + loadStandardItemModelXML(mw.mdlPlots, + files["plots.xml"], fromString=True) + else: + errors.append("plots.xml") + + if "outline.xml" in files: + mw.mdlOutline.loadFromXML(files["outline.xml"], fromString=True) + else: + errors.append("outline.xml") + + if "settings.pickle" in files: + settings.load(files["settings.pickle"], fromString=True) + else: + errors.append("settings.pickle") + + return errors + + +def loadFilesFromZip(zipname): + """Returns the content of zipfile as a dict of filename:content.""" + zf = zipfile.ZipFile(zipname) + files = {} + for f in zf.namelist(): + files[f] = zf.read(f) + return files + + +def loadStandardItemModelXML(mdl, xml, fromString=False): + """Load data to a QStandardItemModel mdl from xml. + By default xml is a filename. If fromString=True, xml is a string containg the data.""" + + # print(qApp.tr("Loading {}... ").format(xml), end="") + + if not fromString: + try: + tree = ET.parse(xml) + except: + print("Failed.") + return + else: + root = ET.fromstring(xml) + + # root = tree.getroot() + + # Header + hLabels = [] + vLabels = [] + for l in root.find("header").find("horizontal").findall("label"): + hLabels.append(l.attrib["text"]) + for l in root.find("header").find("vertical").findall("label"): + vLabels.append(l.attrib["text"]) + + # print(root.find("header").find("vertical").text) + + # mdl.setVerticalHeaderLabels(vLabels) + # mdl.setHorizontalHeaderLabels(hLabels) + + # Populates with empty items + for i in enumerate(vLabels): + row = [] + for r in enumerate(hLabels): + row.append(QStandardItem()) + mdl.appendRow(row) + + # Data + data = root.find("data") + loadItem(data, mdl) + + return True + + +def loadItem(root, mdl, parent=QModelIndex()): + for row in root: + r = int(row.attrib["row"]) + for col in row: + c = int(col.attrib["col"]) + item = mdl.itemFromIndex(mdl.index(r, c, parent)) + if not item: + item = QStandardItem() + mdl.itemFromIndex(parent).setChild(r, c, item) + + if col.text: + # mdl.setData(mdl.index(r, c, parent), col.text) + item.setText(col.text) + + if "color" in col.attrib: + # mdl.itemFromIndex(mdl.index(r, c, parent)).setIcon(iconFromColorString(col.attrib["color"])) + item.setIcon(iconFromColorString(col.attrib["color"])) + + if len(col) != 0: + # loadItem(col, mdl, mdl.index(r, c, parent)) + loadItem(col, mdl, mdl.indexFromItem(item)) + + +def loadStandardItemModelXMLForCharacters(mdl, xml): + """ + Loads a standardItemModel saved to XML by version 0, but for the new characterModel. + @param mdl: characterModel + @param xml: the content of the xml + @return: nothing + """ + mdl = mainWindow().mdlCharacter + root = ET.fromstring(xml) + data = root.find("data") + + for row in data: + char = Character(mdl) + + for col in row: + c = int(col.attrib["col"]) + + # Value + if col.text: + char._data[c] = col.text + + # Color + if "color" in col.attrib: + char.setColor(QColor(col.attrib["color"])) + + # Infos + if len(col) != 0: + for rrow in col: + info = CharacterInfo(char) + for ccol in rrow: + cc = int(ccol.attrib["col"]) + if cc == 11 and ccol.text: + info.description = ccol.text + if cc == 12 and ccol.text: + info.value = ccol.text + char.infos.append(info) + + mdl.characters.append(char) diff --git a/manuskript/load_save/version_1.py b/manuskript/load_save/version_1.py new file mode 100644 index 00000000..f0d23de1 --- /dev/null +++ b/manuskript/load_save/version_1.py @@ -0,0 +1,1027 @@ +#!/usr/bin/env python +# --!-- coding: utf8 --!-- + +# Version 1 of file saving format. +# Aims at providing a plain-text way of saving a project +# (except for some elements), allowing collaborative work +# versioning and third-partty editing. + +import os +import re +import shutil +import string +import zipfile +from collections import OrderedDict + +from PyQt5.QtCore import Qt, QModelIndex +from PyQt5.QtGui import QColor, QStandardItem + +from manuskript import settings +from manuskript.enums import Character, World, Plot, PlotStep, Outline +from manuskript.functions import mainWindow, iconColor, iconFromColorString +from lxml import etree as ET + +from manuskript.load_save.version_0 import loadFilesFromZip +from manuskript.models.characterModel import CharacterInfo +from manuskript.models.outlineModel import outlineItem + +try: + import zlib # Used with zipfile for compression + + compression = zipfile.ZIP_DEFLATED +except: + compression = zipfile.ZIP_STORED + +cache = {} + + +characterMap = OrderedDict([ + (Character.name, "Name"), + (Character.ID, "ID"), + (Character.importance, "Importance"), + (Character.motivation, "Motivation"), + (Character.goal, "Goal"), + (Character.conflict, "Conflict"), + (Character.summarySentence, "Phrase Summary"), + (Character.summaryPara, "Paragraph Summary"), + (Character.summaryFull, "Full Summary"), + (Character.notes, "Notes"), +]) + +# If true, logs infos while saving and loading. +LOG = False + +def formatMetaData(name, value, tabLength=10): + + # Multiline formatting + if len(value.split("\n")) > 1: + value = "\n".join([" " * (tabLength + 1) + l for l in value.split("\n")])[tabLength + 1:] + + # Avoid empty description (don't know how much MMD loves that) + if name == "": + name = "None" + + # Escapes ":" in name + name = name.replace(":", "_.._") + + return "{name}:{spaces}{value}\n".format( + name=name, + spaces=" " * (tabLength - len(name)), + value=value + ) + + +def slugify(name): + """ + A basic slug function, that escapes all spaces to "_" and all non letters/digits to "-". + @param name: name to slugify (str) + @return: str + """ + valid = string.ascii_letters + string.digits + newName = "" + for c in name: + if c in valid: + newName += c + elif c in string.whitespace: + newName += "_" + else: + newName += "-" + return newName + + +def log(*args): + if LOG: + print(" ".join(str(a) for a in args)) + + +def saveProject(zip=None): + """ + Saves the project. If zip is False, the project is saved as a multitude of plain-text files for the most parts + and some XML or zip? for settings and stuff. + If zip is True, everything is saved as a single zipped file. Easier to carry around, but does not allow + collaborative work, versionning, or third-party editing. + @param zip: if True, saves as a single file. If False, saves as plain-text. If None, tries to determine based on + settings. + @return: Nothing + """ + if zip is None: + zip = False + # FIXME: use value from settings + + log("\n\nSaving to:", "zip" if zip else "folder") + + # List of files to be written + files = [] + # List of files to be removed + removes = [] + # List of files to be moved + moves = [] + + mw = mainWindow() + + if zip: + # File format version + files.append(("VERSION", "1")) + + # General infos (book and author) + # Saved in plain text, in infos.txt + + path = "infos.txt" + content = "" + for name, col in [ + ("Title", 0), + ("Subtitle", 1), + ("Serie", 2), + ("Volume", 3), + ("Genre", 4), + ("License", 5), + ("Author", 6), + ("Email", 7), + ]: + val = mw.mdlFlatData.item(0, col).text().strip() + if val: + content += "{name}:{spaces}{value}\n".format( + name=name, + spaces=" " * (15 - len(name)), + value=val + ) + files.append((path, content)) + + #################################################################################################################### + # Summary + # In plain text, in summary.txt + + path = "summary.txt" + content = "" + for name, col in [ + ("Situation", 0), + ("Sentence", 1), + ("Paragraph", 2), + ("Page", 3), + ("Full", 4), + ]: + val = mw.mdlFlatData.item(1, col).text().strip() + if val: + content += formatMetaData(name, val, 12) + + files.append((path, content)) + + #################################################################################################################### + # Label & Status + # In plain text + + for mdl, path in [ + (mw.mdlStatus, "status.txt"), + (mw.mdlLabels, "labels.txt") + ]: + + content = "" + + # We skip the first row, which is empty and transparent + for i in range(1, mdl.rowCount()): + color = "" + if mdl.data(mdl.index(i, 0), Qt.DecorationRole) is not None: + color = iconColor(mdl.data(mdl.index(i, 0), Qt.DecorationRole)).name(QColor.HexRgb) + color = color if color != "#ff000000" else "#00000000" + + text = mdl.data(mdl.index(i, 0)) + + if text: + content += "{name}{color}\n".format( + name=text, + color="" if color == "" else ":" + " " * (20 - len(text)) + color + ) + + files.append((path, content)) + + #################################################################################################################### + # Characters + # In a character folder + + path = os.path.join("characters", "{name}.txt") + mdl = mw.mdlCharacter + + # Review characters + for c in mdl.characters: + + # Generates file's content + content = "" + for m in characterMap: + val = mdl.data(c.index(m.value)).strip() + if val: + content += formatMetaData(characterMap[m], val, 20) + + # Character's color: + content += formatMetaData("Color", c.color().name(QColor.HexRgb), 20) + + # Character's infos + for info in c.infos: + content += formatMetaData(info.description, info.value, 20) + + # generate file's path + cpath = path.format(name="{ID}-{slugName}".format( + ID=c.ID(), + slugName=slugify(c.name()) + )) + + # Has the character been renamed? + if c.lastPath and cpath != c.lastPath: + moves.append((c.lastPath, cpath)) + + # Update character's path + c.lastPath = cpath + + files.append((cpath, content)) + + #################################################################################################################### + # Texts + # In an outline folder + + mdl = mw.mdlOutline + + # Go through the tree + f, m, r = exportOutlineItem(mdl.rootItem) + files += f + moves += m + removes += r + + # Writes revisions (if asked for) + if settings.revisions["keep"]: + files.append(("revisions.xml", mdl.saveToXML())) + + #################################################################################################################### + # World + # Either in an XML file, or in lots of plain texts? + # More probably text, since there might be writing done in third-party. + + path = "world.opml" + mdl = mw.mdlWorld + + root = ET.Element("opml") + root.attrib["version"] = "1.0" + body = ET.SubElement(root, "body") + addWorldItem(body, mdl) + content = ET.tostring(root, encoding="UTF-8", xml_declaration=True, pretty_print=True) + files.append((path, content)) + + #################################################################################################################### + # Plots (mw.mdlPlots) + # Either in XML or lots of plain texts? + # More probably XML since there is not really a lot if writing to do (third-party) + + path = "plots.xml" + mdl = mw.mdlPlots + + root = ET.Element("root") + addPlotItem(root, mdl) + content = ET.tostring(root, encoding="UTF-8", xml_declaration=True, pretty_print=True) + files.append((path, content)) + + #################################################################################################################### + # Settings + # Saved in readable text (json) for easier versionning. But they mustn't be shared, it seems. + # Maybe include them only if zipped? + # Well, for now, we keep them here... + + files.append(("settings.txt", settings.save(protocol=0))) + + project = mw.currentProject + + #################################################################################################################### + # Save to zip + + if zip: + # project = os.path.join( + # os.path.dirname(project), + # "_" + os.path.basename(project) + # ) + + zf = zipfile.ZipFile(project, mode="w") + + for filename, content in files: + zf.writestr(filename, content, compress_type=compression) + + zf.close() + + #################################################################################################################### + # Save to plain text + + else: + + global cache + + # Project path + dir = os.path.dirname(project) + + # Folder containing file: name of the project file (without .msk extension) + folder = os.path.splitext(os.path.basename(project))[0] + + # Debug + log("\nSaving to folder", folder) + + # If cache is empty (meaning we haven't loaded from disk), we wipe folder, just to be sure. + if not cache: + shutil.rmtree(os.path.join(dir, folder)) + + # Moving files that have been renamed + for old, new in moves: + + # Get full path + oldPath = os.path.join(dir, folder, old) + newPath = os.path.join(dir, folder, new) + + # Move the old file to the new place + try: + os.replace(oldPath, newPath) + log("* Renaming/moving {} to {}".format(old, new)) + except FileNotFoundError: + # Maybe parent folder has been renamed + pass + + # Update cache + cache2 = {} + for f in cache: + f2 = f.replace(old, new) + if f2 != f: + log(" * Updating cache:", f, f2) + cache2[f2] = cache[f] + cache = cache2 + + # Writing files + for path, content in files: + filename = os.path.join(dir, folder, path) + os.makedirs(os.path.dirname(filename), exist_ok=True) + + # Check if content is in cache, and write if necessary + if path not in cache or cache[path] != content: + log("* Writing file {} ({})".format(path, "not in cache" if path not in cache else "different")) + mode = "w" + ("b" if type(content) == bytes else "") + with open(filename, mode) as f: + f.write(content) + cache[path] = content + + # Removing phantoms + for path in [p for p in cache if p not in [p for p, c in files]]: + filename = os.path.join(dir, folder, path) + log("* Removing", path) + + if os.path.isdir(filename): + shutil.rmtree(filename) + + elif path == "VERSION": + # If loading from zip, but saving to path, file VERSION is not needed. + continue + + else: # elif os.path.exists(filename) + os.remove(filename) + + # Clear cache + cache.pop(path, 0) + + # Removing empty directories + for root, dirs, files in os.walk(os.path.join(dir, folder, "outline")): + for dir in dirs: + newDir = os.path.join(root, dir) + try: + os.removedirs(newDir) + log("* Removing empty directory:", newDir) + except: + # Directory not empty, we don't remove. + pass + + # Write the project file's content + with open(project, "w") as f: + f.write("1") # Format number + + +def addWorldItem(root, mdl, parent=QModelIndex()): + """ + Lists elements in a world model and create an OPML xml file. + @param root: an Etree element + @param mdl: a worldModel + @param parent: the parent index in the world model + @return: root, to which sub element have been added + """ + # List every row (every world item) + for x in range(mdl.rowCount(parent)): + + # For each row, create an outline item. + outline = ET.SubElement(root, "outline") + for y in range(mdl.columnCount(parent)): + + val = mdl.data(mdl.index(x, y, parent)) + + if not val: + continue + + for w in World: + if y == w.value: + outline.attrib[w.name] = val + + if mdl.hasChildren(mdl.index(x, y, parent)): + addWorldItem(outline, mdl, mdl.index(x, y, parent)) + + return root + + +def addPlotItem(root, mdl, parent=QModelIndex()): + """ + Lists elements in a plot model and create an xml file. + @param root: an Etree element + @param mdl: a plotModel + @param parent: the parent index in the plot model + @return: root, to which sub element have been added + """ + + # List every row (every plot item) + for x in range(mdl.rowCount(parent)): + + # For each row, create an outline item. + outline = ET.SubElement(root, "plot") + for y in range(mdl.columnCount(parent)): + + index = mdl.index(x, y, parent) + val = mdl.data(index) + # + # if not val: + # continue + + for w in Plot: + if y == w.value and val: + outline.attrib[w.name] = val + + # List characters as attrib + if y == Plot.characters.value: + if mdl.hasChildren(index): + characters = [] + for cX in range(mdl.rowCount(index)): + for cY in range(mdl.columnCount(index)): + cIndex = mdl.index(cX, cY, index) + characters.append(mdl.data(cIndex)) + outline.attrib[Plot.characters.name] = ",".join(characters) + + elif Plot.characters.name in outline.attrib: + outline.attrib.pop(Plot.characters.name) + + # List resolution steps as sub items + elif y == Plot.steps.value: + if mdl.hasChildren(index): + for cX in range(mdl.rowCount(index)): + step = ET.SubElement(outline, "step") + for cY in range(mdl.columnCount(index)): + cIndex = mdl.index(cX, cY, index) + val = mdl.data(cIndex) + + for w in PlotStep: + if cY == w.value: + step.attrib[w.name] = val + + elif Plot.steps.name in outline.attrib: + outline.attrib.pop(Plot.steps.name) + + return root + + +def exportOutlineItem(root): + """ + Takes an outline item, and returns two lists: + 1. of (`filename`, `content`), representing the whole tree of files to be written, in multimarkdown. + 3. of (`filename`, `filename`) listing files to be moved + 2. of `filename`, representing files to be removed. + + @param root: OutlineItem + @return: [(str, str)], [str] + """ + + files = [] + moves = [] + removes = [] + + k = 0 + for child in root.children(): + spath = os.path.join(*outlineItemPath(child)) + + k += 1 + + # Has the item been renamed? + lp = child._lastPath + if lp and spath != lp: + moves.append((lp, spath)) + log(child.title(), "has been renamed (", lp, " → ", spath, ")") + log(" → We mark for moving:", lp) + + # Updates item last's path + child._lastPath = spath + + # Generating content + if child.type() == "folder": + fpath = os.path.join(spath, "folder.txt") + content = outlineToMMD(child) + files.append((fpath, content)) + + elif child.type() in ["txt", "t2t"]: + content = outlineToMMD(child) + files.append((spath, content)) + + elif child.type() in ["html"]: + # Save as html. Not the most beautiful, but hey. + content = outlineToMMD(child) + files.append((spath, content)) + + else: + log("Unknown type") + + f, m, r = exportOutlineItem(child) + files += f + moves += m + removes += r + + return files, moves, removes + + +def outlineItemPath(item): + """ + Returns the outlineItem file path (like the path where it will be written on the disk). As a list of folder's + name. To be joined by os.path.join. + @param item: outlineItem + @return: list of folder's names + """ + # Root item + if not item.parent(): + return ["outline"] + else: + name = "{ID}-{name}{ext}".format( + ID=item.row(), + name=slugify(item.title()), + ext="" if item.type() == "folder" else ".md" # ".{}".format(item.type()) # To have .txt, .t2t, .html, ... + ) + return outlineItemPath(item.parent()) + [name] + + +def outlineToMMD(item): + content = "" + + # We don't want to write some datas (computed) + exclude = [Outline.wordCount, Outline.goal, Outline.goalPercentage, Outline.revisions, Outline.text] + # We want to force some data even if they're empty + force = [Outline.compile] + + for attrib in Outline: + if attrib in exclude: + continue + val = item.data(attrib.value) + if val or attrib in force: + content += formatMetaData(attrib.name, str(val), 15) + + content += "\n\n" + content += item.data(Outline.text.value) + + return content + +######################################################################################################################## +# LOAD +######################################################################################################################## + +def loadProject(project, zip=None): + """ + Loads a project. + @param project: the filename of the project to open. + @param zip: whether the project is a zipped or not. + @return: an array of errors, empty if None. + """ + + mw = mainWindow() + errors = [] + + #################################################################################################################### + # Read and store everything in a dict + + log("\nLoading {} ({})".format(project, "ZIP" if zip else "not zip")) + if zip: + files = loadFilesFromZip(project) + + # Decode files + for f in files: + if f[-4:] not in [".xml", "opml"]: + files[f] = files[f].decode("utf-8") + + else: + # Project path + dir = os.path.dirname(project) + + # Folder containing file: name of the project file (without .msk extension) + folder = os.path.splitext(os.path.basename(project))[0] + + # The full path towards the folder containing files + path = os.path.join(dir, folder, "") + + files = {} + for dirpath, dirnames, filenames in os.walk(path): + p = dirpath.replace(path, "") + for f in filenames: + mode = "r" + ("b" if f[-4:] in [".xml", "opml"] else "") + with open(os.path.join(dirpath, f), mode) as fo: + files[os.path.join(p, f)] = fo.read() + + # Saves to cache (only if we loaded from disk and not zip) + global cache + cache = files + + # FIXME: watch directory for changes + + # Sort files by keys + files = OrderedDict(sorted(files.items())) + + #################################################################################################################### + # Settings + + if "settings.txt" in files: + settings.load(files["settings.txt"], fromString=True, protocol=0) + else: + errors.append("settings.txt") + + #################################################################################################################### + # Labels + + mdl = mw.mdlLabels + mdl.appendRow(QStandardItem("")) # Empty = No labels + if "labels.txt" in files: + log("\nReading labels:") + for s in files["labels.txt"].split("\n"): + if not s: + continue + + m = re.search(r"^(.*?):\s*(.*)$", s) + txt = m.group(1) + col = m.group(2) + log("* Add status: {} ({})".format(txt, col)) + icon = iconFromColorString(col) + mdl.appendRow(QStandardItem(icon, txt)) + + else: + errors.append("labels.txt") + + #################################################################################################################### + # Status + + mdl = mw.mdlStatus + mdl.appendRow(QStandardItem("")) # Empty = No status + if "status.txt" in files: + log("\nReading Status:") + for s in files["status.txt"].split("\n"): + if not s: + continue + log("* Add status:", s) + mdl.appendRow(QStandardItem(s)) + else: + errors.append("status.txt") + + #################################################################################################################### + # Infos + + mdl = mw.mdlFlatData + if "infos.txt" in files: + md, body = parseMMDFile(files["infos.txt"], asDict=True) + + row = [] + for name in ["Title", "Subtitle", "Serie", "Volume", "Genre", "License", "Author", "Email"]: + row.append(QStandardItem(md.get(name, ""))) + + mdl.appendRow(row) + + else: + errors.append("infos.txt") + + #################################################################################################################### + # Summary + + mdl = mw.mdlFlatData + if "summary.txt" in files: + md, body = parseMMDFile(files["summary.txt"], asDict=True) + + row = [] + for name in ["Situation", "Sentence", "Paragraph", "Page", "Full"]: + row.append(QStandardItem(md.get(name, ""))) + + mdl.appendRow(row) + + else: + errors.append("summary.txt") + + #################################################################################################################### + # Plots + + mdl = mw.mdlPlots + if "plots.xml" in files: + log("\nReading plots:") + # xml = bytearray(files["plots.xml"], "utf-8") + root = ET.fromstring(files["plots.xml"]) + + for plot in root: + # Create row + row = getStandardItemRowFromXMLEnum(plot, Plot) + + # Log + log("* Add plot: ", row[0].text()) + + # Characters + if row[Plot.characters.value].text(): + IDs = row[Plot.characters.value].text().split(",") + item = QStandardItem() + for ID in IDs: + item.appendRow(QStandardItem(ID.strip())) + row[Plot.characters.value] = item + + # Subplots + for step in plot: + row[Plot.steps.value].appendRow( + getStandardItemRowFromXMLEnum(step, PlotStep) + ) + + # Add row to the model + mdl.appendRow(row) + + else: + errors.append("plots.xml") + + #################################################################################################################### + # World + + mdl = mw.mdlWorld + if "world.opml" in files: + log("\nReading World:") + # xml = bytearray(files["plots.xml"], "utf-8") + root = ET.fromstring(files["world.opml"]) + body = root.find("body") + + for outline in body: + row = getOutlineItem(outline, World) + mdl.appendRow(row) + + else: + errors.append("world.opml") + + #################################################################################################################### + # Characters + + mdl = mw.mdlCharacter + log("\nReading Characters:") + for f in [f for f in files if "characters" in f]: + md, body = parseMMDFile(files[f]) + c = mdl.addCharacter() + c.lastPath = f + + color = False + for desc, val in md: + + # Base infos + if desc in characterMap.values(): + key = [key for key, value in characterMap.items() if value == desc][0] + index = c.index(key.value) + mdl.setData(index, val) + + # Character color + elif desc == "Color" and not color: + c.setColor(QColor(val)) + # We remember the first time we found "Color": it is the icon color. + # If "Color" comes a second time, it is a Character's info. + color = True + + # Character's infos + else: + c.infos.append(CharacterInfo(c, desc, val)) + + log("* Adds {} ({})".format(c.name(), c.ID())) + + #################################################################################################################### + # Texts + # We read outline form the outline folder. If revisions are saved, then there's also a revisions.xml which contains + # everything, but the outline folder takes precedence (in cases it's been edited outside of manuksript. + + mdl = mw.mdlOutline + log("\nReading outline:") + paths = [f for f in files if "outline" in f] + outline = OrderedDict() + + # We create a structure of imbricated OrderedDict to store the whole tree. + for f in paths: + split = f.split(os.path.sep)[1:] + # log("* ", split) + + last = "" + parent = outline + parentLastPath = "outline" + for i in split: + if last: + parent = parent[last] + parentLastPath = os.path.join(parentLastPath, last) + last = i + + if not i in parent: + # If not last item, then it is a folder + if i != split[-1]: + parent[i] = OrderedDict() + + # If file, we store it + else: + parent[i] = files[f] + + # We store f to add it later as lastPath + parent[i + ":lastPath"] = os.path.join(parentLastPath, i) + + + + # We now just have to recursively add items. + addTextItems(mdl, outline) + + # Adds revisions + if "revisions.xml" in files: + root = ET.fromstring(files["revisions.xml"]) + appendRevisions(mdl, root) + + # Check IDS + mdl.rootItem.checkIDs() + + return errors + + +def addTextItems(mdl, odict, parent=None): + """ + Adds a text / outline items from an OrderedDict. + @param mdl: model to add to + @param odict: OrderedDict + @return: nothing + """ + if parent is None: + parent = mdl.rootItem + + for k in odict: + + # In case k is a folder: + if type(odict[k]) == OrderedDict and "folder.txt" in odict[k]: + + # Adds folder + log("{}* Adds {} to {} (folder)".format(" " * parent.level(), k, parent.title())) + item = outlineFromMMD(odict[k]["folder.txt"], parent=parent) + item._lastPath = odict[k + ":lastPath"] + + # Read content + addTextItems(mdl, odict[k], parent=item) + + # k is not a folder + elif type(odict[k]) == str and k != "folder.txt" and not ":lastPath" in k: + log("{}* Adds {} to {} (file)".format(" " * parent.level(), k, parent.title())) + item = outlineFromMMD(odict[k], parent=parent) + item._lastPath = odict[k + ":lastPath"] + + elif not ":lastPath" in k and k != "folder.txt": + print("* Strange things in file {}".format(k)) + + +def outlineFromMMD(text, parent): + """ + Creates outlineItem from multimarkdown file. + @param text: content of the file + @param parent: appends item to parent (outlineItem) + @return: outlineItem + """ + + item = outlineItem(parent=parent) + md, body = parseMMDFile(text, asDict=True) + + # Store metadata + for k in md: + if k in Outline.__members__: + item.setData(Outline.__members__[k].value, str(md[k])) + + # Store body + item.setData(Outline.text.value, str(body)) + + # FIXME: add lastpath + + return item + + +def appendRevisions(mdl, root): + """ + Parse etree item to find outlineItem's with revisions, and adds them to model `mdl`. + @param mdl: outlineModel + @param root: etree + @return: nothing + """ + for child in root: + # Recursively go through items + if child.tag == "outlineItem": + appendRevisions(mdl, child) + + # Revision found. + elif child.tag == "revision": + # Get root's ID + ID = root.attrib["ID"] + if not ID: + log("* Serious problem: no ID!") + return + + # Find outline item in model + item = mdl.getItemByID(ID) + + # Store revision + log("* Appends revision ({}) to {}".format(child.attrib["timestamp"], item.title())) + item.appendRevision(child.attrib["timestamp"], child.attrib["text"]) + + +def getOutlineItem(item, enum): + """ + Reads outline items from an opml file. Returns a row of QStandardItem, easy to add to a QStandardItemModel. + @param item: etree item + @param enum: enum to read keys from + @return: [QStandardItem] + """ + row = getStandardItemRowFromXMLEnum(item, enum) + log("* Add worldItem:", row[0].text()) + for child in item: + sub = getOutlineItem(child, enum) + row[0].appendRow(sub) + + return row + + +def getStandardItemRowFromXMLEnum(item, enum): + """ + Reads and etree item and creates a row of QStandardItems by cross-referencing an enum. + Returns a list of QStandardItems that can be added to a QStandardItemModel by appendRow. + @param item: the etree item + @param enum: the enum + @return: list of QStandardItems + """ + row = [] + for i in range(len(enum)): + row.append(QStandardItem("")) + + for name in item.attrib: + if name in enum.__members__: + row[enum[name].value] = QStandardItem(item.attrib[name]) + return row + +def parseMMDFile(text, asDict=False): + """ + Takes the content of a MultiMarkDown file (str) and returns: + 1. A list containing metadatas: (description, value) if asDict is False. + If asDict is True, returns metadatas as an OrderedDict. Be aware that if multiple metadatas have the same description + (which is stupid, but hey), they will be lost except the last one. + 2. The body of the file + @param text: the content of the file + @return: (list, str) or (OrderedDict, str) + """ + md = [] + mdd = OrderedDict() + body = [] + descr = "" + val = "" + inBody = False + for s in text.split("\n"): + if not inBody: + m = re.match(r"^(.*?):\s*(.*)$", s) + if m: + # Commit last metadata + if descr: + if descr == "None": + descr = "" + md.append((descr, val)) + mdd[descr] = val + descr = "" + val = "" + + # Store new values + descr = m.group(1) + val = m.group(2) + + elif s[:4] == " ": + val += "\n" + s.strip() + + elif s == "": + # End of metadatas + inBody = True + + # Commit last metadata + if descr: + if descr == "None": + descr = "" + md.append((descr, val)) + mdd[descr] = val + + else: + body.append(s) + + # We remove the second empty line (since we save with two empty lines) + if body and body[0] == "": + body = body[1:] + + body = "\n".join(body) + + if not asDict: + return md, body + else: + return mdd, body + + diff --git a/manuskript/mainWindow.py b/manuskript/mainWindow.py index 9fadcf25..a60f5613 100644 --- a/manuskript/mainWindow.py +++ b/manuskript/mainWindow.py @@ -9,13 +9,11 @@ from PyQt5.QtWidgets import QMainWindow, QHeaderView, qApp, QMenu, QActionGroup, QLabel from manuskript import settings -from manuskript.enums import Perso, Subplot, Plot, World +from manuskript.enums import Character, PlotStep, Plot, World from manuskript.functions import AUC, wordCount, appPath -from manuskript.loadSave import loadStandardItemModelXML, loadFilesFromZip -from manuskript.loadSave import saveFilesToZip -from manuskript.loadSave import saveStandardItemModelXML +from manuskript.loadSave import saveProject, loadProject +from manuskript.models.characterModel import characterModel from manuskript.models.outlineModel import outlineModel -from manuskript.models.persosModel import persosModel from manuskript.models.plotModel import plotModel from manuskript.models.worldModel import worldModel from manuskript.settingsWindow import settingsWindow @@ -24,7 +22,7 @@ from manuskript.ui.compileDialog import compileDialog from manuskript.ui.helpLabel import helpLabel from manuskript.ui.mainWindow import Ui_MainWindow from manuskript.ui.tools.frequencyAnalyzer import frequencyAnalyzer -from manuskript.ui.views.outlineDelegates import outlinePersoDelegate +from manuskript.ui.views.outlineDelegates import outlineCharacterDelegate from manuskript.ui.views.plotDelegate import plotDelegate # Spellcheck support @@ -144,15 +142,18 @@ class MainWindow(QMainWindow, Ui_MainWindow): # PERSOS ############################################################################### - def changeCurrentPerso(self, trash=None): + def changeCurrentCharacter(self, trash=None): + """ - index = self.lstPersos.currentPersoIndex() - - if not index.isValid(): + @return: + """ + c = self.lstCharacters.currentCharacter() + if not c: self.tabPlot.setEnabled(False) return self.tabPersos.setEnabled(True) + index = c.index() for w in [ self.txtPersoName, @@ -169,27 +170,24 @@ class MainWindow(QMainWindow, Ui_MainWindow): w.setCurrentModelIndex(index) # Button color - self.mdlPersos.updatePersoColor(index) + self.updateCharacterColor(c.ID()) - # Perso Infos + # Character Infos self.tblPersoInfos.setRootIndex(index) - if self.mdlPersos.rowCount(index): + if self.mdlCharacter.rowCount(index): self.updatePersoInfoView() def updatePersoInfoView(self): - # Hide columns - for i in range(self.mdlPersos.columnCount()): - self.tblPersoInfos.hideColumn(i) - self.tblPersoInfos.showColumn(Perso.infoName.value) - self.tblPersoInfos.showColumn(Perso.infoData.value) - - self.tblPersoInfos.horizontalHeader().setSectionResizeMode( - Perso.infoName.value, QHeaderView.ResizeToContents) - self.tblPersoInfos.horizontalHeader().setSectionResizeMode( - Perso.infoData.value, QHeaderView.Stretch) + self.tblPersoInfos.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeToContents) + self.tblPersoInfos.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch) self.tblPersoInfos.verticalHeader().hide() + def updateCharacterColor(self, ID): + c = self.mdlCharacter.getCharacterByID(ID) + color = c.color().name() + self.btnPersoColor.setStyleSheet("background:{};".format(color)) + ############################################################################### # PLOTS ############################################################################### @@ -207,8 +205,8 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.txtPlotResult.setCurrentModelIndex(index) self.sldPlotImportance.setCurrentModelIndex(index) self.lstPlotPerso.setRootIndex(index.sibling(index.row(), - Plot.persos.value)) - subplotindex = index.sibling(index.row(), Plot.subplots.value) + Plot.characters.value)) + subplotindex = index.sibling(index.row(), Plot.steps.value) self.lstSubPlots.setRootIndex(subplotindex) if self.mdlPlots.rowCount(subplotindex): self.updateSubPlotView() @@ -224,18 +222,18 @@ class MainWindow(QMainWindow, Ui_MainWindow): # Hide columns for i in range(self.mdlPlots.columnCount()): self.lstSubPlots.hideColumn(i) - self.lstSubPlots.showColumn(Subplot.name.value) - self.lstSubPlots.showColumn(Subplot.meta.value) + self.lstSubPlots.showColumn(PlotStep.name.value) + self.lstSubPlots.showColumn(PlotStep.meta.value) self.lstSubPlots.horizontalHeader().setSectionResizeMode( - Subplot.name.value, QHeaderView.Stretch) + PlotStep.name.value, QHeaderView.Stretch) self.lstSubPlots.horizontalHeader().setSectionResizeMode( - Subplot.meta.value, QHeaderView.ResizeToContents) + PlotStep.meta.value, QHeaderView.ResizeToContents) self.lstSubPlots.verticalHeader().hide() def changeCurrentSubPlot(self, index): # Got segfaults when using textEditView model system, so ad hoc stuff. - index = index.sibling(index.row(), Subplot.summary.value) + index = index.sibling(index.row(), PlotStep.summary.value) item = self.mdlPlots.itemFromIndex(index) if not item: self.txtSubPlotSummary.setEnabled(False) @@ -253,7 +251,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): index = self.lstSubPlots.currentIndex() if not index.isValid(): return - index = index.sibling(index.row(), Subplot.summary.value) + index = index.sibling(index.row(), PlotStep.summary.value) item = self.mdlPlots.itemFromIndex(index) self._updatingSubPlot = True @@ -310,7 +308,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): # Load settings for i in settings.openIndexes: - idx = self.mdlOutline.indexFromPath(i) + idx = self.mdlOutline.getIndexByID(i) self.mainEditor.setCurrentModelIndex(idx, newTab=True) self.generateViewMenu() self.mainEditor.sldCorkSizeFactor.setValue(settings.corkSizeFactor) @@ -340,7 +338,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.saveTimerNoChanges.setSingleShot(True) self.mdlFlatData.dataChanged.connect(self.startTimerNoChanges) self.mdlOutline.dataChanged.connect(self.startTimerNoChanges) - self.mdlPersos.dataChanged.connect(self.startTimerNoChanges) + self.mdlCharacter.dataChanged.connect(self.startTimerNoChanges) self.mdlPlots.dataChanged.connect(self.startTimerNoChanges) self.mdlWorld.dataChanged.connect(self.startTimerNoChanges) # self.mdlPersosInfos.dataChanged.connect(self.startTimerNoChanges) @@ -434,10 +432,10 @@ class MainWindow(QMainWindow, Ui_MainWindow): settings.lastTab = self.tabMain.currentIndex() if self.currentProject: - # Remembering the current items + # Remembering the current items (stores outlineItem's ID) sel = [] for i in range(self.mainEditor.tab.count()): - sel.append(self.mdlOutline.pathToIndex(self.mainEditor.tab.widget(i).currentIndex)) + sel.append(self.mdlOutline.ID(self.mainEditor.tab.widget(i).currentIndex)) settings.openIndexes = sel # Save data from models @@ -462,27 +460,8 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.currentProject = projectName QSettings().setValue("lastProject", projectName) - # Saving - files = [] - - files.append((saveStandardItemModelXML(self.mdlFlatData), - "flatModel.xml")) - files.append((saveStandardItemModelXML(self.mdlPersos), - "perso.xml")) - files.append((saveStandardItemModelXML(self.mdlWorld), - "world.xml")) - files.append((saveStandardItemModelXML(self.mdlLabels), - "labels.xml")) - files.append((saveStandardItemModelXML(self.mdlStatus), - "status.xml")) - files.append((saveStandardItemModelXML(self.mdlPlots), - "plots.xml")) - files.append((self.mdlOutline.saveToXML(), - "outline.xml")) - files.append((settings.save(), - "settings.pickle")) - - saveFilesToZip(files, self.currentProject) + saveProject() # version=0 + self.saveTimerNoChanges.stop() # Giving some feedback print(self.tr("Project {} saved.").format(self.currentProject)) @@ -491,7 +470,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): def loadEmptyDatas(self): self.mdlFlatData = QStandardItemModel(self) - self.mdlPersos = persosModel(self) + self.mdlCharacter = characterModel(self) # self.mdlPersosProxy = persosProxyModel(self) # self.mdlPersosInfos = QStandardItemModel(self) self.mdlLabels = QStandardItemModel(self) @@ -501,56 +480,8 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.mdlWorld = worldModel(self) def loadDatas(self, project): - # Loading - files = loadFilesFromZip(project) - errors = [] - - if "flatModel.xml" in files: - loadStandardItemModelXML(self.mdlFlatData, - files["flatModel.xml"], fromString=True) - else: - errors.append("flatModel.xml") - - if "perso.xml" in files: - loadStandardItemModelXML(self.mdlPersos, - files["perso.xml"], fromString=True) - else: - errors.append("perso.xml") - - if "world.xml" in files: - loadStandardItemModelXML(self.mdlWorld, - files["world.xml"], fromString=True) - else: - errors.append("world.xml") - - if "labels.xml" in files: - loadStandardItemModelXML(self.mdlLabels, - files["labels.xml"], fromString=True) - else: - errors.append("perso.xml") - - if "status.xml" in files: - loadStandardItemModelXML(self.mdlStatus, - files["status.xml"], fromString=True) - else: - errors.append("perso.xml") - - if "plots.xml" in files: - loadStandardItemModelXML(self.mdlPlots, - files["plots.xml"], fromString=True) - else: - errors.append("perso.xml") - - if "outline.xml" in files: - self.mdlOutline.loadFromXML(files["outline.xml"], fromString=True) - else: - errors.append("perso.xml") - - if "settings.pickle" in files: - settings.load(files["settings.pickle"], fromString=True) - else: - errors.append("perso.xml") + errors = loadProject(project) # Giving some feedback if not errors: @@ -570,7 +501,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): def makeUIConnections(self): "Connections that have to be made once only, event when new project is loaded." - self.lstPersos.currentItemChanged.connect(self.changeCurrentPerso, AUC) + self.lstCharacters.currentItemChanged.connect(self.changeCurrentCharacter, AUC) self.txtPlotFilter.textChanged.connect(self.lstPlots.setFilter, AUC) self.lstPlots.currentItemChanged.connect(self.changeCurrentPlot, AUC) @@ -622,29 +553,32 @@ class MainWindow(QMainWindow, Ui_MainWindow): widget.setCurrentModelIndex(self.mdlFlatData.index(0, col)) # Persos - self.lstPersos.setPersosModel(self.mdlPersos) - self.tblPersoInfos.setModel(self.mdlPersos) + self.lstCharacters.setCharactersModel(self.mdlCharacter) + self.tblPersoInfos.setModel(self.mdlCharacter) - self.btnAddPerso.clicked.connect(self.mdlPersos.addPerso, AUC) - self.btnRmPerso.clicked.connect(self.mdlPersos.removePerso, AUC) - self.btnPersoColor.clicked.connect(self.mdlPersos.chosePersoColor, AUC) - - self.btnPersoAddInfo.clicked.connect(self.mdlPersos.addPersoInfo, AUC) - self.btnPersoRmInfo.clicked.connect(self.mdlPersos.removePersoInfo, AUC) + self.btnAddPerso.clicked.connect(self.mdlCharacter.addCharacter, AUC) + try: + self.btnRmPerso.clicked.connect(self.lstCharacters.removeCharacter, AUC) + self.btnPersoColor.clicked.connect(self.lstCharacters.choseCharacterColor, AUC) + self.btnPersoAddInfo.clicked.connect(self.lstCharacters.addCharacterInfo, AUC) + self.btnPersoRmInfo.clicked.connect(self.lstCharacters.removeCharacterInfo, AUC) + except TypeError: + # Connection has already been made + pass for w, c in [ - (self.txtPersoName, Perso.name.value), - (self.sldPersoImportance, Perso.importance.value), - (self.txtPersoMotivation, Perso.motivation.value), - (self.txtPersoGoal, Perso.goal.value), - (self.txtPersoConflict, Perso.conflict.value), - (self.txtPersoEpiphany, Perso.epiphany.value), - (self.txtPersoSummarySentence, Perso.summarySentence.value), - (self.txtPersoSummaryPara, Perso.summaryPara.value), - (self.txtPersoSummaryFull, Perso.summaryFull.value), - (self.txtPersoNotes, Perso.notes.value) + (self.txtPersoName, Character.name.value), + (self.sldPersoImportance, Character.importance.value), + (self.txtPersoMotivation, Character.motivation.value), + (self.txtPersoGoal, Character.goal.value), + (self.txtPersoConflict, Character.conflict.value), + (self.txtPersoEpiphany, Character.epiphany.value), + (self.txtPersoSummarySentence, Character.summarySentence.value), + (self.txtPersoSummaryPara, Character.summaryPara.value), + (self.txtPersoSummaryFull, Character.summaryFull.value), + (self.txtPersoNotes, Character.notes.value) ]: - w.setModel(self.mdlPersos) + w.setModel(self.mdlCharacter) w.setColumn(c) self.tabPersos.setEnabled(False) @@ -672,13 +606,13 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.tabPlot.setEnabled(False) self.mdlPlots.updatePlotPersoButton() - self.mdlPersos.dataChanged.connect(self.mdlPlots.updatePlotPersoButton) + self.mdlCharacter.dataChanged.connect(self.mdlPlots.updatePlotPersoButton) self.lstOutlinePlots.setPlotModel(self.mdlPlots) self.lstOutlinePlots.setShowSubPlot(True) - self.plotPersoDelegate = outlinePersoDelegate(self.mdlPersos, self) - self.lstPlotPerso.setItemDelegate(self.plotPersoDelegate) + self.plotCharacterDelegate = outlineCharacterDelegate(self.mdlCharacter, self) + self.lstPlotPerso.setItemDelegate(self.plotCharacterDelegate) self.plotDelegate = plotDelegate(self) - self.lstSubPlots.setItemDelegateForColumn(Subplot.meta.value, self.plotDelegate) + self.lstSubPlots.setItemDelegateForColumn(PlotStep.meta.value, self.plotDelegate) # World self.treeWorld.setModel(self.mdlWorld) @@ -702,18 +636,18 @@ class MainWindow(QMainWindow, Ui_MainWindow): # Outline self.treeRedacOutline.setModel(self.mdlOutline) - self.treeOutlineOutline.setModelPersos(self.mdlPersos) + self.treeOutlineOutline.setModelCharacters(self.mdlCharacter) self.treeOutlineOutline.setModelLabels(self.mdlLabels) self.treeOutlineOutline.setModelStatus(self.mdlStatus) - self.redacMetadata.setModels(self.mdlOutline, self.mdlPersos, + self.redacMetadata.setModels(self.mdlOutline, self.mdlCharacter, self.mdlLabels, self.mdlStatus) - self.outlineItemEditor.setModels(self.mdlOutline, self.mdlPersos, + self.outlineItemEditor.setModels(self.mdlOutline, self.mdlCharacter, self.mdlLabels, self.mdlStatus) self.treeOutlineOutline.setModel(self.mdlOutline) # self.redacEditor.setModel(self.mdlOutline) - self.storylineView.setModels(self.mdlOutline, self.mdlPersos, self.mdlPlots) + self.storylineView.setModels(self.mdlOutline, self.mdlCharacter, self.mdlPlots) self.treeOutlineOutline.selectionModel().selectionChanged.connect(lambda: self.outlineItemEditor.selectionChanged( @@ -735,12 +669,12 @@ class MainWindow(QMainWindow, Ui_MainWindow): # Debug self.mdlFlatData.setVerticalHeaderLabels(["Infos générales", "Summary"]) self.tblDebugFlatData.setModel(self.mdlFlatData) - self.tblDebugPersos.setModel(self.mdlPersos) - self.tblDebugPersosInfos.setModel(self.mdlPersos) + self.tblDebugPersos.setModel(self.mdlCharacter) + self.tblDebugPersosInfos.setModel(self.mdlCharacter) self.tblDebugPersos.selectionModel().currentChanged.connect( - lambda: self.tblDebugPersosInfos.setRootIndex(self.mdlPersos.index( + lambda: self.tblDebugPersosInfos.setRootIndex(self.mdlCharacter.index( self.tblDebugPersos.selectionModel().currentIndex().row(), - Perso.name.value)), AUC) + Character.name.value)), AUC) self.tblDebugPlots.setModel(self.mdlPlots) self.tblDebugPlotsPersos.setModel(self.mdlPlots) @@ -748,11 +682,11 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.tblDebugPlots.selectionModel().currentChanged.connect( lambda: self.tblDebugPlotsPersos.setRootIndex(self.mdlPlots.index( self.tblDebugPlots.selectionModel().currentIndex().row(), - Plot.persos.value)), AUC) + Plot.characters.value)), AUC) self.tblDebugPlots.selectionModel().currentChanged.connect( lambda: self.tblDebugSubPlots.setRootIndex(self.mdlPlots.index( self.tblDebugPlots.selectionModel().currentIndex().row(), - Plot.subplots.value)), AUC) + Plot.steps.value)), AUC) self.treeDebugWorld.setModel(self.mdlWorld) self.treeDebugOutline.setModel(self.mdlOutline) self.lstDebugLabels.setModel(self.mdlLabels) diff --git a/manuskript/models/characterModel.py b/manuskript/models/characterModel.py new file mode 100644 index 00000000..d6f7016c --- /dev/null +++ b/manuskript/models/characterModel.py @@ -0,0 +1,293 @@ +#!/usr/bin/env python +# --!-- coding: utf8 --!-- +from PyQt5.QtCore import QModelIndex, Qt, QAbstractItemModel, QVariant +from PyQt5.QtGui import QIcon, QPixmap, QColor + +from manuskript.functions import randomColor, iconColor, mainWindow +from manuskript.enums import Character as C + + +class characterModel(QAbstractItemModel): + + def __init__(self, parent): + QAbstractItemModel.__init__(self, parent) + + # CharacterItems are stored in this list + self.characters = [] + +############################################################################### +# QAbstractItemModel subclassed +############################################################################### + + def rowCount(self, parent=QModelIndex()): + if parent.isValid(): + c = parent.internalPointer() + return len(c.infos) + else: + return len(self.characters) + + def columnCount(self, parent=QModelIndex()): + if parent.isValid(): + # Returns characters infos + return 2 + else: + return len(C) + + def data(self, index, role=Qt.DisplayRole): + c = index.internalPointer() + if type(c) == Character: + if role == Qt.DisplayRole: + if index.column() in c._data: + return c._data[index.column()] + else: + return "" + + elif type(c) == CharacterInfo: + if role == Qt.DisplayRole or role == Qt.EditRole: + if index.column() == 0: + return c.description + elif index.column() == 1: + return c.value + + def setData(self, index, value, role=Qt.EditRole): + c = index.internalPointer() + if type(c) == Character: + if role == Qt.EditRole: + # We update only if data is different + if index.column() not in c._data or c._data[index.column()] != value: + c._data[index.column()] = value + self.dataChanged.emit(index, index) + return True + + elif type(c) == CharacterInfo: + if role == Qt.EditRole: + if index.column() == 0: + c.description = value + elif index.column() == 1: + c.value = value + self.dataChanged.emit(index, index) + return True + + return False + + def index(self, row, column, parent=QModelIndex()): + if not parent.isValid(): + return self.createIndex(row, column, self.characters[row]) + + else: + c = parent.internalPointer() + if row < len(c.infos): + return self.createIndex(row, column, c.infos[row]) + else: + return QModelIndex() + + def indexFromItem(self, item, column=0): + if not item: + return QModelIndex() + + row = self.characters.index(item) + col = column + return self.createIndex(row, col, item) + + def parent(self, index): + if not index.isValid(): + return QModelIndex() + + child = index.internalPointer() + + if type(child) == Character: + return QModelIndex() + + elif type(child) == CharacterInfo: + return child.character.index() + + def flags(self, index): + if index.parent().isValid(): + return QAbstractItemModel.flags(self, index) | Qt.ItemIsEditable + else: + return QAbstractItemModel.flags(self, index) + +############################################################################### +# CHARACTER QUERRIES +############################################################################### + + def character(self, row): + return self.characters[row] + + def name(self, row): + return self.character(row).name() + + def icon(self, row): + return self.character(row).icon + + def ID(self, row): + return self.character(row).ID() + + def importance(self, row): + return self.character(row).importance() + +############################################################################### +# MODEL QUERRIES +############################################################################### + + def getCharactersByImportance(self): + """ + Lists characters by importance. + + @return: array of array of ´character´, by importance. + """ + r = [[], [], []] + for c in self.characters: + r[2-int(c.importance())].append(c) + return r + + def getCharacterByID(self, ID): + if ID is not None: + ID = str(ID) + for c in self.characters: + if c.ID() == ID: + return c + return None + +############################################################################### +# ADDING / REMOVING +############################################################################### + + def addCharacter(self): + """ + Creates a new character + @return: the character + """ + c = Character(model=self, name=self.tr("New character")) + self.beginInsertRows(QModelIndex(), len(self.characters), len(self.characters)) + self.characters.append(c) + self.endInsertRows() + return c + + def removeCharacter(self, ID): + """ + Removes character whose ID is ID... + @param ID: the ID of the character to remove + @return: nothing + """ + c = self.getCharacterByID(ID) + self.beginRemoveRows(QModelIndex(), self.characters.index(c), self.characters.index(c)) + self.characters.remove(c) + self.endRemoveRows() + +############################################################################### +# CHARACTER INFOS +############################################################################### + + def headerData(self, section, orientation, role=Qt.DisplayRole): + if role == Qt.DisplayRole and orientation == Qt.Horizontal: + if section == 0: + return self.tr("Name") + elif section == 1: + return self.tr("Value") + else: + return C(section).name + + def addCharacterInfo(self, ID): + c = self.getCharacterByID(ID) + self.beginInsertRows(c.index(), len(c.infos), len(c.infos)) + c.infos.append(CharacterInfo(c, description="Description", value="Value")) + self.endInsertRows() + + mainWindow().updatePersoInfoView() + + def removeCharacterInfo(self, ID): + c = self.getCharacterByID(ID) + + rm = [] + for idx in mainWindow().tblPersoInfos.selectedIndexes(): + if not idx.row() in rm: + rm.append(idx.row()) + + rm.sort() + rm.reverse() + for r in rm: + self.beginRemoveRows(c.index(), r, r) + c.infos.pop(r) + self.endRemoveRows() + +############################################################################### +# CHARACTER +############################################################################### + +class Character(): + def __init__(self, model, name="No name"): + self._model = model + self.lastPath = "" + + self._data = {} + self._data[C.name.value] = name + self.assignUniqueID() + self.assignRandomColor() + self._data[C.importance.value] = "0" + + self.infos = [] + + def name(self): + return self._data[C.name.value] + + def importance(self): + return self._data[C.importance.value] + + def ID(self): + return self._data[C.ID.value] + + def index(self, column=0): + return self._model.indexFromItem(self, column) + + def assignRandomColor(self): + """ + Assigns a random color the the character. + """ + color = randomColor(QColor(Qt.white)) + self.setColor(color) + + def setColor(self, color): + """ + Sets the character's color + @param color: QColor. + """ + px = QPixmap(32, 32) + px.fill(color) + self.icon = QIcon(px) + try: + self._model.dataChanged.emit(self.index(), self.index()) + except: + # If it is the initialisation, won't be able to emit + pass + + def color(self): + """ + Returns character's color in QColor + @return: QColor + """ + return iconColor(self.icon) + + def assignUniqueID(self, parent=QModelIndex()): + """Assigns an unused character ID.""" + vals = [] + for c in self._model.characters: + vals.append(int(c.ID())) + + k = 0 + while k in vals: + k += 1 + + self._data[C.ID.value] = str(k) + + def listInfos(self): + r = [] + for i in self.infos: + r.append((i.description, i.value)) + return r + +class CharacterInfo(): + def __init__(self, character, description="", value=""): + self.description = description + self.value = value + self.character = character \ No newline at end of file diff --git a/manuskript/models/outlineModel.py b/manuskript/models/outlineModel.py index a3450a8b..564c818b 100644 --- a/manuskript/models/outlineModel.py +++ b/manuskript/models/outlineModel.py @@ -18,7 +18,7 @@ from manuskript.enums import Outline from manuskript.functions import mainWindow, toInt, wordCount locale.setlocale(locale.LC_ALL, '') -import time +import time, os class outlineModel(QAbstractItemModel): @@ -27,6 +27,9 @@ class outlineModel(QAbstractItemModel): self.rootItem = outlineItem(self, title="root", ID="0") + # Stores removed item, in order to remove them on disk when saving, depending on the file format. + self.removed = [] + def index(self, row, column, parent): if not self.hasIndex(row, column, parent): @@ -74,9 +77,7 @@ class outlineModel(QAbstractItemModel): in columns ``columns`` (being a list of int).""" return self.rootItem.findItemsContaining(text, columns, mainWindow(), caseSensitive) - def getIndexByID(self, ID): - "Returns the index of item whose ID is ``ID``. If none, returns QModelIndex()." - + def getItemByID(self, ID): def search(item): if item.ID() == ID: return item @@ -86,6 +87,11 @@ class outlineModel(QAbstractItemModel): return r item = search(self.rootItem) + return item + + def getIndexByID(self, ID): + "Returns the index of item whose ID is ``ID``. If none, returns QModelIndex()." + item = self.getItemByID(ID) if not item: return QModelIndex() else: @@ -363,7 +369,8 @@ class outlineModel(QAbstractItemModel): self.beginRemoveRows(parent, row, row + count - 1) for i in range(count): - parentItem.removeChild(row) + item = parentItem.removeChild(row) + self.removed.append(item) self.endRemoveRows() return True @@ -400,19 +407,6 @@ class outlineModel(QAbstractItemModel): self.rootItem = outlineItem(model=self, xml=ET.tostring(root), ID="0") self.rootItem.checkIDs() - def pathToIndex(self, index, path=""): - # FIXME: Use item's ID instead of rows - if not index.isValid(): - return "" - if index.parent().isValid(): - path = self.pathToIndex(index.parent()) - if path: - path = "{},{}".format(path, str(index.row())) - else: - path = str(index.row()) - - return path - def indexFromPath(self, path): path = path.split(",") item = self.rootItem @@ -431,6 +425,8 @@ class outlineItem(): self._model = model self.defaultTextType = None self.IDs = [] # used by root item to store unique IDs + self._lastPath = "" # used by loadSave version_1 to remember which files the items comes from, + # in case it is renamed / removed if title: self._data[Outline.title] = title @@ -603,7 +599,7 @@ class outlineItem(): self.parent().updateWordCount(emit) def row(self): - if self.parent: + if self.parent(): return self.parent().childItems.index(self) def appendChild(self, child): @@ -645,9 +641,15 @@ class outlineItem(): c.emitDataChanged(cols, recursive=True) def removeChild(self, row): - self.childItems.pop(row) + """ + Removes child at position `row` and returns it. + @param row: index (int) of the child to remove. + @return: the removed outlineItem + """ + r = self.childItems.pop(row) # Might be causing segfault when updateWordCount emits dataChanged self.updateWordCount(emit=False) + return r def parent(self): return self._parent @@ -758,6 +760,9 @@ class outlineItem(): revItem.set("text", r[1]) item.append(revItem) + # Saving lastPath + item.set("lastPath", self._lastPath) + for i in self.childItems: item.append(ET.XML(i.toXML())) @@ -773,6 +778,9 @@ class outlineItem(): # else: self.setData(Outline.__members__[k].value, str(root.attrib[k])) + if "lastPath" in root.attrib: + self._lastPath = root.attrib["lastPath"] + for child in root: if child.tag == "outlineItem": item = outlineItem(self._model, xml=ET.tostring(child), parent=self) @@ -854,8 +862,13 @@ class outlineItem(): text = text.lower() if not caseSensitive else text for c in columns: - if c == Outline.POV.value: - searchIn = mainWindow.mdlPersos.getPersoNameByID(self.POV()) + if c == Outline.POV.value and self.POV(): + c = mainWindow.mdlCharacter.getCharacterByID(self.POV()) + if c: + searchIn = c.name() + else: + searchIn = "" + print("Character POV not found:", self.POV()) elif c == Outline.status.value: searchIn = mainWindow.mdlStatus.item(toInt(self.status()), 0).text() diff --git a/manuskript/models/persosModel.py b/manuskript/models/persosModel.py deleted file mode 100644 index bd261911..00000000 --- a/manuskript/models/persosModel.py +++ /dev/null @@ -1,183 +0,0 @@ -#!/usr/bin/env python -# --!-- coding: utf8 --!-- -from PyQt5.QtCore import QModelIndex, Qt -from PyQt5.QtGui import QStandardItemModel, QStandardItem, QColor, QPixmap, QIcon -from PyQt5.QtWidgets import QColorDialog - -from manuskript.enums import Perso -from manuskript.enums import Plot -from manuskript.functions import iconColor -from manuskript.functions import mainWindow -from manuskript.functions import randomColor -from manuskript.functions import toInt - - -class persosModel(QStandardItemModel): - - def __init__(self, parent): - QStandardItemModel.__init__(self, 0, 3, parent) - self.setHorizontalHeaderLabels([i.name for i in Perso]) - self.mw = mainWindow() - # self._proxy = plotsProxyModel() - # self._proxy.setSourceModel(self) - -############################################################################### -# PERSOS QUERRIES -############################################################################### - - def name(self, row): - return self.item(row, Perso.name.value).text() - - def icon(self, row): - return self.item(row, Perso.name.value).icon() - - def ID(self, row): - return self.item(row, Perso.ID.value).text() - - def importance(self, row): - return self.item(row, Perso.importance.value).text() - -############################################################################### -# MODEL QUERRIES -############################################################################### - - def getPersosByImportance(self): - persos = [[], [], []] - for i in range(self.rowCount()): - importance = self.item(i, Perso.importance.value).text() - ID = self.item(i, Perso.ID.value).text() - persos[2-toInt(importance)].append(ID) - return persos - - def getPersoNameByID(self, ID): - index = self.getIndexFromID(ID) - if index.isValid(): - return self.name(index.row()) - return "" - - def getIndexFromID(self, ID): - for i in range(self.rowCount()): - _ID = self.item(i, Perso.ID.value).text() - if _ID == ID or toInt(_ID) == ID: - return self.index(i, 0) - return QModelIndex() - - def getPersoColorByID(self, ID): - idx = self.getIndexFromID(ID) - return self.getPersoColorName(idx) - - def getPersoColorName(self, index): - icon = self.item(index.row()).icon() - return iconColor(icon).name() if icon else "" - - def currentListIndex(self): - i = self.mw.lstPersos.currentIndex() - if i .isValid(): - return i - else: - return None - - def currentPersoIndex(self): - return self.mw.lstPersos.currentPersoIndex() - -############################################################################### -# ADDING / REMOVING -############################################################################### - - def addPerso(self): - """Creates a perso by adding a row in mdlPersos - and a column in mdlPersosInfos with same ID""" - p = QStandardItem(self.tr("New character")) - self.setPersoColor(p, randomColor(QColor(Qt.white))) - - pid = self.getUniqueID() - self.appendRow([p, QStandardItem(pid), QStandardItem("0")]) - - def getUniqueID(self, parent=QModelIndex()): - """Returns an unused perso ID (row 1).""" - vals = [] - for i in range(self.rowCount(parent)): - index = self.index(i, Perso.ID.value, parent) - if index.isValid() and index.data(): - vals.append(int(index.data())) - - k = 0 - while k in vals: - k += 1 - return str(k) - - def removePerso(self): - index = self.currentPersoIndex() - self.takeRow(index.row()) - - def setPersoColor(self, item, color): - px = QPixmap(32, 32) - px.fill(color) - item.setIcon(QIcon(px)) - - def chosePersoColor(self): - idx = self.currentPersoIndex() - item = self.item(idx.row(), Perso.name.value) - if item: - color = iconColor(item.icon()) - else: - color = Qt.white - self.colorDialog = QColorDialog(color, self.mw) - color = self.colorDialog.getColor(color) - if color.isValid(): - self.setPersoColor(item, color) - self.updatePersoColor(idx) - -############################################################################### -# UI -############################################################################### - - def updatePersoColor(self, idx): - # idx = self.currentPersoIndex() - color = self.getPersoColorName(idx) - self.mw.btnPersoColor.setStyleSheet("background:{};".format(color)) - -############################################################################### -# PERSO INFOS -############################################################################### - - def headerData(self, section, orientation, role=Qt.DisplayRole): - if role == Qt.DisplayRole and orientation == Qt.Horizontal: - if section == Perso.infoName.value: - return self.tr("Name") - elif section == Perso.infoData.value: - return self.tr("Value") - else: - return Perso(section).name - else: - return QStandardItemModel.headerData(self, section, orientation, role) - - def addPersoInfo(self): - perso = self.itemFromIndex(self.currentPersoIndex()) - row = perso.rowCount() - perso.setChild(row, Perso.infoName.value, QStandardItem("")) - perso.setChild(row, Perso.infoData.value, QStandardItem("")) - - self.mw.updatePersoInfoView() - - def removePersoInfo(self): - perso = self.itemFromIndex(self.currentPersoIndex()) - - rm = [] - for idx in self.mw.tblPersoInfos.selectedIndexes(): - if not idx.row() in rm: - rm.append(idx.row()) - - rm.sort() - rm.reverse() - for r in rm: - perso.takeRow(r) - - def listPersoInfos(self, index): - infos = [] - for i in range(self.rowCount(index)): - name = self.data(index.child(i, Perso.infoName.value)) - val = self.data(index.child(i, Perso.infoData.value)) - infos.append((name, val)) - - return infos diff --git a/manuskript/models/plotModel.py b/manuskript/models/plotModel.py index 456b8504..5ac706f5 100644 --- a/manuskript/models/plotModel.py +++ b/manuskript/models/plotModel.py @@ -9,7 +9,7 @@ from PyQt5.QtGui import QStandardItemModel from PyQt5.QtWidgets import QAction, QMenu from manuskript.enums import Plot -from manuskript.enums import Subplot +from manuskript.enums import PlotStep from manuskript.functions import toInt, mainWindow @@ -37,7 +37,7 @@ class plotModel(QStandardItemModel): index = self.getIndexFromID(ID) if not index.isValid(): return - index = index.sibling(index.row(), Plot.subplots.value) + index = index.sibling(index.row(), Plot.steps.value) item = self.itemFromIndex(index) lst = [] for i in range(item.rowCount()): @@ -86,8 +86,8 @@ class plotModel(QStandardItemModel): p = QStandardItem(self.tr("New plot")) _id = QStandardItem(self.getUniqueID()) importance = QStandardItem(str(0)) - self.appendRow([p, _id, importance, QStandardItem("Persos"), - QStandardItem(), QStandardItem(), QStandardItem("Subplots")]) + self.appendRow([p, _id, importance, QStandardItem("Characters"), + QStandardItem(), QStandardItem(), QStandardItem("Resolution steps")]) def getUniqueID(self, parent=QModelIndex()): """Returns an unused ID""" @@ -114,9 +114,9 @@ class plotModel(QStandardItemModel): def headerData(self, section, orientation, role=Qt.DisplayRole): if role == Qt.DisplayRole: if orientation == Qt.Horizontal: - if section == Subplot.name.value: + if section == PlotStep.name.value: return self.tr("Name") - elif section == Subplot.meta.value: + elif section == PlotStep.meta.value: return self.tr("Meta") else: return "" @@ -127,8 +127,8 @@ class plotModel(QStandardItemModel): def data(self, index, role=Qt.DisplayRole): if index.parent().isValid() and \ - index.parent().column() == Plot.subplots.value and \ - index.column() == Subplot.meta.value: + index.parent().column() == Plot.steps.value and \ + index.column() == PlotStep.meta.value: if role == Qt.TextAlignmentRole: return Qt.AlignRight | Qt.AlignVCenter elif role == Qt.ForegroundRole: @@ -144,13 +144,13 @@ class plotModel(QStandardItemModel): if not index.isValid(): return - parent = index.sibling(index.row(), Plot.subplots.value) - parentItem = self.item(index.row(), Plot.subplots.value) + parent = index.sibling(index.row(), Plot.steps.value) + parentItem = self.item(index.row(), Plot.steps.value) if not parentItem: return - p = QStandardItem(self.tr("New subplot")) + p = QStandardItem(self.tr("New step")) _id = QStandardItem(self.getUniqueID(parent)) summary = QStandardItem() @@ -182,10 +182,10 @@ class plotModel(QStandardItemModel): def addPlotPerso(self, v): index = self.mw.lstPlots.currentPlotIndex() if index.isValid(): - if not self.item(index.row(), Plot.persos.value): - self.setItem(index.row(), Plot.persos.value, QStandardItem()) + if not self.item(index.row(), Plot.characters.value): + self.setItem(index.row(), Plot.characters.value, QStandardItem()) - item = self.item(index.row(), Plot.persos.value) + item = self.item(index.row(), Plot.characters.value) # We check that the PersoID is not in the list yet for i in range(item.rowCount()): @@ -212,13 +212,13 @@ class plotModel(QStandardItemModel): menu.addMenu(m) mpr = QSignalMapper(menu) - for i in range(self.mw.mdlPersos.rowCount()): - a = QAction(self.mw.mdlPersos.name(i), menu) - a.setIcon(self.mw.mdlPersos.icon(i)) + for i in range(self.mw.mdlCharacter.rowCount()): + a = QAction(self.mw.mdlCharacter.name(i), menu) + a.setIcon(self.mw.mdlCharacter.icon(i)) a.triggered.connect(mpr.map) - mpr.setMapping(a, int(self.mw.mdlPersos.ID(i))) + mpr.setMapping(a, int(self.mw.mdlCharacter.ID(i))) - imp = toInt(self.mw.mdlPersos.importance(i)) + imp = toInt(self.mw.mdlCharacter.importance(i)) menus[2 - imp].addAction(a) diff --git a/manuskript/models/references.py b/manuskript/models/references.py index 6ee37ba7..a8a86cb7 100644 --- a/manuskript/models/references.py +++ b/manuskript/models/references.py @@ -11,9 +11,9 @@ import re from PyQt5.QtWidgets import qApp from manuskript.enums import Outline -from manuskript.enums import Perso +from manuskript.enums import Character from manuskript.enums import Plot -from manuskript.enums import Subplot +from manuskript.enums import PlotStep from manuskript.functions import mainWindow RegEx = r"{(\w):(\d+):?.*?}" @@ -22,7 +22,7 @@ RegExNonCapturing = r"{\w:\d+:?.*?}" # The basic format of the references EmptyRef = "{{{}:{}:{}}}" EmptyRefSearchable = "{{{}:{}:" -PersoLetter = "C" +CharacterLetter = "C" TextLetter = "T" PlotLetter = "P" WorldLetter = "W" @@ -37,13 +37,13 @@ def plotReference(ID, searchable=False): return EmptyRefSearchable.format(PlotLetter, ID, "") -def persoReference(ID, searchable=False): +def characterReference(ID, searchable=False): """Takes the ID of a character and returns a reference for that character. @searchable: returns a stripped version that allows simple text search.""" if not searchable: - return EmptyRef.format(PersoLetter, ID, "") + return EmptyRef.format(CharacterLetter, ID, "") else: - return EmptyRefSearchable.format(PersoLetter, ID, "") + return EmptyRefSearchable.format(CharacterLetter, ID, "") def textReference(ID, searchable=False): @@ -103,8 +103,8 @@ def infos(ref): POV = "" if item.POV(): POV = "{text}".format( - ref=persoReference(item.POV()), - text=mainWindow().mdlPersos.getPersoNameByID(item.POV())) + ref=characterReference(item.POV()), + text=mainWindow().mdlCharacter.getCharacterByID(item.POV()).name()) # The status of the scene status = item.status() @@ -174,10 +174,12 @@ def infos(ref): return text # A character - elif _type == PersoLetter: - m = mainWindow().mdlPersos - index = m.getIndexFromID(_ref) - name = m.name(index.row()) + elif _type == CharacterLetter: + m = mainWindow().mdlCharacter + c = m.getCharacterByID(int(_ref)) + index = c.index() + + name = c.name() # Titles basicTitle = qApp.translate("references", "Basic infos") @@ -191,14 +193,16 @@ def infos(ref): # basic infos basic = [] for i in [ - (Perso.motivation, qApp.translate("references", "Motivation"), False), - (Perso.goal, qApp.translate("references", "Goal"), False), - (Perso.conflict, qApp.translate("references", "Conflict"), False), - (Perso.epiphany, qApp.translate("references", "Epiphany"), False), - (Perso.summarySentence, qApp.translate("references", "Short summary"), True), - (Perso.summaryPara, qApp.translate("references", "Longer summary"), True), + (Character.motivation, qApp.translate("references", "Motivation"), False), + (Character.goal, qApp.translate("references", "Goal"), False), + (Character.conflict, qApp.translate("references", "Conflict"), False), + (Character.epiphany, qApp.translate("references", "Epiphany"), False), + (Character.summarySentence, qApp.translate("references", "Short summary"), True), + (Character.summaryPara, qApp.translate("references", "Longer summary"), True), ]: + val = m.data(index.sibling(index.row(), i[0].value)) + if val: basic.append("{title}:{n}{val}".format( title=i[1], @@ -208,7 +212,7 @@ def infos(ref): # detailed infos detailed = [] - for _name, _val in m.listPersoInfos(index): + for _name, _val in c.listInfos(): detailed.append("{}: {}".format( _name, _val)) @@ -272,24 +276,24 @@ def infos(ref): Plot.result.value)) # Characters - pM = mainWindow().mdlPersos - item = m.item(index.row(), Plot.persos.value) + pM = mainWindow().mdlCharacter + item = m.item(index.row(), Plot.characters.value) characters = "" if item: for r in range(item.rowCount()): ID = item.child(r, 0).text() characters += "
  • {text}".format( - link=persoReference(ID), + link=characterReference(ID), text=pM.getPersoNameByID(ID)) # Resolution steps steps = "" - item = m.item(index.row(), Plot.subplots.value) + item = m.item(index.row(), Plot.steps.value) if item: for r in range(item.rowCount()): - title = item.child(r, Subplot.name.value).text() - summary = item.child(r, Subplot.summary.value).text() - meta = item.child(r, Subplot.meta.value).text() + title = item.child(r, PlotStep.name.value).text() + summary = item.child(r, PlotStep.summary.value).text() + meta = item.child(r, PlotStep.meta.value).text() if meta: meta = " ({})".format(meta) steps += "
  • {title}{summary}{meta}
  • ".format( @@ -408,15 +412,16 @@ def shortInfos(ref): infos["path"] = item.path() return infos - elif _type == PersoLetter: + elif _type == CharacterLetter: - infos["type"] = PersoLetter + infos["type"] = CharacterLetter - m = mainWindow().mdlPersos - item = m.item(int(_ref), Perso.name.value) - if item: - infos["title"] = item.text() - infos["name"] = item.text() + m = mainWindow().mdlCharacter + c = m.getCharacterByID(_ref) + + if c: + infos["title"] = c.name() + infos["name"] = c.name() return infos elif _type == PlotLetter: @@ -482,7 +487,7 @@ def tooltip(ref): tt += "
    {}".format(infos["path"]) return tt - elif infos["type"] == PersoLetter: + elif infos["type"] == CharacterLetter: return qApp.translate("references", "Character: {}").format(infos["title"]) elif infos["type"] == PlotLetter: @@ -515,9 +520,9 @@ def refToLink(ref): item = idx.internalPointer() text = item.title() - elif _type == PersoLetter: - m = mainWindow().mdlPersos - text = m.item(int(_ref), Perso.name.value).text() + elif _type == CharacterLetter: + m = mainWindow().mdlCharacter + text = m.getCharacterByID(int(_ref)).name() elif _type == PlotLetter: m = mainWindow().mdlPlots @@ -618,16 +623,16 @@ def open(ref): _type = match.group(1) _ref = match.group(2) - if _type == PersoLetter: + if _type == CharacterLetter: mw = mainWindow() - item = mw.lstPersos.getItemByID(_ref) + item = mw.lstCharacters.getItemByID(int(_ref)) if item: mw.tabMain.setCurrentIndex(mw.TabPersos) - mw.lstPersos.setCurrentItem(item) + mw.lstCharacters.setCurrentItem(item) return True - print("Ref not found") + print("Error: Ref {} not found".format(ref)) return False elif _type == TextLetter: diff --git a/manuskript/settings.py b/manuskript/settings.py index 4f342f3a..fc6065f0 100644 --- a/manuskript/settings.py +++ b/manuskript/settings.py @@ -1,12 +1,16 @@ # -*- coding: utf-8 -*- import collections +import json import pickle from PyQt5.QtWidgets import qApp from manuskript.enums import Outline +# TODO: move some/all of those settings to application settings and not project settings +# in order to allow a shared project between several writers + viewSettings = { "Tree": { "Icon": "Nothing", @@ -28,7 +32,8 @@ viewSettings = { "Background": "Nothing", }, } - + +# Application spellcheck = False dict = None corkSizeFactor = 100 @@ -81,7 +86,7 @@ frequencyAnalyzer = { "phraseMax": 5 } -def save(filename=None): +def save(filename=None, protocol=None): global spellcheck, dict, corkSliderFactor, viewSettings, corkSizeFactor, folderView, lastTab, openIndexes, \ autoSave, autoSaveDelay, saveOnQuit, autoSaveNoChanges, autoSaveNoChangesDelay, outlineViewColumns, \ @@ -107,7 +112,7 @@ def save(filename=None): "textEditor":textEditor, "revisions":revisions, "frequencyAnalyzer": frequencyAnalyzer - } + } #pp=pprint.PrettyPrinter(indent=4, compact=False) #print("Saving:") @@ -117,9 +122,16 @@ def save(filename=None): f = open(filename, "wb") pickle.dump(allSettings, f) else: - return pickle.dumps(allSettings) - -def load(string, fromString=False): + if protocol == 0: + # This looks stupid + # But a simple json.dumps with sort_keys will throw a TypeError + # because of unorderable types. + return json.dumps(json.loads(json.dumps(allSettings)), indent=4, sort_keys=True) + else: + return pickle.dumps(allSettings) + + +def load(string, fromString=False, protocol=None): """Load settings from 'string'. 'string' is the filename of the pickle dump. If fromString=True, string is the data of the pickle dumps.""" global allSettings @@ -133,8 +145,11 @@ def load(string, fromString=False): print("{} doesn't exist, cannot load settings.".format(string)) return else: - allSettings = pickle.loads(string) - + if protocol == 0: + allSettings = json.loads(string) + else: + allSettings = pickle.loads(string) + #pp=pprint.PrettyPrinter(indent=4, compact=False) #print("Loading:") #pp.pprint(allSettings) @@ -211,6 +226,17 @@ def load(string, fromString=False): global revisions revisions = allSettings["revisions"] + # With JSON we had to convert int keys to str, and None to "null", so we roll back. + r = {} + for i in revisions["rules"]: + if i == "null": + r[None] = revisions["rules"]["null"] + continue + elif i == None: + continue + r[int(i)] = revisions["rules"][i] + revisions["rules"] = r + if "frequencyAnalyzer" in allSettings: global frequencyAnalyzer frequencyAnalyzer = allSettings["frequencyAnalyzer"] diff --git a/manuskript/ui/cheatSheet.py b/manuskript/ui/cheatSheet.py index fc445ab7..2e3a4401 100644 --- a/manuskript/ui/cheatSheet.py +++ b/manuskript/ui/cheatSheet.py @@ -4,7 +4,7 @@ from PyQt5.QtCore import pyqtSignal, Qt, QTimer, QRect from PyQt5.QtGui import QBrush, QCursor, QPalette, QFontMetrics from PyQt5.QtWidgets import QWidget, QListWidgetItem, QToolTip, QStyledItemDelegate, QStyle -from manuskript.enums import Perso +from manuskript.enums import Character from manuskript.enums import Plot from manuskript.functions import lightBlue from manuskript.functions import mainWindow @@ -36,7 +36,7 @@ class cheatSheet(QWidget, Ui_cheatSheet): self.line.hide() self.outlineModel = None - self.persoModel = None + self.characterModel = None self.plotModel = None self.worldModel = None @@ -53,12 +53,14 @@ class cheatSheet(QWidget, Ui_cheatSheet): def setModels(self): mw = mainWindow() self.outlineModel = mw.mdlOutline - self.persoModel = mw.mdlPersos + self.characterModel = mw.mdlCharacter self.plotModel = mw.mdlPlots self.worldModel = mw.mdlWorld self.outlineModel.dataChanged.connect(self.populateTimer.start) - self.persoModel.dataChanged.connect(self.populateTimer.start) + self.characterModel.dataChanged.connect(self.populateTimer.start) + self.characterModel.rowsInserted.connect(self.populateTimer.start) + self.characterModel.rowsRemoved.connect(self.populateTimer.start) self.plotModel.dataChanged.connect(self.populateTimer.start) self.worldModel.dataChanged.connect(self.populateTimer.start) @@ -75,17 +77,14 @@ class cheatSheet(QWidget, Ui_cheatSheet): self.list.hide() def populate(self): - if self.persoModel: + if self.characterModel: d = [] - for r in range(self.persoModel.rowCount()): - name = self.persoModel.item(r, Perso.name.value).text() - ID = self.persoModel.item(r, Perso.ID.value).text() - imp = self.persoModel.item(r, Perso.importance.value).text() - imp = [self.tr("Minor"), self.tr("Secondary"), self.tr("Main")][int(imp)] - d.append((name, ID, imp)) + for c in self.characterModel.characters: + imp = [self.tr("Minor"), self.tr("Secondary"), self.tr("Main")][int(c.importance())] + d.append((c.name(), c.ID(), imp)) - self.data[(self.tr("Characters"), Ref.PersoLetter)] = d + self.data[(self.tr("Characters"), Ref.CharacterLetter)] = d if self.outlineModel: d = [] diff --git a/manuskript/ui/editors/basicHighlighter.py b/manuskript/ui/editors/basicHighlighter.py index b5055060..f0d5050b 100644 --- a/manuskript/ui/editors/basicHighlighter.py +++ b/manuskript/ui/editors/basicHighlighter.py @@ -61,7 +61,7 @@ class basicHighlighter(QSyntaxHighlighter): fmt.setFontWeight(QFont.DemiBold) if txt.group(1) == Ref.TextLetter: fmt.setBackground(QBrush(QColor(Qt.blue).lighter(190))) - elif txt.group(1) == Ref.PersoLetter: + elif txt.group(1) == Ref.CharacterLetter: fmt.setBackground(QBrush(QColor(Qt.yellow).lighter(170))) elif txt.group(1) == Ref.PlotLetter: fmt.setBackground(QBrush(QColor(Qt.red).lighter(170))) diff --git a/manuskript/ui/editors/editorWidget.py b/manuskript/ui/editors/editorWidget.py index 0451377f..20487b38 100644 --- a/manuskript/ui/editors/editorWidget.py +++ b/manuskript/ui/editors/editorWidget.py @@ -173,7 +173,7 @@ class editorWidget(QWidget, Ui_editorWidget_ui): elif item and item.isFolder() and self.folderView == "outline": self.stack.setCurrentIndex(3) - self.outlineView.setModelPersos(mainWindow().mdlPersos) + self.outlineView.setModelCharacters(mainWindow().mdlCharacter) self.outlineView.setModelLabels(mainWindow().mdlLabels) self.outlineView.setModelStatus(mainWindow().mdlStatus) self.outlineView.setModel(self.mw.mdlOutline) diff --git a/manuskript/ui/mainWindow.py b/manuskript/ui/mainWindow.py index 793c2d8e..ed9ab078 100644 --- a/manuskript/ui/mainWindow.py +++ b/manuskript/ui/mainWindow.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'manuskript/ui/mainWindow.ui' # -# Created: Wed Mar 2 00:30:17 2016 +# Created: Thu Mar 3 18:52:22 2016 # by: PyQt5 UI code generator 5.2.1 # # WARNING! All changes made in this file will be lost! @@ -337,12 +337,12 @@ class Ui_MainWindow(object): self.groupBox.setObjectName("groupBox") self.verticalLayout_8 = QtWidgets.QVBoxLayout(self.groupBox) self.verticalLayout_8.setObjectName("verticalLayout_8") - self.lstPersos = persoTreeView(self.groupBox) - self.lstPersos.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - self.lstPersos.setDragEnabled(True) - self.lstPersos.setObjectName("lstPersos") - self.lstPersos.headerItem().setText(0, "1") - self.verticalLayout_8.addWidget(self.lstPersos) + self.lstCharacters = characterTreeView(self.groupBox) + self.lstCharacters.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + self.lstCharacters.setDragEnabled(True) + self.lstCharacters.setObjectName("lstCharacters") + self.lstCharacters.headerItem().setText(0, "1") + self.verticalLayout_8.addWidget(self.lstCharacters) self.horizontalLayout_14 = QtWidgets.QHBoxLayout() self.horizontalLayout_14.setObjectName("horizontalLayout_14") self.btnAddPerso = QtWidgets.QPushButton(self.groupBox) @@ -1166,7 +1166,7 @@ class Ui_MainWindow(object): self.retranslateUi(MainWindow) self.stack.setCurrentIndex(1) - self.tabMain.setCurrentIndex(6) + self.tabMain.setCurrentIndex(2) self.tabSummary.setCurrentIndex(0) self.tabPersos.setCurrentIndex(0) self.tabPlot.setCurrentIndex(0) @@ -1303,18 +1303,18 @@ class Ui_MainWindow(object): self.actCompile.setShortcut(_translate("MainWindow", "F6")) self.actToolFrequency.setText(_translate("MainWindow", "&Frequency Analyzer")) -from manuskript.ui.views.storylineView import storylineView -from manuskript.ui.views.textEditView import textEditView -from manuskript.ui.views.lineEditView import lineEditView -from manuskript.ui.views.treeView import treeView -from manuskript.ui.editors.mainEditor import mainEditor -from manuskript.ui.views.basicItemView import basicItemView -from manuskript.ui.views.persoTreeView import persoTreeView -from manuskript.ui.views.plotTreeView import plotTreeView from manuskript.ui.views.outlineView import outlineView -from manuskript.ui.views.metadataView import metadataView +from manuskript.ui.views.textEditView import textEditView +from manuskript.ui.views.basicItemView import basicItemView +from manuskript.ui.views.plotTreeView import plotTreeView from manuskript.ui.cheatSheet import cheatSheet -from manuskript.ui.views.textEditCompleter import textEditCompleter -from manuskript.ui.sldImportance import sldImportance -from manuskript.ui.welcome import welcome +from manuskript.ui.views.sldImportance import sldImportance +from manuskript.ui.views.metadataView import metadataView +from manuskript.ui.views.characterTreeView import characterTreeView +from manuskript.ui.editors.mainEditor import mainEditor from manuskript.ui.search import search +from manuskript.ui.views.lineEditView import lineEditView +from manuskript.ui.welcome import welcome +from manuskript.ui.views.treeView import treeView +from manuskript.ui.views.textEditCompleter import textEditCompleter +from manuskript.ui.views.storylineView import storylineView diff --git a/manuskript/ui/mainWindow.ui b/manuskript/ui/mainWindow.ui index 1a5390d1..9f9e7c6e 100644 --- a/manuskript/ui/mainWindow.ui +++ b/manuskript/ui/mainWindow.ui @@ -124,7 +124,7 @@ QTabWidget::Rounded - 6 + 2 true @@ -714,7 +714,7 @@ - + Qt::ScrollBarAlwaysOff @@ -2358,7 +2358,7 @@ QListView::item:hover { sldImportance QWidget -
    manuskript.ui.sldImportance.h
    +
    manuskript.ui.views.sldImportance.h
    1
    @@ -2396,9 +2396,9 @@ QListView::item:hover { 1 - persoTreeView + characterTreeView QTreeWidget -
    manuskript.ui.views.persoTreeView.h
    +
    manuskript.ui.views.characterTreeView.h
    cheatSheet diff --git a/manuskript/ui/views/basicItemView.py b/manuskript/ui/views/basicItemView.py index 2da5fe96..42dc1cb3 100644 --- a/manuskript/ui/views/basicItemView.py +++ b/manuskript/ui/views/basicItemView.py @@ -14,8 +14,8 @@ class basicItemView(QWidget, Ui_basicItemView): self.txtSummaryFull.setColumn(Outline.summaryFull.value) self.txtGoal.setColumn(Outline.setGoal.value) - def setModels(self, mdlOutline, mdlPersos, mdlLabels, mdlStatus): - self.cmbPOV.setModels(mdlPersos, mdlOutline) + def setModels(self, mdlOutline, mdlCharacter, mdlLabels, mdlStatus): + self.cmbPOV.setModels(mdlCharacter, mdlOutline) self.txtSummarySentence.setModel(mdlOutline) self.txtSummaryFull.setModel(mdlOutline) self.txtGoal.setModel(mdlOutline) diff --git a/manuskript/ui/views/basicItemView_ui.py b/manuskript/ui/views/basicItemView_ui.py index 6be70e81..f0594fad 100644 --- a/manuskript/ui/views/basicItemView_ui.py +++ b/manuskript/ui/views/basicItemView_ui.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'manuskript/ui/views/basicItemView_ui.ui' # -# Created: Wed Mar 2 00:33:34 2016 +# Created: Thu Mar 3 17:26:11 2016 # by: PyQt5 UI code generator 5.2.1 # # WARNING! All changes made in this file will be lost! @@ -24,7 +24,7 @@ class Ui_basicItemView(object): self.lblPlanPOV.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.lblPlanPOV.setObjectName("lblPlanPOV") self.horizontalLayout_11.addWidget(self.lblPlanPOV) - self.cmbPOV = cmbOutlinePersoChoser(basicItemView) + self.cmbPOV = cmbOutlineCharacterChoser(basicItemView) self.cmbPOV.setFrame(False) self.cmbPOV.setObjectName("cmbPOV") self.horizontalLayout_11.addWidget(self.cmbPOV) @@ -67,6 +67,6 @@ class Ui_basicItemView(object): self.txtSummarySentence.setPlaceholderText(_translate("basicItemView", "One line summary")) self.label_9.setText(_translate("basicItemView", "Few sentences summary:")) -from manuskript.ui.views.cmbOutlinePersoChoser import cmbOutlinePersoChoser -from manuskript.ui.views.textEditView import textEditView +from manuskript.ui.views.cmbOutlineCharacterChoser import cmbOutlineCharacterChoser from manuskript.ui.views.lineEditView import lineEditView +from manuskript.ui.views.textEditView import textEditView diff --git a/manuskript/ui/views/basicItemView_ui.ui b/manuskript/ui/views/basicItemView_ui.ui index 64552858..6466cc73 100644 --- a/manuskript/ui/views/basicItemView_ui.ui +++ b/manuskript/ui/views/basicItemView_ui.ui @@ -43,7 +43,7 @@
    - + false @@ -112,9 +112,9 @@
    manuskript.ui.views.textEditView.h
    - cmbOutlinePersoChoser + cmbOutlineCharacterChoser QComboBox -
    manuskript.ui.views.cmbOutlinePersoChoser.h
    +
    manuskript.ui.views.cmbOutlineCharacterChoser.h
    lineEditView diff --git a/manuskript/ui/views/persoTreeView.py b/manuskript/ui/views/characterTreeView.py similarity index 61% rename from manuskript/ui/views/persoTreeView.py rename to manuskript/ui/views/characterTreeView.py index fd60d192..2bfbe55d 100644 --- a/manuskript/ui/views/persoTreeView.py +++ b/manuskript/ui/views/characterTreeView.py @@ -2,12 +2,16 @@ # --!-- coding: utf8 --!-- from PyQt5.QtCore import QSize, QModelIndex, Qt from PyQt5.QtGui import QPixmap, QColor, QIcon, QBrush -from PyQt5.QtWidgets import QTreeWidget, QTreeWidgetItem +from PyQt5.QtWidgets import QTreeWidget, QTreeWidgetItem, QColorDialog -from manuskript.enums import Perso +from manuskript.enums import Character +from manuskript.functions import iconColor, mainWindow -class persoTreeView(QTreeWidget): +class characterTreeView(QTreeWidget): + """ + A QTreeWidget that displays characters from a characterModel in respect of their importance. + """ def __init__(self, parent=None): QTreeWidget.__init__(self, parent) self._model = None @@ -24,7 +28,7 @@ class persoTreeView(QTreeWidget): self._rootItem = QTreeWidgetItem() self.insertTopLevelItem(0, self._rootItem) - def setPersosModel(self, model): + def setCharactersModel(self, model): self._model = model self._model.dataChanged.connect(self.updateMaybe) self._model.rowsInserted.connect(self.updateMaybe2) @@ -39,11 +43,11 @@ class persoTreeView(QTreeWidget): if topLeft.parent() != QModelIndex(): return - if topLeft.column() <= Perso.name.value <= bottomRight.column(): + if topLeft.column() <= Character.name.value <= bottomRight.column(): # Update name self.updateNames() - elif topLeft.column() <= Perso.importance.value <= bottomRight.column(): + elif topLeft.column() <= Character.importance.value <= bottomRight.column(): # Importance changed self.updateItems() @@ -56,16 +60,17 @@ class persoTreeView(QTreeWidget): for i in range(self.topLevelItemCount()): item = self.topLevelItem(i) - for c in range(item.childCount()): - sub = item.child(c) + for child in range(item.childCount()): + sub = item.child(child) ID = sub.data(0, Qt.UserRole) - if ID: + if ID is not None: # Update name - name = self._model.getPersoNameByID(ID) + c = self._model.getCharacterByID(ID) + name = c.name() sub.setText(0, name) # Update icon px = QPixmap(32, 32) - color = QColor(self._model.getPersoColorByID(ID)) + color = c.color() px.fill(color) sub.setIcon(0, QIcon(px)) @@ -78,10 +83,12 @@ class persoTreeView(QTreeWidget): self._updating = True self.clear() - persos = self._model.getPersosByImportance() + characters = self._model.getCharactersByImportance() h = [self.tr("Main"), self.tr("Secondary"), self.tr("Minor")] + for i in range(3): + # Create category item cat = QTreeWidgetItem(self, [h[i]]) cat.setBackground(0, QBrush(QColor(Qt.blue).lighter(190))) cat.setForeground(0, QBrush(Qt.darkBlue)) @@ -92,23 +99,68 @@ class persoTreeView(QTreeWidget): self.addTopLevelItem(cat) # cat.setChildIndicatorPolicy(cat.DontShowIndicator) - for ID in persos[i]: - name = self._model.getPersoNameByID(ID) + for c in characters[i]: + name = c.name() + # Check if name passes filter if not self._filter.lower() in name.lower(): continue + item = QTreeWidgetItem(cat, [name]) - item.setData(0, Qt.UserRole, ID) + item.setData(0, Qt.UserRole, c.ID()) px = QPixmap(32, 32) - color = QColor(self._model.getPersoColorByID(ID)) + color = QColor(c.color()) px.fill(color) item.setIcon(0, QIcon(px)) - if ID == self._lastID: + if c.ID() == self._lastID: self.setCurrentItem(item) self.expandAll() self._updating = False + def removeCharacter(self): + """ + Removes selected character. + """ + ID = self.currentCharacterID() + if ID: + self._model.removeCharacter(ID) + + def choseCharacterColor(self): + ID = self.currentCharacterID() + c = self._model.getCharacterByID(ID) + if c: + color = iconColor(c.icon) + else: + color = Qt.white + self.colorDialog = QColorDialog(color, mainWindow()) + color = self.colorDialog.getColor(color) + if color.isValid(): + c.setColor(color) + mainWindow().updateCharacterColor(ID) + + def addCharacterInfo(self): + self._model.addCharacterInfo(self.currentCharacterID()) + + def removeCharacterInfo(self): + self._model.removeCharacterInfo(self.currentCharacterID(), + ) + + def currentCharacterID(self): + ID = None + if self.currentItem(): + ID = self.currentItem().data(0, Qt.UserRole) + + return ID + + def currentCharacter(self): + """ + Returns the selected character + @return: Character + """ + ID = self.currentCharacterID() + return self._model.getCharacterByID(ID) + def getItemByID(self, ID): for t in range(self.topLevelItemCount()): for i in range(self.topLevelItem(t).childCount()): @@ -116,13 +168,6 @@ class persoTreeView(QTreeWidget): if item.data(0, Qt.UserRole) == ID: return item - def currentPersoIndex(self): - ID = None - if self.currentItem(): - ID = self.currentItem().data(0, Qt.UserRole) - - return self._model.getIndexFromID(ID) - def mouseDoubleClickEvent(self, event): item = self.currentItem() # Catching double clicks to forbid collapsing of toplevel items diff --git a/manuskript/ui/views/cmbOutlinePersoChoser.py b/manuskript/ui/views/cmbOutlineCharacterChoser.py similarity index 86% rename from manuskript/ui/views/cmbOutlinePersoChoser.py rename to manuskript/ui/views/cmbOutlineCharacterChoser.py index 6747b510..4185aca9 100644 --- a/manuskript/ui/views/cmbOutlinePersoChoser.py +++ b/manuskript/ui/views/cmbOutlineCharacterChoser.py @@ -8,7 +8,7 @@ from manuskript.enums import Outline from manuskript.functions import toInt -class cmbOutlinePersoChoser(QComboBox): +class cmbOutlineCharacterChoser(QComboBox): def __init__(self, parent=None): QComboBox.__init__(self, parent) self.activated[int].connect(self.submit) @@ -18,9 +18,12 @@ class cmbOutlinePersoChoser(QComboBox): self._updating = False self._various = False - def setModels(self, mdlPersos, mdlOutline): - self.mdlPersos = mdlPersos - self.mdlPersos.dataChanged.connect(self.updateItems) + def setModels(self, mdlCharacter, mdlOutline): + self.mdlCharacters = mdlCharacter + self.mdlCharacters.dataChanged.connect(self.updateItems) + self.mdlCharacters.rowsInserted.connect(self.updateItems) + self.mdlCharacters.rowsRemoved.connect(self.updateItems) + self.mdlOutline = mdlOutline self.mdlOutline.dataChanged.connect(self.update) self.updateItems() @@ -37,14 +40,14 @@ class cmbOutlinePersoChoser(QComboBox): self.setItemData(self.count() - 1, QBrush(QColor(Qt.blue).lighter(190)), Qt.BackgroundRole) item = self.model().item(self.count() - 1) item.setFlags(Qt.ItemIsEnabled) - for i in range(self.mdlPersos.rowCount()): - imp = toInt(self.mdlPersos.importance(i)) + for i in range(self.mdlCharacters.rowCount()): + imp = toInt(self.mdlCharacters.importance(i)) if not 2 - imp == importance: continue - self.addItem(self.mdlPersos.icon(i), self.mdlPersos.name(i), self.mdlPersos.ID(i)) - self.setItemData(self.count() - 1, self.mdlPersos.name(i), Qt.ToolTipRole) + self.addItem(self.mdlCharacters.icon(i), self.mdlCharacters.name(i), self.mdlCharacters.ID(i)) + self.setItemData(self.count() - 1, self.mdlCharacters.name(i), Qt.ToolTipRole) self._various = False diff --git a/manuskript/ui/views/metadataView.py b/manuskript/ui/views/metadataView.py index 4791f5b1..0f06d6eb 100644 --- a/manuskript/ui/views/metadataView.py +++ b/manuskript/ui/views/metadataView.py @@ -16,8 +16,8 @@ class metadataView(QWidget, Ui_metadataView): self.txtNotes.setColumn(Outline.notes.value) self.revisions.setEnabled(False) - def setModels(self, mdlOutline, mdlPersos, mdlLabels, mdlStatus): - self.properties.setModels(mdlOutline, mdlPersos, mdlLabels, mdlStatus) + def setModels(self, mdlOutline, mdlCharacter, mdlLabels, mdlStatus): + self.properties.setModels(mdlOutline, mdlCharacter, mdlLabels, mdlStatus) self.txtSummarySentence.setModel(mdlOutline) self.txtSummaryFull.setModel(mdlOutline) self.txtNotes.setModel(mdlOutline) diff --git a/manuskript/ui/views/outlineBasics.py b/manuskript/ui/views/outlineBasics.py index 4d548f60..1a40dac3 100644 --- a/manuskript/ui/views/outlineBasics.py +++ b/manuskript/ui/views/outlineBasics.py @@ -83,12 +83,12 @@ class outlineBasics(QAbstractItemView): self.menuPOV.addMenu(m) mpr = QSignalMapper(self.menuPOV) - for i in range(mw.mdlPersos.rowCount()): - a = QAction(mw.mdlPersos.icon(i), mw.mdlPersos.name(i), self.menuPOV) + for i in range(mw.mdlCharacter.rowCount()): + a = QAction(mw.mdlCharacter.icon(i), mw.mdlCharacter.name(i), self.menuPOV) a.triggered.connect(mpr.map) - mpr.setMapping(a, int(mw.mdlPersos.ID(i))) + mpr.setMapping(a, int(mw.mdlCharacter.ID(i))) - imp = toInt(mw.mdlPersos.importance(i)) + imp = toInt(mw.mdlCharacter.importance(i)) menus[2 - imp].addAction(a) diff --git a/manuskript/ui/views/outlineDelegates.py b/manuskript/ui/views/outlineDelegates.py index 1dcd0c57..d091070f 100644 --- a/manuskript/ui/views/outlineDelegates.py +++ b/manuskript/ui/views/outlineDelegates.py @@ -6,7 +6,7 @@ from PyQt5.QtWidgets import QStyledItemDelegate, QStyleOptionViewItem, QStyle, Q from PyQt5.QtWidgets import qApp from manuskript import settings -from manuskript.enums import Perso, Outline +from manuskript.enums import Character, Outline from manuskript.functions import outlineItemColors, mixColors, colorifyPixmap, toInt, toFloat, drawProgress @@ -92,18 +92,18 @@ class outlineTitleDelegate(QStyledItemDelegate): # QStyledItemDelegate.paint(self, painter, option, index) -class outlinePersoDelegate(QStyledItemDelegate): - def __init__(self, mdlPersos, parent=None): +class outlineCharacterDelegate(QStyledItemDelegate): + def __init__(self, mdlCharacter, parent=None): QStyledItemDelegate.__init__(self, parent) - self.mdlPersos = mdlPersos + self.mdlCharacter = mdlCharacter def sizeHint(self, option, index): # s = QStyledItemDelegate.sizeHint(self, option, index) item = QModelIndex() - for i in range(self.mdlPersos.rowCount()): - if self.mdlPersos.ID(i) == index.data(): - item = self.mdlPersos.index(i, Perso.name.value) + character = self.mdlCharacter.getCharacterByID(index.data()) + if character: + item = character.index(Character.name.value) opt = QStyleOptionViewItem(option) self.initStyleOption(opt, item) @@ -136,13 +136,13 @@ class outlinePersoDelegate(QStyledItemDelegate): editor.setItemData(editor.count() - 1, QBrush(QColor(Qt.blue).lighter(190)), Qt.BackgroundRole) item = editor.model().item(editor.count() - 1) item.setFlags(Qt.ItemIsEnabled) - for i in range(self.mdlPersos.rowCount()): - imp = toInt(self.mdlPersos.importance(i)) + for i in range(self.mdlCharacter.rowCount()): + imp = toInt(self.mdlCharacter.importance(i)) if not 2 - imp == importance: continue # try: - editor.addItem(self.mdlPersos.icon(i), self.mdlPersos.name(i), self.mdlPersos.ID(i)) - editor.setItemData(editor.count() - 1, self.mdlPersos.name(i), Qt.ToolTipRole) + editor.addItem(self.mdlCharacter.icon(i), self.mdlCharacter.name(i), self.mdlCharacter.ID(i)) + editor.setItemData(editor.count() - 1, self.mdlCharacter.name(i), Qt.ToolTipRole) # except: # pass @@ -158,18 +158,21 @@ class outlinePersoDelegate(QStyledItemDelegate): # QStyledItemDelegate.paint(self, painter, option, index) ##option.rect.setWidth(option.rect.width() + 18) - item = QModelIndex() - for i in range(self.mdlPersos.rowCount()): - if self.mdlPersos.ID(i) == index.data(): - item = self.mdlPersos.index(i, Perso.name.value) + itemIndex = QModelIndex() + character = self.mdlCharacter.getCharacterByID(index.data()) + if character: + itemIndex = character.index(Character.name.value) + else: + # Character ID not found in character model. + return opt = QStyleOptionViewItem(option) - self.initStyleOption(opt, item) + self.initStyleOption(opt, itemIndex) qApp.style().drawControl(QStyle.CE_ItemViewItem, opt, painter) # if index.isValid() and index.internalPointer().data(Outline.POV.value) not in ["", None]: - if index.isValid() and self.mdlPersos.data(index) not in ["", None]: + if itemIndex.isValid() and self.mdlCharacter.data(itemIndex) not in ["", None]: opt = QStyleOptionComboBox() opt.rect = option.rect r = qApp.style().subControlRect(QStyle.CC_ComboBox, opt, QStyle.SC_ComboBoxArrow) diff --git a/manuskript/ui/views/outlineView.py b/manuskript/ui/views/outlineView.py index 848a8246..50e44ecc 100644 --- a/manuskript/ui/views/outlineView.py +++ b/manuskript/ui/views/outlineView.py @@ -6,25 +6,25 @@ from manuskript import settings from manuskript.enums import Outline from manuskript.ui.views.dndView import dndView from manuskript.ui.views.outlineBasics import outlineBasics -from manuskript.ui.views.outlineDelegates import outlineTitleDelegate, outlinePersoDelegate, outlineCompileDelegate, \ +from manuskript.ui.views.outlineDelegates import outlineTitleDelegate, outlineCharacterDelegate, outlineCompileDelegate, \ outlineStatusDelegate, outlineGoalPercentageDelegate, outlineLabelDelegate class outlineView(QTreeView, dndView, outlineBasics): - def __init__(self, parent=None, modelPersos=None, modelLabels=None, modelStatus=None): + def __init__(self, parent=None, modelCharacters=None, modelLabels=None, modelStatus=None): QTreeView.__init__(self, parent) dndView.__init__(self) outlineBasics.__init__(self, parent) - self.modelPersos = modelPersos + self.modelCharacters = modelCharacters self.modelLabels = modelLabels self.modelStatus = modelStatus self.header().setStretchLastSection(False) - def setModelPersos(self, model): - # This is used by outlinePersoDelegate to select character - self.modelPersos = model + def setModelCharacters(self, model): + # This is used by outlineCharacterDelegate to select character + self.modelCharacters = model def setModelLabels(self, model): # This is used by outlineLabelDelegate to display labels @@ -41,8 +41,8 @@ class outlineView(QTreeView, dndView, outlineBasics): self.outlineTitleDelegate = outlineTitleDelegate(self) # self.outlineTitleDelegate.setView(self) self.setItemDelegateForColumn(Outline.title.value, self.outlineTitleDelegate) - self.outlinePersoDelegate = outlinePersoDelegate(self.modelPersos) - self.setItemDelegateForColumn(Outline.POV.value, self.outlinePersoDelegate) + self.outlineCharacterDelegate = outlineCharacterDelegate(self.modelCharacters) + self.setItemDelegateForColumn(Outline.POV.value, self.outlineCharacterDelegate) self.outlineCompileDelegate = outlineCompileDelegate() self.setItemDelegateForColumn(Outline.compile.value, self.outlineCompileDelegate) self.outlineStatusDelegate = outlineStatusDelegate(self.modelStatus) diff --git a/manuskript/ui/views/propertiesView.py b/manuskript/ui/views/propertiesView.py index 8cdedda3..ce18cf0c 100644 --- a/manuskript/ui/views/propertiesView.py +++ b/manuskript/ui/views/propertiesView.py @@ -12,8 +12,8 @@ class propertiesView(QWidget, Ui_propertiesView): self.setupUi(self) self.txtGoal.setColumn(Outline.setGoal.value) - def setModels(self, mdlOutline, mdlPersos, mdlLabels, mdlStatus): - self.cmbPOV.setModels(mdlPersos, mdlOutline) + def setModels(self, mdlOutline, mdlCharacter, mdlLabels, mdlStatus): + self.cmbPOV.setModels(mdlCharacter, mdlOutline) self.cmbLabel.setModels(mdlLabels, mdlOutline) self.cmbStatus.setModels(mdlStatus, mdlOutline) self.cmbType.setModel(mdlOutline) diff --git a/manuskript/ui/views/propertiesView_ui.py b/manuskript/ui/views/propertiesView_ui.py index 0d9a56e2..217b3ab5 100644 --- a/manuskript/ui/views/propertiesView_ui.py +++ b/manuskript/ui/views/propertiesView_ui.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'manuskript/ui/views/propertiesView_ui.ui' # -# Created: Wed Mar 2 00:30:18 2016 +# Created: Thu Mar 3 17:26:11 2016 # by: PyQt5 UI code generator 5.2.1 # # WARNING! All changes made in this file will be lost! @@ -37,7 +37,7 @@ class Ui_propertiesView(object): self.lblPOV = QtWidgets.QLabel(self.page) self.lblPOV.setObjectName("lblPOV") self.formLayout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.lblPOV) - self.cmbPOV = cmbOutlinePersoChoser(self.page) + self.cmbPOV = cmbOutlineCharacterChoser(self.page) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -116,7 +116,7 @@ class Ui_propertiesView(object): self.lblPOV_2 = QtWidgets.QLabel(self.page_2) self.lblPOV_2.setObjectName("lblPOV_2") self.formLayout_2.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.lblPOV_2) - self.cmbPOVMulti = cmbOutlinePersoChoser(self.page_2) + self.cmbPOVMulti = cmbOutlineCharacterChoser(self.page_2) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -195,9 +195,9 @@ class Ui_propertiesView(object): self.label_36.setText(_translate("propertiesView", "Goal")) self.txtGoalMulti.setPlaceholderText(_translate("propertiesView", "Word count")) -from manuskript.ui.views.cmbOutlineStatusChoser import cmbOutlineStatusChoser from manuskript.ui.views.lineEditView import lineEditView -from manuskript.ui.views.chkOutlineCompile import chkOutlineCompile -from manuskript.ui.views.cmbOutlineLabelChoser import cmbOutlineLabelChoser +from manuskript.ui.views.cmbOutlineCharacterChoser import cmbOutlineCharacterChoser from manuskript.ui.views.cmbOutlineTypeChoser import cmbOutlineTypeChoser -from manuskript.ui.views.cmbOutlinePersoChoser import cmbOutlinePersoChoser +from manuskript.ui.views.chkOutlineCompile import chkOutlineCompile +from manuskript.ui.views.cmbOutlineStatusChoser import cmbOutlineStatusChoser +from manuskript.ui.views.cmbOutlineLabelChoser import cmbOutlineLabelChoser diff --git a/manuskript/ui/views/propertiesView_ui.ui b/manuskript/ui/views/propertiesView_ui.ui index 0c342b84..d76b439d 100644 --- a/manuskript/ui/views/propertiesView_ui.ui +++ b/manuskript/ui/views/propertiesView_ui.ui @@ -53,7 +53,7 @@
    - + 0 @@ -190,7 +190,7 @@ - + 0 @@ -300,9 +300,9 @@
    manuskript.ui.views.lineEditView.h
    - cmbOutlinePersoChoser + cmbOutlineCharacterChoser QComboBox -
    manuskript.ui.views.cmbOutlinePersoChoser.h
    +
    manuskript.ui.views.cmbOutlineCharacterChoser.h
    cmbOutlineStatusChoser diff --git a/manuskript/ui/sldImportance.py b/manuskript/ui/views/sldImportance.py similarity index 97% rename from manuskript/ui/sldImportance.py rename to manuskript/ui/views/sldImportance.py index ad239a8d..bd303d1e 100644 --- a/manuskript/ui/sldImportance.py +++ b/manuskript/ui/views/sldImportance.py @@ -2,9 +2,9 @@ # --!-- coding: utf8 --!-- from PyQt5.QtCore import pyqtSignal, pyqtProperty from PyQt5.QtWidgets import QWidget +from manuskript.ui.views.sldImportance_ui import Ui_sldImportance from manuskript.functions import toInt -from manuskript.ui.sldImportance_ui import Ui_sldImportance class sldImportance(QWidget, Ui_sldImportance): diff --git a/manuskript/ui/sldImportance_ui.py b/manuskript/ui/views/sldImportance_ui.py similarity index 89% rename from manuskript/ui/sldImportance_ui.py rename to manuskript/ui/views/sldImportance_ui.py index f7e3c0b2..639bbdc7 100644 --- a/manuskript/ui/sldImportance_ui.py +++ b/manuskript/ui/views/sldImportance_ui.py @@ -1,12 +1,13 @@ # -*- coding: utf-8 -*- -# Form implementation generated from reading ui file 'manuskript/ui/sldImportance_ui.ui' +# Form implementation generated from reading ui file 'manuskript/ui/views/sldImportance_ui.ui' # -# Created by: PyQt5 UI code generator 5.4.1 +# Created: Thu Mar 3 18:52:22 2016 +# by: PyQt5 UI code generator 5.2.1 # # WARNING! All changes made in this file will be lost! -from PyQt5 import QtCore, QtWidgets +from PyQt5 import QtCore, QtGui, QtWidgets class Ui_sldImportance(object): def setupUi(self, sldImportance): diff --git a/manuskript/ui/sldImportance_ui.ui b/manuskript/ui/views/sldImportance_ui.ui similarity index 100% rename from manuskript/ui/sldImportance_ui.ui rename to manuskript/ui/views/sldImportance_ui.ui diff --git a/manuskript/ui/views/storylineView.py b/manuskript/ui/views/storylineView.py index a5f81362..e10fc81e 100644 --- a/manuskript/ui/views/storylineView.py +++ b/manuskript/ui/views/storylineView.py @@ -46,7 +46,7 @@ class storylineView(QWidget, Ui_storylineView): self.btnSettings.setMenu(m) - def setModels(self, mdlOutline, mdlPersos, mdlPlots): + def setModels(self, mdlOutline, mdlCharacter, mdlPlots): self._mdlPlots = mdlPlots # self._mdlPlots.dataChanged.connect(self.refresh) # self._mdlPlots.rowsInserted.connect(self.refresh) @@ -54,8 +54,8 @@ class storylineView(QWidget, Ui_storylineView): self._mdlOutline = mdlOutline self._mdlOutline.dataChanged.connect(self.reloadTimer.start) - self._mdlPersos = mdlPersos - self._mdlPersos.dataChanged.connect(self.reloadTimer.start) + self._mdlCharacter = mdlCharacter + self._mdlCharacter.dataChanged.connect(self.reloadTimer.start) def plotReferences(self): "Returns a list of plot references" @@ -71,22 +71,22 @@ class storylineView(QWidget, Ui_storylineView): return r - def persosReferences(self): + def charactersReferences(self): "Returns a list of character references" - if not self._mdlPersos: + if not self._mdlCharacter: pass - IDs = self._mdlPersos.getPersosByImportance() + chars = self._mdlCharacter.getCharactersByImportance() r = [] - for importance in IDs: - for ID in importance: - ref = references.persoReference(ID) + for importance in chars: + for c in importance: + ref = references.characterReference(c.ID()) r.append(ref) return r def refresh(self): - if not self._mdlPlots or not self._mdlOutline or not self._mdlPersos: + if not self._mdlPlots or not self._mdlOutline or not self._mdlCharacter: pass LINE_HEIGHT = 18 @@ -118,7 +118,7 @@ class storylineView(QWidget, Ui_storylineView): trackedItems += self.plotReferences() if self.actCharacters.isChecked(): - trackedItems += self.persosReferences() + trackedItems += self.charactersReferences() ROWS_HEIGHT = len(trackedItems) * (LINE_HEIGHT + SPACING ) @@ -185,7 +185,7 @@ class storylineView(QWidget, Ui_storylineView): # Tests if POV scenePOV = False # Will hold true of character is POV of the current text, not containing folder - if references.type(ref) == references.PersoLetter: + if references.type(ref) == references.CharacterLetter: ID = references.ID(ref) c = child while c: @@ -221,8 +221,8 @@ class storylineView(QWidget, Ui_storylineView): itemsRect.setPos(0, MAX_LEVEL * LEVEL_HEIGHT + SPACING) for ref in trackedItems: - if references.type(ref) == references.PersoLetter: - color = QColor(self._mdlPersos.getPersoColorByID(references.ID(ref))) + if references.type(ref) == references.CharacterLetter: + color = self._mdlCharacter.getCharacterByID(references.ID(ref)).color() else: color = randomColor() diff --git a/manuskript/ui/welcome.py b/manuskript/ui/welcome.py index 137ceb22..e1248dd1 100644 --- a/manuskript/ui/welcome.py +++ b/manuskript/ui/welcome.py @@ -12,9 +12,9 @@ from PyQt5.QtWidgets import QWidget, QAction, QFileDialog, QSpinBox, QLineEdit, from manuskript import settings from manuskript.enums import Outline from manuskript.functions import mainWindow, iconFromColor, appPath +from manuskript.models.characterModel import characterModel from manuskript.models.outlineModel import outlineItem from manuskript.models.outlineModel import outlineModel -from manuskript.models.persosModel import persosModel from manuskript.models.plotModel import plotModel from manuskript.models.worldModel import worldModel from manuskript.ui.welcome_ui import Ui_welcome @@ -345,7 +345,7 @@ class welcome(QWidget, Ui_welcome): # Persos # self.mw.mdlPersos = QStandardItemModel(0, 0, self.mw) - self.mw.mdlPersos = persosModel(self.mw) + self.mw.mdlCharacter = characterModel(self.mw) # self.mdlPersosProxy = None # persosProxyModel() # None # self.mw.mdlPersosProxy = persosProxyModel(self.mw)