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.
Files changed (162) hide show
  1. {novelWriter-2.2rc1.dist-info → novelWriter-2.3.dist-info}/METADATA +1 -1
  2. {novelWriter-2.2rc1.dist-info → novelWriter-2.3.dist-info}/RECORD +149 -132
  3. {novelWriter-2.2rc1.dist-info → novelWriter-2.3.dist-info}/WHEEL +1 -1
  4. novelWriter-2.3.dist-info/entry_points.txt +2 -0
  5. novelwriter/__init__.py +11 -6
  6. novelwriter/assets/i18n/nw_de_DE.qm +0 -0
  7. novelwriter/assets/i18n/nw_en_US.qm +0 -0
  8. novelwriter/assets/i18n/nw_es_419.qm +0 -0
  9. novelwriter/assets/i18n/nw_fr_FR.qm +0 -0
  10. novelwriter/assets/i18n/nw_it_IT.qm +0 -0
  11. novelwriter/assets/i18n/nw_ja_JP.qm +0 -0
  12. novelwriter/assets/i18n/nw_nb_NO.qm +0 -0
  13. novelwriter/assets/i18n/nw_nl_NL.qm +0 -0
  14. novelwriter/assets/i18n/nw_zh_CN.qm +0 -0
  15. novelwriter/assets/i18n/project_de_DE.json +1 -0
  16. novelwriter/assets/i18n/project_en_US.json +1 -0
  17. novelwriter/assets/i18n/project_es_419.json +11 -0
  18. novelwriter/assets/i18n/project_fr_FR.json +11 -0
  19. novelwriter/assets/i18n/project_it_IT.json +11 -0
  20. novelwriter/assets/i18n/project_ja_JP.json +2 -1
  21. novelwriter/assets/i18n/project_nb_NO.json +1 -0
  22. novelwriter/assets/i18n/project_nl_NL.json +11 -0
  23. novelwriter/assets/i18n/project_pt_BR.json +11 -0
  24. novelwriter/assets/i18n/project_zh_CN.json +11 -0
  25. novelwriter/assets/icons/typicons_dark/icons.conf +11 -2
  26. novelwriter/assets/icons/typicons_dark/mixed_document-new.svg +6 -0
  27. novelwriter/assets/icons/typicons_dark/mixed_import.svg +5 -0
  28. novelwriter/assets/icons/typicons_dark/nw_tb-bold-md.svg +4 -0
  29. novelwriter/assets/icons/typicons_dark/nw_tb-bold.svg +3 -1
  30. novelwriter/assets/icons/typicons_dark/nw_tb-italic-md.svg +4 -0
  31. novelwriter/assets/icons/typicons_dark/nw_tb-italic.svg +3 -1
  32. novelwriter/assets/icons/typicons_dark/nw_tb-strike-md.svg +4 -0
  33. novelwriter/assets/icons/typicons_dark/nw_tb-strike.svg +3 -1
  34. novelwriter/assets/icons/typicons_dark/nw_tb-subscript.svg +4 -2
  35. novelwriter/assets/icons/typicons_dark/nw_tb-superscript.svg +4 -2
  36. novelwriter/assets/icons/typicons_dark/nw_tb-underline.svg +4 -2
  37. novelwriter/assets/icons/typicons_dark/typ_document-add.svg +4 -0
  38. novelwriter/assets/icons/typicons_dark/typ_document.svg +4 -0
  39. novelwriter/assets/icons/typicons_dark/typ_th-dot-more.svg +4 -0
  40. novelwriter/assets/icons/typicons_dark/typ_th-list.svg +9 -0
  41. novelwriter/assets/icons/typicons_light/icons.conf +11 -2
  42. novelwriter/assets/icons/typicons_light/mixed_document-new.svg +6 -0
  43. novelwriter/assets/icons/typicons_light/mixed_import.svg +5 -0
  44. novelwriter/assets/icons/typicons_light/nw_tb-bold-md.svg +4 -0
  45. novelwriter/assets/icons/typicons_light/nw_tb-bold.svg +3 -1
  46. novelwriter/assets/icons/typicons_light/nw_tb-italic-md.svg +4 -0
  47. novelwriter/assets/icons/typicons_light/nw_tb-italic.svg +3 -1
  48. novelwriter/assets/icons/typicons_light/nw_tb-strike-md.svg +4 -0
  49. novelwriter/assets/icons/typicons_light/nw_tb-strike.svg +3 -1
  50. novelwriter/assets/icons/typicons_light/nw_tb-subscript.svg +4 -2
  51. novelwriter/assets/icons/typicons_light/nw_tb-superscript.svg +4 -2
  52. novelwriter/assets/icons/typicons_light/nw_tb-underline.svg +4 -2
  53. novelwriter/assets/icons/typicons_light/typ_document-add.svg +4 -0
  54. novelwriter/assets/icons/typicons_light/typ_document.svg +4 -0
  55. novelwriter/assets/icons/typicons_light/typ_th-dot-more.svg +4 -0
  56. novelwriter/assets/icons/typicons_light/typ_th-list.svg +9 -0
  57. novelwriter/assets/images/novelwriter-text-dark.svg +4 -0
  58. novelwriter/assets/images/novelwriter-text-light.svg +4 -0
  59. novelwriter/assets/images/welcome-dark.jpg +0 -0
  60. novelwriter/assets/images/welcome-light.jpg +0 -0
  61. novelwriter/assets/manual.pdf +0 -0
  62. novelwriter/assets/sample.zip +0 -0
  63. novelwriter/assets/syntax/cyberpunk_night.conf +26 -0
  64. novelwriter/assets/syntax/default_dark.conf +1 -0
  65. novelwriter/assets/syntax/default_light.conf +1 -0
  66. novelwriter/assets/syntax/grey_dark.conf +1 -0
  67. novelwriter/assets/syntax/grey_light.conf +1 -0
  68. novelwriter/assets/syntax/light_owl.conf +1 -0
  69. novelwriter/assets/syntax/night_owl.conf +1 -0
  70. novelwriter/assets/syntax/solarized_dark.conf +1 -0
  71. novelwriter/assets/syntax/solarized_light.conf +1 -0
  72. novelwriter/assets/syntax/tango.conf +23 -0
  73. novelwriter/assets/syntax/tomorrow.conf +1 -0
  74. novelwriter/assets/syntax/tomorrow_night.conf +1 -0
  75. novelwriter/assets/syntax/tomorrow_night_blue.conf +1 -0
  76. novelwriter/assets/syntax/tomorrow_night_bright.conf +1 -0
  77. novelwriter/assets/syntax/tomorrow_night_eighties.conf +1 -0
  78. novelwriter/assets/text/credits_en.htm +4 -2
  79. novelwriter/assets/themes/cyberpunk_night.conf +29 -0
  80. novelwriter/assets/themes/default_dark.conf +2 -2
  81. novelwriter/assets/themes/default_light.conf +2 -2
  82. novelwriter/common.py +64 -66
  83. novelwriter/config.py +39 -44
  84. novelwriter/constants.py +39 -17
  85. novelwriter/core/buildsettings.py +8 -8
  86. novelwriter/core/coretools.py +198 -157
  87. novelwriter/core/docbuild.py +7 -4
  88. novelwriter/core/document.py +7 -7
  89. novelwriter/core/index.py +90 -57
  90. novelwriter/core/item.py +23 -5
  91. novelwriter/core/options.py +11 -10
  92. novelwriter/core/project.py +73 -47
  93. novelwriter/core/projectdata.py +3 -16
  94. novelwriter/core/projectxml.py +14 -42
  95. novelwriter/core/sessions.py +4 -3
  96. novelwriter/core/spellcheck.py +6 -4
  97. novelwriter/core/status.py +5 -4
  98. novelwriter/core/storage.py +183 -141
  99. novelwriter/core/tohtml.py +6 -4
  100. novelwriter/core/tokenizer.py +110 -83
  101. novelwriter/core/tomd.py +2 -2
  102. novelwriter/core/toodt.py +41 -31
  103. novelwriter/core/tree.py +5 -4
  104. novelwriter/dialogs/about.py +88 -179
  105. novelwriter/dialogs/docmerge.py +30 -20
  106. novelwriter/dialogs/docsplit.py +33 -22
  107. novelwriter/dialogs/editlabel.py +20 -8
  108. novelwriter/dialogs/preferences.py +562 -725
  109. novelwriter/dialogs/{projsettings.py → projectsettings.py} +301 -270
  110. novelwriter/dialogs/quotes.py +47 -36
  111. novelwriter/dialogs/wordlist.py +128 -59
  112. novelwriter/enum.py +25 -22
  113. novelwriter/error.py +2 -2
  114. novelwriter/extensions/circularprogress.py +12 -12
  115. novelwriter/extensions/configlayout.py +185 -146
  116. novelwriter/extensions/{wheeleventfilter.py → eventfilters.py} +15 -5
  117. novelwriter/extensions/modified.py +81 -0
  118. novelwriter/extensions/novelselector.py +27 -13
  119. novelwriter/extensions/pagedsidebar.py +15 -20
  120. novelwriter/extensions/simpleprogress.py +8 -9
  121. novelwriter/extensions/statusled.py +9 -9
  122. novelwriter/extensions/switch.py +32 -64
  123. novelwriter/extensions/switchbox.py +2 -7
  124. novelwriter/extensions/versioninfo.py +153 -0
  125. novelwriter/gui/doceditor.py +250 -214
  126. novelwriter/gui/dochighlight.py +66 -94
  127. novelwriter/gui/docviewer.py +71 -98
  128. novelwriter/gui/docviewerpanel.py +140 -47
  129. novelwriter/gui/editordocument.py +3 -3
  130. novelwriter/gui/itemdetails.py +9 -9
  131. novelwriter/gui/mainmenu.py +47 -47
  132. novelwriter/gui/noveltree.py +53 -61
  133. novelwriter/gui/outline.py +100 -76
  134. novelwriter/gui/projtree.py +246 -112
  135. novelwriter/gui/sidebar.py +9 -8
  136. novelwriter/gui/statusbar.py +49 -7
  137. novelwriter/gui/theme.py +74 -76
  138. novelwriter/guimain.py +175 -330
  139. novelwriter/shared.py +68 -30
  140. novelwriter/tools/dictionaries.py +7 -8
  141. novelwriter/tools/lipsum.py +34 -28
  142. novelwriter/tools/manusbuild.py +3 -4
  143. novelwriter/tools/manuscript.py +25 -32
  144. novelwriter/tools/manussettings.py +194 -225
  145. novelwriter/tools/noveldetails.py +525 -0
  146. novelwriter/tools/welcome.py +819 -0
  147. novelwriter/tools/writingstats.py +26 -13
  148. novelWriter-2.2rc1.dist-info/entry_points.txt +0 -5
  149. novelwriter/assets/icons/typicons_dark/nw_tb-markdown.svg +0 -8
  150. novelwriter/assets/icons/typicons_dark/nw_tb-shortcode.svg +0 -8
  151. novelwriter/assets/icons/typicons_light/nw_tb-markdown.svg +0 -8
  152. novelwriter/assets/icons/typicons_light/nw_tb-shortcode.svg +0 -8
  153. novelwriter/assets/images/wizard-back.jpg +0 -0
  154. novelwriter/assets/text/gplv3_en.htm +0 -641
  155. novelwriter/assets/text/release_notes.htm +0 -17
  156. novelwriter/dialogs/projdetails.py +0 -525
  157. novelwriter/dialogs/projload.py +0 -298
  158. novelwriter/dialogs/updates.py +0 -182
  159. novelwriter/extensions/pageddialog.py +0 -130
  160. novelwriter/tools/projwizard.py +0 -478
  161. {novelWriter-2.2rc1.dist-info → novelWriter-2.3.dist-info}/LICENSE.md +0 -0
  162. {novelWriter-2.2rc1.dist-info → novelWriter-2.3.dist-info}/top_level.txt +0 -0
@@ -3,10 +3,10 @@ novelWriter – GUI Quotes Dialog
3
3
  ===============================
4
4
 
5
5
  File History:
6
- Created: 2020-06-18 [0.9]
6
+ Created: 2020-06-18 [0.9.0] GuiQuoteSelect
7
7
 
8
8
  This file is a part of novelWriter
9
- Copyright 2018–2023, Veronica Berglyd Olsen
9
+ Copyright 2018–2024, Veronica Berglyd Olsen
10
10
 
11
11
  This program is free software: you can redistribute it and/or modify
12
12
  it under the terms of the GNU General Public License as published by
@@ -26,10 +26,10 @@ from __future__ import annotations
26
26
  import logging
27
27
 
28
28
  from PyQt5.QtGui import QFontMetrics
29
- from PyQt5.QtCore import Qt, QSize
29
+ from PyQt5.QtCore import QSize, Qt, pyqtSlot
30
30
  from PyQt5.QtWidgets import (
31
- QLabel, QVBoxLayout, QHBoxLayout, QDialog, QDialogButtonBox,
32
- QListWidget, QListWidgetItem, QFrame
31
+ QDialog, QDialogButtonBox, QFrame, QHBoxLayout, QLabel, QListWidget,
32
+ QListWidgetItem, QVBoxLayout, QWidget
33
33
  )
34
34
 
35
35
  from novelwriter import CONFIG
@@ -40,18 +40,21 @@ logger = logging.getLogger(__name__)
40
40
 
41
41
  class GuiQuoteSelect(QDialog):
42
42
 
43
- selectedQuote = ""
43
+ _selected = ""
44
44
 
45
45
  D_KEY = Qt.ItemDataRole.UserRole
46
46
 
47
- def __init__(self, parent=None, currentQuote='"'):
47
+ def __init__(self, parent: QWidget, current: str = '"') -> None:
48
48
  super().__init__(parent=parent)
49
49
 
50
+ logger.debug("Create: GuiQuoteSelect")
51
+ self.setObjectName("GuiQuoteSelect")
52
+
50
53
  self.outerBox = QVBoxLayout()
51
54
  self.innerBox = QHBoxLayout()
52
55
  self.labelBox = QVBoxLayout()
53
56
 
54
- self.selectedQuote = currentQuote
57
+ self._selected = current
55
58
 
56
59
  qMetrics = QFontMetrics(self.font())
57
60
  pxW = 7*qMetrics.boundingRectChar("M").width()
@@ -62,7 +65,7 @@ class GuiQuoteSelect(QDialog):
62
65
  lblFont.setPointSizeF(4*lblFont.pointSizeF())
63
66
 
64
67
  # Preview Label
65
- self.previewLabel = QLabel(currentQuote)
68
+ self.previewLabel = QLabel(current)
66
69
  self.previewLabel.setFont(lblFont)
67
70
  self.previewLabel.setFixedSize(QSize(pxW, pxH))
68
71
  self.previewLabel.setAlignment(Qt.AlignCenter)
@@ -74,12 +77,12 @@ class GuiQuoteSelect(QDialog):
74
77
 
75
78
  minSize = 100
76
79
  for sKey, sLabel in nwQuotes.SYMBOLS.items():
77
- theText = "[ %s ] %s" % (sKey, trConst(sLabel))
78
- minSize = max(minSize, qMetrics.boundingRect(theText).width())
79
- qtItem = QListWidgetItem(theText)
80
+ text = "[ %s ] %s" % (sKey, trConst(sLabel))
81
+ minSize = max(minSize, qMetrics.boundingRect(text).width())
82
+ qtItem = QListWidgetItem(text)
80
83
  qtItem.setData(self.D_KEY, sKey)
81
84
  self.listBox.addItem(qtItem)
82
- if sKey == currentQuote:
85
+ if sKey == current:
83
86
  self.listBox.setCurrentItem(qtItem)
84
87
 
85
88
  self.listBox.setMinimumWidth(minSize + CONFIG.pxInt(40))
@@ -87,8 +90,8 @@ class GuiQuoteSelect(QDialog):
87
90
 
88
91
  # Buttons
89
92
  self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
90
- self.buttonBox.accepted.connect(self._doAccept)
91
- self.buttonBox.rejected.connect(self._doReject)
93
+ self.buttonBox.accepted.connect(self.accept)
94
+ self.buttonBox.rejected.connect(self.reject)
92
95
 
93
96
  # Assemble
94
97
  self.labelBox.addWidget(self.previewLabel, 0, Qt.AlignTop)
@@ -102,32 +105,40 @@ class GuiQuoteSelect(QDialog):
102
105
 
103
106
  self.setLayout(self.outerBox)
104
107
 
105
- return
106
-
107
- ##
108
- # Slots
109
- ##
108
+ logger.debug("Ready: GuiQuoteSelect")
110
109
 
111
- def _selectedSymbol(self):
112
- """Update the preview label and the selected quote style.
113
- """
114
- selItems = self.listBox.selectedItems()
115
- if selItems:
116
- theSymbol = selItems[0].data(self.D_KEY)
117
- self.previewLabel.setText(theSymbol)
118
- self.selectedQuote = theSymbol
119
110
  return
120
111
 
121
- def _doAccept(self):
122
- """Ok button clicked.
123
- """
124
- self.accept()
112
+ def __del__(self) -> None: # pragma: no cover
113
+ logger.debug("Delete: GuiQuoteSelect")
125
114
  return
126
115
 
127
- def _doReject(self):
128
- """Cancel button clicked.
129
- """
130
- self.reject()
116
+ @property
117
+ def selectedQuote(self) -> str:
118
+ """Return the selected quote symbol."""
119
+ return self._selected
120
+
121
+ @classmethod
122
+ def getQuote(cls, parent: QWidget, current: str = "") -> tuple[str, bool]:
123
+ """Pop the dialog and return the result."""
124
+ cls = GuiQuoteSelect(parent, current=current)
125
+ cls.exec_()
126
+ quote = cls._selected
127
+ accepted = cls.result() == QDialog.DialogCode.Accepted
128
+ cls.deleteLater()
129
+ return quote, accepted
130
+
131
+ ##
132
+ # Private Slots
133
+ ##
134
+
135
+ @pyqtSlot()
136
+ def _selectedSymbol(self) -> None:
137
+ """Update the preview label and the selected quote style."""
138
+ if items := self.listBox.selectedItems():
139
+ quote = items[0].data(self.D_KEY)
140
+ self.previewLabel.setText(quote)
141
+ self._selected = quote
131
142
  return
132
143
 
133
144
  # END Class GuiQuoteSelect
@@ -3,10 +3,10 @@ novelWriter – GUI User Wordlist
3
3
  ===============================
4
4
 
5
5
  File History:
6
- Created: 2021-02-12 [1.2rc1]
6
+ Created: 2021-02-12 [1.2rc1] GuiWordList
7
7
 
8
8
  This file is a part of novelWriter
9
- Copyright 2018–2023, Veronica Berglyd Olsen
9
+ Copyright 2018–2024, Veronica Berglyd Olsen
10
10
 
11
11
  This program is free software: you can redistribute it and/or modify
12
12
  it under the terms of the GNU General Public License as published by
@@ -26,15 +26,19 @@ from __future__ import annotations
26
26
  import logging
27
27
 
28
28
  from typing import TYPE_CHECKING
29
+ from pathlib import Path
29
30
 
30
- from PyQt5.QtCore import Qt
31
+ from PyQt5.QtGui import QCloseEvent
32
+ from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot
31
33
  from PyQt5.QtWidgets import (
32
- QAbstractItemView, QDialog, QDialogButtonBox, QHBoxLayout, QLabel,
33
- QLineEdit, QListWidget, QListWidgetItem, QPushButton, QVBoxLayout
34
+ QAbstractItemView, QDialog, QDialogButtonBox, QFileDialog, QHBoxLayout,
35
+ QLineEdit, QListWidget, QPushButton, QVBoxLayout, qApp
34
36
  )
35
37
 
36
38
  from novelwriter import CONFIG, SHARED
39
+ from novelwriter.common import formatFileFilter
37
40
  from novelwriter.core.spellcheck import UserDictionary
41
+ from novelwriter.extensions.configlayout import NColourLabel
38
42
 
39
43
  if TYPE_CHECKING: # pragma: no cover
40
44
  from novelwriter.guimain import GuiMain
@@ -44,7 +48,9 @@ logger = logging.getLogger(__name__)
44
48
 
45
49
  class GuiWordList(QDialog):
46
50
 
47
- def __init__(self, mainGui: GuiMain):
51
+ newWordListReady = pyqtSignal()
52
+
53
+ def __init__(self, mainGui: GuiMain) -> None:
48
54
  super().__init__(parent=mainGui)
49
55
 
50
56
  logger.debug("Create: GuiWordList")
@@ -54,30 +60,46 @@ class GuiWordList(QDialog):
54
60
  mS = CONFIG.pxInt(250)
55
61
  wW = CONFIG.pxInt(320)
56
62
  wH = CONFIG.pxInt(340)
57
- pOptions = SHARED.project.options
58
63
 
59
64
  self.setMinimumWidth(mS)
60
65
  self.setMinimumHeight(mS)
61
66
  self.resize(
62
- CONFIG.pxInt(pOptions.getInt("GuiWordList", "winWidth", wW)),
63
- CONFIG.pxInt(pOptions.getInt("GuiWordList", "winHeight", wH))
67
+ CONFIG.pxInt(SHARED.project.options.getInt("GuiWordList", "winWidth", wW)),
68
+ CONFIG.pxInt(SHARED.project.options.getInt("GuiWordList", "winHeight", wH))
69
+ )
70
+
71
+ # Header
72
+ self.headLabel = NColourLabel(
73
+ "Project Word List", SHARED.theme.helpText, parent=self,
74
+ scale=NColourLabel.HEADER_SCALE
64
75
  )
65
76
 
66
- # Main Widgets
67
- # ============
77
+ self.importButton = QPushButton(SHARED.theme.getIcon("import"), "", self)
78
+ self.importButton.setToolTip(self.tr("Import words from text file"))
79
+ self.importButton.clicked.connect(self._importWords)
68
80
 
69
- self.headLabel = QLabel("<b>%s</b>" % self.tr("Project Word List"))
81
+ self.exportButton = QPushButton(SHARED.theme.getIcon("export"), "", self)
82
+ self.exportButton.setToolTip(self.tr("Export words to text file"))
83
+ self.exportButton.clicked.connect(self._exportWords)
70
84
 
71
- self.listBox = QListWidget()
85
+ self.headerBox = QHBoxLayout()
86
+ self.headerBox.addWidget(self.headLabel, 1)
87
+ self.headerBox.addWidget(self.importButton, 0)
88
+ self.headerBox.addWidget(self.exportButton, 0)
89
+
90
+ # List Box
91
+ self.listBox = QListWidget(self)
72
92
  self.listBox.setDragDropMode(QAbstractItemView.NoDragDrop)
93
+ self.listBox.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
73
94
  self.listBox.setSortingEnabled(True)
74
95
 
75
- self.newEntry = QLineEdit()
96
+ # Add/Remove Form
97
+ self.newEntry = QLineEdit(self)
76
98
 
77
- self.addButton = QPushButton(SHARED.theme.getIcon("add"), "")
99
+ self.addButton = QPushButton(SHARED.theme.getIcon("add"), "", self)
78
100
  self.addButton.clicked.connect(self._doAdd)
79
101
 
80
- self.delButton = QPushButton(SHARED.theme.getIcon("remove"), "")
102
+ self.delButton = QPushButton(SHARED.theme.getIcon("remove"), "", self)
81
103
  self.delButton.clicked.connect(self._doDelete)
82
104
 
83
105
  self.editBox = QHBoxLayout()
@@ -85,20 +107,19 @@ class GuiWordList(QDialog):
85
107
  self.editBox.addWidget(self.addButton, 0)
86
108
  self.editBox.addWidget(self.delButton, 0)
87
109
 
110
+ # Buttons
88
111
  self.buttonBox = QDialogButtonBox(QDialogButtonBox.Save | QDialogButtonBox.Close)
89
112
  self.buttonBox.accepted.connect(self._doSave)
90
- self.buttonBox.rejected.connect(self._doClose)
113
+ self.buttonBox.rejected.connect(self.close)
91
114
 
92
115
  # Assemble
93
- # ========
94
-
95
116
  self.outerBox = QVBoxLayout()
96
- self.outerBox.addWidget(self.headLabel)
97
- self.outerBox.addSpacing(CONFIG.pxInt(8))
117
+ self.outerBox.addLayout(self.headerBox, 0)
98
118
  self.outerBox.addWidget(self.listBox, 1)
99
119
  self.outerBox.addLayout(self.editBox, 0)
100
120
  self.outerBox.addSpacing(CONFIG.pxInt(12))
101
121
  self.outerBox.addWidget(self.buttonBox, 0)
122
+ self.outerBox.setSpacing(CONFIG.pxInt(4))
102
123
 
103
124
  self.setLayout(self.outerBox)
104
125
 
@@ -108,82 +129,130 @@ class GuiWordList(QDialog):
108
129
 
109
130
  return
110
131
 
111
- def __del__(self): # pragma: no cover
132
+ def __del__(self) -> None: # pragma: no cover
112
133
  logger.debug("Delete: GuiWordList")
113
134
  return
114
135
 
115
136
  ##
116
- # Slots
137
+ # Events
117
138
  ##
118
139
 
119
- def _doAdd(self):
120
- """Add a new word to the word list."""
121
- word = self.newEntry.text().strip()
122
- if word == "":
123
- SHARED.error(self.tr("Cannot add a blank word."))
124
- return
140
+ def closeEvent(self, event: QCloseEvent) -> None:
141
+ """Capture the close event and perform cleanup."""
142
+ self._saveGuiSettings()
143
+ event.accept()
144
+ self.deleteLater()
145
+ return
125
146
 
126
- if self.listBox.findItems(word, Qt.MatchExactly):
127
- SHARED.error(self.tr(
128
- "The word '{0}' is already in the word list."
129
- ).format(word))
130
- return
147
+ ##
148
+ # Private Slots
149
+ ##
131
150
 
132
- self.listBox.addItem(word)
151
+ @pyqtSlot()
152
+ def _doAdd(self) -> None:
153
+ """Add a new word to the word list."""
154
+ word = self.newEntry.text().strip()
133
155
  self.newEntry.setText("")
134
-
156
+ self.listBox.clearSelection()
157
+ self._addWord(word)
158
+ if items := self.listBox.findItems(word, Qt.MatchExactly):
159
+ self.listBox.setCurrentItem(items[0])
160
+ self.listBox.scrollToItem(items[0], QAbstractItemView.ScrollHint.PositionAtCenter)
135
161
  return
136
162
 
137
- def _doDelete(self):
138
- """Delete the selected item."""
139
- selItem = self.listBox.selectedItems()
140
- if selItem:
141
- self.listBox.takeItem(self.listBox.row(selItem[0]))
163
+ @pyqtSlot()
164
+ def _doDelete(self) -> None:
165
+ """Delete the selected items."""
166
+ for item in self.listBox.selectedItems():
167
+ self.listBox.takeItem(self.listBox.row(item))
142
168
  return
143
169
 
144
- def _doSave(self):
170
+ @pyqtSlot()
171
+ def _doSave(self) -> None:
145
172
  """Save the new word list and close."""
146
- self._saveGuiSettings()
147
173
  userDict = UserDictionary(SHARED.project)
148
- for i in range(self.listBox.count()):
149
- item = self.listBox.item(i)
150
- if isinstance(item, QListWidgetItem):
151
- word = item.text().strip()
152
- if word:
153
- userDict.add(word)
174
+ for word in self._listWords():
175
+ userDict.add(word)
154
176
  userDict.save()
155
- self.accept()
156
- return True
177
+ self.newWordListReady.emit()
178
+ qApp.processEvents()
179
+ self.close()
180
+ return
157
181
 
158
- def _doClose(self):
159
- """Close without saving the word list."""
160
- self._saveGuiSettings()
161
- self.reject()
182
+ @pyqtSlot()
183
+ def _importWords(self) -> None:
184
+ """Import words from file."""
185
+ SHARED.info(self.tr(
186
+ "Note: The import file must be a plain text file with UTF-8 or ASCII encoding."
187
+ ))
188
+ ffilter = formatFileFilter(["*.txt", "*"])
189
+ path, _ = QFileDialog.getOpenFileName(
190
+ self, self.tr("Import File"), str(Path.home()), filter=ffilter
191
+ )
192
+ if path:
193
+ try:
194
+ with open(path, mode="r", encoding="utf-8") as fo:
195
+ words = set(w.strip() for w in fo.read().split())
196
+ except Exception as exc:
197
+ SHARED.error("Could not read file.", exc=exc)
198
+ return
199
+ for word in words:
200
+ self._addWord(word)
201
+ return
202
+
203
+ @pyqtSlot()
204
+ def _exportWords(self) -> None:
205
+ """Export words to file."""
206
+ path, _ = QFileDialog.getSaveFileName(
207
+ self, self.tr("Export File"), str(Path.home())
208
+ )
209
+ if path:
210
+ try:
211
+ path = Path(path).with_suffix(".txt")
212
+ with open(path, mode="w", encoding="utf-8") as fo:
213
+ fo.write("\n".join(self._listWords()))
214
+ except Exception as exc:
215
+ SHARED.error("Could not write file.", exc=exc)
162
216
  return
163
217
 
164
218
  ##
165
219
  # Internal Functions
166
220
  ##
167
221
 
168
- def _loadWordList(self):
222
+ def _loadWordList(self) -> None:
169
223
  """Load the project's word list, if it exists."""
170
224
  userDict = UserDictionary(SHARED.project)
171
225
  userDict.load()
172
226
  self.listBox.clear()
173
227
  for word in userDict:
174
- if word:
175
- self.listBox.addItem(word)
228
+ self.listBox.addItem(word)
176
229
  return
177
230
 
178
- def _saveGuiSettings(self):
231
+ def _saveGuiSettings(self) -> None:
179
232
  """Save GUI settings."""
180
233
  winWidth = CONFIG.rpxInt(self.width())
181
234
  winHeight = CONFIG.rpxInt(self.height())
182
235
 
236
+ logger.debug("Saving State: GuiWordList")
183
237
  pOptions = SHARED.project.options
184
238
  pOptions.setValue("GuiWordList", "winWidth", winWidth)
185
239
  pOptions.setValue("GuiWordList", "winHeight", winHeight)
186
240
 
187
241
  return
188
242
 
243
+ def _addWord(self, word: str) -> None:
244
+ """Add a single word to the list."""
245
+ if word and not self.listBox.findItems(word, Qt.MatchExactly):
246
+ self.listBox.addItem(word)
247
+ self._changed = True
248
+ return
249
+
250
+ def _listWords(self) -> list[str]:
251
+ """List all words in the list box."""
252
+ result = []
253
+ for i in range(self.listBox.count()):
254
+ if (item := self.listBox.item(i)) and (word := item.text().strip()):
255
+ result.append(word)
256
+ return result
257
+
189
258
  # END Class GuiWordList
novelwriter/enum.py CHANGED
@@ -6,7 +6,7 @@ File History:
6
6
  Created: 2018-11-02 [0.0.1]
7
7
 
8
8
  This file is a part of novelWriter
9
- Copyright 2018–2023, Veronica Berglyd Olsen
9
+ Copyright 2018–2024, Veronica Berglyd Olsen
10
10
 
11
11
  This program is free software: you can redistribute it and/or modify
12
12
  it under the terms of the GNU General Public License as published by
@@ -47,7 +47,8 @@ class nwItemClass(Enum):
47
47
  ENTITY = 7
48
48
  CUSTOM = 8
49
49
  ARCHIVE = 9
50
- TRASH = 10
50
+ TEMPLATE = 10
51
+ TRASH = 11
51
52
 
52
53
  # END Enum nwItemClass
53
54
 
@@ -95,9 +96,9 @@ class nwDocAction(Enum):
95
96
  CUT = 3
96
97
  COPY = 4
97
98
  PASTE = 5
98
- EMPH = 6
99
- STRONG = 7
100
- STRIKE = 8
99
+ MD_ITALIC = 6
100
+ MD_BOLD = 7
101
+ MD_STRIKE = 8
101
102
  S_QUOTE = 9
102
103
  D_QUOTE = 10
103
104
  SEL_ALL = 11
@@ -107,23 +108,24 @@ class nwDocAction(Enum):
107
108
  BLOCK_H3 = 15
108
109
  BLOCK_H4 = 16
109
110
  BLOCK_COM = 17
110
- BLOCK_TXT = 18
111
- BLOCK_TTL = 19
112
- BLOCK_UNN = 20
113
- REPL_SNG = 21
114
- REPL_DBL = 22
115
- RM_BREAKS = 23
116
- ALIGN_L = 24
117
- ALIGN_C = 25
118
- ALIGN_R = 26
119
- INDENT_L = 27
120
- INDENT_R = 28
121
- SC_ITALIC = 29
122
- SC_BOLD = 30
123
- SC_STRIKE = 31
124
- SC_ULINE = 32
125
- SC_SUP = 33
126
- SC_SUB = 34
111
+ BLOCK_IGN = 18
112
+ BLOCK_TXT = 19
113
+ BLOCK_TTL = 20
114
+ BLOCK_UNN = 21
115
+ REPL_SNG = 22
116
+ REPL_DBL = 23
117
+ RM_BREAKS = 24
118
+ ALIGN_L = 25
119
+ ALIGN_C = 26
120
+ ALIGN_R = 27
121
+ INDENT_L = 28
122
+ INDENT_R = 29
123
+ SC_ITALIC = 30
124
+ SC_BOLD = 31
125
+ SC_STRIKE = 32
126
+ SC_ULINE = 33
127
+ SC_SUP = 34
128
+ SC_SUB = 35
127
129
 
128
130
  # END Enum nwDocAction
129
131
 
@@ -140,6 +142,7 @@ class nwDocInsert(Enum):
140
142
  NEW_PAGE = 7
141
143
  VSPACE_S = 8
142
144
  VSPACE_M = 9
145
+ LIPSUM = 10
143
146
 
144
147
  # END Enum nwDocInsert
145
148
 
novelwriter/error.py CHANGED
@@ -6,7 +6,7 @@ File History:
6
6
  Created: 2020-08-02 [0.10.2]
7
7
 
8
8
  This file is a part of novelWriter
9
- Copyright 2018–2023, Veronica Berglyd Olsen
9
+ Copyright 2018–2024, Veronica Berglyd Olsen
10
10
 
11
11
  This program is free software: you can redistribute it and/or modify
12
12
  it under the terms of the GNU General Public License as published by
@@ -50,7 +50,7 @@ def logException() -> None:
50
50
  """Log the content of an exception message."""
51
51
  exType, exValue, _ = sys.exc_info()
52
52
  if exType is not None:
53
- logger.error("%s: %s", exType.__name__, str(exValue))
53
+ logger.error(f"{exType.__name__}: {str(exValue)}", stacklevel=2)
54
54
  return
55
55
 
56
56
 
@@ -6,7 +6,7 @@ File History:
6
6
  Created: 2023-06-07 [2.1b1]
7
7
 
8
8
  This file is a part of novelWriter
9
- Copyright 2018–2023, Veronica Berglyd Olsen
9
+ Copyright 2018–2024, Veronica Berglyd Olsen
10
10
 
11
11
  This program is free software: you can redistribute it and/or modify
12
12
  it under the terms of the GNU General Public License as published by
@@ -84,17 +84,17 @@ class NProgressCircle(QProgressBar):
84
84
  """Custom painter for the progress bar."""
85
85
  progress = 100.0*self.value()/self.maximum()
86
86
  angle = ceil(16*3.6*progress)
87
- qPaint = QPainter(self)
88
- qPaint.setRenderHint(QPainter.Antialiasing, True)
89
- qPaint.setPen(self._dPen)
90
- qPaint.setBrush(self._dBrush)
91
- qPaint.drawEllipse(self._dRect)
92
- qPaint.setPen(self._bPen)
93
- qPaint.drawArc(self._cRect, 0, 360*16)
94
- qPaint.setPen(self._cPen)
95
- qPaint.drawArc(self._cRect, 90*16, -angle)
96
- qPaint.setPen(self._tColor)
97
- qPaint.drawText(self._cRect, Qt.AlignCenter, self._text or f"{progress:.1f} %")
87
+ painter = QPainter(self)
88
+ painter.setRenderHint(QPainter.Antialiasing, True)
89
+ painter.setPen(self._dPen)
90
+ painter.setBrush(self._dBrush)
91
+ painter.drawEllipse(self._dRect)
92
+ painter.setPen(self._bPen)
93
+ painter.drawArc(self._cRect, 0, 360*16)
94
+ painter.setPen(self._cPen)
95
+ painter.drawArc(self._cRect, 90*16, -angle)
96
+ painter.setPen(self._tColor)
97
+ painter.drawText(self._cRect, Qt.AlignCenter, self._text or f"{progress:.1f} %")
98
98
  return
99
99
 
100
100
  # END Class NProgressCircle