novelWriter 2.2b1__py3-none-any.whl → 2.2.1__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 (134) hide show
  1. {novelWriter-2.2b1.dist-info → novelWriter-2.2.1.dist-info}/METADATA +3 -3
  2. {novelWriter-2.2b1.dist-info → novelWriter-2.2.1.dist-info}/RECORD +128 -114
  3. {novelWriter-2.2b1.dist-info → novelWriter-2.2.1.dist-info}/WHEEL +1 -1
  4. novelwriter/__init__.py +10 -5
  5. novelwriter/assets/i18n/nw_de_DE.qm +0 -0
  6. novelwriter/assets/i18n/nw_en_US.qm +0 -0
  7. novelwriter/assets/i18n/nw_es_419.qm +0 -0
  8. novelwriter/assets/i18n/nw_fr_FR.qm +0 -0
  9. novelwriter/assets/i18n/nw_it_IT.qm +0 -0
  10. novelwriter/assets/i18n/nw_ja_JP.qm +0 -0
  11. novelwriter/assets/i18n/nw_nb_NO.qm +0 -0
  12. novelwriter/assets/i18n/nw_zh_CN.qm +0 -0
  13. novelwriter/assets/i18n/project_de_DE.json +1 -0
  14. novelwriter/assets/i18n/project_en_GB.json +1 -0
  15. novelwriter/assets/i18n/project_en_US.json +1 -0
  16. novelwriter/assets/i18n/project_es_419.json +11 -0
  17. novelwriter/assets/i18n/project_fr_FR.json +11 -0
  18. novelwriter/assets/i18n/project_it_IT.json +11 -0
  19. novelwriter/assets/i18n/project_ja_JP.json +2 -1
  20. novelwriter/assets/i18n/project_nb_NO.json +1 -0
  21. novelwriter/assets/i18n/project_zh_CN.json +11 -0
  22. novelwriter/assets/icons/novelwriter.ico +0 -0
  23. novelwriter/assets/icons/typicons_dark/icons.conf +11 -3
  24. novelwriter/assets/icons/typicons_dark/nw_deco-h2-narrow.svg +4 -0
  25. novelwriter/assets/icons/typicons_dark/nw_deco-h3-narrow.svg +4 -0
  26. novelwriter/assets/icons/typicons_dark/nw_deco-h4-narrow.svg +4 -0
  27. novelwriter/assets/icons/typicons_dark/nw_deco-note.svg +4 -0
  28. novelwriter/assets/icons/typicons_dark/nw_panel.svg +4 -0
  29. novelwriter/assets/icons/typicons_dark/nw_tb-bold-md.svg +4 -0
  30. novelwriter/assets/icons/typicons_dark/nw_tb-bold.svg +3 -1
  31. novelwriter/assets/icons/typicons_dark/nw_tb-italic-md.svg +4 -0
  32. novelwriter/assets/icons/typicons_dark/nw_tb-italic.svg +3 -1
  33. novelwriter/assets/icons/typicons_dark/nw_tb-strike-md.svg +4 -0
  34. novelwriter/assets/icons/typicons_dark/nw_tb-strike.svg +3 -1
  35. novelwriter/assets/icons/typicons_dark/nw_tb-subscript.svg +4 -2
  36. novelwriter/assets/icons/typicons_dark/nw_tb-superscript.svg +4 -2
  37. novelwriter/assets/icons/typicons_dark/nw_tb-underline.svg +4 -2
  38. novelwriter/assets/icons/typicons_dark/typ_eye.svg +4 -0
  39. novelwriter/assets/icons/typicons_light/icons.conf +11 -3
  40. novelwriter/assets/icons/typicons_light/nw_deco-h2-narrow.svg +4 -0
  41. novelwriter/assets/icons/typicons_light/nw_deco-h3-narrow.svg +4 -0
  42. novelwriter/assets/icons/typicons_light/nw_deco-h4-narrow.svg +4 -0
  43. novelwriter/assets/icons/typicons_light/nw_deco-note.svg +4 -0
  44. novelwriter/assets/icons/typicons_light/nw_panel.svg +4 -0
  45. novelwriter/assets/icons/typicons_light/nw_tb-bold-md.svg +4 -0
  46. novelwriter/assets/icons/typicons_light/nw_tb-bold.svg +3 -1
  47. novelwriter/assets/icons/typicons_light/nw_tb-italic-md.svg +4 -0
  48. novelwriter/assets/icons/typicons_light/nw_tb-italic.svg +3 -1
  49. novelwriter/assets/icons/typicons_light/nw_tb-strike-md.svg +4 -0
  50. novelwriter/assets/icons/typicons_light/nw_tb-strike.svg +3 -1
  51. novelwriter/assets/icons/typicons_light/nw_tb-subscript.svg +4 -2
  52. novelwriter/assets/icons/typicons_light/nw_tb-superscript.svg +4 -2
  53. novelwriter/assets/icons/typicons_light/nw_tb-underline.svg +4 -2
  54. novelwriter/assets/icons/typicons_light/typ_eye.svg +4 -0
  55. novelwriter/assets/icons/x-novelwriter-project.ico +0 -0
  56. novelwriter/assets/manual.pdf +0 -0
  57. novelwriter/assets/sample.zip +0 -0
  58. novelwriter/assets/text/release_notes.htm +50 -7
  59. novelwriter/common.py +35 -27
  60. novelwriter/config.py +13 -28
  61. novelwriter/constants.py +21 -4
  62. novelwriter/core/buildsettings.py +2 -2
  63. novelwriter/core/coretools.py +8 -2
  64. novelwriter/core/docbuild.py +1 -1
  65. novelwriter/core/document.py +1 -1
  66. novelwriter/core/index.py +102 -36
  67. novelwriter/core/item.py +2 -2
  68. novelwriter/core/options.py +6 -3
  69. novelwriter/core/project.py +5 -5
  70. novelwriter/core/projectdata.py +3 -3
  71. novelwriter/core/projectxml.py +1 -1
  72. novelwriter/core/sessions.py +2 -2
  73. novelwriter/core/spellcheck.py +4 -3
  74. novelwriter/core/status.py +3 -3
  75. novelwriter/core/storage.py +1 -1
  76. novelwriter/core/tohtml.py +11 -5
  77. novelwriter/core/tokenizer.py +28 -21
  78. novelwriter/core/tomd.py +6 -2
  79. novelwriter/core/toodt.py +12 -5
  80. novelwriter/core/tree.py +2 -2
  81. novelwriter/dialogs/about.py +30 -31
  82. novelwriter/dialogs/docmerge.py +24 -15
  83. novelwriter/dialogs/docsplit.py +27 -16
  84. novelwriter/dialogs/editlabel.py +19 -7
  85. novelwriter/dialogs/preferences.py +116 -131
  86. novelwriter/dialogs/projdetails.py +29 -36
  87. novelwriter/dialogs/projload.py +32 -36
  88. novelwriter/dialogs/projsettings.py +20 -15
  89. novelwriter/dialogs/quotes.py +32 -25
  90. novelwriter/dialogs/updates.py +17 -16
  91. novelwriter/dialogs/wordlist.py +34 -21
  92. novelwriter/enum.py +19 -8
  93. novelwriter/error.py +1 -1
  94. novelwriter/extensions/circularprogress.py +1 -1
  95. novelwriter/extensions/configlayout.py +3 -15
  96. novelwriter/extensions/{wheeleventfilter.py → eventfilters.py} +15 -5
  97. novelwriter/extensions/novelselector.py +1 -1
  98. novelwriter/extensions/pageddialog.py +1 -1
  99. novelwriter/extensions/pagedsidebar.py +2 -5
  100. novelwriter/extensions/simpleprogress.py +8 -9
  101. novelwriter/extensions/statusled.py +1 -1
  102. novelwriter/extensions/switch.py +4 -4
  103. novelwriter/extensions/switchbox.py +1 -6
  104. novelwriter/gui/doceditor.py +349 -236
  105. novelwriter/gui/dochighlight.py +10 -11
  106. novelwriter/gui/docviewer.py +158 -360
  107. novelwriter/gui/docviewerpanel.py +502 -0
  108. novelwriter/gui/editordocument.py +4 -4
  109. novelwriter/gui/itemdetails.py +2 -2
  110. novelwriter/gui/mainmenu.py +50 -36
  111. novelwriter/gui/noveltree.py +44 -53
  112. novelwriter/gui/outline.py +12 -7
  113. novelwriter/gui/projtree.py +465 -381
  114. novelwriter/gui/sidebar.py +9 -7
  115. novelwriter/gui/statusbar.py +48 -5
  116. novelwriter/gui/theme.py +26 -8
  117. novelwriter/guimain.py +212 -208
  118. novelwriter/shared.py +76 -30
  119. novelwriter/tools/dictionaries.py +268 -0
  120. novelwriter/tools/lipsum.py +34 -28
  121. novelwriter/tools/manusbuild.py +20 -10
  122. novelwriter/tools/manuscript.py +20 -27
  123. novelwriter/tools/manussettings.py +2 -4
  124. novelwriter/tools/projwizard.py +3 -3
  125. novelwriter/tools/writingstats.py +18 -5
  126. novelwriter/assets/icons/typicons_dark/nw_tb-markdown.svg +0 -8
  127. novelwriter/assets/icons/typicons_dark/nw_tb-shortcode.svg +0 -8
  128. novelwriter/assets/icons/typicons_dark/typ_at.svg +0 -4
  129. novelwriter/assets/icons/typicons_light/nw_tb-markdown.svg +0 -8
  130. novelwriter/assets/icons/typicons_light/nw_tb-shortcode.svg +0 -8
  131. novelwriter/assets/icons/typicons_light/typ_at.svg +0 -4
  132. {novelWriter-2.2b1.dist-info → novelWriter-2.2.1.dist-info}/LICENSE.md +0 -0
  133. {novelWriter-2.2b1.dist-info → novelWriter-2.2.1.dist-info}/entry_points.txt +0 -0
  134. {novelWriter-2.2b1.dist-info → novelWriter-2.2.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,502 @@
1
+ """
2
+ novelWriter – GUI Document Viewer Panel
3
+ =======================================
4
+
5
+ File History:
6
+ Created: 2023-11-14 [2.2rc1] GuiDocViewerPanel
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 logging
27
+
28
+ from enum import Enum
29
+
30
+ from PyQt5.QtCore import QModelIndex, QSize, Qt, pyqtSignal, pyqtSlot
31
+ from PyQt5.QtWidgets import (
32
+ QAbstractItemView, QFrame, QHeaderView, QTabWidget, QTreeWidget,
33
+ QTreeWidgetItem, QVBoxLayout, QWidget
34
+ )
35
+
36
+ from novelwriter import CONFIG, SHARED
37
+ from novelwriter.enum import nwDocMode, nwItemClass
38
+ from novelwriter.common import checkInt
39
+ from novelwriter.constants import nwHeaders, nwLabels, nwLists, trConst
40
+ from novelwriter.core.index import IndexHeading, IndexItem
41
+
42
+ logger = logging.getLogger(__name__)
43
+
44
+
45
+ class GuiDocViewerPanel(QWidget):
46
+
47
+ openDocumentRequest = pyqtSignal(str, Enum, str, bool)
48
+ loadDocumentTagRequest = pyqtSignal(str, Enum)
49
+
50
+ def __init__(self, parent: QWidget) -> None:
51
+ super().__init__(parent=parent)
52
+
53
+ logger.debug("Create: GuiDocViewerPanel")
54
+
55
+ self._lastHandle = None
56
+
57
+ self.tabBackRefs = _ViewPanelBackRefs(self)
58
+
59
+ self.mainTabs = QTabWidget(self)
60
+ self.mainTabs.addTab(self.tabBackRefs, self.tr("References"))
61
+
62
+ self.kwTabs: dict[str, _ViewPanelKeyWords] = {}
63
+ self.idTabs: dict[str, int] = {}
64
+ for itemClass in nwLists.USER_CLASSES:
65
+ cTab = _ViewPanelKeyWords(self, itemClass)
66
+ tabId = self.mainTabs.addTab(cTab, trConst(nwLabels.CLASS_NAME[itemClass]))
67
+ self.kwTabs[itemClass.name] = cTab
68
+ self.idTabs[itemClass.name] = tabId
69
+
70
+ # Assemble
71
+ self.outerBox = QVBoxLayout()
72
+ self.outerBox.addWidget(self.mainTabs)
73
+ self.outerBox.setContentsMargins(0, 0, 0, 0)
74
+
75
+ self.setLayout(self.outerBox)
76
+ self.updateTheme(updateTabs=False)
77
+
78
+ logger.debug("Ready: GuiDocViewerPanel")
79
+
80
+ return
81
+
82
+ ##
83
+ # Methods
84
+ ##
85
+
86
+ def updateTheme(self, updateTabs: bool = True) -> None:
87
+ """Update theme elements."""
88
+ vPx = CONFIG.pxInt(4)
89
+ lPx = CONFIG.pxInt(2)
90
+ rPx = CONFIG.pxInt(14)
91
+ hCol = self.palette().highlight().color()
92
+
93
+ styleSheet = (
94
+ "QTabWidget::pane {border: 0;} "
95
+ "QTabWidget QTabBar::tab {"
96
+ f"border: 0; padding: {vPx}px {rPx}px {vPx}px {lPx}px;"
97
+ "} "
98
+ "QTabWidget QTabBar::tab:selected {"
99
+ f"color: rgb({hCol.red()}, {hCol.green()}, {hCol.blue()});"
100
+ "} "
101
+ )
102
+ self.mainTabs.setStyleSheet(styleSheet)
103
+ self.updateHandle(self._lastHandle)
104
+
105
+ if updateTabs:
106
+ self.tabBackRefs.updateTheme()
107
+ for tab in self.kwTabs.values():
108
+ tab.updateTheme()
109
+
110
+ return
111
+
112
+ def openProjectTasks(self) -> None:
113
+ """Run open project tasks."""
114
+ widths = SHARED.project.options.getValue("GuiDocViewerPanel", "colWidths", {})
115
+ if isinstance(widths, dict):
116
+ for key, value in widths.items():
117
+ if key in self.kwTabs and isinstance(value, list):
118
+ self.kwTabs[key].setColumnWidths(value)
119
+ return
120
+
121
+ def closeProjectTasks(self) -> None:
122
+ """Run close project tasks."""
123
+ widths = {}
124
+ for key, tab in self.kwTabs.items():
125
+ widths[key] = tab.getColumnWidths()
126
+ logger.debug("Saving State: GuiDocViewerPanel")
127
+ SHARED.project.options.setValue("GuiDocViewerPanel", "colWidths", widths)
128
+ return
129
+
130
+ ##
131
+ # Public Slots
132
+ ##
133
+
134
+ @pyqtSlot()
135
+ def indexWasCleared(self) -> None:
136
+ """Handle event when the index has been cleared of content."""
137
+ self.tabBackRefs.clearContent()
138
+ for cTab in self.kwTabs.values():
139
+ cTab.clearContent()
140
+ return
141
+
142
+ @pyqtSlot()
143
+ def indexHasAppeared(self) -> None:
144
+ """Handle event when the index has appeared."""
145
+ for key, name, tClass, iItem, hItem in SHARED.project.index.getTagsData():
146
+ if tClass in self.kwTabs and iItem and hItem:
147
+ self.kwTabs[tClass].addUpdateEntry(key, name, iItem, hItem)
148
+ self._updateTabVisibility()
149
+ self.updateHandle(self._lastHandle)
150
+ return
151
+
152
+ @pyqtSlot(str)
153
+ def projectItemChanged(self, tHandle: str) -> None:
154
+ """Update meta data for project item."""
155
+ self.tabBackRefs.refreshDocument(tHandle)
156
+ for key in SHARED.project.index.getDocumentTags(tHandle):
157
+ name, tClass, iItem, hItem = SHARED.project.index.getSingleTag(key)
158
+ if tClass in self.kwTabs and iItem and hItem:
159
+ self.kwTabs[tClass].addUpdateEntry(key, name, iItem, hItem)
160
+ return
161
+
162
+ @pyqtSlot(str)
163
+ def updateHandle(self, tHandle: str | None) -> None:
164
+ """Update the document handle."""
165
+ self._lastHandle = tHandle
166
+ self.tabBackRefs.refreshContent(tHandle or None)
167
+ return
168
+
169
+ @pyqtSlot(list, list)
170
+ def updateChangedTags(self, updated: list[str], deleted: list[str]) -> None:
171
+ """Forward tags changes to the lists."""
172
+ for key in updated:
173
+ name, tClass, iItem, hItem = SHARED.project.index.getSingleTag(key)
174
+ if tClass in self.kwTabs and iItem and hItem:
175
+ self.kwTabs[tClass].addUpdateEntry(key, name, iItem, hItem)
176
+ for key in deleted:
177
+ for cTab in self.kwTabs.values():
178
+ if cTab.removeEntry(key):
179
+ break
180
+ else:
181
+ logger.warning("Could not remove tag '%s' from view panel", key)
182
+ self._updateTabVisibility()
183
+ return
184
+
185
+ ##
186
+ # Internal Functions
187
+ ##
188
+
189
+ def _updateTabVisibility(self) -> None:
190
+ """Hide class tabs with no content."""
191
+ if CONFIG.verQtValue >= 0x050f00:
192
+ for tClass, cTab in self.kwTabs.items():
193
+ self.mainTabs.setTabVisible(self.idTabs[tClass], cTab.countEntries() > 0)
194
+ return
195
+
196
+ # END Class GuiDocViewerPanel
197
+
198
+
199
+ class _ViewPanelBackRefs(QTreeWidget):
200
+
201
+ C_DATA = 0
202
+ C_DOC = 0
203
+ C_EDIT = 1
204
+ C_VIEW = 2
205
+ C_TITLE = 3
206
+
207
+ D_HANDLE = Qt.ItemDataRole.UserRole
208
+
209
+ def __init__(self, parent: GuiDocViewerPanel) -> None:
210
+ super().__init__(parent=parent)
211
+
212
+ self._parent = parent
213
+ self._treeMap: dict[str, QTreeWidgetItem] = {}
214
+
215
+ iPx = SHARED.theme.baseIconSize
216
+ cMg = CONFIG.pxInt(6)
217
+
218
+ self.setHeaderLabels([self.tr("Document"), "", "", self.tr("First Heading")])
219
+ self.setIndentation(0)
220
+ self.setSelectionMode(QAbstractItemView.SelectionMode.NoSelection)
221
+ self.setIconSize(QSize(iPx, iPx))
222
+ self.setFrameStyle(QFrame.Shape.NoFrame)
223
+
224
+ # Set Header Sizes
225
+ treeHeader = self.header()
226
+ treeHeader.setStretchLastSection(True)
227
+ treeHeader.setMinimumSectionSize(iPx + cMg) # See Issue #1627
228
+ treeHeader.setSectionResizeMode(self.C_DOC, QHeaderView.ResizeMode.ResizeToContents)
229
+ treeHeader.setSectionResizeMode(self.C_EDIT, QHeaderView.ResizeMode.Fixed)
230
+ treeHeader.setSectionResizeMode(self.C_VIEW, QHeaderView.ResizeMode.Fixed)
231
+ treeHeader.setSectionResizeMode(self.C_TITLE, QHeaderView.ResizeMode.ResizeToContents)
232
+ treeHeader.resizeSection(self.C_EDIT, iPx + cMg)
233
+ treeHeader.resizeSection(self.C_VIEW, iPx + cMg)
234
+ treeHeader.setSectionsMovable(False)
235
+
236
+ # Cache Icons Locally
237
+ self._editIcon = SHARED.theme.getIcon("edit")
238
+ self._viewIcon = SHARED.theme.getIcon("view")
239
+
240
+ # Signals
241
+ self.clicked.connect(self._treeItemClicked)
242
+ self.doubleClicked.connect(self._treeItemDoubleClicked)
243
+
244
+ return
245
+
246
+ def updateTheme(self) -> None:
247
+ """Update theme elements."""
248
+ self._editIcon = SHARED.theme.getIcon("edit")
249
+ self._viewIcon = SHARED.theme.getIcon("view")
250
+ for i in range(self.topLevelItemCount()):
251
+ if item := self.topLevelItem(i):
252
+ item.setIcon(self.C_EDIT, self._editIcon)
253
+ item.setIcon(self.C_VIEW, self._viewIcon)
254
+ return
255
+
256
+ def clearContent(self) -> None:
257
+ """Clear the widget."""
258
+ self.clear()
259
+ self._treeMap = {}
260
+ return
261
+
262
+ def refreshContent(self, dHandle: str | None) -> None:
263
+ """Update the content."""
264
+ self.clearContent()
265
+ if dHandle:
266
+ refs = SHARED.project.index.getBackReferenceList(dHandle)
267
+ for tHandle, (sTitle, hItem) in refs.items():
268
+ self._setTreeItemValues(tHandle, sTitle, hItem)
269
+ return
270
+
271
+ def refreshDocument(self, tHandle: str) -> None:
272
+ """Refresh document meta data."""
273
+ if iItem := SHARED.project.index.getItemData(tHandle):
274
+ for sTitle, hItem in iItem.items():
275
+ if f"{tHandle}:{sTitle}" in self._treeMap:
276
+ self._setTreeItemValues(tHandle, sTitle, hItem)
277
+ return
278
+
279
+ ##
280
+ # Private Slots
281
+ ##
282
+
283
+ @pyqtSlot("QModelIndex")
284
+ def _treeItemClicked(self, index: QModelIndex) -> None:
285
+ """Emit document open signal on user click."""
286
+ tHandle = index.siblingAtColumn(self.C_DATA).data(self.D_HANDLE)
287
+ if index.column() == self.C_EDIT:
288
+ self._parent.openDocumentRequest.emit(tHandle, nwDocMode.EDIT, "", True)
289
+ elif index.column() == self.C_VIEW:
290
+ self._parent.openDocumentRequest.emit(tHandle, nwDocMode.VIEW, "", True)
291
+ return
292
+
293
+ @pyqtSlot("QModelIndex")
294
+ def _treeItemDoubleClicked(self, index: QModelIndex) -> None:
295
+ """Emit follow tag signal on user double click."""
296
+ tHandle = index.siblingAtColumn(self.C_DATA).data(self.D_HANDLE)
297
+ if index.column() not in (self.C_EDIT, self.C_VIEW):
298
+ self._parent.openDocumentRequest.emit(tHandle, nwDocMode.VIEW, "", True)
299
+ return
300
+
301
+ ##
302
+ # Internal Functions
303
+ ##
304
+
305
+ def _setTreeItemValues(self, tHandle: str, sTitle: str, hItem: IndexHeading) -> None:
306
+ """Add or update a tree item."""
307
+ if nwItem := SHARED.project.tree[tHandle]:
308
+ docIcon = SHARED.theme.getItemIcon(
309
+ nwItem.itemType, nwItem.itemClass,
310
+ nwItem.itemLayout, nwItem.mainHeading
311
+ )
312
+ iLevel = nwHeaders.H_LEVEL.get(hItem.level, 0) if nwItem.isDocumentLayout() else 5
313
+ hDec = SHARED.theme.getHeaderDecorationNarrow(iLevel)
314
+
315
+ tKey = f"{tHandle}:{sTitle}"
316
+ trItem = self._treeMap[tKey] if tKey in self._treeMap else QTreeWidgetItem()
317
+
318
+ trItem.setIcon(self.C_DOC, docIcon)
319
+ trItem.setText(self.C_DOC, nwItem.itemName)
320
+ trItem.setToolTip(self.C_DOC, nwItem.itemName)
321
+ trItem.setIcon(self.C_EDIT, self._editIcon)
322
+ trItem.setIcon(self.C_VIEW, self._viewIcon)
323
+ trItem.setData(self.C_TITLE, Qt.ItemDataRole.DecorationRole, hDec)
324
+ trItem.setText(self.C_TITLE, hItem.title)
325
+ trItem.setToolTip(self.C_TITLE, hItem.title)
326
+ trItem.setData(self.C_DATA, self.D_HANDLE, tHandle)
327
+
328
+ if tKey not in self._treeMap:
329
+ self.addTopLevelItem(trItem)
330
+ self._treeMap[tKey] = trItem
331
+
332
+ return
333
+
334
+ # END Class _ViewPanelBackRefs
335
+
336
+
337
+ class _ViewPanelKeyWords(QTreeWidget):
338
+
339
+ C_DATA = 0
340
+ C_NAME = 0
341
+ C_EDIT = 1
342
+ C_VIEW = 2
343
+ C_IMPORT = 3
344
+ C_DOC = 4
345
+ C_TITLE = 5
346
+ C_SHORT = 6
347
+
348
+ D_TAG = Qt.ItemDataRole.UserRole
349
+
350
+ def __init__(self, parent: GuiDocViewerPanel, itemClass: nwItemClass) -> None:
351
+ super().__init__(parent=parent)
352
+
353
+ self._parent = parent
354
+ self._class = itemClass
355
+ self._treeMap: dict[str, QTreeWidgetItem] = {}
356
+
357
+ iPx = SHARED.theme.baseIconSize
358
+ cMg = CONFIG.pxInt(6)
359
+
360
+ self.setHeaderLabels([
361
+ self.tr("Tag"), "", "", self.tr("Importance"), self.tr("Document"),
362
+ self.tr("Heading"), self.tr("Short Description")
363
+ ])
364
+ self.setIndentation(0)
365
+ self.setIconSize(QSize(iPx, iPx))
366
+ self.setFrameStyle(QFrame.Shape.NoFrame)
367
+ self.setSelectionMode(QAbstractItemView.SelectionMode.NoSelection)
368
+ self.setExpandsOnDoubleClick(False)
369
+ self.setDragEnabled(False)
370
+ self.setSortingEnabled(True)
371
+ self.sortByColumn(self.C_NAME, Qt.SortOrder.AscendingOrder)
372
+
373
+ # Set Header Sizes
374
+ treeHeader = self.header()
375
+ treeHeader.setStretchLastSection(True)
376
+ treeHeader.setMinimumSectionSize(iPx + cMg) # See Issue #1627
377
+ treeHeader.setSectionResizeMode(self.C_EDIT, QHeaderView.ResizeMode.Fixed)
378
+ treeHeader.setSectionResizeMode(self.C_VIEW, QHeaderView.ResizeMode.Fixed)
379
+ treeHeader.resizeSection(self.C_EDIT, iPx + cMg)
380
+ treeHeader.resizeSection(self.C_VIEW, iPx + cMg)
381
+ treeHeader.setSectionsMovable(False)
382
+
383
+ # Cache Icons Locally
384
+ self._classIcon = SHARED.theme.getIcon(nwLabels.CLASS_ICON[itemClass])
385
+ self._editIcon = SHARED.theme.getIcon("edit")
386
+ self._viewIcon = SHARED.theme.getIcon("view")
387
+
388
+ # Signals
389
+ self.clicked.connect(self._treeItemClicked)
390
+ self.doubleClicked.connect(self._treeItemDoubleClicked)
391
+
392
+ return
393
+
394
+ def updateTheme(self) -> None:
395
+ """Update theme elements."""
396
+ self._classIcon = SHARED.theme.getIcon(nwLabels.CLASS_ICON[self._class])
397
+ self._editIcon = SHARED.theme.getIcon("edit")
398
+ self._viewIcon = SHARED.theme.getIcon("view")
399
+ for i in range(self.topLevelItemCount()):
400
+ if item := self.topLevelItem(i):
401
+ item.setIcon(self.C_EDIT, self._editIcon)
402
+ item.setIcon(self.C_VIEW, self._viewIcon)
403
+ return
404
+
405
+ def countEntries(self) -> int:
406
+ """Return the number of items in the list."""
407
+ return self.topLevelItemCount()
408
+
409
+ def clearContent(self) -> None:
410
+ """Clear the list."""
411
+ self._treeMap = {}
412
+ self.clear()
413
+ return
414
+
415
+ def addUpdateEntry(self, tag: str, name: str, iItem: IndexItem, hItem: IndexHeading) -> None:
416
+ """Add a new entry, or update an existing one."""
417
+ nwItem = iItem.item
418
+ docIcon = SHARED.theme.getItemIcon(
419
+ nwItem.itemType, nwItem.itemClass,
420
+ nwItem.itemLayout, nwItem.mainHeading
421
+ )
422
+ impLabel, impIcon = nwItem.getImportStatus(incIcon=True)
423
+ iLevel = nwHeaders.H_LEVEL.get(hItem.level, 0) if nwItem.isDocumentLayout() else 5
424
+ hDec = SHARED.theme.getHeaderDecorationNarrow(iLevel)
425
+
426
+ # This can not use a get call to the dictionary as that would create an
427
+ # instance of the QTreeWidgetItem, which has some weird side effects
428
+ trItem = self._treeMap[tag] if tag in self._treeMap else QTreeWidgetItem()
429
+
430
+ trItem.setIcon(self.C_NAME, self._classIcon)
431
+ trItem.setText(self.C_NAME, name)
432
+ trItem.setToolTip(self.C_NAME, name)
433
+ trItem.setIcon(self.C_EDIT, self._editIcon)
434
+ trItem.setIcon(self.C_VIEW, self._viewIcon)
435
+ trItem.setIcon(self.C_IMPORT, impIcon)
436
+ trItem.setText(self.C_IMPORT, impLabel)
437
+ trItem.setToolTip(self.C_IMPORT, impLabel)
438
+ trItem.setIcon(self.C_DOC, docIcon)
439
+ trItem.setText(self.C_DOC, nwItem.itemName)
440
+ trItem.setToolTip(self.C_DOC, nwItem.itemName)
441
+ trItem.setData(self.C_TITLE, Qt.ItemDataRole.DecorationRole, hDec)
442
+ trItem.setText(self.C_TITLE, hItem.title)
443
+ trItem.setToolTip(self.C_TITLE, hItem.title)
444
+ trItem.setText(self.C_SHORT, hItem.synopsis)
445
+ trItem.setToolTip(self.C_SHORT, hItem.synopsis)
446
+ trItem.setData(self.C_DATA, self.D_TAG, tag)
447
+
448
+ if tag not in self._treeMap:
449
+ self.addTopLevelItem(trItem)
450
+ self._treeMap[tag] = trItem
451
+
452
+ return
453
+
454
+ def removeEntry(self, tag: str) -> bool:
455
+ """Remove a tag from the list."""
456
+ if tag in self._treeMap:
457
+ self.takeTopLevelItem(self.indexOfTopLevelItem(self._treeMap[tag]))
458
+ self._treeMap.pop(tag, None)
459
+ return True
460
+ return False
461
+
462
+ def setColumnWidths(self, widths: list[int]) -> None:
463
+ """Set the column widths."""
464
+ if isinstance(widths, list) and len(widths) >= 4:
465
+ self.setColumnWidth(self.C_NAME, CONFIG.pxInt(checkInt(widths[0], 100)))
466
+ self.setColumnWidth(self.C_IMPORT, CONFIG.pxInt(checkInt(widths[1], 100)))
467
+ self.setColumnWidth(self.C_DOC, CONFIG.pxInt(checkInt(widths[2], 100)))
468
+ self.setColumnWidth(self.C_TITLE, CONFIG.pxInt(checkInt(widths[3], 100)))
469
+ return
470
+
471
+ def getColumnWidths(self) -> list[int]:
472
+ """Get the widths of the user-adjustable columns."""
473
+ return [
474
+ CONFIG.rpxInt(self.columnWidth(self.C_NAME)),
475
+ CONFIG.rpxInt(self.columnWidth(self.C_IMPORT)),
476
+ CONFIG.rpxInt(self.columnWidth(self.C_DOC)),
477
+ CONFIG.rpxInt(self.columnWidth(self.C_TITLE)),
478
+ ]
479
+
480
+ ##
481
+ # Private Slots
482
+ ##
483
+
484
+ @pyqtSlot("QModelIndex")
485
+ def _treeItemClicked(self, index: QModelIndex) -> None:
486
+ """Emit follow tag signal on user click."""
487
+ tag = index.siblingAtColumn(self.C_DATA).data(self.D_TAG)
488
+ if index.column() == self.C_EDIT:
489
+ self._parent.loadDocumentTagRequest.emit(tag, nwDocMode.EDIT)
490
+ elif index.column() == self.C_VIEW:
491
+ self._parent.loadDocumentTagRequest.emit(tag, nwDocMode.VIEW)
492
+ return
493
+
494
+ @pyqtSlot("QModelIndex")
495
+ def _treeItemDoubleClicked(self, index: QModelIndex) -> None:
496
+ """Emit follow tag signal on user double click."""
497
+ tag = index.siblingAtColumn(self.C_DATA).data(self.D_TAG)
498
+ if index.column() not in (self.C_EDIT, self.C_VIEW):
499
+ self._parent.loadDocumentTagRequest.emit(tag, nwDocMode.VIEW)
500
+ return
501
+
502
+ # END Class _ViewPanelKeyWords
@@ -3,10 +3,10 @@ novelWriter – GUI Text Document
3
3
  ===============================
4
4
 
5
5
  File History:
6
- Created: 2023-09-07 [2.2b1]
6
+ Created: 2023-09-07 [2.2b1] GuiTextDocument
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 @@ class GuiTextDocument(QTextDocument):
50
50
 
51
51
  return
52
52
 
53
- def __del__(self): # pragma: no cover
53
+ def __del__(self) -> None: # pragma: no cover
54
54
  logger.debug("Delete: GuiTextDocument")
55
55
  return
56
56
 
@@ -64,7 +64,7 @@ class GuiTextDocument(QTextDocument):
64
64
  return self._syntax
65
65
 
66
66
  ##
67
- # Metods
67
+ # Methods
68
68
  ##
69
69
 
70
70
  def setTextContent(self, text: str, tHandle: str) -> None:
@@ -3,10 +3,10 @@ novelWriter – GUI Item Details Panel
3
3
  ====================================
4
4
 
5
5
  File History:
6
- Created: 2019-04-24 [0.0.1]
6
+ Created: 2019-04-24 [0.0.1] GuiItemDetails
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