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
@@ -8,7 +8,7 @@ Created: 2023-02-21 [2.1b1] NPagedToolButton
8
8
  Created: 2023-02-21 [2.1b1] NPagedToolLabel
9
9
 
10
10
  This file is a part of novelWriter
11
- Copyright 2018–2023, Veronica Berglyd Olsen
11
+ Copyright 2018–2024, Veronica Berglyd Olsen
12
12
 
13
13
  This program is free software: you can redistribute it and/or modify
14
14
  it under the terms of the GNU General Public License as published by
@@ -26,7 +26,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
26
26
  from __future__ import annotations
27
27
 
28
28
  from PyQt5.QtGui import QColor, QPaintEvent, QPainter, QPolygon
29
- from PyQt5.QtCore import QPoint, QRectF, Qt, pyqtSignal, pyqtSlot
29
+ from PyQt5.QtCore import QPoint, QRectF, QSize, Qt, pyqtSignal, pyqtSlot
30
30
  from PyQt5.QtWidgets import (
31
31
  QAbstractButton, QAction, QButtonGroup, QLabel, QSizePolicy, QStyle,
32
32
  QStyleOptionToolButton, QToolBar, QToolButton, QWidget
@@ -45,10 +45,9 @@ class NPagedSideBar(QToolBar):
45
45
  def __init__(self, parent: QWidget) -> None:
46
46
  super().__init__(parent=parent)
47
47
 
48
- self._buttons = []
49
- self._actions = []
50
48
  self._labelCol = None
51
49
  self._spacerHeight = self.fontMetrics().height() // 2
50
+ self._buttons: dict[int, _NPagedToolButton] = {}
52
51
 
53
52
  self._group = QButtonGroup(self)
54
53
  self._group.setExclusive(True)
@@ -63,20 +62,13 @@ class NPagedSideBar(QToolBar):
63
62
 
64
63
  return
65
64
 
66
- def setLabelColor(self, color: list | QColor) -> None:
67
- """Set the text color for the labels."""
68
- if isinstance(color, list):
69
- self._labelCol = QColor(*color)
70
- elif isinstance(color, QColor):
71
- self._labelCol = color
72
- return
65
+ def button(self, buttonId: int) -> _NPagedToolButton:
66
+ """Return a specific button."""
67
+ return self._buttons[buttonId]
73
68
 
74
- def addSeparator(self) -> None:
75
- """Add a spacer widget."""
76
- spacer = QWidget(self)
77
- spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
78
- spacer.setFixedHeight(self._spacerHeight)
79
- self.insertWidget(self._stretchAction, spacer)
69
+ def setLabelColor(self, color: QColor) -> None:
70
+ """Set the text color for the labels."""
71
+ self._labelCol = color
80
72
  return
81
73
 
82
74
  def addLabel(self, text: str) -> None:
@@ -94,8 +86,7 @@ class NPagedSideBar(QToolBar):
94
86
  action = self.insertWidget(self._stretchAction, button)
95
87
  self._group.addButton(button, id=buttonId)
96
88
 
97
- self._buttons.append(button)
98
- self._actions.append(action)
89
+ self._buttons[buttonId] = button
99
90
 
100
91
  return action
101
92
 
@@ -139,6 +130,10 @@ class _NPagedToolButton(QToolButton):
139
130
 
140
131
  return
141
132
 
133
+ def sizeHint(self) -> QSize:
134
+ """Return a size hint that includes the arrow."""
135
+ return super().sizeHint() + QSize(4*self._aH, 0)
136
+
142
137
  def paintEvent(self, event: QPaintEvent) -> None:
143
138
  """Overload the paint event to draw a simple, left aligned text
144
139
  label, with a highlight when selected and a transparent base
@@ -165,7 +160,7 @@ class _NPagedToolButton(QToolButton):
165
160
  if self.isChecked():
166
161
  backCol = palette.highlight()
167
162
  paint.setBrush(backCol)
168
- paint.setOpacity(0.5)
163
+ paint.setOpacity(0.35)
169
164
  paint.drawRoundedRect(0, 0, width, height, self._cR, self._cR)
170
165
  textCol = palette.highlightedText().color()
171
166
  else:
@@ -6,7 +6,7 @@ File History:
6
6
  Created: 2023-06-09 [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
@@ -41,14 +41,13 @@ class NProgressSimple(QProgressBar):
41
41
 
42
42
  def paintEvent(self, event: QPaintEvent) -> None:
43
43
  """Custom painter for the progress bar."""
44
- if self.value() == 0:
45
- return
46
- progress = ceil(self.width()*float(self.value())/self.maximum())
47
- qPaint = QPainter(self)
48
- qPaint.setRenderHint(QPainter.Antialiasing, True)
49
- qPaint.setPen(self.palette().highlight().color())
50
- qPaint.setBrush(self.palette().highlight())
51
- qPaint.drawRect(0, 0, progress, self.height())
44
+ if (value := self.value()) > 0:
45
+ progress = ceil(self.width()*float(value)/self.maximum())
46
+ painter = QPainter(self)
47
+ painter.setRenderHint(QPainter.Antialiasing, True)
48
+ painter.setPen(self.palette().highlight().color())
49
+ painter.setBrush(self.palette().highlight())
50
+ painter.drawRect(0, 0, progress, self.height())
52
51
  return
53
52
 
54
53
  # END Class NProgressSimple
@@ -6,7 +6,7 @@ File History:
6
6
  Created: 2020-05-17 [0.5.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
@@ -24,6 +24,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
24
24
  from __future__ import annotations
25
25
 
26
26
  import logging
27
+
27
28
  from typing import Literal
28
29
 
29
30
  from PyQt5.QtGui import QColor, QPaintEvent, QPainter
@@ -64,14 +65,13 @@ class StatusLED(QAbstractButton):
64
65
  return
65
66
 
66
67
  def paintEvent(self, event: QPaintEvent) -> None:
67
- """Drawing the LED."""
68
- qPalette = self.palette()
69
- qPaint = QPainter(self)
70
- qPaint.setRenderHint(QPainter.Antialiasing, True)
71
- qPaint.setPen(qPalette.dark().color())
72
- qPaint.setBrush(self._theCol)
73
- qPaint.setOpacity(1.0)
74
- qPaint.drawEllipse(1, 1, self.width() - 2, self.height() - 2)
68
+ """Draw the LED."""
69
+ painter = QPainter(self)
70
+ painter.setRenderHint(QPainter.Antialiasing, True)
71
+ painter.setPen(self.palette().dark().color())
72
+ painter.setBrush(self._theCol)
73
+ painter.setOpacity(1.0)
74
+ painter.drawEllipse(1, 1, self.width() - 2, self.height() - 2)
75
75
  return
76
76
 
77
77
  # END Class StatusLED
@@ -6,7 +6,7 @@ File History:
6
6
  Created: 2020-05-03 [0.4.5]
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
@@ -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
- theFont = qPaint.font()
138
- theFont.setPixelSize(self._xT)
139
- qPaint.setPen(textColor)
140
- qPaint.setFont(theFont)
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:
@@ -6,7 +6,7 @@ File History:
6
6
  Created: 2023-04-16 [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
@@ -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)
@@ -106,11 +106,6 @@ class NSwitchBox(QScrollArea):
106
106
  self._bumpIndex()
107
107
  return
108
108
 
109
- def setInnerContentsMargins(self, left: int, top: int, right: int, bottom: int) -> None:
110
- """Set the contents margins of the inner layout."""
111
- self._content.setContentsMargins(left, top, right, bottom)
112
- return
113
-
114
109
  ##
115
110
  # Internal Functions
116
111
  ##
@@ -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