Add project fill options to new project page on welcome dialog

This commit is contained in:
Veronica Berglyd Olsen
2023-12-20 20:21:51 +01:00
parent 956abf3e8e
commit a71d0d8cfe
10 changed files with 145 additions and 53 deletions
@@ -44,6 +44,7 @@ cls_timeline = typ_calendar.svg
cls_trash = typ_trash.svg
cls_world = typ_location.svg
cross = typ_times.svg
document = typ_document.svg
down = typ_chevron-down.svg
edit = typ_pencil.svg
export = typ_export.svg
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="m21.42 6.247-4.8889-4.8889c-0.22856-0.22978-0.539-0.35811-0.86411-0.35811h-9.7778c-2.0216 0-3.6667 1.6451-3.6667 3.6667v14.667c0 2.0216 1.6451 3.6667 3.6667 3.6667h12.222c2.0216 0 3.6667-1.6451 3.6667-3.6667v-12.222c0-0.32511-0.12833-0.63556-0.35811-0.86411zm-2.5923 0.86411h-1.3273c-1.0108 0-1.8333-0.82256-1.8333-1.8333v-1.3273zm-0.71622 13.444h-12.222c-0.67467 0-1.2222-0.54756-1.2222-1.2222v-14.667c0-0.67467 0.54756-1.2222 1.2222-1.2222h8.5556v1.8333c0 1.6854 1.3701 3.0556 3.0556 3.0556h1.8333v11c0 0.67467-0.54756 1.2222-1.2222 1.2222z" fill="#aeaeae" stroke-width="1.2222"/>
</svg>

After

Width:  |  Height:  |  Size: 737 B

@@ -44,6 +44,7 @@ cls_timeline = typ_calendar.svg
cls_trash = typ_trash.svg
cls_world = typ_location.svg
cross = typ_times.svg
document = typ_document.svg
down = typ_chevron-down.svg
edit = typ_pencil.svg
export = typ_export.svg
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="m21.42 6.247-4.8889-4.8889c-0.22856-0.22978-0.539-0.35811-0.86411-0.35811h-9.7778c-2.0216 0-3.6667 1.6451-3.6667 3.6667v14.667c0 2.0216 1.6451 3.6667 3.6667 3.6667h12.222c2.0216 0 3.6667-1.6451 3.6667-3.6667v-12.222c0-0.32511-0.12833-0.63556-0.35811-0.86411zm-2.5923 0.86411h-1.3273c-1.0108 0-1.8333-0.82256-1.8333-1.8333v-1.3273zm-0.71622 13.444h-12.222c-0.67467 0-1.2222-0.54756-1.2222-1.2222v-14.667c0-0.67467 0.54756-1.2222 1.2222-1.2222h8.5556v1.8333c0 1.6854 1.3701 3.0556 3.0556 3.0556h1.8333v11c0 0.67467-0.54756 1.2222-1.2222 1.2222z" fill-opacity=".78039" stroke-width="1.2222"/>
</svg>

After

Width:  |  Height:  |  Size: 744 B

+17 -19
View File
@@ -421,10 +421,10 @@ class Config:
"""Compile and return error messages from the initialisation of
the Config class, and clear the error buffer.
"""
errMessage = "<br>".join(self._errData)
message = "<br>".join(self._errData)
self._hasError = False
self._errData = []
return errMessage
return message
def listLanguages(self, lngSet: int) -> list[tuple[str, str]]:
"""List localisation files in the i18n folder. The default GUI
@@ -647,7 +647,7 @@ class Config:
conf = NWConfigParser()
conf["Meta"] = {
"timestamp": formatTimeStamp(time()),
"timestamp": formatTimeStamp(time()),
}
conf["Main"] = {
@@ -787,22 +787,20 @@ class RecentProjects:
self._data = {}
cacheFile = self._conf.dataPath(nwFiles.RECENT_FILE)
if not cacheFile.is_file():
return True
try:
with open(cacheFile, mode="r", encoding="utf-8") as inFile:
theData = json.load(inFile)
for projPath, theEntry in theData.items():
self._data[projPath] = {
"title": theEntry.get("title", ""),
"words": theEntry.get("words", 0),
"time": theEntry.get("time", 0),
}
except Exception:
logger.error("Could not load recent project cache")
logException()
return False
if cacheFile.is_file():
try:
with open(cacheFile, mode="r", encoding="utf-8") as inFile:
data = json.load(inFile)
for path, entry in data.items():
self._data[path] = {
"title": entry.get("title", ""),
"words": entry.get("words", 0),
"time": entry.get("time", 0),
}
except Exception:
logger.error("Could not load recent project cache")
logException()
return False
return True
+13 -11
View File
@@ -33,6 +33,8 @@ from PyQt5.QtWidgets import (
from novelwriter import CONFIG
FONT_SCALE = 0.9
RIGHT_TOP = Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignTop
LEFT_TOP = Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop
class NConfigLayout(QGridLayout):
@@ -103,31 +105,31 @@ class NConfigLayout(QGridLayout):
labelBox.addWidget(qHelp)
labelBox.setSpacing(0)
labelBox.addStretch(1)
self.addLayout(labelBox, self._nextRow, 0, 1, 1, Qt.AlignLeft | Qt.AlignTop)
self.addLayout(labelBox, self._nextRow, 0, 1, 1, LEFT_TOP)
else:
self.addWidget(qLabel, self._nextRow, 0, 1, 1, Qt.AlignLeft | Qt.AlignTop)
self.addWidget(qLabel, self._nextRow, 0, 1, 1, LEFT_TOP)
if isinstance(unit, str):
controlBox = QHBoxLayout()
controlBox.addWidget(widget, 0, Qt.AlignVCenter)
controlBox.addWidget(QLabel(unit), 0, Qt.AlignVCenter)
controlBox.setSpacing(wSp)
self.addLayout(controlBox, self._nextRow, 1, 1, 1, Qt.AlignRight | Qt.AlignTop)
self.addLayout(controlBox, self._nextRow, 1, 1, 1, RIGHT_TOP)
elif isinstance(button, QAbstractButton):
controlBox = QHBoxLayout()
controlBox.addWidget(widget, 0, Qt.AlignVCenter)
controlBox.addWidget(button, 0, Qt.AlignVCenter)
controlBox.setSpacing(wSp)
self.addLayout(controlBox, self._nextRow, 1, 1, 1, Qt.AlignRight | Qt.AlignTop)
self.addLayout(controlBox, self._nextRow, 1, 1, 1, RIGHT_TOP)
else:
if isinstance(widget, QLineEdit):
qLayout = QHBoxLayout()
qLayout.addWidget(widget)
self.addLayout(qLayout, self._nextRow, 1, 1, 1, Qt.AlignRight | Qt.AlignTop)
self.addLayout(qLayout, self._nextRow, 1, 1, 1, RIGHT_TOP)
else:
self.addWidget(widget, self._nextRow, 1, 1, 1, Qt.AlignRight | Qt.AlignTop)
self.addWidget(widget, self._nextRow, 1, 1, 1, RIGHT_TOP)
self.setRowStretch(self._nextRow, 0)
self.setRowStretch(self._nextRow+1, 1)
@@ -145,14 +147,14 @@ class NSimpleLayout(QGridLayout):
column layout.
"""
def __init__(self) -> None:
def __init__(self, stretcColumn: int = 0) -> None:
super().__init__()
self._nextRow = 0
wSp = CONFIG.pxInt(8)
self.setHorizontalSpacing(wSp)
self.setVerticalSpacing(wSp)
self.setColumnStretch(0, 1)
self.setColumnStretch(stretcColumn, 1)
return
@@ -176,14 +178,14 @@ class NSimpleLayout(QGridLayout):
wSp = CONFIG.pxInt(8)
qLabel = QLabel(label)
qLabel.setIndent(wSp)
self.addWidget(qLabel, self._nextRow, 0, 1, 1, Qt.AlignLeft | Qt.AlignTop)
self.addWidget(qLabel, self._nextRow, 0, 1, 1, LEFT_TOP)
if isinstance(widget, QLineEdit):
qLayout = QHBoxLayout()
qLayout.addWidget(widget)
self.addLayout(qLayout, self._nextRow, 1, 1, 1, Qt.AlignRight | Qt.AlignTop)
self.addLayout(qLayout, self._nextRow, 1, 1, 1, RIGHT_TOP)
else:
self.addWidget(widget, self._nextRow, 1, 1, 1, Qt.AlignRight | Qt.AlignTop)
self.addWidget(widget, self._nextRow, 1, 1, 1, RIGHT_TOP)
qLabel.setBuddy(widget)
+1 -2
View File
@@ -167,8 +167,7 @@ class _PopRightMenu(QMenu):
def event(self, event: QEvent) -> bool:
"""Overload the show event and move the menu popup location."""
if event.type() == QEvent.Show:
parent = self.parent()
if isinstance(parent, QWidget):
if isinstance(parent := self.parent(), QWidget):
offset = QPoint(parent.width(), parent.height() - self.height())
self.move(parent.mapToGlobal(offset))
return super(_PopRightMenu, self).event(event)
+3 -3
View File
@@ -467,9 +467,9 @@ class GuiIcons:
# General Button Icons
"add", "add_document", "backward", "bookmark", "browse", "checked", "close", "cross",
"down", "edit", "export", "forward", "maximise", "menu", "minimise", "noncheckable",
"panel", "refresh", "remove", "revert", "search_replace", "search", "settings", "star",
"unchecked", "up", "view",
"document", "down", "edit", "export", "forward", "maximise", "menu", "minimise",
"noncheckable", "panel", "refresh", "remove", "revert", "search_replace", "search",
"settings", "star", "unchecked", "up", "view",
# Switches
"sticky-on", "sticky-off",
+15 -1
View File
@@ -31,8 +31,9 @@ from typing import TYPE_CHECKING
from pathlib import Path
from PyQt5.QtCore import QObject, QRunnable, QThreadPool, pyqtSignal
from PyQt5.QtWidgets import QMessageBox, QWidget
from PyQt5.QtWidgets import QFileDialog, QMessageBox, QWidget
from novelwriter.constants import nwFiles
from novelwriter.core.spellcheck import NWSpellEnchant
if TYPE_CHECKING: # pragma: no cover
@@ -215,6 +216,19 @@ class SharedData(QObject):
QThreadPool.globalInstance().start(runnable, priority=priority)
return
def getProjectPath(self, parent: QWidget, allowZip: bool = False) -> Path | None:
"""Open the file dialog and select a novelWriter project file."""
ext = []
ext.append(self.tr("novelWriter Project File ({0})").format(nwFiles.PROJ_FILE))
if allowZip:
ext.append(self.tr("Zip Archives ({0})").format("*.zip"))
ext.append(self.tr("All files ({0})").format("*"))
projFile, _ = QFileDialog.getOpenFileName(
parent, self.tr("Open Project"), "", filter=";;".join(ext)
)
return Path(projFile) if projFile else None
##
# Signal Proxy
##
+86 -17
View File
@@ -30,11 +30,11 @@ from pathlib import Path
from datetime import datetime
from PyQt5.QtGui import QCloseEvent, QPaintEvent, QPainter
from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot
from PyQt5.QtCore import QEvent, QPoint, Qt, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import (
QComboBox, QDialog, QDialogButtonBox, QFileDialog, QFormLayout,
QHBoxLayout, QLabel, QLineEdit, QPushButton, QScrollArea, QSpinBox,
QStackedWidget, QTreeWidget, QVBoxLayout, QWidget
QHBoxLayout, QLabel, QLineEdit, QMenu, QPushButton, QScrollArea, QSpinBox,
QStackedWidget, QToolButton, QTreeWidget, QVBoxLayout, QWidget
)
from novelwriter import CONFIG, SHARED, __version__, __date__
@@ -265,18 +265,17 @@ class _NewProjectForm(QWidget):
FILL_BLANK = 0
FILL_SAMPLE = 1
FILL_TEMPLATE = 2
FILL_COPY = 2
def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent)
self._basePath = CONFIG.lastPath()
self._fillMode = self.FILL_BLANK
self._tmplPath = None
self._copyPath = None
# Project Settings
# ================
posLast = QLineEdit.ActionPosition.TrailingPosition
# Project Name
self.projName = QLineEdit()
@@ -303,22 +302,51 @@ class _NewProjectForm(QWidget):
# Project Path
self.projPath = QLineEdit(self)
self.projPath.setReadOnly(True)
self.projPath.setPlaceholderText(self.tr("Required"))
action = self.projPath.addAction(SHARED.theme.getIcon("browse"), posLast)
action.triggered.connect(self._doBrowse)
self.browsePath = QToolButton(self)
self.browsePath.setIcon(SHARED.theme.getIcon("browse"))
self.browsePath.clicked.connect(self._doBrowse)
self.pathBox = QHBoxLayout()
self.pathBox.addWidget(self.projPath)
self.pathBox.addWidget(self.browsePath)
# Fill Project
self.projFill = QLineEdit(self)
self.projFill.setReadOnly(True)
action = self.projFill.addAction(SHARED.theme.getIcon("add_document"), posLast)
self.browseFill = QToolButton(self)
self.browseFill.setIcon(SHARED.theme.getIcon("add_document"))
self.fillMenu = _PopLeftDirectionMenu(self.browseFill)
self.fillBlank = self.fillMenu.addAction(self.tr("Create a fresh project"))
self.fillBlank.setIcon(SHARED.theme.getIcon("document"))
self.fillBlank.triggered.connect(self._setFillBlank)
self.fillSample = self.fillMenu.addAction(self.tr("Create an example project"))
self.fillSample.setIcon(SHARED.theme.getIcon("add_document"))
self.fillSample.triggered.connect(self._setFillSample)
self.fillCopy = self.fillMenu.addAction(self.tr("Copy an existing project"))
self.fillCopy.setIcon(SHARED.theme.getIcon("browse"))
self.fillCopy.triggered.connect(self._setFillCopy)
self.browseFill.setMenu(self.fillMenu)
self.browseFill.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
self.fillBox = QHBoxLayout()
self.fillBox.addWidget(self.projFill)
self.fillBox.addWidget(self.browseFill)
# Project Form
self.projectForm = QFormLayout()
self.projectForm.setAlignment(Qt.AlignmentFlag.AlignLeft)
self.projectForm.addRow(self.tr("Project Name"), self.projName)
self.projectForm.addRow(self.tr("Author(s)"), self.projAuthor)
self.projectForm.addRow(self.tr("Author"), self.projAuthor)
self.projectForm.addRow(self.tr("Language"), self.projLang)
self.projectForm.addRow(self.tr("Project Path"), self.projPath)
self.projectForm.addRow(self.tr("Prefill Project"), self.projFill)
self.projectForm.addRow(self.tr("Project Path"), self.pathBox)
self.projectForm.addRow(self.tr("Prefill Project"), self.fillBox)
# Chapters and Scenes
# ===================
@@ -367,6 +395,7 @@ class _NewProjectForm(QWidget):
self.addNotes.setChecked(False)
self.notesForm = QFormLayout()
self.notesForm.setAlignment(Qt.AlignmentFlag.AlignLeft)
self.notesForm.addRow(self.tr("Add a folder for plot notes"), self.addPlot)
self.notesForm.addRow(self.tr("Add a folder for character notes"), self.addChar)
self.notesForm.addRow(self.tr("Add a folder for location notes"), self.addWorld)
@@ -426,18 +455,45 @@ class _NewProjectForm(QWidget):
self.addNotes.setChecked(False)
return
@pyqtSlot()
def _setFillBlank(self) -> None:
"""Set fill mode to blank project."""
self._fillMode = self.FILL_BLANK
self._updateFillInfo()
return
@pyqtSlot()
def _setFillSample(self) -> None:
"""Set fill mode to sample project."""
self._fillMode = self.FILL_SAMPLE
self._updateFillInfo()
return
@pyqtSlot()
def _setFillCopy(self) -> None:
"""Set fill mode to copy project."""
self._fillMode = self.FILL_COPY
self._copyPath = SHARED.getProjectPath(self, allowZip=True)
self._updateFillInfo()
return
##
# Internal Functions
##
def _updateFillInfo(self) -> None:
"""Update the text of the project fill box."""
text = ""
if self._fillMode == self.FILL_BLANK:
self.projFill.setText(self.tr("Fresh Project"))
text = self.tr("Fresh Project")
elif self._fillMode == self.FILL_SAMPLE:
self.projFill.setText(self.tr("Example Project"))
elif self._fillMode == self.FILL_TEMPLATE:
self.projFill.setText(self.tr("Template: {0}").format(str(self._tmplPath)))
text = self.tr("Example Project")
elif self._fillMode == self.FILL_COPY:
text = self.tr("Template: {0}").format(str(self._copyPath))
self.projFill.setText(text)
self.projFill.setToolTip(text)
self.projFill.setCursorPosition(0)
isBlank = self._fillMode == self.FILL_BLANK
self.numChapters.setEnabled(isBlank)
@@ -450,3 +506,16 @@ class _NewProjectForm(QWidget):
return
# END Class _NewProjectForm
class _PopLeftDirectionMenu(QMenu):
def event(self, event: QEvent) -> bool:
"""Overload the show event and move the menu popup location."""
if event.type() == QEvent.Show:
if isinstance(parent := self.parent(), QWidget):
offset = QPoint(parent.width() - self.width(), parent.height())
self.move(parent.mapToGlobal(offset))
return super(_PopLeftDirectionMenu, self).event(event)
# END Class _PopLeftDirectionMenu