From 1f85ee617196ff45a31bf8d2c60c866cbc3d82c8 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Fri, 4 Mar 2016 21:57:38 +0100 Subject: [PATCH] Working on a more flexible loading/saving system --- manuskript/functions.py | 4 + manuskript/loadSave.py | 159 ++----------------- manuskript/load_save/__init__.py | 0 manuskript/load_save/version_0.py | 249 ++++++++++++++++++++++++++++++ manuskript/load_save/version_1.py | 81 ++++++++++ manuskript/mainWindow.py | 76 +-------- 6 files changed, 352 insertions(+), 217 deletions(-) create mode 100644 manuskript/load_save/__init__.py create mode 100644 manuskript/load_save/version_0.py create mode 100644 manuskript/load_save/version_1.py diff --git a/manuskript/functions.py b/manuskript/functions.py index 69799f7..fa8cffd 100644 --- a/manuskript/functions.py +++ b/manuskript/functions.py @@ -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 38c45bb..1476b7d 100644 --- a/manuskript/loadSave.py +++ b/manuskript/loadSave.py @@ -1,156 +1,27 @@ #!/usr/bin/env python # --!-- coding: utf8 --!-- -import zipfile +# 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 -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 +def saveProject(version=None): -try: - import zlib # Used with zipfile for compression - - compression = zipfile.ZIP_DEFLATED -except: - compression = zipfile.ZIP_STORED - - -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) + if version == 0: + v0.saveProject() else: - return ET.tostring(root, encoding="UTF-8", xml_declaration=True, pretty_print=True) + v1.saveProject() -def saveItem(root, mdl, parent=QModelIndex()): - for x in range(mdl.rowCount(parent)): - row = ET.SubElement(root, "row") - row.attrib["row"] = str(x) +def loadProject(project): - 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)) + # Detect version + # FIXME + version = 0 - -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 + if version == 0: + v0.loadProject(project) 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)) + v1.loadProject(project) diff --git a/manuskript/load_save/__init__.py b/manuskript/load_save/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/manuskript/load_save/version_0.py b/manuskript/load_save/version_0.py new file mode 100644 index 0000000..4023539 --- /dev/null +++ b/manuskript/load_save/version_0.py @@ -0,0 +1,249 @@ +#!/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 + +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")) + 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: + loadStandardItemModelXML(mw.mdlCharacter, + files["perso.xml"], fromString=True) + 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("perso.xml") + + if "status.xml" in files: + loadStandardItemModelXML(mw.mdlStatus, + files["status.xml"], fromString=True) + else: + errors.append("perso.xml") + + if "plots.xml" in files: + loadStandardItemModelXML(mw.mdlPlots, + files["plots.xml"], fromString=True) + else: + errors.append("perso.xml") + + if "outline.xml" in files: + mw.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") + + return errors + + +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 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)) \ No newline at end of file diff --git a/manuskript/load_save/version_1.py b/manuskript/load_save/version_1.py new file mode 100644 index 0000000..2760ea8 --- /dev/null +++ b/manuskript/load_save/version_1.py @@ -0,0 +1,81 @@ +#!/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 zipfile + +from manuskript import settings +from manuskript.functions import mainWindow + +try: + import zlib # Used with zipfile for compression + + compression = zipfile.ZIP_DEFLATED +except: + compression = zipfile.ZIP_STORED + + +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 + + + files = [] + mw = mainWindow() + + # files.append((saveStandardItemModelXML(mw.mdlFlatData), + # "flatModel.xml")) + # # files.append((saveStandardItemModelXML(self.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")) + + files.append(("blabla", "test/machin.txt")) + files.append(("youpi", "encore/truc.txt")) + + project = mw.currentProject + + project = os.path.join( + os.path.dirname(project), + "_" + os.path.basename(project) + ) + + zf = zipfile.ZipFile(project, mode="w") + + for content, filename in files: + zf.writestr(filename, content, compress_type=compression) + + zf.close() + + +def loadProject(project): + """ + Loads a project. + @param project: the filename of the project to open. + @return: an array of errors, empty if None. + """ + pass \ No newline at end of file diff --git a/manuskript/mainWindow.py b/manuskript/mainWindow.py index 5c6511c..b7fea96 100644 --- a/manuskript/mainWindow.py +++ b/manuskript/mainWindow.py @@ -11,9 +11,7 @@ from PyQt5.QtWidgets import QMainWindow, QHeaderView, qApp, QMenu, QActionGroup, from manuskript import settings from manuskript.enums import Character, Subplot, 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.plotModel import plotModel @@ -462,27 +460,7 @@ 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.mdlCharacter), - # "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) # Giving some feedback print(self.tr("Project {} saved.").format(self.currentProject)) @@ -501,56 +479,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.mdlCharacter, - 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: