novelWriter 2.2.1__py3-none-any.whl → 2.3b1__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 (110) hide show
  1. {novelWriter-2.2.1.dist-info → novelWriter-2.3b1.dist-info}/METADATA +1 -1
  2. {novelWriter-2.2.1.dist-info → novelWriter-2.3b1.dist-info}/RECORD +102 -92
  3. novelwriter/__init__.py +4 -4
  4. novelwriter/assets/icons/typicons_dark/icons.conf +6 -0
  5. novelwriter/assets/icons/typicons_dark/mixed_import.svg +5 -0
  6. novelwriter/assets/icons/typicons_dark/typ_document-add-col.svg +8 -0
  7. novelwriter/assets/icons/typicons_dark/typ_document-add.svg +4 -0
  8. novelwriter/assets/icons/typicons_dark/typ_document.svg +4 -0
  9. novelwriter/assets/icons/typicons_dark/typ_th-dot-more.svg +4 -0
  10. novelwriter/assets/icons/typicons_light/icons.conf +6 -0
  11. novelwriter/assets/icons/typicons_light/mixed_import.svg +5 -0
  12. novelwriter/assets/icons/typicons_light/typ_document-add-col.svg +8 -0
  13. novelwriter/assets/icons/typicons_light/typ_document-add.svg +4 -0
  14. novelwriter/assets/icons/typicons_light/typ_document.svg +4 -0
  15. novelwriter/assets/icons/typicons_light/typ_th-dot-more.svg +4 -0
  16. novelwriter/assets/images/novelwriter-text-dark.svg +4 -0
  17. novelwriter/assets/images/novelwriter-text-light.svg +4 -0
  18. novelwriter/assets/images/welcome-dark.jpg +0 -0
  19. novelwriter/assets/images/welcome-light.jpg +0 -0
  20. novelwriter/assets/manual.pdf +0 -0
  21. novelwriter/assets/sample.zip +0 -0
  22. novelwriter/assets/syntax/default_dark.conf +1 -0
  23. novelwriter/assets/syntax/default_light.conf +1 -0
  24. novelwriter/assets/syntax/grey_dark.conf +1 -0
  25. novelwriter/assets/syntax/grey_light.conf +1 -0
  26. novelwriter/assets/syntax/light_owl.conf +1 -0
  27. novelwriter/assets/syntax/night_owl.conf +1 -0
  28. novelwriter/assets/syntax/solarized_dark.conf +1 -0
  29. novelwriter/assets/syntax/solarized_light.conf +1 -0
  30. novelwriter/assets/syntax/tomorrow.conf +1 -0
  31. novelwriter/assets/syntax/tomorrow_night.conf +1 -0
  32. novelwriter/assets/syntax/tomorrow_night_blue.conf +1 -0
  33. novelwriter/assets/syntax/tomorrow_night_bright.conf +1 -0
  34. novelwriter/assets/syntax/tomorrow_night_eighties.conf +1 -0
  35. novelwriter/assets/text/credits_en.htm +4 -2
  36. novelwriter/assets/themes/default_dark.conf +2 -2
  37. novelwriter/assets/themes/default_light.conf +2 -2
  38. novelwriter/common.py +48 -37
  39. novelwriter/config.py +36 -41
  40. novelwriter/constants.py +38 -16
  41. novelwriter/core/buildsettings.py +7 -7
  42. novelwriter/core/coretools.py +192 -154
  43. novelwriter/core/docbuild.py +6 -3
  44. novelwriter/core/document.py +6 -6
  45. novelwriter/core/index.py +89 -56
  46. novelwriter/core/item.py +21 -3
  47. novelwriter/core/options.py +8 -7
  48. novelwriter/core/project.py +69 -44
  49. novelwriter/core/projectdata.py +1 -14
  50. novelwriter/core/projectxml.py +13 -41
  51. novelwriter/core/sessions.py +2 -1
  52. novelwriter/core/spellcheck.py +2 -1
  53. novelwriter/core/status.py +2 -1
  54. novelwriter/core/storage.py +178 -140
  55. novelwriter/core/tohtml.py +4 -2
  56. novelwriter/core/tokenizer.py +73 -45
  57. novelwriter/core/toodt.py +40 -30
  58. novelwriter/core/tree.py +3 -2
  59. novelwriter/dialogs/about.py +70 -160
  60. novelwriter/dialogs/docmerge.py +6 -5
  61. novelwriter/dialogs/docsplit.py +6 -6
  62. novelwriter/dialogs/editlabel.py +1 -1
  63. novelwriter/dialogs/preferences.py +553 -703
  64. novelwriter/dialogs/{projsettings.py → projectsettings.py} +288 -262
  65. novelwriter/dialogs/quotes.py +27 -23
  66. novelwriter/dialogs/wordlist.py +96 -40
  67. novelwriter/enum.py +20 -18
  68. novelwriter/error.py +1 -1
  69. novelwriter/extensions/circularprogress.py +11 -11
  70. novelwriter/extensions/configlayout.py +185 -134
  71. novelwriter/extensions/modified.py +81 -0
  72. novelwriter/extensions/novelselector.py +26 -12
  73. novelwriter/extensions/pagedsidebar.py +14 -16
  74. novelwriter/extensions/simpleprogress.py +5 -5
  75. novelwriter/extensions/statusled.py +8 -8
  76. novelwriter/extensions/switch.py +31 -63
  77. novelwriter/extensions/switchbox.py +1 -1
  78. novelwriter/extensions/versioninfo.py +153 -0
  79. novelwriter/gui/doceditor.py +178 -150
  80. novelwriter/gui/dochighlight.py +63 -92
  81. novelwriter/gui/docviewer.py +49 -51
  82. novelwriter/gui/docviewerpanel.py +72 -24
  83. novelwriter/gui/itemdetails.py +7 -7
  84. novelwriter/gui/mainmenu.py +14 -18
  85. novelwriter/gui/noveltree.py +9 -8
  86. novelwriter/gui/outline.py +98 -75
  87. novelwriter/gui/projtree.py +188 -61
  88. novelwriter/gui/sidebar.py +3 -4
  89. novelwriter/gui/statusbar.py +3 -4
  90. novelwriter/gui/theme.py +60 -68
  91. novelwriter/guimain.py +49 -156
  92. novelwriter/shared.py +15 -1
  93. novelwriter/tools/dictionaries.py +5 -6
  94. novelwriter/tools/manuscript.py +6 -6
  95. novelwriter/tools/manussettings.py +192 -221
  96. novelwriter/tools/noveldetails.py +525 -0
  97. novelwriter/tools/welcome.py +802 -0
  98. novelwriter/tools/writingstats.py +9 -9
  99. novelwriter/assets/images/wizard-back.jpg +0 -0
  100. novelwriter/assets/text/gplv3_en.htm +0 -641
  101. novelwriter/assets/text/release_notes.htm +0 -60
  102. novelwriter/dialogs/projdetails.py +0 -518
  103. novelwriter/dialogs/projload.py +0 -294
  104. novelwriter/dialogs/updates.py +0 -172
  105. novelwriter/extensions/pageddialog.py +0 -130
  106. novelwriter/tools/projwizard.py +0 -478
  107. {novelWriter-2.2.1.dist-info → novelWriter-2.3b1.dist-info}/LICENSE.md +0 -0
  108. {novelWriter-2.2.1.dist-info → novelWriter-2.3b1.dist-info}/WHEEL +0 -0
  109. {novelWriter-2.2.1.dist-info → novelWriter-2.3b1.dist-info}/entry_points.txt +0 -0
  110. {novelWriter-2.2.1.dist-info → novelWriter-2.3b1.dist-info}/top_level.txt +0 -0
@@ -24,31 +24,22 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
24
24
  from __future__ import annotations
25
25
 
26
26
  from PyQt5.QtGui import QMouseEvent, QPainter, QPaintEvent, QResizeEvent
27
- from PyQt5.QtCore import QEvent, QPropertyAnimation, QRectF, Qt, pyqtProperty
27
+ from PyQt5.QtCore import QEvent, QPropertyAnimation, Qt, pyqtProperty
28
28
  from PyQt5.QtWidgets import QAbstractButton, QSizePolicy, QWidget
29
29
 
30
30
  from novelwriter import CONFIG
31
- from novelwriter.constants import nwUnicode
32
31
 
33
32
 
34
33
  class NSwitch(QAbstractButton):
35
34
 
36
- def __init__(self, parent: QWidget | None = None,
37
- width: int | None = None, height: int | None = None) -> None:
38
- super().__init__(parent=parent)
39
-
40
- if width is None:
41
- self._xW = CONFIG.pxInt(40)
42
- else:
43
- self._xW = width
35
+ __slots__ = ("_xW", "_xH", "_xR", "_rB", "_rH", "_rR", "_offset")
44
36
 
45
- if height is None:
46
- self._xH = CONFIG.pxInt(20)
47
- else:
48
- self._xH = height
37
+ def __init__(self, parent: QWidget | None = None, height: int = 0) -> None:
38
+ super().__init__(parent=parent)
49
39
 
40
+ self._xH = height or CONFIG.pxInt(20)
41
+ self._xW = 2*self._xH
50
42
  self._xR = int(self._xH*0.5)
51
- self._xT = int(self._xH*0.6)
52
43
  self._rB = int(CONFIG.guiScale*2)
53
44
  self._rH = self._xH - 2*self._rB
54
45
  self._rR = self._xR - self._rB
@@ -70,7 +61,7 @@ class NSwitch(QAbstractButton):
70
61
  return self._offset
71
62
 
72
63
  @offset.setter # type: ignore
73
- def offset(self, offset: int):
64
+ def offset(self, offset: int) -> None:
74
65
  self._offset = offset
75
66
  self.update()
76
67
  return
@@ -82,10 +73,7 @@ class NSwitch(QAbstractButton):
82
73
  def setChecked(self, checked: bool) -> None:
83
74
  """Overload setChecked to also alter the offset."""
84
75
  super().setChecked(checked)
85
- if checked:
86
- self._offset = self._xW - self._xR
87
- else:
88
- self._offset = self._xR
76
+ self._offset = (self._xW - self._xR) if checked else self._xR
89
77
  return
90
78
 
91
79
  ##
@@ -95,53 +83,36 @@ class NSwitch(QAbstractButton):
95
83
  def resizeEvent(self, event: QResizeEvent) -> None:
96
84
  """Overload resize to ensure correct offset."""
97
85
  super().resizeEvent(event)
98
- if self.isChecked():
99
- self._offset = self._xW - self._xR
100
- else:
101
- self._offset = self._xR
86
+ self._offset = (self._xW - self._xR) if self.isChecked() else self._xR
102
87
  return
103
88
 
104
89
  def paintEvent(self, event: QPaintEvent) -> None:
105
90
  """Drawing the switch itself."""
106
- qPaint = QPainter(self)
107
- qPaint.setRenderHint(QPainter.Antialiasing, True)
108
- qPaint.setPen(Qt.NoPen)
91
+ painter = QPainter(self)
92
+ painter.setRenderHint(QPainter.Antialiasing, True)
93
+ painter.setPen(Qt.NoPen)
109
94
 
110
- qPalette = self.palette()
95
+ palette = self.palette()
111
96
  if self.isChecked():
112
- trackBrush = qPalette.highlight()
113
- thumbBrush = qPalette.highlightedText()
114
- textColor = qPalette.highlight().color()
115
- thumbText = nwUnicode.U_CHECK
97
+ trackBrush = palette.highlight()
98
+ thumbBrush = palette.highlightedText()
116
99
  else:
117
- trackBrush = qPalette.dark()
118
- thumbBrush = qPalette.light()
119
- textColor = qPalette.dark().color()
120
- thumbText = nwUnicode.U_CROSS
100
+ trackBrush = palette.dark()
101
+ thumbBrush = palette.light()
121
102
 
122
103
  if self.isEnabled():
123
104
  trackOpacity = 1.0
124
105
  else:
125
106
  trackOpacity = 0.6
126
- trackBrush = qPalette.shadow()
127
- thumbBrush = qPalette.mid()
128
- textColor = qPalette.shadow().color()
129
-
130
- qPaint.setBrush(trackBrush)
131
- qPaint.setOpacity(trackOpacity)
132
- qPaint.drawRoundedRect(0, 0, self._xW, self._xH, self._xR, self._xR)
133
-
134
- qPaint.setBrush(thumbBrush)
135
- qPaint.drawEllipse(self._offset - self._rR, self._rB, self._rH, self._rH)
136
-
137
- font = qPaint.font()
138
- font.setPixelSize(self._xT)
139
- qPaint.setPen(textColor)
140
- qPaint.setFont(font)
141
- qPaint.drawText(
142
- QRectF(self._offset - self._rR, self._rB, self._rH, self._rH),
143
- Qt.AlignCenter, thumbText
144
- )
107
+ trackBrush = palette.shadow()
108
+ thumbBrush = palette.mid()
109
+
110
+ painter.setBrush(trackBrush)
111
+ painter.setOpacity(trackOpacity)
112
+ painter.drawRoundedRect(0, 0, self._xW, self._xH, self._xR, self._xR)
113
+
114
+ painter.setBrush(thumbBrush)
115
+ painter.drawEllipse(self._offset - self._rR, self._rB, self._rH, self._rH)
145
116
 
146
117
  return
147
118
 
@@ -149,14 +120,11 @@ class NSwitch(QAbstractButton):
149
120
  """Animate the switch on mouse release."""
150
121
  super().mouseReleaseEvent(event)
151
122
  if event.button() == Qt.LeftButton:
152
- doAnim = QPropertyAnimation(self, b"offset", self)
153
- doAnim.setDuration(120)
154
- doAnim.setStartValue(self._offset)
155
- if self.isChecked():
156
- doAnim.setEndValue(self._xW - self._xR)
157
- else:
158
- doAnim.setEndValue(self._xR)
159
- doAnim.start()
123
+ anim = QPropertyAnimation(self, b"offset", self)
124
+ anim.setDuration(120)
125
+ anim.setStartValue(self._offset)
126
+ anim.setEndValue((self._xW - self._xR) if self.isChecked() else self._xR)
127
+ anim.start()
160
128
  return
161
129
 
162
130
  def enterEvent(self, event: QEvent) -> None:
@@ -87,7 +87,7 @@ class NSwitchBox(QScrollArea):
87
87
  label = QLabel(text)
88
88
  self._content.addWidget(label, self._index, 1, Qt.AlignLeft)
89
89
 
90
- switch = NSwitch(width=self._wSwitch, height=self._hSwitch)
90
+ switch = NSwitch(self, height=self._hSwitch)
91
91
  switch.setChecked(default)
92
92
  switch.toggled.connect(lambda state: self._emitSwitchSignal(identifier, state))
93
93
  self._content.addWidget(switch, self._index, 2, Qt.AlignRight)
@@ -0,0 +1,153 @@
1
+ """
2
+ novelWriter – Custom Widget: Version Info
3
+ =========================================
4
+
5
+ File History:
6
+ Created: 2024-02-14 [2.3b1] VersionInfoWidget
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 json
27
+ import logging
28
+
29
+ from time import sleep
30
+ from datetime import datetime
31
+ from urllib.error import HTTPError
32
+ from urllib.request import Request, urlopen
33
+
34
+ from PyQt5.QtGui import QDesktopServices
35
+ from PyQt5.QtCore import QObject, QRunnable, QUrl, pyqtSignal, pyqtSlot
36
+ from PyQt5.QtWidgets import QLabel, QVBoxLayout, QWidget
37
+
38
+ from novelwriter import CONFIG, SHARED, __version__, __date__, __domain__
39
+ from novelwriter.common import formatVersion
40
+ from novelwriter.constants import nwConst
41
+
42
+ logger = logging.getLogger(__name__)
43
+
44
+ API_URL = "https://api.github.com/repos/vkbo/novelwriter/releases/latest"
45
+
46
+
47
+ class VersionInfoWidget(QWidget):
48
+
49
+ def __init__(self, parent: QWidget) -> None:
50
+ super().__init__(parent=parent)
51
+
52
+ # Label Strings
53
+ self._trLatest = self.tr("Latest Version: {0}")
54
+ self._trChecking = self.tr("Checking ...")
55
+ self._trDownload = self.tr("Download from {0}")
56
+
57
+ # Labels
58
+ self._lblInfo = QLabel("{0} {1} \u2013 {2} {3} \u2013 {4}".format(
59
+ self.tr("Version"), formatVersion(__version__),
60
+ self.tr("Released on"), datetime.strptime(__date__, "%Y-%m-%d").strftime("%x"),
61
+ "<a href='#notes'>{0}</a>".format(self.tr("Release Notes")),
62
+ ), self)
63
+ self._lblInfo.linkActivated.connect(self._processLink)
64
+ self._lblRelease = QLabel(self._trLatest.format(
65
+ "<a href='#update'>{0}</a>".format(self.tr("Check Now"))
66
+ ), self)
67
+ self._lblRelease.linkActivated.connect(self._processLink)
68
+
69
+ # Assemble
70
+ self._layout = QVBoxLayout()
71
+ self._layout.addWidget(self._lblInfo)
72
+ self._layout.addWidget(self._lblRelease)
73
+ self._layout.setSpacing(CONFIG.pxInt(2))
74
+ self._layout.setContentsMargins(0, 0, 0, 0)
75
+
76
+ self.setLayout(self._layout)
77
+
78
+ return
79
+
80
+ ##
81
+ # Private Slots
82
+ ##
83
+
84
+ @pyqtSlot(str)
85
+ def _processLink(self, link: str) -> None:
86
+ """Process an activated link."""
87
+ if link == "#notes":
88
+ QDesktopServices.openUrl(QUrl(nwConst.URL_RELEASES))
89
+ elif link == "#website":
90
+ QDesktopServices.openUrl(QUrl(nwConst.URL_WEB))
91
+ elif link == "#update":
92
+ self._lblRelease.setText(self._trLatest.format(self._trChecking))
93
+ lookup = _Retriever()
94
+ lookup.signals.dataReady.connect(self._updateReleaseInfo)
95
+ SHARED.runInThreadPool(lookup)
96
+ return
97
+
98
+ ##
99
+ # Private Slots
100
+ ##
101
+
102
+ @pyqtSlot(str, str)
103
+ def _updateReleaseInfo(self, tag: str, reason: str) -> None:
104
+ """Update the widget release info."""
105
+ if version := tag.lstrip("v"):
106
+ download = f"<a href='#website'>{__domain__}</a>"
107
+ self._lblRelease.setText(self._trLatest.format(
108
+ f"{version} \u2013 {self._trDownload.format(download)}"
109
+ ))
110
+ else:
111
+ self._lblRelease.setText(self._trLatest.format(reason or self.tr("Failed")))
112
+ return
113
+
114
+ # END Class VersionInfoWidget
115
+
116
+
117
+ class _Retriever(QRunnable):
118
+
119
+ def __init__(self) -> None:
120
+ super().__init__()
121
+ self.signals = _RetrieverSignal()
122
+ return
123
+
124
+ @pyqtSlot()
125
+ def run(self) -> None:
126
+ """Poll the GitHub API in the background.
127
+ Note: The GitHub API is rate limited at 60 requests per hour.
128
+ """
129
+ logger.info("Contacting: %s", API_URL)
130
+ req = Request(API_URL)
131
+ req.add_header("User-Agent", nwConst.USER_AGENT)
132
+ req.add_header("Accept", "application/vnd.github.v3+json")
133
+ sleep(0.2)
134
+ try:
135
+ with urlopen(req, timeout=15) as ret:
136
+ if isinstance(raw := json.loads(ret.read().decode()), dict):
137
+ self.signals.dataReady.emit(raw.get("tag_name", ""), "")
138
+ except HTTPError as e:
139
+ reason = f"{e.reason.capitalize()} (HTTP {e.code})"
140
+ logger.error(reason)
141
+ self.signals.dataReady.emit("", reason)
142
+ except Exception as e:
143
+ logger.error("Failed to retrieve release info")
144
+ self.signals.dataReady.emit("", str(e))
145
+ return
146
+
147
+ # END Class _Retriever
148
+
149
+
150
+ class _RetrieverSignal(QObject):
151
+ dataReady = pyqtSignal(str, str)
152
+
153
+ # END Class _RetrieverSignal