manuskript/manuskript/ui/welcome.py
Curtis Gedak 8abb274c9f Fixes: Contents missing when non-single file project saved with Save as
See issue #128.

With the "Save to one single file" setting **disabled** (accessible
via "Edit -> Settings" under the General tab), then using
"File -> Save as..." creates only a portion of the previous project
files and content.

Steps to Reproduce:

1.  Open or create project with data (characters, plots, outline, etc.)
2.  Ensure existing project is saved with "File -> Save".
3.  Choose "File -> Save as..."
4.  Enter a new filename and click Save button.
5.  Choose "File -> Close Project"
6.  Choose "File -> Open" and choose the newly created project filename.

    Note that most of the project contents are missing or empty
    (plots, outline, etc.).

This enhancement disables smart caching for the first save of the new
project filename so that all project files are saved under the new
filename.
2017-07-04 12:42:27 -06:00

441 lines
16 KiB
Python

#!/usr/bin/env python
# --!-- coding: utf8 --!--
import locale
import imp
import os
from PyQt5.QtCore import QSettings, QRegExp, Qt, QDir
from PyQt5.QtGui import QIcon, QBrush, QColor, QStandardItemModel, QStandardItem
from PyQt5.QtWidgets import QWidget, QAction, QFileDialog, QSpinBox, QLineEdit, QLabel, QPushButton, QTreeWidgetItem, \
qApp, QMessageBox
from manuskript import loadSave
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.plotModel import plotModel
from manuskript.models.worldModel import worldModel
from manuskript.ui.welcome_ui import Ui_welcome
locale.setlocale(locale.LC_ALL, '')
class welcome(QWidget, Ui_welcome):
def __init__(self, parent=None):
QWidget.__init__(self, parent)
self.setupUi(self)
self.template = []
self.mw = mainWindow()
self.btnOpen.clicked.connect(self.openFile)
self.btnCreate.clicked.connect(self.createFile)
self.chkLoadLastProject.toggled.connect(self.setAutoLoad)
self.tree.itemClicked.connect(self.changeTemplate)
self.btnAddLevel.clicked.connect(self.templateAddLevel)
self.btnAddWC.clicked.connect(self.templateAddWordCount)
self.btnCreateText = self.btnCreate.text()
self.populateTemplates()
def updateValues(self):
# Auto load
autoLoad, last = self.getAutoLoadValues()
self.chkLoadLastProject.setChecked(autoLoad)
# Recent Files
self.loadRecents()
###############################################################################
# AUTOLOAD
###############################################################################
def showEvent(self, event):
"""Waiting for things to be fully loaded to start opening projects."""
QWidget.showEvent(self, event)
# Auto load last project
autoLoad, last = self.getAutoLoadValues()
if autoLoad and last:
self.mw.loadProject(last)
def getAutoLoadValues(self):
sttgns = QSettings()
if sttgns.contains("autoLoad"):
autoLoad = True if sttgns.value("autoLoad") in ["true", True] else False
else:
autoLoad = False
if autoLoad and sttgns.contains("lastProject"):
last = sttgns.value("lastProject")
else:
last = ""
return autoLoad, last
def setAutoLoad(self, v):
QSettings().setValue("autoLoad", v)
###############################################################################
# RECENTS
###############################################################################
def loadRecents(self):
sttgns = QSettings()
self.mw.menuRecents.setIcon(QIcon.fromTheme("folder-recent"))
if sttgns.contains("recentFiles"):
lst = sttgns.value("recentFiles")
self.mw.menuRecents.clear()
for f in [f for f in lst if os.path.exists(f)]:
name = os.path.split(f)[1]
a = QAction(name, self)
a.setData(f)
a.setStatusTip(f)
a.triggered.connect(self.loadRecentFile)
self.mw.menuRecents.addAction(a)
self.btnRecent.setMenu(self.mw.menuRecents)
def appendToRecentFiles(self, project):
sttgns = QSettings()
if sttgns.contains("recentFiles"):
recentFiles = sttgns.value("recentFiles")
else:
recentFiles = []
while project in recentFiles:
recentFiles.remove(project)
recentFiles.insert(0, project)
recentFiles = recentFiles[:10]
sttgns.setValue("recentFiles", recentFiles)
def loadRecentFile(self):
act = self.sender()
self.appendToRecentFiles(act.data())
self.mw.closeProject()
self.mw.loadProject(act.data())
###############################################################################
# DIALOGS
###############################################################################
def openFile(self):
"""File dialog that request an existing file. For opening project."""
filename = QFileDialog.getOpenFileName(self,
self.tr("Open project"),
".",
self.tr("Manuskript project (*.msk);;All files (*)"))[0]
if filename:
self.appendToRecentFiles(filename)
self.mw.loadProject(filename)
def saveAsFile(self):
"""File dialog that request a file, existing or not.
Save datas to that file, which then becomes the current project."""
filename = QFileDialog.getSaveFileName(self,
self.tr("Save project as..."),
".",
self.tr("Manuskript project (*.msk)"))[0]
if filename:
self.appendToRecentFiles(filename)
loadSave.clearSaveCache() # Ensure all file(s) are saved under new filename
self.mw.saveDatas(filename)
# Update Window's project name with new filename
pName = os.path.split(filename)[1]
if pName.endswith('.msk'):
pName=pName[:-4]
self.mw.setWindowTitle(pName + " - " + self.tr("Manuskript"))
def createFile(self):
"""When starting a new project, ask for a place to save it.
Datas are not loaded from file, so they must be populated another way."""
filename = QFileDialog.getSaveFileName(self,
self.tr("Create New Project"),
".",
self.tr("Manuskript project (*.msk)"))[0]
if filename:
if filename[-4:] != ".msk":
filename += ".msk"
if os.path.exists(filename):
# Check if okay to overwrite existing project
result = QMessageBox.warning(self, self.tr("Warning"),
self.tr("Overwrite existing project {} ?").format(filename),
QMessageBox.Ok|QMessageBox.Cancel, QMessageBox.Cancel)
if result == QMessageBox.Cancel:
return
# Create new project
self.appendToRecentFiles(filename)
self.loadDefaultDatas()
self.mw.loadProject(filename, loadFromFile=False)
###############################################################################
# TEMPLATES
###############################################################################
def templates(self):
return [
(self.tr("Empty fiction"), [], "Fiction"),
(self.tr("Novel"), [
(20, self.tr("Chapter")),
(5, self.tr("Scene")),
(500, None) # A line with None is word count
], "Fiction"),
(self.tr("Novella"), [
(10, self.tr("Chapter")),
(5, self.tr("Scene")),
(500, None)
], "Fiction"),
(self.tr("Short Story"), [
(10, self.tr("Scene")),
(1000, None)
], "Fiction"),
(self.tr("Trilogy"), [
(3, self.tr("Book")),
(3, self.tr("Section")),
(10, self.tr("Chapter")),
(5, self.tr("Scene")),
(500, None)
], "Fiction"),
(self.tr("Empty non-fiction"), [], "Non-fiction"),
(self.tr("Research paper"), [
(3, self.tr("Section")),
(1000, None)
], "Non-fiction")
]
def changeTemplate(self, item, column):
template = [i for i in self.templates() if i[0] == item.text(0)]
self.btnCreate.setText(self.btnCreateText)
# Selected item is a template
if len(template):
self.template = template[0]
self.updateTemplate()
# Selected item is a sample project
elif item.data(0, Qt.UserRole):
name = item.data(0, Qt.UserRole)
# Clear templates
self.template = self.templates()[0]
self.updateTemplate()
# Change button text
self.btnCreate.setText("Open {}".format(name))
# Load project
self.mw.loadProject(appPath("sample-projects/{}".format(name)))
def updateTemplate(self):
# Clear layout
def clearLayout(l):
while l.count() != 0:
i = l.takeAt(0)
if i.widget():
i.widget().deleteLater()
if i.layout():
clearLayout(i.layout())
clearLayout(self.lytTemplate)
# self.templateLayout.addStretch()
# l = QGridLayout()
# self.templateLayout.addLayout(l)
k = 0
hasWC = False
for d in self.template[1]:
spin = QSpinBox(self)
spin.setRange(0, 999999)
spin.setValue(d[0])
spin.valueChanged.connect(self.updateWordCount)
if d[1] != None:
txt = QLineEdit(self)
txt.setText(d[1])
else:
hasWC = True
txt = QLabel(self.tr("words each."), self)
if k != 0:
of = QLabel(self.tr("of"), self)
self.lytTemplate.addWidget(of, k, 0)
btn = QPushButton("", self)
btn.setIcon(QIcon.fromTheme("edit-delete"))
btn.setProperty("deleteRow", k)
btn.clicked.connect(self.deleteTemplateRow)
self.lytTemplate.addWidget(btn, k, 3)
self.lytTemplate.addWidget(spin, k, 1)
self.lytTemplate.addWidget(txt, k, 2)
k += 1
self.btnAddWC.setEnabled(not hasWC and len(self.template[1]) > 0)
self.btnAddLevel.setEnabled(True)
self.lblTotal.setVisible(hasWC)
self.updateWordCount()
def templateAddLevel(self):
if len(self.template[1]) > 0 and \
self.template[1][len(self.template[1]) - 1][1] == None:
# has word cound, so insert before
self.template[1].insert(len(self.template[1]) - 1, (10, self.tr("Text")))
else:
# No word count, so insert at end
self.template[1].append((10, self.tr("Something")))
self.updateTemplate()
def templateAddWordCount(self):
self.template[1].append((500, None))
self.updateTemplate()
def deleteTemplateRow(self):
btn = self.sender()
row = btn.property("deleteRow")
self.template[1].pop(row)
self.updateTemplate()
def updateWordCount(self):
total = 1
for s in self.findChildren(QSpinBox, QRegExp(".*"),
Qt.FindChildrenRecursively):
total = total * s.value()
if total == 1:
total = 0
self.lblTotal.setText(self.tr("<b>Total:</b> {} words (~ {} pages)").format(
locale.format("%d", total, grouping=True),
locale.format("%d", total / 250, grouping=True)
))
def addTopLevelItem(self, name):
item = QTreeWidgetItem(self.tree, [name])
item.setBackground(0, QBrush(QColor(Qt.blue).lighter(190)))
item.setForeground(0, QBrush(Qt.darkBlue))
item.setTextAlignment(0, Qt.AlignCenter)
item.setFlags(Qt.ItemIsEnabled)
f = item.font(0)
f.setBold(True)
item.setFont(0, f)
return item
def populateTemplates(self):
self.tree.clear()
self.tree.setIndentation(0)
# Add templates
item = self.addTopLevelItem(self.tr("Fiction"))
templates = [i for i in self.templates() if i[2] == "Fiction"]
for t in templates:
sub = QTreeWidgetItem(item, [t[0]])
# Add templates: non-fiction
item = self.addTopLevelItem(self.tr("Non-fiction"))
templates = [i for i in self.templates() if i[2] == "Non-fiction"]
for t in templates:
sub = QTreeWidgetItem(item, [t[0]])
# Add Demo project
item = self.addTopLevelItem(self.tr("Demo projects"))
dir = QDir(appPath("sample-projects"))
for f in dir.entryList(["*.msk"], filters=QDir.Files):
sub = QTreeWidgetItem(item, [f[:-4]])
sub.setData(0, Qt.UserRole, f)
self.tree.expandAll()
def loadDefaultDatas(self):
# Empty settings
imp.reload(settings)
if self.template:
t = [i for i in self.templates() if i[0] == self.template[0]]
if t and t[0][2] == "Non-fiction":
settings.viewMode = "simple"
# Données
self.mw.mdlFlatData = QStandardItemModel(2, 8, self.mw)
# Persos
# self.mw.mdlPersos = QStandardItemModel(0, 0, self.mw)
self.mw.mdlCharacter = characterModel(self.mw)
# self.mdlPersosProxy = None # persosProxyModel() # None
# self.mw.mdlPersosProxy = persosProxyModel(self.mw)
# self.mw.mdlPersosInfos = QStandardItemModel(1, 0, self.mw)
# self.mw.mdlPersosInfos.insertColumn(0, [QStandardItem("ID")])
# self.mw.mdlPersosInfos.setHorizontalHeaderLabels(["Description"])
# Labels
self.mw.mdlLabels = QStandardItemModel(self.mw)
for color, text in [
(Qt.transparent, ""),
(Qt.yellow, self.tr("Idea")),
(Qt.green, self.tr("Note")),
(Qt.blue, self.tr("Chapter")),
(Qt.red, self.tr("Scene")),
(Qt.cyan, self.tr("Research"))
]:
self.mw.mdlLabels.appendRow(QStandardItem(iconFromColor(color), text))
# Status
self.mw.mdlStatus = QStandardItemModel(self.mw)
for text in [
"",
self.tr("TODO"),
self.tr("First draft"),
self.tr("Second draft"),
self.tr("Final")
]:
self.mw.mdlStatus.appendRow(QStandardItem(text))
# Plot
self.mw.mdlPlots = plotModel(self.mw)
# Outline
self.mw.mdlOutline = outlineModel(self.mw)
# World
self.mw.mdlWorld = worldModel(self.mw)
root = self.mw.mdlOutline.rootItem
_type = "md"
def addElement(parent, datas):
if len(datas) == 2 and datas[1][1] == None or \
len(datas) == 1:
# Next item is word count
n = 0
for i in range(datas[0][0]):
n += 1
item = outlineItem(title="{} {}".format(
datas[0][1],
str(n)),
_type=_type,
parent=parent)
if len(datas) == 2:
item.setData(Outline.setGoal.value, datas[1][0])
# parent.appendChild(item)
else:
n = 0
for i in range(datas[0][0]):
n += 1
item = outlineItem(title="{} {}".format(
datas[0][1],
str(n)),
_type="folder",
parent=parent)
# parent.appendChild(item)
addElement(item, datas[1:])
if self.template and self.template[1]:
addElement(root, self.template[1])