novelWriter 2.2rc1__py3-none-any.whl → 2.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {novelWriter-2.2rc1.dist-info → novelWriter-2.3.dist-info}/METADATA +1 -1
- {novelWriter-2.2rc1.dist-info → novelWriter-2.3.dist-info}/RECORD +149 -132
- {novelWriter-2.2rc1.dist-info → novelWriter-2.3.dist-info}/WHEEL +1 -1
- novelWriter-2.3.dist-info/entry_points.txt +2 -0
- novelwriter/__init__.py +11 -6
- novelwriter/assets/i18n/nw_de_DE.qm +0 -0
- novelwriter/assets/i18n/nw_en_US.qm +0 -0
- novelwriter/assets/i18n/nw_es_419.qm +0 -0
- novelwriter/assets/i18n/nw_fr_FR.qm +0 -0
- novelwriter/assets/i18n/nw_it_IT.qm +0 -0
- novelwriter/assets/i18n/nw_ja_JP.qm +0 -0
- novelwriter/assets/i18n/nw_nb_NO.qm +0 -0
- novelwriter/assets/i18n/nw_nl_NL.qm +0 -0
- novelwriter/assets/i18n/nw_zh_CN.qm +0 -0
- novelwriter/assets/i18n/project_de_DE.json +1 -0
- novelwriter/assets/i18n/project_en_US.json +1 -0
- novelwriter/assets/i18n/project_es_419.json +11 -0
- novelwriter/assets/i18n/project_fr_FR.json +11 -0
- novelwriter/assets/i18n/project_it_IT.json +11 -0
- novelwriter/assets/i18n/project_ja_JP.json +2 -1
- novelwriter/assets/i18n/project_nb_NO.json +1 -0
- novelwriter/assets/i18n/project_nl_NL.json +11 -0
- novelwriter/assets/i18n/project_pt_BR.json +11 -0
- novelwriter/assets/i18n/project_zh_CN.json +11 -0
- novelwriter/assets/icons/typicons_dark/icons.conf +11 -2
- novelwriter/assets/icons/typicons_dark/mixed_document-new.svg +6 -0
- novelwriter/assets/icons/typicons_dark/mixed_import.svg +5 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-bold-md.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-bold.svg +3 -1
- novelwriter/assets/icons/typicons_dark/nw_tb-italic-md.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-italic.svg +3 -1
- novelwriter/assets/icons/typicons_dark/nw_tb-strike-md.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-strike.svg +3 -1
- novelwriter/assets/icons/typicons_dark/nw_tb-subscript.svg +4 -2
- novelwriter/assets/icons/typicons_dark/nw_tb-superscript.svg +4 -2
- novelwriter/assets/icons/typicons_dark/nw_tb-underline.svg +4 -2
- novelwriter/assets/icons/typicons_dark/typ_document-add.svg +4 -0
- novelwriter/assets/icons/typicons_dark/typ_document.svg +4 -0
- novelwriter/assets/icons/typicons_dark/typ_th-dot-more.svg +4 -0
- novelwriter/assets/icons/typicons_dark/typ_th-list.svg +9 -0
- novelwriter/assets/icons/typicons_light/icons.conf +11 -2
- novelwriter/assets/icons/typicons_light/mixed_document-new.svg +6 -0
- novelwriter/assets/icons/typicons_light/mixed_import.svg +5 -0
- novelwriter/assets/icons/typicons_light/nw_tb-bold-md.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_tb-bold.svg +3 -1
- novelwriter/assets/icons/typicons_light/nw_tb-italic-md.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_tb-italic.svg +3 -1
- novelwriter/assets/icons/typicons_light/nw_tb-strike-md.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_tb-strike.svg +3 -1
- novelwriter/assets/icons/typicons_light/nw_tb-subscript.svg +4 -2
- novelwriter/assets/icons/typicons_light/nw_tb-superscript.svg +4 -2
- novelwriter/assets/icons/typicons_light/nw_tb-underline.svg +4 -2
- novelwriter/assets/icons/typicons_light/typ_document-add.svg +4 -0
- novelwriter/assets/icons/typicons_light/typ_document.svg +4 -0
- novelwriter/assets/icons/typicons_light/typ_th-dot-more.svg +4 -0
- novelwriter/assets/icons/typicons_light/typ_th-list.svg +9 -0
- novelwriter/assets/images/novelwriter-text-dark.svg +4 -0
- novelwriter/assets/images/novelwriter-text-light.svg +4 -0
- novelwriter/assets/images/welcome-dark.jpg +0 -0
- novelwriter/assets/images/welcome-light.jpg +0 -0
- novelwriter/assets/manual.pdf +0 -0
- novelwriter/assets/sample.zip +0 -0
- novelwriter/assets/syntax/cyberpunk_night.conf +26 -0
- novelwriter/assets/syntax/default_dark.conf +1 -0
- novelwriter/assets/syntax/default_light.conf +1 -0
- novelwriter/assets/syntax/grey_dark.conf +1 -0
- novelwriter/assets/syntax/grey_light.conf +1 -0
- novelwriter/assets/syntax/light_owl.conf +1 -0
- novelwriter/assets/syntax/night_owl.conf +1 -0
- novelwriter/assets/syntax/solarized_dark.conf +1 -0
- novelwriter/assets/syntax/solarized_light.conf +1 -0
- novelwriter/assets/syntax/tango.conf +23 -0
- novelwriter/assets/syntax/tomorrow.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night_blue.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night_bright.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night_eighties.conf +1 -0
- novelwriter/assets/text/credits_en.htm +4 -2
- novelwriter/assets/themes/cyberpunk_night.conf +29 -0
- novelwriter/assets/themes/default_dark.conf +2 -2
- novelwriter/assets/themes/default_light.conf +2 -2
- novelwriter/common.py +64 -66
- novelwriter/config.py +39 -44
- novelwriter/constants.py +39 -17
- novelwriter/core/buildsettings.py +8 -8
- novelwriter/core/coretools.py +198 -157
- novelwriter/core/docbuild.py +7 -4
- novelwriter/core/document.py +7 -7
- novelwriter/core/index.py +90 -57
- novelwriter/core/item.py +23 -5
- novelwriter/core/options.py +11 -10
- novelwriter/core/project.py +73 -47
- novelwriter/core/projectdata.py +3 -16
- novelwriter/core/projectxml.py +14 -42
- novelwriter/core/sessions.py +4 -3
- novelwriter/core/spellcheck.py +6 -4
- novelwriter/core/status.py +5 -4
- novelwriter/core/storage.py +183 -141
- novelwriter/core/tohtml.py +6 -4
- novelwriter/core/tokenizer.py +110 -83
- novelwriter/core/tomd.py +2 -2
- novelwriter/core/toodt.py +41 -31
- novelwriter/core/tree.py +5 -4
- novelwriter/dialogs/about.py +88 -179
- novelwriter/dialogs/docmerge.py +30 -20
- novelwriter/dialogs/docsplit.py +33 -22
- novelwriter/dialogs/editlabel.py +20 -8
- novelwriter/dialogs/preferences.py +562 -725
- novelwriter/dialogs/{projsettings.py → projectsettings.py} +301 -270
- novelwriter/dialogs/quotes.py +47 -36
- novelwriter/dialogs/wordlist.py +128 -59
- novelwriter/enum.py +25 -22
- novelwriter/error.py +2 -2
- novelwriter/extensions/circularprogress.py +12 -12
- novelwriter/extensions/configlayout.py +185 -146
- novelwriter/extensions/{wheeleventfilter.py → eventfilters.py} +15 -5
- novelwriter/extensions/modified.py +81 -0
- novelwriter/extensions/novelselector.py +27 -13
- novelwriter/extensions/pagedsidebar.py +15 -20
- novelwriter/extensions/simpleprogress.py +8 -9
- novelwriter/extensions/statusled.py +9 -9
- novelwriter/extensions/switch.py +32 -64
- novelwriter/extensions/switchbox.py +2 -7
- novelwriter/extensions/versioninfo.py +153 -0
- novelwriter/gui/doceditor.py +250 -214
- novelwriter/gui/dochighlight.py +66 -94
- novelwriter/gui/docviewer.py +71 -98
- novelwriter/gui/docviewerpanel.py +140 -47
- novelwriter/gui/editordocument.py +3 -3
- novelwriter/gui/itemdetails.py +9 -9
- novelwriter/gui/mainmenu.py +47 -47
- novelwriter/gui/noveltree.py +53 -61
- novelwriter/gui/outline.py +100 -76
- novelwriter/gui/projtree.py +246 -112
- novelwriter/gui/sidebar.py +9 -8
- novelwriter/gui/statusbar.py +49 -7
- novelwriter/gui/theme.py +74 -76
- novelwriter/guimain.py +175 -330
- novelwriter/shared.py +68 -30
- novelwriter/tools/dictionaries.py +7 -8
- novelwriter/tools/lipsum.py +34 -28
- novelwriter/tools/manusbuild.py +3 -4
- novelwriter/tools/manuscript.py +25 -32
- novelwriter/tools/manussettings.py +194 -225
- novelwriter/tools/noveldetails.py +525 -0
- novelwriter/tools/welcome.py +819 -0
- novelwriter/tools/writingstats.py +26 -13
- novelWriter-2.2rc1.dist-info/entry_points.txt +0 -5
- novelwriter/assets/icons/typicons_dark/nw_tb-markdown.svg +0 -8
- novelwriter/assets/icons/typicons_dark/nw_tb-shortcode.svg +0 -8
- novelwriter/assets/icons/typicons_light/nw_tb-markdown.svg +0 -8
- novelwriter/assets/icons/typicons_light/nw_tb-shortcode.svg +0 -8
- novelwriter/assets/images/wizard-back.jpg +0 -0
- novelwriter/assets/text/gplv3_en.htm +0 -641
- novelwriter/assets/text/release_notes.htm +0 -17
- novelwriter/dialogs/projdetails.py +0 -525
- novelwriter/dialogs/projload.py +0 -298
- novelwriter/dialogs/updates.py +0 -182
- novelwriter/extensions/pageddialog.py +0 -130
- novelwriter/tools/projwizard.py +0 -478
- {novelWriter-2.2rc1.dist-info → novelWriter-2.3.dist-info}/LICENSE.md +0 -0
- {novelWriter-2.2rc1.dist-info → novelWriter-2.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,819 @@
|
|
1
|
+
"""
|
2
|
+
novelWriter – GUI Welcome Dialog
|
3
|
+
================================
|
4
|
+
|
5
|
+
File History:
|
6
|
+
Created: 2023-12-14 [2.3b1] GuiWelcome
|
7
|
+
|
8
|
+
This file is a part of novelWriter
|
9
|
+
Copyright 2018–2024, Veronica Berglyd Olsen
|
10
|
+
|
11
|
+
This program is free software: you can redistribute it and/or modify
|
12
|
+
it under the terms of the GNU General Public License as published by
|
13
|
+
the Free Software Foundation, either version 3 of the License, or
|
14
|
+
(at your option) any later version.
|
15
|
+
|
16
|
+
This program is distributed in the hope that it will be useful, but
|
17
|
+
WITHOUT ANY WARRANTY; without even the implied warranty of
|
18
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
19
|
+
General Public License for more details.
|
20
|
+
|
21
|
+
You should have received a copy of the GNU General Public License
|
22
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
23
|
+
"""
|
24
|
+
from __future__ import annotations
|
25
|
+
|
26
|
+
import logging
|
27
|
+
|
28
|
+
from pathlib import Path
|
29
|
+
from datetime import datetime
|
30
|
+
|
31
|
+
from PyQt5.QtGui import QCloseEvent, QColor, QFont, QPaintEvent, QPainter, QPen
|
32
|
+
from PyQt5.QtCore import (
|
33
|
+
QAbstractListModel, QEvent, QModelIndex, QObject, QPoint, QSize, Qt,
|
34
|
+
pyqtSignal, pyqtSlot
|
35
|
+
)
|
36
|
+
from PyQt5.QtWidgets import (
|
37
|
+
QAction, QDialog, QFileDialog, QFormLayout, QHBoxLayout, QLabel, QLineEdit,
|
38
|
+
QListView, QMenu, QPushButton, QScrollArea, QShortcut, QStackedWidget,
|
39
|
+
QStyle, QStyleOptionViewItem, QStyledItemDelegate, QToolButton,
|
40
|
+
QVBoxLayout, QWidget, qApp
|
41
|
+
)
|
42
|
+
|
43
|
+
from novelwriter import CONFIG, SHARED
|
44
|
+
from novelwriter.enum import nwItemClass
|
45
|
+
from novelwriter.common import formatInt, makeFileNameSafe
|
46
|
+
from novelwriter.constants import nwFiles
|
47
|
+
from novelwriter.core.coretools import ProjectBuilder
|
48
|
+
from novelwriter.extensions.switch import NSwitch
|
49
|
+
from novelwriter.extensions.modified import NSpinBox
|
50
|
+
from novelwriter.extensions.versioninfo import VersionInfoWidget
|
51
|
+
|
52
|
+
logger = logging.getLogger(__name__)
|
53
|
+
|
54
|
+
PANEL_ALPHA = 0.7
|
55
|
+
|
56
|
+
|
57
|
+
class GuiWelcome(QDialog):
|
58
|
+
|
59
|
+
openProjectRequest = pyqtSignal(Path)
|
60
|
+
|
61
|
+
def __init__(self, parent: QWidget) -> None:
|
62
|
+
super().__init__(parent=parent)
|
63
|
+
|
64
|
+
logger.debug("Create: GuiWelcome")
|
65
|
+
self.setObjectName("GuiWelcome")
|
66
|
+
|
67
|
+
self.setWindowTitle(self.tr("Welcome"))
|
68
|
+
self.setMinimumWidth(CONFIG.pxInt(650))
|
69
|
+
self.setMinimumHeight(CONFIG.pxInt(450))
|
70
|
+
|
71
|
+
hA = CONFIG.pxInt(8)
|
72
|
+
hB = CONFIG.pxInt(16)
|
73
|
+
hC = CONFIG.pxInt(24)
|
74
|
+
hD = CONFIG.pxInt(36)
|
75
|
+
hE = CONFIG.pxInt(48)
|
76
|
+
hF = CONFIG.pxInt(128)
|
77
|
+
self._hPx = CONFIG.pxInt(600)
|
78
|
+
|
79
|
+
self.resize(*CONFIG.welcomeWinSize)
|
80
|
+
|
81
|
+
# Elements
|
82
|
+
# ========
|
83
|
+
|
84
|
+
self.bgImage = SHARED.theme.loadDecoration("welcome")
|
85
|
+
self.nwImage = SHARED.theme.loadDecoration("nw-text", h=hD)
|
86
|
+
self.bgColor = QColor(255, 255, 255) if SHARED.theme.isLightTheme else QColor(54, 54, 54)
|
87
|
+
|
88
|
+
self.nwLogo = QLabel(self)
|
89
|
+
self.nwLogo.setPixmap(SHARED.theme.getPixmap("novelwriter", (hF, hF)))
|
90
|
+
|
91
|
+
self.nwLabel = QLabel(self)
|
92
|
+
self.nwLabel.setPixmap(self.nwImage)
|
93
|
+
|
94
|
+
self.nwInfo = VersionInfoWidget(self)
|
95
|
+
|
96
|
+
self.tabOpen = _OpenProjectPage(self)
|
97
|
+
self.tabOpen.openProjectRequest.connect(self._openProjectPath)
|
98
|
+
|
99
|
+
self.tabNew = _NewProjectPage(self)
|
100
|
+
self.tabNew.openProjectRequest.connect(self._openProjectPath)
|
101
|
+
|
102
|
+
self.mainStack = QStackedWidget(self)
|
103
|
+
self.mainStack.addWidget(self.tabOpen)
|
104
|
+
self.mainStack.addWidget(self.tabNew)
|
105
|
+
|
106
|
+
# Buttons
|
107
|
+
# =======
|
108
|
+
|
109
|
+
self.btnList = QPushButton(self.tr("List"), self)
|
110
|
+
self.btnList.setIcon(SHARED.theme.getIcon("list"))
|
111
|
+
self.btnList.clicked.connect(self._showOpenProjectPage)
|
112
|
+
|
113
|
+
self.btnNew = QPushButton(self.tr("New"), self)
|
114
|
+
self.btnNew.setIcon(SHARED.theme.getIcon("add"))
|
115
|
+
self.btnNew.clicked.connect(self._showNewProjectPage)
|
116
|
+
|
117
|
+
self.btnBrowse = QPushButton(self.tr("Browse"), self)
|
118
|
+
self.btnBrowse.setIcon(SHARED.theme.getIcon("browse"))
|
119
|
+
self.btnBrowse.clicked.connect(self._browseForProject)
|
120
|
+
|
121
|
+
self.btnCancel = QPushButton(self.tr("Cancel"), self)
|
122
|
+
self.btnCancel.setIcon(SHARED.theme.getIcon("cross"))
|
123
|
+
self.btnCancel.clicked.connect(self.close)
|
124
|
+
|
125
|
+
self.btnCreate = QPushButton(self.tr("Create"), self)
|
126
|
+
self.btnCreate.setIcon(SHARED.theme.getIcon("star"))
|
127
|
+
self.btnCreate.clicked.connect(self.tabNew.createNewProject)
|
128
|
+
|
129
|
+
self.btnOpen = QPushButton(self.tr("Open"), self)
|
130
|
+
self.btnOpen.setIcon(SHARED.theme.getIcon("open"))
|
131
|
+
self.btnOpen.clicked.connect(self._openSelectedItem)
|
132
|
+
|
133
|
+
self.btnBox = QHBoxLayout()
|
134
|
+
self.btnBox.addStretch(1)
|
135
|
+
self.btnBox.addWidget(self.btnList)
|
136
|
+
self.btnBox.addWidget(self.btnNew)
|
137
|
+
self.btnBox.addWidget(self.btnBrowse)
|
138
|
+
self.btnBox.addWidget(self.btnCancel)
|
139
|
+
self.btnBox.addWidget(self.btnCreate)
|
140
|
+
self.btnBox.addWidget(self.btnOpen)
|
141
|
+
self._setButtonVisibility()
|
142
|
+
|
143
|
+
# Assemble
|
144
|
+
# ========
|
145
|
+
|
146
|
+
self.innerBox = QVBoxLayout()
|
147
|
+
self.innerBox.addSpacing(hB)
|
148
|
+
self.innerBox.addWidget(self.nwLabel)
|
149
|
+
self.innerBox.addWidget(self.nwInfo)
|
150
|
+
self.innerBox.addSpacing(hA)
|
151
|
+
self.innerBox.addWidget(self.mainStack)
|
152
|
+
self.innerBox.addSpacing(hB)
|
153
|
+
self.innerBox.addLayout(self.btnBox)
|
154
|
+
|
155
|
+
topRight = Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignRight
|
156
|
+
|
157
|
+
self.outerBox = QHBoxLayout()
|
158
|
+
self.outerBox.addWidget(self.nwLogo, 3, topRight)
|
159
|
+
self.outerBox.addLayout(self.innerBox, 9)
|
160
|
+
self.outerBox.setContentsMargins(hF, hE, hC, hE)
|
161
|
+
|
162
|
+
self.setLayout(self.outerBox)
|
163
|
+
self.setSizeGripEnabled(True)
|
164
|
+
|
165
|
+
logger.debug("Ready: GuiWelcome")
|
166
|
+
|
167
|
+
return
|
168
|
+
|
169
|
+
def __del__(self) -> None: # pragma: no cover
|
170
|
+
logger.debug("Delete: GuiWelcome")
|
171
|
+
return
|
172
|
+
|
173
|
+
##
|
174
|
+
# Events
|
175
|
+
##
|
176
|
+
|
177
|
+
def paintEvent(self, event: QPaintEvent) -> None:
|
178
|
+
"""Overload the paint event to draw the background image."""
|
179
|
+
hWin = self.height()
|
180
|
+
hPix = min(hWin, self._hPx)
|
181
|
+
tMode = Qt.TransformationMode.SmoothTransformation
|
182
|
+
painter = QPainter(self)
|
183
|
+
painter.fillRect(self.rect(), self.bgColor)
|
184
|
+
painter.drawPixmap(0, hWin - hPix, self.bgImage.scaledToHeight(hPix, tMode))
|
185
|
+
super().paintEvent(event)
|
186
|
+
return
|
187
|
+
|
188
|
+
def closeEvent(self, event: QCloseEvent) -> None:
|
189
|
+
"""Capture the user closing the window and save settings."""
|
190
|
+
self._saveSettings()
|
191
|
+
event.accept()
|
192
|
+
self.deleteLater()
|
193
|
+
return
|
194
|
+
|
195
|
+
##
|
196
|
+
# Private Slots
|
197
|
+
##
|
198
|
+
|
199
|
+
@pyqtSlot()
|
200
|
+
def _showNewProjectPage(self) -> None:
|
201
|
+
"""Show the create new project page."""
|
202
|
+
self.mainStack.setCurrentWidget(self.tabNew)
|
203
|
+
self._setButtonVisibility()
|
204
|
+
return
|
205
|
+
|
206
|
+
@pyqtSlot()
|
207
|
+
def _showOpenProjectPage(self) -> None:
|
208
|
+
"""Show the open exiting project page."""
|
209
|
+
self.mainStack.setCurrentWidget(self.tabOpen)
|
210
|
+
self._setButtonVisibility()
|
211
|
+
return
|
212
|
+
|
213
|
+
@pyqtSlot()
|
214
|
+
def _browseForProject(self) -> None:
|
215
|
+
"""Browse for a project to open."""
|
216
|
+
if path := SHARED.getProjectPath(self, path=CONFIG.lastPath(), allowZip=False):
|
217
|
+
CONFIG.setLastPath(path)
|
218
|
+
self._openProjectPath(path)
|
219
|
+
return
|
220
|
+
|
221
|
+
@pyqtSlot()
|
222
|
+
def _openSelectedItem(self) -> None:
|
223
|
+
"""Open the currently selected project item."""
|
224
|
+
if self.mainStack.currentWidget() == self.tabOpen:
|
225
|
+
self.tabOpen.openSelectedItem()
|
226
|
+
return
|
227
|
+
|
228
|
+
@pyqtSlot(Path)
|
229
|
+
def _openProjectPath(self, path: Path) -> None:
|
230
|
+
"""Emit a project open signal."""
|
231
|
+
if isinstance(path, Path):
|
232
|
+
# Hide before emitting the open project signal so that any
|
233
|
+
# close/backup dialogs don't pop up over it.
|
234
|
+
self.hide()
|
235
|
+
self.openProjectRequest.emit(path)
|
236
|
+
self.close()
|
237
|
+
return
|
238
|
+
|
239
|
+
##
|
240
|
+
# Internal Functions
|
241
|
+
##
|
242
|
+
|
243
|
+
def _saveSettings(self) -> None:
|
244
|
+
"""Save the user GUI settings."""
|
245
|
+
logger.debug("Saving State: GuiWelcome")
|
246
|
+
CONFIG.setWelcomeWinSize(self.width(), self.height())
|
247
|
+
return
|
248
|
+
|
249
|
+
def _setButtonVisibility(self) -> None:
|
250
|
+
"""Change the visibility of the dialog buttons."""
|
251
|
+
listMode = self.mainStack.currentWidget() == self.tabOpen
|
252
|
+
self.btnList.setVisible(not listMode)
|
253
|
+
self.btnNew.setVisible(listMode)
|
254
|
+
self.btnBrowse.setVisible(listMode)
|
255
|
+
self.btnCreate.setVisible(not listMode)
|
256
|
+
self.btnOpen.setVisible(listMode)
|
257
|
+
if listMode:
|
258
|
+
self.btnOpen.setFocus()
|
259
|
+
else:
|
260
|
+
self.btnCreate.setFocus()
|
261
|
+
return
|
262
|
+
|
263
|
+
# END Class GuiWelcome
|
264
|
+
|
265
|
+
|
266
|
+
class _OpenProjectPage(QWidget):
|
267
|
+
|
268
|
+
openProjectRequest = pyqtSignal(Path)
|
269
|
+
|
270
|
+
def __init__(self, parent: QWidget) -> None:
|
271
|
+
super().__init__(parent=parent)
|
272
|
+
|
273
|
+
# List View
|
274
|
+
self.listModel = _ProjectListModel(self)
|
275
|
+
self.itemDelegate = _ProjectListItem(self)
|
276
|
+
|
277
|
+
self.listWidget = QListView(self)
|
278
|
+
self.listWidget.setItemDelegate(self.itemDelegate)
|
279
|
+
self.listWidget.setModel(self.listModel)
|
280
|
+
self.listWidget.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
|
281
|
+
self.listWidget.clicked.connect(self._projectClicked)
|
282
|
+
self.listWidget.doubleClicked.connect(self._projectDoubleClicked)
|
283
|
+
self.listWidget.customContextMenuRequested.connect(self._openContextMenu)
|
284
|
+
|
285
|
+
# Info / Tool
|
286
|
+
self.aMissing = QAction(self)
|
287
|
+
self.aMissing.setIcon(SHARED.theme.getIcon("alert_warn"))
|
288
|
+
self.aMissing.setToolTip(self.tr("The project path is not reachable."))
|
289
|
+
|
290
|
+
self.selectedPath = QLineEdit(self)
|
291
|
+
self.selectedPath.setReadOnly(True)
|
292
|
+
self.selectedPath.addAction(self.aMissing, QLineEdit.ActionPosition.TrailingPosition)
|
293
|
+
|
294
|
+
self.keyDelete = QShortcut(self)
|
295
|
+
self.keyDelete.setKey(Qt.Key.Key_Delete)
|
296
|
+
self.keyDelete.activated.connect(self._deleteSelectedItem)
|
297
|
+
|
298
|
+
# Assemble
|
299
|
+
self.outerBox = QVBoxLayout()
|
300
|
+
self.outerBox.addWidget(self.listWidget)
|
301
|
+
self.outerBox.addWidget(self.selectedPath)
|
302
|
+
self.outerBox.setContentsMargins(0, 0, 0, 0)
|
303
|
+
|
304
|
+
self.setLayout(self.outerBox)
|
305
|
+
|
306
|
+
self._selectFirstItem()
|
307
|
+
|
308
|
+
baseCol = self.palette().base().color()
|
309
|
+
self.setStyleSheet((
|
310
|
+
"QListView {{border: none; background: rgba({r},{g},{b},{a});}} "
|
311
|
+
"QLineEdit {{border: none; background: rgba({r},{g},{b},{a}); padding: {m}px;}} "
|
312
|
+
).format(r=baseCol.red(), g=baseCol.green(), b=baseCol.blue(),
|
313
|
+
a=PANEL_ALPHA, m=CONFIG.pxInt(4)))
|
314
|
+
|
315
|
+
return
|
316
|
+
|
317
|
+
##
|
318
|
+
# Public Slots
|
319
|
+
##
|
320
|
+
|
321
|
+
@pyqtSlot()
|
322
|
+
def openSelectedItem(self) -> None:
|
323
|
+
"""Open the currently selected project item."""
|
324
|
+
if (selection := self.listWidget.selectedIndexes()) and (index := selection[0]).isValid():
|
325
|
+
self.openProjectRequest.emit(Path(str(index.data()[1])))
|
326
|
+
return
|
327
|
+
|
328
|
+
##
|
329
|
+
# Private Slots
|
330
|
+
##
|
331
|
+
|
332
|
+
@pyqtSlot(QModelIndex)
|
333
|
+
def _projectClicked(self, index: QModelIndex) -> None:
|
334
|
+
"""Process single click on project item."""
|
335
|
+
path = self.tr("Path")
|
336
|
+
value = index.data()[1] if index.isValid() else ""
|
337
|
+
text = f"{path}: {value}"
|
338
|
+
self.selectedPath.setText(text)
|
339
|
+
self.selectedPath.setToolTip(text)
|
340
|
+
self.selectedPath.setCursorPosition(0)
|
341
|
+
self.aMissing.setVisible(not (Path(value) / nwFiles.PROJ_FILE).is_file())
|
342
|
+
return
|
343
|
+
|
344
|
+
@pyqtSlot(QModelIndex)
|
345
|
+
def _projectDoubleClicked(self, index: QModelIndex) -> None:
|
346
|
+
"""Process double click on project item."""
|
347
|
+
if index.isValid():
|
348
|
+
self.openProjectRequest.emit(Path(str(index.data()[1])))
|
349
|
+
return
|
350
|
+
|
351
|
+
@pyqtSlot()
|
352
|
+
def _deleteSelectedItem(self) -> None:
|
353
|
+
"""Delete the currently selected project item."""
|
354
|
+
if (selection := self.listWidget.selectedIndexes()) and (index := selection[0]).isValid():
|
355
|
+
text = self.tr(
|
356
|
+
"Remove '{0}' from the recent projects list? "
|
357
|
+
"The project files will not be deleted."
|
358
|
+
).format(index.data()[0])
|
359
|
+
if SHARED.question(text):
|
360
|
+
self.listModel.removeEntry(index)
|
361
|
+
self._selectFirstItem()
|
362
|
+
return
|
363
|
+
|
364
|
+
@pyqtSlot("QPoint")
|
365
|
+
def _openContextMenu(self, pos: QPoint) -> None:
|
366
|
+
"""Open the custom context menu."""
|
367
|
+
ctxMenu = QMenu(self)
|
368
|
+
ctxMenu.setObjectName("ContextMenu") # Used for testing
|
369
|
+
action = ctxMenu.addAction(self.tr("Open Project"))
|
370
|
+
action.triggered.connect(self.openSelectedItem)
|
371
|
+
action = ctxMenu.addAction(self.tr("Remove Project"))
|
372
|
+
action.triggered.connect(self._deleteSelectedItem)
|
373
|
+
ctxMenu.exec_(self.mapToGlobal(pos))
|
374
|
+
ctxMenu.deleteLater()
|
375
|
+
return
|
376
|
+
|
377
|
+
##
|
378
|
+
# Internal Functions
|
379
|
+
##
|
380
|
+
|
381
|
+
def _selectFirstItem(self) -> None:
|
382
|
+
"""Select the first item, if any are available."""
|
383
|
+
index = self.listModel.index(0)
|
384
|
+
self.listWidget.setCurrentIndex(index)
|
385
|
+
self._projectClicked(index)
|
386
|
+
return
|
387
|
+
|
388
|
+
# END Class _OpenProjectPage
|
389
|
+
|
390
|
+
|
391
|
+
class _ProjectListItem(QStyledItemDelegate):
|
392
|
+
|
393
|
+
__slots__ = ("_pPx", "_hPx", "_tFont", "_dFont", "_dPen", "_icon")
|
394
|
+
|
395
|
+
def __init__(self, parent: QWidget) -> None:
|
396
|
+
super().__init__(parent=parent)
|
397
|
+
|
398
|
+
fPx = SHARED.theme.fontPixelSize
|
399
|
+
fPt = SHARED.theme.fontPointSize
|
400
|
+
tPx = round(1.2 * fPx)
|
401
|
+
mPx = CONFIG.pxInt(4)
|
402
|
+
iPx = tPx + fPx
|
403
|
+
|
404
|
+
self._pPx = (mPx//2, 3*mPx//2, iPx + mPx, mPx, mPx + tPx) # Painter coordinates
|
405
|
+
self._hPx = 2*mPx + tPx + fPx # Fixed height
|
406
|
+
|
407
|
+
self._tFont = qApp.font()
|
408
|
+
self._tFont.setPointSizeF(1.2*fPt)
|
409
|
+
self._tFont.setWeight(QFont.Weight.Bold)
|
410
|
+
|
411
|
+
self._dFont = qApp.font()
|
412
|
+
self._dFont.setPointSizeF(fPt)
|
413
|
+
self._dPen = QPen(SHARED.theme.helpText)
|
414
|
+
|
415
|
+
self._icon = SHARED.theme.getPixmap("proj_nwx", (iPx, iPx))
|
416
|
+
|
417
|
+
return
|
418
|
+
|
419
|
+
def paint(self, painter: QPainter, opt: QStyleOptionViewItem, index: QModelIndex) -> None:
|
420
|
+
"""Paint a project entry on the canvas."""
|
421
|
+
rect = opt.rect
|
422
|
+
title, _, details = index.data()
|
423
|
+
tFlag = Qt.TextFlag.TextSingleLine
|
424
|
+
ix, iy, x, y1, y2 = self._pPx
|
425
|
+
|
426
|
+
painter.save()
|
427
|
+
if opt.state & QStyle.StateFlag.State_Selected == QStyle.StateFlag.State_Selected:
|
428
|
+
painter.setOpacity(0.25)
|
429
|
+
painter.fillRect(rect, qApp.palette().highlight())
|
430
|
+
painter.setOpacity(1.0)
|
431
|
+
|
432
|
+
painter.drawPixmap(ix, rect.top() + iy, self._icon)
|
433
|
+
painter.setFont(self._tFont)
|
434
|
+
painter.drawText(rect.adjusted(x, y1, 0, 0), tFlag, title)
|
435
|
+
painter.setFont(self._dFont)
|
436
|
+
painter.setPen(self._dPen)
|
437
|
+
painter.drawText(rect.adjusted(x, y2, 0, 0), tFlag, details)
|
438
|
+
painter.restore()
|
439
|
+
|
440
|
+
return
|
441
|
+
|
442
|
+
def sizeHint(self, opt: QStyleOptionViewItem, index: QModelIndex) -> QSize:
|
443
|
+
"""Set the size hint to fixed height."""
|
444
|
+
return QSize(opt.rect.width(), self._hPx)
|
445
|
+
|
446
|
+
# END Class _ProjectListItem
|
447
|
+
|
448
|
+
|
449
|
+
class _ProjectListModel(QAbstractListModel):
|
450
|
+
|
451
|
+
def __init__(self, parent: QObject) -> None:
|
452
|
+
super().__init__(parent=parent)
|
453
|
+
data = []
|
454
|
+
words = self.tr("Word Count")
|
455
|
+
opened = self.tr("Last Opened")
|
456
|
+
records = sorted(CONFIG.recentProjects.listEntries(), key=lambda x: x[3], reverse=True)
|
457
|
+
for path, title, count, time in records:
|
458
|
+
when = datetime.fromtimestamp(time).strftime("%x")
|
459
|
+
data.append((title, path, f"{opened}: {when}, {words}: {formatInt(count)}"))
|
460
|
+
self._data = data
|
461
|
+
return
|
462
|
+
|
463
|
+
def rowCount(self, parent: QModelIndex | None = None) -> int:
|
464
|
+
"""Return the size of the model."""
|
465
|
+
return len(self._data)
|
466
|
+
|
467
|
+
def data(self, index: QModelIndex, role: int = 0) -> tuple[str, str, str]:
|
468
|
+
"""Return data for an individual item."""
|
469
|
+
try:
|
470
|
+
return self._data[index.row()] if index.isValid() else ("", "", "")
|
471
|
+
except IndexError:
|
472
|
+
return "", "", ""
|
473
|
+
|
474
|
+
def removeEntry(self, index: QModelIndex) -> bool:
|
475
|
+
"""Remove an entry in the model."""
|
476
|
+
if index.isValid() and (path := index.data()[1]):
|
477
|
+
try:
|
478
|
+
self.beginRemoveRows(index.parent(), index.row(), index.row())
|
479
|
+
self._data.pop(index.row())
|
480
|
+
self.endRemoveRows()
|
481
|
+
except IndexError:
|
482
|
+
return False
|
483
|
+
CONFIG.recentProjects.remove(path)
|
484
|
+
return True
|
485
|
+
return False
|
486
|
+
|
487
|
+
# END Class _ProjectListModel
|
488
|
+
|
489
|
+
|
490
|
+
class _NewProjectPage(QWidget):
|
491
|
+
|
492
|
+
openProjectRequest = pyqtSignal(Path)
|
493
|
+
|
494
|
+
def __init__(self, parent: QWidget) -> None:
|
495
|
+
super().__init__(parent=parent)
|
496
|
+
|
497
|
+
# Main Form
|
498
|
+
# =========
|
499
|
+
|
500
|
+
self.projectForm = _NewProjectForm(self)
|
501
|
+
|
502
|
+
self.scrollArea = QScrollArea(self)
|
503
|
+
self.scrollArea.setWidget(self.projectForm)
|
504
|
+
self.scrollArea.setWidgetResizable(True)
|
505
|
+
self.scrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
|
506
|
+
self.scrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
|
507
|
+
|
508
|
+
# Assemble
|
509
|
+
# ========
|
510
|
+
|
511
|
+
self.outerBox = QVBoxLayout()
|
512
|
+
self.outerBox.addWidget(self.scrollArea)
|
513
|
+
self.outerBox.setContentsMargins(0, 0, 0, 0)
|
514
|
+
|
515
|
+
self.setLayout(self.outerBox)
|
516
|
+
|
517
|
+
# Styles
|
518
|
+
# ======
|
519
|
+
|
520
|
+
baseCol = self.palette().base().color()
|
521
|
+
self.setStyleSheet((
|
522
|
+
"QScrollArea {{border: none; background: rgba({r},{g},{b},{a});}} "
|
523
|
+
"_NewProjectForm {{border: none; background: rgba({r},{g},{b},{a});}} "
|
524
|
+
).format(r=baseCol.red(), g=baseCol.green(), b=baseCol.blue(), a=PANEL_ALPHA))
|
525
|
+
|
526
|
+
return
|
527
|
+
|
528
|
+
##
|
529
|
+
# Public Slots
|
530
|
+
##
|
531
|
+
|
532
|
+
@pyqtSlot()
|
533
|
+
def createNewProject(self) -> None:
|
534
|
+
"""Create a new project from the data in the form."""
|
535
|
+
data = self.projectForm.getProjectData()
|
536
|
+
if not data.get("name"):
|
537
|
+
SHARED.error(self.tr("A project name is required."))
|
538
|
+
return
|
539
|
+
builder = ProjectBuilder()
|
540
|
+
if builder.buildProject(data) and (path := builder.projPath):
|
541
|
+
self.openProjectRequest.emit(path)
|
542
|
+
return
|
543
|
+
|
544
|
+
# END Class _NewProjectPage
|
545
|
+
|
546
|
+
|
547
|
+
class _NewProjectForm(QWidget):
|
548
|
+
|
549
|
+
FILL_BLANK = 0
|
550
|
+
FILL_SAMPLE = 1
|
551
|
+
FILL_COPY = 2
|
552
|
+
|
553
|
+
def __init__(self, parent: QWidget) -> None:
|
554
|
+
super().__init__(parent=parent)
|
555
|
+
|
556
|
+
self._basePath = CONFIG.lastPath()
|
557
|
+
self._fillMode = self.FILL_BLANK
|
558
|
+
self._copyPath = None
|
559
|
+
|
560
|
+
iPx = SHARED.theme.baseIconSize
|
561
|
+
sPx = CONFIG.pxInt(16)
|
562
|
+
|
563
|
+
# Project Settings
|
564
|
+
# ================
|
565
|
+
|
566
|
+
# Project Name
|
567
|
+
self.projName = QLineEdit(self)
|
568
|
+
self.projName.setMaxLength(200)
|
569
|
+
self.projName.setPlaceholderText(self.tr("Required"))
|
570
|
+
self.projName.textChanged.connect(self._updateProjPath)
|
571
|
+
|
572
|
+
# Author(s)
|
573
|
+
self.projAuthor = QLineEdit(self)
|
574
|
+
self.projAuthor.setMaxLength(200)
|
575
|
+
self.projAuthor.setPlaceholderText(self.tr("Optional"))
|
576
|
+
|
577
|
+
# Project Path
|
578
|
+
self.projPath = QLineEdit(self)
|
579
|
+
self.projPath.setReadOnly(True)
|
580
|
+
|
581
|
+
self.browsePath = QToolButton(self)
|
582
|
+
self.browsePath.setIcon(SHARED.theme.getIcon("browse"))
|
583
|
+
self.browsePath.clicked.connect(self._doBrowse)
|
584
|
+
|
585
|
+
self.pathBox = QHBoxLayout()
|
586
|
+
self.pathBox.addWidget(self.projPath)
|
587
|
+
self.pathBox.addWidget(self.browsePath)
|
588
|
+
|
589
|
+
# Fill Project
|
590
|
+
self.projFill = QLineEdit(self)
|
591
|
+
self.projFill.setReadOnly(True)
|
592
|
+
|
593
|
+
self.browseFill = QToolButton(self)
|
594
|
+
self.browseFill.setIcon(SHARED.theme.getIcon("add_document"))
|
595
|
+
|
596
|
+
self.fillMenu = _PopLeftDirectionMenu(self.browseFill)
|
597
|
+
|
598
|
+
self.fillBlank = self.fillMenu.addAction(self.tr("Create a fresh project"))
|
599
|
+
self.fillBlank.setIcon(SHARED.theme.getIcon("document"))
|
600
|
+
self.fillBlank.triggered.connect(self._setFillBlank)
|
601
|
+
|
602
|
+
self.fillSample = self.fillMenu.addAction(self.tr("Create an example project"))
|
603
|
+
self.fillSample.setIcon(SHARED.theme.getIcon("add_document"))
|
604
|
+
self.fillSample.triggered.connect(self._setFillSample)
|
605
|
+
|
606
|
+
self.fillCopy = self.fillMenu.addAction(self.tr("Copy an existing project"))
|
607
|
+
self.fillCopy.setIcon(SHARED.theme.getIcon("browse"))
|
608
|
+
self.fillCopy.triggered.connect(self._setFillCopy)
|
609
|
+
|
610
|
+
self.browseFill.setMenu(self.fillMenu)
|
611
|
+
self.browseFill.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
|
612
|
+
|
613
|
+
self.fillBox = QHBoxLayout()
|
614
|
+
self.fillBox.addWidget(self.projFill)
|
615
|
+
self.fillBox.addWidget(self.browseFill)
|
616
|
+
|
617
|
+
# Project Form
|
618
|
+
self.projectForm = QFormLayout()
|
619
|
+
self.projectForm.setAlignment(Qt.AlignmentFlag.AlignLeft)
|
620
|
+
self.projectForm.addRow(self.tr("Project Name"), self.projName)
|
621
|
+
self.projectForm.addRow(self.tr("Author"), self.projAuthor)
|
622
|
+
self.projectForm.addRow(self.tr("Project Path"), self.pathBox)
|
623
|
+
self.projectForm.addRow(self.tr("Prefill Project"), self.fillBox)
|
624
|
+
|
625
|
+
# Chapters and Scenes
|
626
|
+
# ===================
|
627
|
+
|
628
|
+
self.numChapters = NSpinBox(self)
|
629
|
+
self.numChapters.setRange(0, 200)
|
630
|
+
self.numChapters.setValue(5)
|
631
|
+
self.numChapters.setToolTip(self.tr("Set to 0 to only add scenes"))
|
632
|
+
|
633
|
+
self.chapterBox = QHBoxLayout()
|
634
|
+
self.chapterBox.addWidget(QLabel(self.tr("Add")))
|
635
|
+
self.chapterBox.addWidget(self.numChapters)
|
636
|
+
self.chapterBox.addWidget(QLabel(self.tr("chapter documents")))
|
637
|
+
self.chapterBox.addStretch(1)
|
638
|
+
|
639
|
+
self.numScenes = NSpinBox(self)
|
640
|
+
self.numScenes.setRange(0, 200)
|
641
|
+
self.numScenes.setValue(5)
|
642
|
+
|
643
|
+
self.sceneBox = QHBoxLayout()
|
644
|
+
self.sceneBox.addWidget(QLabel(self.tr("Add")))
|
645
|
+
self.sceneBox.addWidget(self.numScenes)
|
646
|
+
self.sceneBox.addWidget(QLabel(self.tr("scene documents (to each chapter)")))
|
647
|
+
self.sceneBox.addStretch(1)
|
648
|
+
|
649
|
+
self.novelForm = QVBoxLayout()
|
650
|
+
self.novelForm.addLayout(self.chapterBox)
|
651
|
+
self.novelForm.addLayout(self.sceneBox)
|
652
|
+
|
653
|
+
# Project Notes
|
654
|
+
# =============
|
655
|
+
|
656
|
+
self.addPlot = NSwitch(self, height=iPx)
|
657
|
+
self.addPlot.setChecked(True)
|
658
|
+
self.addPlot.clicked.connect(self._syncSwitches)
|
659
|
+
|
660
|
+
self.addChar = NSwitch(self, height=iPx)
|
661
|
+
self.addChar.setChecked(True)
|
662
|
+
self.addChar.clicked.connect(self._syncSwitches)
|
663
|
+
|
664
|
+
self.addWorld = NSwitch(self, height=iPx)
|
665
|
+
self.addWorld.setChecked(False)
|
666
|
+
self.addWorld.clicked.connect(self._syncSwitches)
|
667
|
+
|
668
|
+
self.addNotes = NSwitch(self, height=iPx)
|
669
|
+
self.addNotes.setChecked(False)
|
670
|
+
|
671
|
+
self.notesForm = QFormLayout()
|
672
|
+
self.notesForm.setAlignment(Qt.AlignmentFlag.AlignLeft)
|
673
|
+
self.notesForm.addRow(self.tr("Add a folder for plot notes"), self.addPlot)
|
674
|
+
self.notesForm.addRow(self.tr("Add a folder for character notes"), self.addChar)
|
675
|
+
self.notesForm.addRow(self.tr("Add a folder for location notes"), self.addWorld)
|
676
|
+
self.notesForm.addRow(self.tr("Add example notes to the above"), self.addNotes)
|
677
|
+
|
678
|
+
# Assemble
|
679
|
+
# ========
|
680
|
+
|
681
|
+
self.extraBox = QVBoxLayout()
|
682
|
+
self.extraBox.addWidget(QLabel("<b>{0}</b>".format(self.tr("Chapters and Scenes"))))
|
683
|
+
self.extraBox.addLayout(self.novelForm)
|
684
|
+
self.extraBox.addSpacing(sPx)
|
685
|
+
self.extraBox.addWidget(QLabel("<b>{0}</b>".format(self.tr("Project Notes"))))
|
686
|
+
self.extraBox.addLayout(self.notesForm)
|
687
|
+
self.extraBox.setContentsMargins(0, 0, 0, 0)
|
688
|
+
|
689
|
+
self.extraWidget = QWidget(self)
|
690
|
+
self.extraWidget.setLayout(self.extraBox)
|
691
|
+
self.extraWidget.setContentsMargins(0, 0, 0, 0)
|
692
|
+
|
693
|
+
self.formBox = QVBoxLayout()
|
694
|
+
self.formBox.addWidget(QLabel("<b>{0}</b>".format(self.tr("Create New Project"))))
|
695
|
+
self.formBox.addLayout(self.projectForm)
|
696
|
+
self.formBox.addSpacing(sPx)
|
697
|
+
self.formBox.addWidget(self.extraWidget)
|
698
|
+
self.formBox.addStretch(1)
|
699
|
+
|
700
|
+
self.setLayout(self.formBox)
|
701
|
+
|
702
|
+
self._updateProjPath()
|
703
|
+
self._updateFillInfo()
|
704
|
+
|
705
|
+
return
|
706
|
+
|
707
|
+
def getProjectData(self) -> dict:
|
708
|
+
"""Collect form data and return it as a dictionary."""
|
709
|
+
roots = []
|
710
|
+
if self.addPlot.isChecked():
|
711
|
+
roots.append(nwItemClass.PLOT)
|
712
|
+
if self.addChar.isChecked():
|
713
|
+
roots.append(nwItemClass.CHARACTER)
|
714
|
+
if self.addWorld.isChecked():
|
715
|
+
roots.append(nwItemClass.WORLD)
|
716
|
+
return {
|
717
|
+
"name": self.projName.text().strip(),
|
718
|
+
"author": self.projAuthor.text().strip(),
|
719
|
+
"path": self.projPath.text(),
|
720
|
+
"blank": self._fillMode == self.FILL_BLANK,
|
721
|
+
"sample": self._fillMode == self.FILL_SAMPLE,
|
722
|
+
"template": self._copyPath if self._fillMode == self.FILL_COPY else None,
|
723
|
+
"chapters": self.numChapters.value(),
|
724
|
+
"scenes": self.numScenes.value(),
|
725
|
+
"roots": roots,
|
726
|
+
"notes": self.addNotes.isChecked(),
|
727
|
+
}
|
728
|
+
|
729
|
+
##
|
730
|
+
# Private Slots
|
731
|
+
##
|
732
|
+
|
733
|
+
@pyqtSlot()
|
734
|
+
def _doBrowse(self) -> None:
|
735
|
+
"""Select a project folder."""
|
736
|
+
if projDir := QFileDialog.getExistingDirectory(
|
737
|
+
self, self.tr("Select Project Folder"),
|
738
|
+
str(self._basePath), options=QFileDialog.ShowDirsOnly
|
739
|
+
):
|
740
|
+
self._basePath = Path(projDir)
|
741
|
+
self._updateProjPath()
|
742
|
+
CONFIG.setLastPath(self._basePath)
|
743
|
+
return
|
744
|
+
|
745
|
+
@pyqtSlot()
|
746
|
+
def _updateProjPath(self) -> None:
|
747
|
+
"""Update the path box to show the full project path."""
|
748
|
+
projName = makeFileNameSafe(self.projName.text().strip())
|
749
|
+
self.projPath.setText(str(self._basePath / projName))
|
750
|
+
return
|
751
|
+
|
752
|
+
@pyqtSlot()
|
753
|
+
def _syncSwitches(self):
|
754
|
+
"""Check if the add notes option should also be switched off."""
|
755
|
+
addPlot = self.addPlot.isChecked()
|
756
|
+
addChar = self.addChar.isChecked()
|
757
|
+
addWorld = self.addWorld.isChecked()
|
758
|
+
if not (addPlot or addChar or addWorld):
|
759
|
+
self.addNotes.setChecked(False)
|
760
|
+
return
|
761
|
+
|
762
|
+
@pyqtSlot()
|
763
|
+
def _setFillBlank(self) -> None:
|
764
|
+
"""Set fill mode to blank project."""
|
765
|
+
self._fillMode = self.FILL_BLANK
|
766
|
+
self._updateFillInfo()
|
767
|
+
return
|
768
|
+
|
769
|
+
@pyqtSlot()
|
770
|
+
def _setFillSample(self) -> None:
|
771
|
+
"""Set fill mode to sample project."""
|
772
|
+
self._fillMode = self.FILL_SAMPLE
|
773
|
+
self._updateFillInfo()
|
774
|
+
return
|
775
|
+
|
776
|
+
@pyqtSlot()
|
777
|
+
def _setFillCopy(self) -> None:
|
778
|
+
"""Set fill mode to copy project."""
|
779
|
+
if copyPath := SHARED.getProjectPath(self, allowZip=True):
|
780
|
+
self._fillMode = self.FILL_COPY
|
781
|
+
self._copyPath = copyPath
|
782
|
+
self._updateFillInfo()
|
783
|
+
return
|
784
|
+
|
785
|
+
##
|
786
|
+
# Internal Functions
|
787
|
+
##
|
788
|
+
|
789
|
+
def _updateFillInfo(self) -> None:
|
790
|
+
"""Update the text of the project fill box."""
|
791
|
+
text = ""
|
792
|
+
if self._fillMode == self.FILL_BLANK:
|
793
|
+
text = self.tr("Fresh Project")
|
794
|
+
elif self._fillMode == self.FILL_SAMPLE:
|
795
|
+
text = self.tr("Example Project")
|
796
|
+
elif self._fillMode == self.FILL_COPY:
|
797
|
+
text = self.tr("Template: {0}").format(str(self._copyPath))
|
798
|
+
|
799
|
+
self.projFill.setText(text)
|
800
|
+
self.projFill.setToolTip(text)
|
801
|
+
self.projFill.setCursorPosition(0)
|
802
|
+
self.extraWidget.setVisible(self._fillMode == self.FILL_BLANK)
|
803
|
+
|
804
|
+
return
|
805
|
+
|
806
|
+
# END Class _NewProjectForm
|
807
|
+
|
808
|
+
|
809
|
+
class _PopLeftDirectionMenu(QMenu):
|
810
|
+
|
811
|
+
def event(self, event: QEvent) -> bool:
|
812
|
+
"""Overload the show event and move the menu popup location."""
|
813
|
+
if event.type() == QEvent.Show:
|
814
|
+
if isinstance(parent := self.parent(), QWidget):
|
815
|
+
offset = QPoint(parent.width() - self.width(), parent.height())
|
816
|
+
self.move(parent.mapToGlobal(offset))
|
817
|
+
return super(_PopLeftDirectionMenu, self).event(event)
|
818
|
+
|
819
|
+
# END Class _PopLeftDirectionMenu
|