Working on a more flexible loading/saving system

This commit is contained in:
Olivier Keshavjee 2016-03-04 21:57:38 +01:00
parent aece6ca87f
commit 1f85ee6171
6 changed files with 352 additions and 217 deletions

View file

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

View file

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

View file

View file

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

View file

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

View file

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