novelWriter 2.3rc1__py3-none-any.whl → 2.4__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.3rc1.dist-info → novelWriter-2.4.dist-info}/METADATA +5 -6
- {novelWriter-2.3rc1.dist-info → novelWriter-2.4.dist-info}/RECORD +119 -109
- {novelWriter-2.3rc1.dist-info → novelWriter-2.4.dist-info}/WHEEL +1 -1
- novelWriter-2.4.dist-info/entry_points.txt +2 -0
- novelwriter/__init__.py +17 -10
- 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_pt_BR.qm +0 -0
- novelwriter/assets/i18n/nw_zh_CN.qm +0 -0
- novelwriter/assets/i18n/project_nl_NL.json +11 -0
- novelwriter/assets/i18n/project_pt_BR.json +11 -0
- novelwriter/assets/icons/none.svg +4 -0
- novelwriter/assets/icons/typicons_dark/icons.conf +4 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-mark.svg +7 -0
- novelwriter/assets/icons/typicons_dark/typ_refresh-flipped.svg +1 -1
- novelwriter/assets/icons/typicons_dark/typ_refresh.svg +1 -1
- novelwriter/assets/icons/typicons_dark/typ_search-grey.svg +4 -0
- novelwriter/assets/icons/typicons_dark/typ_times.svg +1 -1
- novelwriter/assets/icons/typicons_dark/typ_unfold-hidden.svg +4 -0
- novelwriter/assets/icons/typicons_dark/typ_unfold-visible.svg +4 -0
- novelwriter/assets/icons/typicons_light/icons.conf +4 -0
- novelwriter/assets/icons/typicons_light/nw_tb-mark.svg +7 -0
- novelwriter/assets/icons/typicons_light/typ_refresh-flipped.svg +1 -1
- novelwriter/assets/icons/typicons_light/typ_refresh.svg +1 -1
- novelwriter/assets/icons/typicons_light/typ_search-grey.svg +4 -0
- novelwriter/assets/icons/typicons_light/typ_times.svg +1 -1
- novelwriter/assets/icons/typicons_light/typ_unfold-hidden.svg +4 -0
- novelwriter/assets/icons/typicons_light/typ_unfold-visible.svg +4 -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 +25 -23
- novelwriter/assets/themes/cyberpunk_night.conf +29 -0
- novelwriter/common.py +12 -4
- novelwriter/config.py +47 -16
- novelwriter/constants.py +5 -6
- novelwriter/core/buildsettings.py +64 -44
- novelwriter/core/coretools.py +97 -13
- novelwriter/core/docbuild.py +74 -7
- novelwriter/core/document.py +24 -3
- novelwriter/core/index.py +31 -112
- novelwriter/core/project.py +11 -15
- novelwriter/core/projectxml.py +3 -2
- novelwriter/core/sessions.py +2 -2
- novelwriter/core/spellcheck.py +3 -3
- novelwriter/core/status.py +6 -5
- novelwriter/core/storage.py +16 -6
- novelwriter/core/tohtml.py +22 -25
- novelwriter/core/tokenizer.py +417 -237
- novelwriter/core/tomd.py +17 -8
- novelwriter/core/toodt.py +386 -351
- novelwriter/core/tree.py +8 -8
- novelwriter/dialogs/about.py +10 -12
- novelwriter/dialogs/docmerge.py +17 -14
- novelwriter/dialogs/docsplit.py +20 -19
- novelwriter/dialogs/editlabel.py +5 -4
- novelwriter/dialogs/preferences.py +32 -40
- novelwriter/dialogs/projectsettings.py +31 -28
- novelwriter/dialogs/quotes.py +10 -9
- novelwriter/dialogs/wordlist.py +18 -15
- novelwriter/enum.py +17 -14
- novelwriter/error.py +14 -12
- novelwriter/extensions/circularprogress.py +12 -8
- novelwriter/extensions/configlayout.py +23 -3
- novelwriter/extensions/modified.py +51 -2
- novelwriter/extensions/pagedsidebar.py +16 -14
- novelwriter/extensions/simpleprogress.py +3 -1
- novelwriter/extensions/statusled.py +3 -1
- novelwriter/extensions/switch.py +10 -9
- novelwriter/extensions/switchbox.py +14 -13
- novelwriter/extensions/versioninfo.py +1 -1
- novelwriter/gui/doceditor.py +433 -496
- novelwriter/gui/dochighlight.py +54 -33
- novelwriter/gui/docviewer.py +162 -175
- novelwriter/gui/docviewerpanel.py +20 -37
- novelwriter/gui/editordocument.py +15 -4
- novelwriter/gui/itemdetails.py +51 -54
- novelwriter/gui/mainmenu.py +37 -17
- novelwriter/gui/noveltree.py +31 -37
- novelwriter/gui/outline.py +120 -98
- novelwriter/gui/projtree.py +114 -112
- novelwriter/gui/search.py +362 -0
- novelwriter/gui/sidebar.py +36 -45
- novelwriter/gui/statusbar.py +14 -14
- novelwriter/gui/theme.py +116 -34
- novelwriter/guimain.py +216 -207
- novelwriter/shared.py +31 -6
- novelwriter/text/counting.py +138 -0
- novelwriter/tools/dictionaries.py +15 -14
- novelwriter/tools/lipsum.py +20 -17
- novelwriter/tools/manusbuild.py +43 -35
- novelwriter/tools/manuscript.py +381 -104
- novelwriter/tools/manussettings.py +263 -125
- novelwriter/tools/noveldetails.py +21 -19
- novelwriter/tools/welcome.py +59 -57
- novelwriter/tools/writingstats.py +61 -55
- novelwriter/types.py +90 -0
- novelWriter-2.3rc1.dist-info/entry_points.txt +0 -5
- novelwriter/core/__init__.py +0 -3
- novelwriter/dialogs/__init__.py +0 -3
- novelwriter/extensions/__init__.py +0 -3
- novelwriter/gui/__init__.py +0 -3
- novelwriter/tools/__init__.py +0 -3
- {novelWriter-2.3rc1.dist-info → novelWriter-2.4.dist-info}/LICENSE.md +0 -0
- {novelWriter-2.3rc1.dist-info → novelWriter-2.4.dist-info}/top_level.txt +0 -0
@@ -26,20 +26,21 @@ from __future__ import annotations
|
|
26
26
|
|
27
27
|
import logging
|
28
28
|
|
29
|
-
from PyQt5.QtGui import QCloseEvent, QColor, QIcon, QPixmap
|
30
29
|
from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot
|
30
|
+
from PyQt5.QtGui import QCloseEvent, QColor, QIcon, QPixmap
|
31
31
|
from PyQt5.QtWidgets import (
|
32
|
-
QColorDialog, QDialog, QDialogButtonBox, QHBoxLayout,
|
33
|
-
QPushButton, QStackedWidget, QTreeWidget, QTreeWidgetItem,
|
34
|
-
|
32
|
+
QApplication, QColorDialog, QDialog, QDialogButtonBox, QHBoxLayout,
|
33
|
+
QLineEdit, QPushButton, QStackedWidget, QTreeWidget, QTreeWidgetItem,
|
34
|
+
QVBoxLayout, QWidget
|
35
35
|
)
|
36
36
|
|
37
37
|
from novelwriter import CONFIG, SHARED
|
38
38
|
from novelwriter.common import simplified
|
39
|
-
from novelwriter.extensions.switch import NSwitch
|
40
|
-
from novelwriter.extensions.modified import NComboBox
|
41
39
|
from novelwriter.extensions.configlayout import NColourLabel, NFixedPage, NScrollableForm
|
40
|
+
from novelwriter.extensions.modified import NComboBox, NIconToolButton
|
42
41
|
from novelwriter.extensions.pagedsidebar import NPagedSideBar
|
42
|
+
from novelwriter.extensions.switch import NSwitch
|
43
|
+
from novelwriter.types import QtDialogCancel, QtDialogSave, QtUserRole
|
43
44
|
|
44
45
|
logger = logging.getLogger(__name__)
|
45
46
|
|
@@ -83,9 +84,7 @@ class GuiProjectSettings(QDialog):
|
|
83
84
|
self.sidebar.buttonClicked.connect(self._sidebarClicked)
|
84
85
|
|
85
86
|
# Buttons
|
86
|
-
self.buttonBox = QDialogButtonBox(
|
87
|
-
QDialogButtonBox.StandardButton.Save | QDialogButtonBox.StandardButton.Cancel
|
88
|
-
)
|
87
|
+
self.buttonBox = QDialogButtonBox(QtDialogSave | QtDialogCancel, self)
|
89
88
|
self.buttonBox.accepted.connect(self._doSave)
|
90
89
|
self.buttonBox.rejected.connect(self.close)
|
91
90
|
|
@@ -195,7 +194,7 @@ class GuiProjectSettings(QDialog):
|
|
195
194
|
project.data.setAutoReplace(newList)
|
196
195
|
|
197
196
|
self.newProjectSettingsReady.emit(rebuildTrees)
|
198
|
-
|
197
|
+
QApplication.processEvents()
|
199
198
|
self.close()
|
200
199
|
|
201
200
|
return
|
@@ -258,17 +257,17 @@ class _SettingsPage(NScrollableForm):
|
|
258
257
|
)
|
259
258
|
|
260
259
|
# Project Language
|
260
|
+
projLang = data.language or CONFIG.guiLocale
|
261
261
|
self.projLang = NComboBox(self)
|
262
262
|
self.projLang.setMinimumWidth(xW)
|
263
263
|
for tag, language in CONFIG.listLanguages(CONFIG.LANG_PROJ):
|
264
264
|
self.projLang.addItem(language, tag)
|
265
|
+
self.projLang.setCurrentData(projLang, projLang)
|
265
266
|
self.addRow(
|
266
267
|
self.tr("Project language"), self.projLang,
|
267
268
|
self.tr("Only used when building the manuscript."),
|
268
269
|
stretch=(3, 2)
|
269
270
|
)
|
270
|
-
if (idx := self.projLang.findData(data.language)) != -1:
|
271
|
-
self.projLang.setCurrentIndex(idx)
|
272
271
|
|
273
272
|
# Spell Check Language
|
274
273
|
self.spellLang = NComboBox(self)
|
@@ -305,9 +304,9 @@ class _StatusPage(NFixedPage):
|
|
305
304
|
COL_LABEL = 0
|
306
305
|
COL_USAGE = 1
|
307
306
|
|
308
|
-
KEY_ROLE =
|
309
|
-
COL_ROLE =
|
310
|
-
NUM_ROLE =
|
307
|
+
KEY_ROLE = QtUserRole
|
308
|
+
COL_ROLE = QtUserRole + 1
|
309
|
+
NUM_ROLE = QtUserRole + 2
|
311
310
|
|
312
311
|
def __init__(self, parent: QWidget, isStatus: bool) -> None:
|
313
312
|
super().__init__(parent=parent)
|
@@ -329,7 +328,9 @@ class _StatusPage(NFixedPage):
|
|
329
328
|
self._colDeleted = []
|
330
329
|
self._selColour = QColor(100, 100, 100)
|
331
330
|
|
332
|
-
self.iPx = SHARED.theme.
|
331
|
+
self.iPx = SHARED.theme.baseIconHeight
|
332
|
+
iSz = SHARED.theme.baseIconSize
|
333
|
+
bSz = SHARED.theme.buttonIconSize
|
333
334
|
|
334
335
|
# Title
|
335
336
|
self.pageTitle = NColourLabel(
|
@@ -348,16 +349,16 @@ class _StatusPage(NFixedPage):
|
|
348
349
|
self._addItem(key, entry["name"], entry["cols"], entry["count"])
|
349
350
|
|
350
351
|
# List Controls
|
351
|
-
self.addButton =
|
352
|
+
self.addButton = NIconToolButton(self, iSz, "add")
|
352
353
|
self.addButton.clicked.connect(self._newItem)
|
353
354
|
|
354
|
-
self.delButton =
|
355
|
+
self.delButton = NIconToolButton(self, iSz, "remove")
|
355
356
|
self.delButton.clicked.connect(self._delItem)
|
356
357
|
|
357
|
-
self.upButton =
|
358
|
+
self.upButton = NIconToolButton(self, iSz, "up")
|
358
359
|
self.upButton.clicked.connect(lambda: self._moveItem(-1))
|
359
360
|
|
360
|
-
self.dnButton =
|
361
|
+
self.dnButton = NIconToolButton(self, iSz, "down")
|
361
362
|
self.dnButton.clicked.connect(lambda: self._moveItem(1))
|
362
363
|
|
363
364
|
# Edit Form
|
@@ -368,12 +369,12 @@ class _StatusPage(NFixedPage):
|
|
368
369
|
|
369
370
|
self.colPixmap = QPixmap(self.iPx, self.iPx)
|
370
371
|
self.colPixmap.fill(QColor(100, 100, 100))
|
371
|
-
self.colButton = QPushButton(QIcon(self.colPixmap), self.tr("Colour"))
|
372
|
-
self.colButton.setIconSize(
|
372
|
+
self.colButton = QPushButton(QIcon(self.colPixmap), self.tr("Colour"), self)
|
373
|
+
self.colButton.setIconSize(bSz)
|
373
374
|
self.colButton.setEnabled(False)
|
374
375
|
self.colButton.clicked.connect(self._selectColour)
|
375
376
|
|
376
|
-
self.saveButton = QPushButton(self.tr("Save"))
|
377
|
+
self.saveButton = QPushButton(self.tr("Save"), self)
|
377
378
|
self.saveButton.setEnabled(False)
|
378
379
|
self.saveButton.clicked.connect(self._saveItem)
|
379
380
|
|
@@ -589,6 +590,8 @@ class _ReplacePage(NFixedPage):
|
|
589
590
|
|
590
591
|
self._changed = False
|
591
592
|
|
593
|
+
iSz = SHARED.theme.baseIconSize
|
594
|
+
|
592
595
|
wCol0 = CONFIG.pxInt(
|
593
596
|
SHARED.project.options.getInt("GuiProjectSettings", "replaceColW", 130)
|
594
597
|
)
|
@@ -600,7 +603,7 @@ class _ReplacePage(NFixedPage):
|
|
600
603
|
)
|
601
604
|
|
602
605
|
# List Box
|
603
|
-
self.listBox = QTreeWidget()
|
606
|
+
self.listBox = QTreeWidget(self)
|
604
607
|
self.listBox.setHeaderLabels([self.tr("Keyword"), self.tr("Replace With")])
|
605
608
|
self.listBox.setColumnWidth(self.COL_KEY, wCol0)
|
606
609
|
self.listBox.setIndentation(0)
|
@@ -610,14 +613,14 @@ class _ReplacePage(NFixedPage):
|
|
610
613
|
newItem = QTreeWidgetItem(["<%s>" % aKey, aVal])
|
611
614
|
self.listBox.addTopLevelItem(newItem)
|
612
615
|
|
613
|
-
self.listBox.sortByColumn(self.COL_KEY, Qt.AscendingOrder)
|
616
|
+
self.listBox.sortByColumn(self.COL_KEY, Qt.SortOrder.AscendingOrder)
|
614
617
|
self.listBox.setSortingEnabled(True)
|
615
618
|
|
616
619
|
# List Controls
|
617
|
-
self.addButton =
|
620
|
+
self.addButton = NIconToolButton(self, iSz, "add")
|
618
621
|
self.addButton.clicked.connect(self._addEntry)
|
619
622
|
|
620
|
-
self.delButton =
|
623
|
+
self.delButton = NIconToolButton(self, iSz, "remove")
|
621
624
|
self.delButton.clicked.connect(self._delEntry)
|
622
625
|
|
623
626
|
# Edit Form
|
@@ -630,7 +633,7 @@ class _ReplacePage(NFixedPage):
|
|
630
633
|
self.editValue.setEnabled(False)
|
631
634
|
self.editValue.setMaxLength(80)
|
632
635
|
|
633
|
-
self.saveButton = QPushButton(self.tr("Save"))
|
636
|
+
self.saveButton = QPushButton(self.tr("Save"), self)
|
634
637
|
self.saveButton.clicked.connect(self._saveEntry)
|
635
638
|
|
636
639
|
# Assemble
|
novelwriter/dialogs/quotes.py
CHANGED
@@ -26,7 +26,7 @@ from __future__ import annotations
|
|
26
26
|
import logging
|
27
27
|
|
28
28
|
from PyQt5.QtGui import QFontMetrics
|
29
|
-
from PyQt5.QtCore import QSize,
|
29
|
+
from PyQt5.QtCore import QSize, pyqtSlot
|
30
30
|
from PyQt5.QtWidgets import (
|
31
31
|
QDialog, QDialogButtonBox, QFrame, QHBoxLayout, QLabel, QListWidget,
|
32
32
|
QListWidgetItem, QVBoxLayout, QWidget
|
@@ -34,6 +34,7 @@ from PyQt5.QtWidgets import (
|
|
34
34
|
|
35
35
|
from novelwriter import CONFIG
|
36
36
|
from novelwriter.constants import trConst, nwQuotes
|
37
|
+
from novelwriter.types import QtAlignCenter, QtAlignTop, QtDialogCancel, QtDialogOk, QtUserRole
|
37
38
|
|
38
39
|
logger = logging.getLogger(__name__)
|
39
40
|
|
@@ -42,7 +43,7 @@ class GuiQuoteSelect(QDialog):
|
|
42
43
|
|
43
44
|
_selected = ""
|
44
45
|
|
45
|
-
D_KEY =
|
46
|
+
D_KEY = QtUserRole
|
46
47
|
|
47
48
|
def __init__(self, parent: QWidget, current: str = '"') -> None:
|
48
49
|
super().__init__(parent=parent)
|
@@ -65,14 +66,14 @@ class GuiQuoteSelect(QDialog):
|
|
65
66
|
lblFont.setPointSizeF(4*lblFont.pointSizeF())
|
66
67
|
|
67
68
|
# Preview Label
|
68
|
-
self.previewLabel = QLabel(current)
|
69
|
+
self.previewLabel = QLabel(current, self)
|
69
70
|
self.previewLabel.setFont(lblFont)
|
70
71
|
self.previewLabel.setFixedSize(QSize(pxW, pxH))
|
71
|
-
self.previewLabel.setAlignment(
|
72
|
-
self.previewLabel.setFrameStyle(QFrame.Box | QFrame.Plain)
|
72
|
+
self.previewLabel.setAlignment(QtAlignCenter)
|
73
|
+
self.previewLabel.setFrameStyle(QFrame.Shape.Box | QFrame.Shadow.Plain)
|
73
74
|
|
74
75
|
# Quote Symbols
|
75
|
-
self.listBox = QListWidget()
|
76
|
+
self.listBox = QListWidget(self)
|
76
77
|
self.listBox.itemSelectionChanged.connect(self._selectedSymbol)
|
77
78
|
|
78
79
|
minSize = 100
|
@@ -89,12 +90,12 @@ class GuiQuoteSelect(QDialog):
|
|
89
90
|
self.listBox.setMinimumHeight(CONFIG.pxInt(150))
|
90
91
|
|
91
92
|
# Buttons
|
92
|
-
self.buttonBox = QDialogButtonBox(
|
93
|
+
self.buttonBox = QDialogButtonBox(QtDialogOk | QtDialogCancel, self)
|
93
94
|
self.buttonBox.accepted.connect(self.accept)
|
94
95
|
self.buttonBox.rejected.connect(self.reject)
|
95
96
|
|
96
97
|
# Assemble
|
97
|
-
self.labelBox.addWidget(self.previewLabel, 0,
|
98
|
+
self.labelBox.addWidget(self.previewLabel, 0, QtAlignTop)
|
98
99
|
self.labelBox.addStretch(1)
|
99
100
|
|
100
101
|
self.innerBox.addLayout(self.labelBox)
|
@@ -122,7 +123,7 @@ class GuiQuoteSelect(QDialog):
|
|
122
123
|
def getQuote(cls, parent: QWidget, current: str = "") -> tuple[str, bool]:
|
123
124
|
"""Pop the dialog and return the result."""
|
124
125
|
cls = GuiQuoteSelect(parent, current=current)
|
125
|
-
cls.
|
126
|
+
cls.exec()
|
126
127
|
quote = cls._selected
|
127
128
|
accepted = cls.result() == QDialog.DialogCode.Accepted
|
128
129
|
cls.deleteLater()
|
novelwriter/dialogs/wordlist.py
CHANGED
@@ -28,17 +28,19 @@ import logging
|
|
28
28
|
from typing import TYPE_CHECKING
|
29
29
|
from pathlib import Path
|
30
30
|
|
31
|
-
from PyQt5.QtGui import QCloseEvent
|
32
31
|
from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot
|
32
|
+
from PyQt5.QtGui import QCloseEvent
|
33
33
|
from PyQt5.QtWidgets import (
|
34
|
-
QAbstractItemView, QDialog, QDialogButtonBox, QFileDialog,
|
35
|
-
QLineEdit, QListWidget,
|
34
|
+
QAbstractItemView, QApplication, QDialog, QDialogButtonBox, QFileDialog,
|
35
|
+
QHBoxLayout, QLineEdit, QListWidget, QVBoxLayout
|
36
36
|
)
|
37
37
|
|
38
38
|
from novelwriter import CONFIG, SHARED
|
39
39
|
from novelwriter.common import formatFileFilter
|
40
40
|
from novelwriter.core.spellcheck import UserDictionary
|
41
41
|
from novelwriter.extensions.configlayout import NColourLabel
|
42
|
+
from novelwriter.extensions.modified import NIconToolButton
|
43
|
+
from novelwriter.types import QtDialogClose, QtDialogSave
|
42
44
|
|
43
45
|
if TYPE_CHECKING: # pragma: no cover
|
44
46
|
from novelwriter.guimain import GuiMain
|
@@ -57,6 +59,7 @@ class GuiWordList(QDialog):
|
|
57
59
|
self.setObjectName("GuiWordList")
|
58
60
|
self.setWindowTitle(self.tr("Project Word List"))
|
59
61
|
|
62
|
+
iSz = SHARED.theme.baseIconSize
|
60
63
|
mS = CONFIG.pxInt(250)
|
61
64
|
wW = CONFIG.pxInt(320)
|
62
65
|
wH = CONFIG.pxInt(340)
|
@@ -70,15 +73,15 @@ class GuiWordList(QDialog):
|
|
70
73
|
|
71
74
|
# Header
|
72
75
|
self.headLabel = NColourLabel(
|
73
|
-
"Project Word List", SHARED.theme.helpText, parent=self,
|
76
|
+
self.tr("Project Word List"), SHARED.theme.helpText, parent=self,
|
74
77
|
scale=NColourLabel.HEADER_SCALE
|
75
78
|
)
|
76
79
|
|
77
|
-
self.importButton =
|
80
|
+
self.importButton = NIconToolButton(self, iSz, "import")
|
78
81
|
self.importButton.setToolTip(self.tr("Import words from text file"))
|
79
82
|
self.importButton.clicked.connect(self._importWords)
|
80
83
|
|
81
|
-
self.exportButton =
|
84
|
+
self.exportButton = NIconToolButton(self, iSz, "export")
|
82
85
|
self.exportButton.setToolTip(self.tr("Export words to text file"))
|
83
86
|
self.exportButton.clicked.connect(self._exportWords)
|
84
87
|
|
@@ -89,17 +92,17 @@ class GuiWordList(QDialog):
|
|
89
92
|
|
90
93
|
# List Box
|
91
94
|
self.listBox = QListWidget(self)
|
92
|
-
self.listBox.setDragDropMode(QAbstractItemView.NoDragDrop)
|
95
|
+
self.listBox.setDragDropMode(QAbstractItemView.DragDropMode.NoDragDrop)
|
93
96
|
self.listBox.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
|
94
97
|
self.listBox.setSortingEnabled(True)
|
95
98
|
|
96
99
|
# Add/Remove Form
|
97
100
|
self.newEntry = QLineEdit(self)
|
98
101
|
|
99
|
-
self.addButton =
|
102
|
+
self.addButton = NIconToolButton(self, iSz, "add")
|
100
103
|
self.addButton.clicked.connect(self._doAdd)
|
101
104
|
|
102
|
-
self.delButton =
|
105
|
+
self.delButton = NIconToolButton(self, iSz, "remove")
|
103
106
|
self.delButton.clicked.connect(self._doDelete)
|
104
107
|
|
105
108
|
self.editBox = QHBoxLayout()
|
@@ -108,7 +111,7 @@ class GuiWordList(QDialog):
|
|
108
111
|
self.editBox.addWidget(self.delButton, 0)
|
109
112
|
|
110
113
|
# Buttons
|
111
|
-
self.buttonBox = QDialogButtonBox(
|
114
|
+
self.buttonBox = QDialogButtonBox(QtDialogSave | QtDialogClose, self)
|
112
115
|
self.buttonBox.accepted.connect(self._doSave)
|
113
116
|
self.buttonBox.rejected.connect(self.close)
|
114
117
|
|
@@ -155,7 +158,7 @@ class GuiWordList(QDialog):
|
|
155
158
|
self.newEntry.setText("")
|
156
159
|
self.listBox.clearSelection()
|
157
160
|
self._addWord(word)
|
158
|
-
if items := self.listBox.findItems(word, Qt.MatchExactly):
|
161
|
+
if items := self.listBox.findItems(word, Qt.MatchFlag.MatchExactly):
|
159
162
|
self.listBox.setCurrentItem(items[0])
|
160
163
|
self.listBox.scrollToItem(items[0], QAbstractItemView.ScrollHint.PositionAtCenter)
|
161
164
|
return
|
@@ -175,7 +178,7 @@ class GuiWordList(QDialog):
|
|
175
178
|
userDict.add(word)
|
176
179
|
userDict.save()
|
177
180
|
self.newWordListReady.emit()
|
178
|
-
|
181
|
+
QApplication.processEvents()
|
179
182
|
self.close()
|
180
183
|
return
|
181
184
|
|
@@ -187,7 +190,7 @@ class GuiWordList(QDialog):
|
|
187
190
|
))
|
188
191
|
ffilter = formatFileFilter(["*.txt", "*"])
|
189
192
|
path, _ = QFileDialog.getOpenFileName(
|
190
|
-
self, self.tr("Import File"), str(
|
193
|
+
self, self.tr("Import File"), str(CONFIG.homePath()), filter=ffilter
|
191
194
|
)
|
192
195
|
if path:
|
193
196
|
try:
|
@@ -204,7 +207,7 @@ class GuiWordList(QDialog):
|
|
204
207
|
def _exportWords(self) -> None:
|
205
208
|
"""Export words to file."""
|
206
209
|
path, _ = QFileDialog.getSaveFileName(
|
207
|
-
self, self.tr("Export File"), str(
|
210
|
+
self, self.tr("Export File"), str(CONFIG.homePath())
|
208
211
|
)
|
209
212
|
if path:
|
210
213
|
try:
|
@@ -242,7 +245,7 @@ class GuiWordList(QDialog):
|
|
242
245
|
|
243
246
|
def _addWord(self, word: str) -> None:
|
244
247
|
"""Add a single word to the list."""
|
245
|
-
if word and not self.listBox.findItems(word, Qt.MatchExactly):
|
248
|
+
if word and not self.listBox.findItems(word, Qt.MatchFlag.MatchExactly):
|
246
249
|
self.listBox.addItem(word)
|
247
250
|
self._changed = True
|
248
251
|
return
|
novelwriter/enum.py
CHANGED
@@ -112,20 +112,22 @@ class nwDocAction(Enum):
|
|
112
112
|
BLOCK_TXT = 19
|
113
113
|
BLOCK_TTL = 20
|
114
114
|
BLOCK_UNN = 21
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
115
|
+
BLOCK_HSC = 22
|
116
|
+
REPL_SNG = 23
|
117
|
+
REPL_DBL = 24
|
118
|
+
RM_BREAKS = 25
|
119
|
+
ALIGN_L = 26
|
120
|
+
ALIGN_C = 27
|
121
|
+
ALIGN_R = 28
|
122
|
+
INDENT_L = 29
|
123
|
+
INDENT_R = 30
|
124
|
+
SC_ITALIC = 31
|
125
|
+
SC_BOLD = 32
|
126
|
+
SC_STRIKE = 33
|
127
|
+
SC_ULINE = 34
|
128
|
+
SC_MARK = 35
|
129
|
+
SC_SUP = 36
|
130
|
+
SC_SUB = 37
|
129
131
|
|
130
132
|
# END Enum nwDocAction
|
131
133
|
|
@@ -153,6 +155,7 @@ class nwView(Enum):
|
|
153
155
|
PROJECT = 1
|
154
156
|
NOVEL = 2
|
155
157
|
OUTLINE = 3
|
158
|
+
SEARCH = 4
|
156
159
|
|
157
160
|
# END Enum nwView
|
158
161
|
|
novelwriter/error.py
CHANGED
@@ -29,11 +29,11 @@ import logging
|
|
29
29
|
|
30
30
|
from typing import TYPE_CHECKING
|
31
31
|
|
32
|
-
from PyQt5.QtGui import QFont, QFontDatabase
|
33
32
|
from PyQt5.QtCore import Qt, pyqtSlot
|
33
|
+
from PyQt5.QtGui import QFont, QFontDatabase
|
34
34
|
from PyQt5.QtWidgets import (
|
35
|
-
|
36
|
-
QDialogButtonBox
|
35
|
+
QApplication, QWidget, QDialog, QGridLayout, QStyle, QPlainTextEdit,
|
36
|
+
QLabel, QDialogButtonBox
|
37
37
|
)
|
38
38
|
|
39
39
|
if TYPE_CHECKING: # pragma: no cover
|
@@ -54,7 +54,7 @@ def logException() -> None:
|
|
54
54
|
return
|
55
55
|
|
56
56
|
|
57
|
-
def formatException(exc) -> str:
|
57
|
+
def formatException(exc: BaseException) -> str:
|
58
58
|
"""Format an exception as a string the same way the default
|
59
59
|
exception handler does.
|
60
60
|
"""
|
@@ -74,7 +74,9 @@ class NWErrorMessage(QDialog):
|
|
74
74
|
# Widgets
|
75
75
|
self.msgIcon = QLabel()
|
76
76
|
self.msgIcon.setPixmap(
|
77
|
-
|
77
|
+
QApplication.style().standardIcon(
|
78
|
+
QStyle.StandardPixmap.SP_MessageBoxCritical
|
79
|
+
).pixmap(64, 64)
|
78
80
|
)
|
79
81
|
self.msgHead = QLabel()
|
80
82
|
self.msgHead.setOpenExternalLinks(True)
|
@@ -88,13 +90,13 @@ class NWErrorMessage(QDialog):
|
|
88
90
|
self.msgBody.setFont(font)
|
89
91
|
self.msgBody.setReadOnly(True)
|
90
92
|
|
91
|
-
self.btnBox = QDialogButtonBox(QDialogButtonBox.Close)
|
93
|
+
self.btnBox = QDialogButtonBox(QDialogButtonBox.StandardButton.Close)
|
92
94
|
self.btnBox.rejected.connect(self._doClose)
|
93
95
|
|
94
96
|
# Assemble
|
95
97
|
self.mainBox = QGridLayout()
|
96
|
-
self.mainBox.addWidget(self.msgIcon, 0, 0, 2, 1, Qt.AlignTop)
|
97
|
-
self.mainBox.addWidget(self.msgHead, 0, 1, 1, 1, Qt.AlignTop)
|
98
|
+
self.mainBox.addWidget(self.msgIcon, 0, 0, 2, 1, Qt.AlignmentFlag.AlignTop)
|
99
|
+
self.mainBox.addWidget(self.msgHead, 0, 1, 1, 1, Qt.AlignmentFlag.AlignTop)
|
98
100
|
self.mainBox.addWidget(self.msgBody, 1, 1, 1, 1)
|
99
101
|
self.mainBox.addWidget(self.btnBox, 2, 0, 1, 2)
|
100
102
|
self.mainBox.setSpacing(16)
|
@@ -179,14 +181,14 @@ class NWErrorMessage(QDialog):
|
|
179
181
|
def exceptionHandler(exType: type, exValue: BaseException, exTrace: TracebackType) -> None:
|
180
182
|
"""Function to catch unhandled global exceptions."""
|
181
183
|
from traceback import print_tb
|
182
|
-
from PyQt5.QtWidgets import
|
184
|
+
from PyQt5.QtWidgets import QApplication
|
183
185
|
|
184
186
|
logger.critical("%s: %s", exType.__name__, str(exValue))
|
185
187
|
print_tb(exTrace)
|
186
188
|
|
187
189
|
try:
|
188
190
|
nwGUI = None
|
189
|
-
for qWin in
|
191
|
+
for qWin in QApplication.topLevelWidgets():
|
190
192
|
if qWin.objectName() == "GuiMain":
|
191
193
|
nwGUI = qWin
|
192
194
|
break
|
@@ -197,7 +199,7 @@ def exceptionHandler(exType: type, exValue: BaseException, exTrace: TracebackTyp
|
|
197
199
|
|
198
200
|
errMsg = NWErrorMessage(nwGUI)
|
199
201
|
errMsg.setMessage(exType, exValue, exTrace)
|
200
|
-
errMsg.
|
202
|
+
errMsg.exec()
|
201
203
|
|
202
204
|
try:
|
203
205
|
# Try a controlled shutdown
|
@@ -209,7 +211,7 @@ def exceptionHandler(exType: type, exValue: BaseException, exTrace: TracebackTyp
|
|
209
211
|
logger.critical("Could not close the project before exiting")
|
210
212
|
logger.critical(formatException(exc))
|
211
213
|
|
212
|
-
|
214
|
+
QApplication.exit(1)
|
213
215
|
|
214
216
|
except Exception as exc:
|
215
217
|
logger.critical(formatException(exc))
|
@@ -25,10 +25,14 @@ from __future__ import annotations
|
|
25
25
|
|
26
26
|
from math import ceil
|
27
27
|
|
28
|
+
from PyQt5.QtCore import QRect
|
28
29
|
from PyQt5.QtGui import QBrush, QColor, QPaintEvent, QPainter, QPen
|
29
|
-
from PyQt5.QtCore import QRect, Qt
|
30
30
|
from PyQt5.QtWidgets import QProgressBar, QSizePolicy, QWidget
|
31
31
|
|
32
|
+
from novelwriter.types import (
|
33
|
+
QtPaintAnitAlias, QtAlignCenter, QtRoundCap, QtSolidLine, QtTransparent
|
34
|
+
)
|
35
|
+
|
32
36
|
|
33
37
|
class NProgressCircle(QProgressBar):
|
34
38
|
"""Extension: Circular Progress Widget
|
@@ -48,14 +52,14 @@ class NProgressCircle(QProgressBar):
|
|
48
52
|
self._point = point
|
49
53
|
self._dRect = QRect(0, 0, size, size)
|
50
54
|
self._cRect = QRect(point, point, size - 2*point, size - 2*point)
|
51
|
-
self._dPen = QPen(
|
52
|
-
self._dBrush = QBrush(
|
55
|
+
self._dPen = QPen(QtTransparent)
|
56
|
+
self._dBrush = QBrush(QtTransparent)
|
53
57
|
self.setColours(
|
54
58
|
track=self.palette().alternateBase().color(),
|
55
59
|
bar=self.palette().highlight().color(),
|
56
60
|
text=self.palette().text().color()
|
57
61
|
)
|
58
|
-
self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
|
62
|
+
self.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
|
59
63
|
self.setFixedWidth(size)
|
60
64
|
self.setFixedHeight(size)
|
61
65
|
return
|
@@ -67,9 +71,9 @@ class NProgressCircle(QProgressBar):
|
|
67
71
|
self._dPen = QPen(back)
|
68
72
|
self._dBrush = QBrush(back)
|
69
73
|
if isinstance(bar, QColor):
|
70
|
-
self._cPen = QPen(QBrush(bar), self._point,
|
74
|
+
self._cPen = QPen(QBrush(bar), self._point, QtSolidLine, QtRoundCap)
|
71
75
|
if isinstance(track, QColor):
|
72
|
-
self._bPen = QPen(QBrush(track), self._point,
|
76
|
+
self._bPen = QPen(QBrush(track), self._point, QtSolidLine, QtRoundCap)
|
73
77
|
if isinstance(text, QColor):
|
74
78
|
self._tColor = text
|
75
79
|
return
|
@@ -85,7 +89,7 @@ class NProgressCircle(QProgressBar):
|
|
85
89
|
progress = 100.0*self.value()/self.maximum()
|
86
90
|
angle = ceil(16*3.6*progress)
|
87
91
|
painter = QPainter(self)
|
88
|
-
painter.setRenderHint(
|
92
|
+
painter.setRenderHint(QtPaintAnitAlias, True)
|
89
93
|
painter.setPen(self._dPen)
|
90
94
|
painter.setBrush(self._dBrush)
|
91
95
|
painter.drawEllipse(self._dRect)
|
@@ -94,7 +98,7 @@ class NProgressCircle(QProgressBar):
|
|
94
98
|
painter.setPen(self._cPen)
|
95
99
|
painter.drawArc(self._cRect, 90*16, -angle)
|
96
100
|
painter.setPen(self._tColor)
|
97
|
-
painter.drawText(self._cRect,
|
101
|
+
painter.drawText(self._cRect, QtAlignCenter, self._text or f"{progress:.1f} %")
|
98
102
|
return
|
99
103
|
|
100
104
|
# END Class NProgressCircle
|
@@ -7,6 +7,7 @@ Created: 2020-05-03 [0.4.5] NColourLabel
|
|
7
7
|
Created: 2024-01-08 [2.3b1] NScrollableForm
|
8
8
|
Created: 2024-01-26 [2.3b1] NScrollablePage
|
9
9
|
Created: 2024-01-26 [2.3b1] NFixedPage
|
10
|
+
Created: 2024-03-12 [2.4b1] NWrappedWidgetBox
|
10
11
|
|
11
12
|
This file is a part of novelWriter
|
12
13
|
Copyright 2018–2024, Veronica Berglyd Olsen
|
@@ -36,8 +37,6 @@ from PyQt5.QtWidgets import (
|
|
36
37
|
from novelwriter import CONFIG
|
37
38
|
|
38
39
|
DEFAULT_SCALE = 0.9
|
39
|
-
RIGHT_TOP = Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignTop
|
40
|
-
LEFT_TOP = Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop
|
41
40
|
|
42
41
|
|
43
42
|
class NFixedPage(QFrame):
|
@@ -259,7 +258,7 @@ class NColourLabel(QLabel):
|
|
259
258
|
font.setWeight(QFont.Weight.Bold if bold else QFont.Weight.Normal)
|
260
259
|
if color:
|
261
260
|
colour = self.palette()
|
262
|
-
colour.setColor(QPalette.WindowText, color)
|
261
|
+
colour.setColor(QPalette.ColorRole.WindowText, color)
|
263
262
|
self.setPalette(colour)
|
264
263
|
|
265
264
|
self.setFont(font)
|
@@ -269,3 +268,24 @@ class NColourLabel(QLabel):
|
|
269
268
|
return
|
270
269
|
|
271
270
|
# END Class NColourLabel
|
271
|
+
|
272
|
+
|
273
|
+
class NWrappedWidgetBox(QHBoxLayout):
|
274
|
+
"""Extension: A Text-Wrapped Widget Box
|
275
|
+
|
276
|
+
A custom layout box where a widget is wrapped in text labels on
|
277
|
+
either side within a layout box. The widget is inserted at the {0}
|
278
|
+
position so that it can be used for translation strings.
|
279
|
+
"""
|
280
|
+
|
281
|
+
def __init__(self, text: str, widget: QWidget) -> None:
|
282
|
+
super().__init__()
|
283
|
+
before, _, after = text.partition(r"{0}")
|
284
|
+
if before:
|
285
|
+
self.addWidget(QLabel(before.rstrip()))
|
286
|
+
self.addWidget(widget)
|
287
|
+
if after:
|
288
|
+
self.addWidget(QLabel(after.lstrip()))
|
289
|
+
return
|
290
|
+
|
291
|
+
# END Class NWrappedWidgetBox
|
@@ -25,9 +25,11 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
25
25
|
"""
|
26
26
|
from __future__ import annotations
|
27
27
|
|
28
|
-
from PyQt5.QtCore import Qt
|
28
|
+
from PyQt5.QtCore import QSize, Qt
|
29
29
|
from PyQt5.QtGui import QWheelEvent
|
30
|
-
from PyQt5.QtWidgets import QComboBox, QDoubleSpinBox, QSpinBox, QWidget
|
30
|
+
from PyQt5.QtWidgets import QComboBox, QDoubleSpinBox, QSpinBox, QToolButton, QWidget
|
31
|
+
|
32
|
+
from novelwriter import SHARED
|
31
33
|
|
32
34
|
|
33
35
|
class NComboBox(QComboBox):
|
@@ -44,6 +46,12 @@ class NComboBox(QComboBox):
|
|
44
46
|
event.ignore()
|
45
47
|
return
|
46
48
|
|
49
|
+
def setCurrentData(self, data: str, default: str) -> None:
|
50
|
+
"""Set the current index from data, with a fallback."""
|
51
|
+
idx = self.findData(data)
|
52
|
+
self.setCurrentIndex(self.findData(default) if idx < 0 else idx)
|
53
|
+
return
|
54
|
+
|
47
55
|
# END Class NComboBox
|
48
56
|
|
49
57
|
|
@@ -79,3 +87,44 @@ class NDoubleSpinBox(QDoubleSpinBox):
|
|
79
87
|
return
|
80
88
|
|
81
89
|
# END Class NDoubleSpinBox
|
90
|
+
|
91
|
+
|
92
|
+
class NIconToolButton(QToolButton):
|
93
|
+
|
94
|
+
def __init__(self, parent: QWidget, iconSize: QSize, icon: str | None = None) -> None:
|
95
|
+
super().__init__(parent=parent)
|
96
|
+
self.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
|
97
|
+
self.setIconSize(iconSize)
|
98
|
+
self.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
|
99
|
+
if icon:
|
100
|
+
self.setThemeIcon(icon)
|
101
|
+
return
|
102
|
+
|
103
|
+
def setThemeIcon(self, iconKey: str) -> None:
|
104
|
+
"""Set an icon from the current theme."""
|
105
|
+
self.setIcon(SHARED.theme.getIcon(iconKey))
|
106
|
+
return
|
107
|
+
|
108
|
+
# END Class NIconToolButton
|
109
|
+
|
110
|
+
|
111
|
+
class NIconToggleButton(QToolButton):
|
112
|
+
|
113
|
+
def __init__(self, parent: QWidget, iconSize: QSize, icon: str | None = None) -> None:
|
114
|
+
super().__init__(parent=parent)
|
115
|
+
self.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
|
116
|
+
self.setIconSize(iconSize)
|
117
|
+
self.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
|
118
|
+
self.setCheckable(True)
|
119
|
+
self.setStyleSheet("border: none; background: transparent;")
|
120
|
+
if icon:
|
121
|
+
self.setThemeIcon(icon)
|
122
|
+
return
|
123
|
+
|
124
|
+
def setThemeIcon(self, iconKey: str) -> None:
|
125
|
+
"""Set an icon from the current theme."""
|
126
|
+
iconSize = self.iconSize()
|
127
|
+
self.setIcon(SHARED.theme.getToggleIcon(iconKey, (iconSize.width(), iconSize.height())))
|
128
|
+
return
|
129
|
+
|
130
|
+
# END Class NUnfoldButton
|