novelWriter 2.5.3__py3-none-any.whl → 2.6b2__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 (83) hide show
  1. {novelWriter-2.5.3.dist-info → novelWriter-2.6b2.dist-info}/METADATA +1 -1
  2. {novelWriter-2.5.3.dist-info → novelWriter-2.6b2.dist-info}/RECORD +80 -60
  3. novelwriter/__init__.py +49 -10
  4. novelwriter/assets/i18n/project_en_GB.json +1 -0
  5. novelwriter/assets/icons/typicons_dark/icons.conf +8 -0
  6. novelwriter/assets/icons/typicons_dark/mixed_copy.svg +4 -0
  7. novelwriter/assets/icons/typicons_dark/mixed_margin-bottom.svg +6 -0
  8. novelwriter/assets/icons/typicons_dark/mixed_margin-left.svg +6 -0
  9. novelwriter/assets/icons/typicons_dark/mixed_margin-right.svg +6 -0
  10. novelwriter/assets/icons/typicons_dark/mixed_margin-top.svg +6 -0
  11. novelwriter/assets/icons/typicons_dark/mixed_size-height.svg +6 -0
  12. novelwriter/assets/icons/typicons_dark/mixed_size-width.svg +6 -0
  13. novelwriter/assets/icons/typicons_dark/nw_toolbar.svg +5 -0
  14. novelwriter/assets/icons/typicons_light/icons.conf +8 -0
  15. novelwriter/assets/icons/typicons_light/mixed_copy.svg +4 -0
  16. novelwriter/assets/icons/typicons_light/mixed_margin-bottom.svg +6 -0
  17. novelwriter/assets/icons/typicons_light/mixed_margin-left.svg +6 -0
  18. novelwriter/assets/icons/typicons_light/mixed_margin-right.svg +6 -0
  19. novelwriter/assets/icons/typicons_light/mixed_margin-top.svg +6 -0
  20. novelwriter/assets/icons/typicons_light/mixed_size-height.svg +6 -0
  21. novelwriter/assets/icons/typicons_light/mixed_size-width.svg +6 -0
  22. novelwriter/assets/icons/typicons_light/nw_toolbar.svg +5 -0
  23. novelwriter/assets/manual.pdf +0 -0
  24. novelwriter/assets/sample.zip +0 -0
  25. novelwriter/common.py +100 -2
  26. novelwriter/config.py +25 -15
  27. novelwriter/constants.py +168 -60
  28. novelwriter/core/buildsettings.py +66 -39
  29. novelwriter/core/coretools.py +145 -147
  30. novelwriter/core/docbuild.py +132 -170
  31. novelwriter/core/index.py +38 -37
  32. novelwriter/core/item.py +41 -8
  33. novelwriter/core/itemmodel.py +518 -0
  34. novelwriter/core/options.py +4 -1
  35. novelwriter/core/project.py +67 -89
  36. novelwriter/core/spellcheck.py +9 -14
  37. novelwriter/core/status.py +7 -5
  38. novelwriter/core/tree.py +268 -287
  39. novelwriter/dialogs/docmerge.py +7 -17
  40. novelwriter/dialogs/preferences.py +46 -33
  41. novelwriter/dialogs/projectsettings.py +5 -5
  42. novelwriter/enum.py +36 -23
  43. novelwriter/extensions/configlayout.py +27 -12
  44. novelwriter/extensions/modified.py +13 -1
  45. novelwriter/extensions/pagedsidebar.py +5 -5
  46. novelwriter/formats/shared.py +155 -0
  47. novelwriter/formats/todocx.py +1191 -0
  48. novelwriter/formats/tohtml.py +451 -0
  49. novelwriter/{core → formats}/tokenizer.py +487 -491
  50. novelwriter/formats/tomarkdown.py +217 -0
  51. novelwriter/{core → formats}/toodt.py +311 -432
  52. novelwriter/formats/toqdoc.py +484 -0
  53. novelwriter/formats/toraw.py +91 -0
  54. novelwriter/gui/doceditor.py +342 -284
  55. novelwriter/gui/dochighlight.py +96 -84
  56. novelwriter/gui/docviewer.py +88 -31
  57. novelwriter/gui/docviewerpanel.py +17 -25
  58. novelwriter/gui/editordocument.py +17 -2
  59. novelwriter/gui/itemdetails.py +25 -28
  60. novelwriter/gui/mainmenu.py +129 -63
  61. novelwriter/gui/noveltree.py +45 -47
  62. novelwriter/gui/outline.py +196 -249
  63. novelwriter/gui/projtree.py +594 -1241
  64. novelwriter/gui/search.py +9 -10
  65. novelwriter/gui/sidebar.py +7 -6
  66. novelwriter/gui/theme.py +10 -5
  67. novelwriter/guimain.py +100 -196
  68. novelwriter/shared.py +66 -27
  69. novelwriter/text/counting.py +2 -0
  70. novelwriter/text/patterns.py +168 -60
  71. novelwriter/tools/manusbuild.py +14 -12
  72. novelwriter/tools/manuscript.py +120 -78
  73. novelwriter/tools/manussettings.py +424 -291
  74. novelwriter/tools/welcome.py +4 -4
  75. novelwriter/tools/writingstats.py +3 -3
  76. novelwriter/types.py +23 -7
  77. novelwriter/core/tohtml.py +0 -530
  78. novelwriter/core/tomarkdown.py +0 -252
  79. novelwriter/core/toqdoc.py +0 -419
  80. {novelWriter-2.5.3.dist-info → novelWriter-2.6b2.dist-info}/LICENSE.md +0 -0
  81. {novelWriter-2.5.3.dist-info → novelWriter-2.6b2.dist-info}/WHEEL +0 -0
  82. {novelWriter-2.5.3.dist-info → novelWriter-2.6b2.dist-info}/entry_points.txt +0 -0
  83. {novelWriter-2.5.3.dist-info → novelWriter-2.6b2.dist-info}/top_level.txt +0 -0
@@ -29,18 +29,18 @@ from enum import Enum
29
29
 
30
30
  from PyQt5.QtCore import QModelIndex, Qt, pyqtSignal, pyqtSlot
31
31
  from PyQt5.QtWidgets import (
32
- QAbstractItemView, QFrame, QHeaderView, QMenu, QTabWidget, QToolButton,
33
- QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget
32
+ QAbstractItemView, QFrame, QMenu, QTabWidget, QToolButton, QTreeWidget,
33
+ QTreeWidgetItem, QVBoxLayout, QWidget
34
34
  )
35
35
 
36
36
  from novelwriter import CONFIG, SHARED
37
37
  from novelwriter.common import checkInt
38
- from novelwriter.constants import nwHeaders, nwLabels, nwLists, trConst
38
+ from novelwriter.constants import nwLabels, nwLists, nwStyles, trConst
39
39
  from novelwriter.core.index import IndexHeading, IndexItem
40
- from novelwriter.enum import nwDocMode, nwItemClass
40
+ from novelwriter.enum import nwChange, nwDocMode, nwItemClass
41
41
  from novelwriter.extensions.modified import NIconToolButton
42
42
  from novelwriter.gui.theme import STYLES_FLAT_TABS, STYLES_MIN_TOOLBUTTON
43
- from novelwriter.types import QtDecoration, QtUserRole
43
+ from novelwriter.types import QtDecoration, QtHeaderFixed, QtHeaderToContents, QtUserRole
44
44
 
45
45
  logger = logging.getLogger(__name__)
46
46
 
@@ -151,8 +151,8 @@ class GuiDocViewerPanel(QWidget):
151
151
  self.updateHandle(self._lastHandle)
152
152
  return
153
153
 
154
- @pyqtSlot(str)
155
- def projectItemChanged(self, tHandle: str) -> None:
154
+ @pyqtSlot(str, Enum)
155
+ def onProjectItemChanged(self, tHandle: str, change: nwChange) -> None:
156
156
  """Update meta data for project item."""
157
157
  self.tabBackRefs.refreshDocument(tHandle)
158
158
  activeOnly = self.aInactive.isChecked()
@@ -259,10 +259,10 @@ class _ViewPanelBackRefs(QTreeWidget):
259
259
  treeHeader = self.header()
260
260
  treeHeader.setStretchLastSection(True)
261
261
  treeHeader.setMinimumSectionSize(iPx + cMg) # See Issue #1627
262
- treeHeader.setSectionResizeMode(self.C_DOC, QHeaderView.ResizeMode.ResizeToContents)
263
- treeHeader.setSectionResizeMode(self.C_EDIT, QHeaderView.ResizeMode.Fixed)
264
- treeHeader.setSectionResizeMode(self.C_VIEW, QHeaderView.ResizeMode.Fixed)
265
- treeHeader.setSectionResizeMode(self.C_TITLE, QHeaderView.ResizeMode.ResizeToContents)
262
+ treeHeader.setSectionResizeMode(self.C_DOC, QtHeaderToContents)
263
+ treeHeader.setSectionResizeMode(self.C_EDIT, QtHeaderFixed)
264
+ treeHeader.setSectionResizeMode(self.C_VIEW, QtHeaderFixed)
265
+ treeHeader.setSectionResizeMode(self.C_TITLE, QtHeaderToContents)
266
266
  treeHeader.resizeSection(self.C_EDIT, iPx + cMg)
267
267
  treeHeader.resizeSection(self.C_VIEW, iPx + cMg)
268
268
  treeHeader.setSectionsMovable(False)
@@ -339,17 +339,13 @@ class _ViewPanelBackRefs(QTreeWidget):
339
339
  def _setTreeItemValues(self, tHandle: str, sTitle: str, hItem: IndexHeading) -> None:
340
340
  """Add or update a tree item."""
341
341
  if nwItem := SHARED.project.tree[tHandle]:
342
- docIcon = SHARED.theme.getItemIcon(
343
- nwItem.itemType, nwItem.itemClass,
344
- nwItem.itemLayout, nwItem.mainHeading
345
- )
346
- iLevel = nwHeaders.H_LEVEL.get(hItem.level, 0) if nwItem.isDocumentLayout() else 5
342
+ iLevel = nwStyles.H_LEVEL.get(hItem.level, 0) if nwItem.isDocumentLayout() else 5
347
343
  hDec = SHARED.theme.getHeaderDecorationNarrow(iLevel)
348
344
 
349
345
  tKey = f"{tHandle}:{sTitle}"
350
346
  trItem = self._treeMap[tKey] if tKey in self._treeMap else QTreeWidgetItem()
351
347
 
352
- trItem.setIcon(self.C_DOC, docIcon)
348
+ trItem.setIcon(self.C_DOC, nwItem.getMainIcon())
353
349
  trItem.setText(self.C_DOC, nwItem.itemName)
354
350
  trItem.setToolTip(self.C_DOC, nwItem.itemName)
355
351
  trItem.setIcon(self.C_EDIT, self._editIcon)
@@ -407,8 +403,8 @@ class _ViewPanelKeyWords(QTreeWidget):
407
403
  treeHeader = self.header()
408
404
  treeHeader.setStretchLastSection(True)
409
405
  treeHeader.setMinimumSectionSize(iPx + cMg) # See Issue #1627
410
- treeHeader.setSectionResizeMode(self.C_EDIT, QHeaderView.ResizeMode.Fixed)
411
- treeHeader.setSectionResizeMode(self.C_VIEW, QHeaderView.ResizeMode.Fixed)
406
+ treeHeader.setSectionResizeMode(self.C_EDIT, QtHeaderFixed)
407
+ treeHeader.setSectionResizeMode(self.C_VIEW, QtHeaderFixed)
412
408
  treeHeader.resizeSection(self.C_EDIT, iPx + cMg)
413
409
  treeHeader.resizeSection(self.C_VIEW, iPx + cMg)
414
410
  treeHeader.setSectionsMovable(False)
@@ -448,12 +444,8 @@ class _ViewPanelKeyWords(QTreeWidget):
448
444
  def addUpdateEntry(self, tag: str, name: str, iItem: IndexItem, hItem: IndexHeading) -> None:
449
445
  """Add a new entry, or update an existing one."""
450
446
  nwItem = iItem.item
451
- docIcon = SHARED.theme.getItemIcon(
452
- nwItem.itemType, nwItem.itemClass,
453
- nwItem.itemLayout, nwItem.mainHeading
454
- )
455
447
  impLabel, impIcon = nwItem.getImportStatus()
456
- iLevel = nwHeaders.H_LEVEL.get(hItem.level, 0) if nwItem.isDocumentLayout() else 5
448
+ iLevel = nwStyles.H_LEVEL.get(hItem.level, 0) if nwItem.isDocumentLayout() else 5
457
449
  hDec = SHARED.theme.getHeaderDecorationNarrow(iLevel)
458
450
 
459
451
  # This can not use a get call to the dictionary as that would create an
@@ -468,7 +460,7 @@ class _ViewPanelKeyWords(QTreeWidget):
468
460
  trItem.setIcon(self.C_IMPORT, impIcon)
469
461
  trItem.setText(self.C_IMPORT, impLabel)
470
462
  trItem.setToolTip(self.C_IMPORT, impLabel)
471
- trItem.setIcon(self.C_DOC, docIcon)
463
+ trItem.setIcon(self.C_DOC, nwItem.getMainIcon())
472
464
  trItem.setText(self.C_DOC, nwItem.itemName)
473
465
  trItem.setToolTip(self.C_DOC, nwItem.itemName)
474
466
  trItem.setData(self.C_TITLE, QtDecoration, hDec)
@@ -95,6 +95,21 @@ class GuiTextDocument(QTextDocument):
95
95
 
96
96
  return
97
97
 
98
+ def metaDataAtPos(self, pos: int) -> tuple[str, str]:
99
+ """Check if there is meta data available at a given position in
100
+ the document, and if so, return it.
101
+ """
102
+ cursor = QTextCursor(self)
103
+ cursor.setPosition(pos)
104
+ block = cursor.block()
105
+ data = block.userData()
106
+ if block.isValid() and isinstance(data, TextBlockData):
107
+ if (check := pos - block.position()) >= 0:
108
+ for cPos, cEnd, cData, cType in data.metaData:
109
+ if cPos <= check <= cEnd:
110
+ return cData, cType
111
+ return "", ""
112
+
98
113
  def spellErrorAtPos(self, pos: int) -> tuple[str, int, int, list[str]]:
99
114
  """Check if there is a misspelled word at a given position in
100
115
  the document, and if so, return it.
@@ -107,8 +122,8 @@ class GuiTextDocument(QTextDocument):
107
122
  text = block.text()
108
123
  check = pos - block.position()
109
124
  if check >= 0:
110
- for cPos, cLen in data.spellErrors:
111
- cEnd = cPos + cLen
125
+ for cPos, cEnd in data.spellErrors:
126
+ cLen = cEnd - cPos
112
127
  if cPos <= check <= cEnd:
113
128
  word = text[cPos:cEnd]
114
129
  return word, cPos, cLen, SHARED.spelling.suggestWords(word)
@@ -25,12 +25,15 @@ from __future__ import annotations
25
25
 
26
26
  import logging
27
27
 
28
+ from enum import Enum
29
+
28
30
  from PyQt5.QtCore import pyqtSlot
29
31
  from PyQt5.QtWidgets import QGridLayout, QLabel, QWidget
30
32
 
31
33
  from novelwriter import CONFIG, SHARED
32
34
  from novelwriter.common import elide
33
- from novelwriter.constants import nwLabels, trConst
35
+ from novelwriter.constants import nwLabels, nwStats, trConst
36
+ from novelwriter.enum import nwChange
34
37
  from novelwriter.types import (
35
38
  QtAlignLeft, QtAlignLeftBase, QtAlignRight, QtAlignRightBase,
36
39
  QtAlignRightMiddle
@@ -62,6 +65,10 @@ class GuiItemDetails(QWidget):
62
65
  fntValue = self.font()
63
66
  fntValue.setPointSizeF(0.9*fPt)
64
67
 
68
+ trStats1 = trConst(nwLabels.STATS_NAME[nwStats.CHARS_ALL])
69
+ trStats2 = trConst(nwLabels.STATS_NAME[nwStats.WORDS_ALL])
70
+ trStats3 = trConst(nwLabels.STATS_NAME[nwStats.PARAGRAPHS])
71
+
65
72
  # Label
66
73
  self.labelName = QLabel(self.tr("Label"), self)
67
74
  self.labelName.setFont(fntLabel)
@@ -113,7 +120,7 @@ class GuiItemDetails(QWidget):
113
120
  self.usageData.setWordWrap(True)
114
121
 
115
122
  # Character Count
116
- self.cCountName = QLabel(" "+self.tr("Characters"), self)
123
+ self.cCountName = QLabel(trStats1, self)
117
124
  self.cCountName.setFont(fntLabel)
118
125
  self.cCountName.setAlignment(QtAlignRight)
119
126
 
@@ -122,7 +129,7 @@ class GuiItemDetails(QWidget):
122
129
  self.cCountData.setAlignment(QtAlignRight)
123
130
 
124
131
  # Word Count
125
- self.wCountName = QLabel(" "+self.tr("Words"), self)
132
+ self.wCountName = QLabel(trStats2, self)
126
133
  self.wCountName.setFont(fntLabel)
127
134
  self.wCountName.setAlignment(QtAlignRight)
128
135
 
@@ -131,7 +138,7 @@ class GuiItemDetails(QWidget):
131
138
  self.wCountData.setAlignment(QtAlignRight)
132
139
 
133
140
  # Paragraph Count
134
- self.pCountName = QLabel(" "+self.tr("Paragraphs"), self)
141
+ self.pCountName = QLabel(trStats3, self)
135
142
  self.pCountName.setFont(fntLabel)
136
143
  self.pCountName.setAlignment(QtAlignRight)
137
144
 
@@ -216,19 +223,9 @@ class GuiItemDetails(QWidget):
216
223
  self.updateViewBox(self._handle)
217
224
  return
218
225
 
219
- ##
220
- # Public Slots
221
- ##
222
-
223
- @pyqtSlot(str)
224
- def updateViewBox(self, tHandle: str) -> None:
226
+ def updateViewBox(self, tHandle: str | None) -> None:
225
227
  """Populate the details box from a given handle."""
226
- if tHandle is None:
227
- self.clearDetails()
228
- return
229
-
230
- nwItem = SHARED.project.tree[tHandle]
231
- if nwItem is None:
228
+ if not (tHandle and (nwItem := SHARED.project.tree[tHandle])):
232
229
  self.clearDetails()
233
230
  return
234
231
 
@@ -265,10 +262,7 @@ class GuiItemDetails(QWidget):
265
262
  # Layout
266
263
  # ======
267
264
 
268
- usageIcon = SHARED.theme.getItemIcon(
269
- nwItem.itemType, nwItem.itemClass, nwItem.itemLayout, nwItem.mainHeading
270
- )
271
- self.usageIcon.setPixmap(usageIcon.pixmap(iPx, iPx))
265
+ self.usageIcon.setPixmap(nwItem.getMainIcon().pixmap(iPx, iPx))
272
266
  self.usageData.setText(nwItem.describeMe())
273
267
 
274
268
  # Counts
@@ -285,13 +279,16 @@ class GuiItemDetails(QWidget):
285
279
 
286
280
  return
287
281
 
288
- @pyqtSlot(str, int, int, int)
289
- def updateCounts(self, tHandle: str, cC: int, wC: int, pC: int) -> None:
290
- """Update the counts if the handle is the same as the one we're
291
- already showing. Otherwise, do nothing.
292
- """
282
+ ##
283
+ # Public Slots
284
+ ##
285
+
286
+ @pyqtSlot(str, Enum)
287
+ def onProjectItemChanged(self, tHandle: str, change: nwChange) -> None:
288
+ """Process project item change."""
293
289
  if tHandle == self._handle:
294
- self.cCountData.setText(f"{cC:n}")
295
- self.wCountData.setText(f"{wC:n}")
296
- self.pCountData.setText(f"{pC:n}")
290
+ if change == nwChange.UPDATE:
291
+ self.updateViewBox(tHandle)
292
+ elif change == nwChange.DELETE:
293
+ self.updateViewBox(None)
297
294
  return