novelWriter 2.6b1__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 (68) hide show
  1. {novelWriter-2.6b1.dist-info → novelWriter-2.6b2.dist-info}/METADATA +3 -3
  2. {novelWriter-2.6b1.dist-info → novelWriter-2.6b2.dist-info}/RECORD +68 -52
  3. {novelWriter-2.6b1.dist-info → novelWriter-2.6b2.dist-info}/WHEEL +1 -1
  4. novelwriter/__init__.py +49 -10
  5. novelwriter/assets/i18n/nw_de_DE.qm +0 -0
  6. novelwriter/assets/i18n/nw_pt_BR.qm +0 -0
  7. novelwriter/assets/i18n/nw_ru_RU.qm +0 -0
  8. novelwriter/assets/i18n/project_de_DE.json +2 -2
  9. novelwriter/assets/i18n/project_ru_RU.json +11 -0
  10. novelwriter/assets/icons/typicons_dark/icons.conf +7 -0
  11. novelwriter/assets/icons/typicons_dark/mixed_margin-bottom.svg +6 -0
  12. novelwriter/assets/icons/typicons_dark/mixed_margin-left.svg +6 -0
  13. novelwriter/assets/icons/typicons_dark/mixed_margin-right.svg +6 -0
  14. novelwriter/assets/icons/typicons_dark/mixed_margin-top.svg +6 -0
  15. novelwriter/assets/icons/typicons_dark/mixed_size-height.svg +6 -0
  16. novelwriter/assets/icons/typicons_dark/mixed_size-width.svg +6 -0
  17. novelwriter/assets/icons/typicons_dark/nw_toolbar.svg +5 -0
  18. novelwriter/assets/icons/typicons_light/icons.conf +7 -0
  19. novelwriter/assets/icons/typicons_light/mixed_margin-bottom.svg +6 -0
  20. novelwriter/assets/icons/typicons_light/mixed_margin-left.svg +6 -0
  21. novelwriter/assets/icons/typicons_light/mixed_margin-right.svg +6 -0
  22. novelwriter/assets/icons/typicons_light/mixed_margin-top.svg +6 -0
  23. novelwriter/assets/icons/typicons_light/mixed_size-height.svg +6 -0
  24. novelwriter/assets/icons/typicons_light/mixed_size-width.svg +6 -0
  25. novelwriter/assets/icons/typicons_light/nw_toolbar.svg +5 -0
  26. novelwriter/assets/manual.pdf +0 -0
  27. novelwriter/assets/sample.zip +0 -0
  28. novelwriter/assets/text/credits_en.htm +1 -0
  29. novelwriter/common.py +37 -2
  30. novelwriter/config.py +15 -12
  31. novelwriter/constants.py +24 -9
  32. novelwriter/core/coretools.py +111 -125
  33. novelwriter/core/docbuild.py +3 -2
  34. novelwriter/core/index.py +9 -19
  35. novelwriter/core/item.py +39 -6
  36. novelwriter/core/itemmodel.py +518 -0
  37. novelwriter/core/project.py +67 -89
  38. novelwriter/core/status.py +7 -5
  39. novelwriter/core/tree.py +268 -287
  40. novelwriter/dialogs/docmerge.py +7 -17
  41. novelwriter/dialogs/preferences.py +3 -3
  42. novelwriter/dialogs/projectsettings.py +2 -2
  43. novelwriter/enum.py +7 -0
  44. novelwriter/extensions/configlayout.py +6 -4
  45. novelwriter/formats/todocx.py +34 -38
  46. novelwriter/formats/tohtml.py +14 -15
  47. novelwriter/formats/tokenizer.py +21 -17
  48. novelwriter/formats/toodt.py +53 -124
  49. novelwriter/formats/toqdoc.py +92 -44
  50. novelwriter/gui/doceditor.py +230 -219
  51. novelwriter/gui/docviewer.py +38 -9
  52. novelwriter/gui/docviewerpanel.py +14 -22
  53. novelwriter/gui/itemdetails.py +17 -24
  54. novelwriter/gui/mainmenu.py +13 -8
  55. novelwriter/gui/noveltree.py +12 -12
  56. novelwriter/gui/outline.py +10 -11
  57. novelwriter/gui/projtree.py +548 -1202
  58. novelwriter/gui/search.py +9 -10
  59. novelwriter/gui/theme.py +7 -3
  60. novelwriter/guimain.py +59 -43
  61. novelwriter/shared.py +52 -23
  62. novelwriter/text/patterns.py +17 -5
  63. novelwriter/tools/manusbuild.py +13 -11
  64. novelwriter/tools/manussettings.py +42 -52
  65. novelwriter/types.py +7 -1
  66. {novelWriter-2.6b1.dist-info → novelWriter-2.6b2.dist-info}/LICENSE.md +0 -0
  67. {novelWriter-2.6b1.dist-info → novelWriter-2.6b2.dist-info}/entry_points.txt +0 -0
  68. {novelWriter-2.6b1.dist-info → novelWriter-2.6b2.dist-info}/top_level.txt +0 -0
novelwriter/gui/search.py CHANGED
@@ -30,15 +30,18 @@ from time import time
30
30
  from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot
31
31
  from PyQt5.QtGui import QCursor, QKeyEvent
32
32
  from PyQt5.QtWidgets import (
33
- QApplication, QFrame, QHBoxLayout, QHeaderView, QLabel, QLineEdit,
34
- QToolBar, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget
33
+ QApplication, QFrame, QHBoxLayout, QLabel, QLineEdit, QToolBar,
34
+ QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget
35
35
  )
36
36
 
37
37
  from novelwriter import CONFIG, SHARED
38
38
  from novelwriter.common import checkInt, cssCol
39
39
  from novelwriter.core.coretools import DocSearch
40
40
  from novelwriter.core.item import NWItem
41
- from novelwriter.types import QtAlignMiddle, QtAlignRight, QtUserRole
41
+ from novelwriter.types import (
42
+ QtAlignMiddle, QtAlignRight, QtHeaderStretch, QtHeaderToContents,
43
+ QtUserRole
44
+ )
42
45
 
43
46
  logger = logging.getLogger(__name__)
44
47
 
@@ -120,8 +123,8 @@ class GuiProjectSearch(QWidget):
120
123
 
121
124
  treeHeader = self.searchResult.header()
122
125
  treeHeader.setStretchLastSection(False)
123
- treeHeader.setSectionResizeMode(self.C_NAME, QHeaderView.ResizeMode.Stretch)
124
- treeHeader.setSectionResizeMode(self.C_COUNT, QHeaderView.ResizeMode.ResizeToContents)
126
+ treeHeader.setSectionResizeMode(self.C_NAME, QtHeaderStretch)
127
+ treeHeader.setSectionResizeMode(self.C_COUNT, QtHeaderToContents)
125
128
 
126
129
  # Assemble
127
130
  self.headerBox = QHBoxLayout()
@@ -331,15 +334,11 @@ class GuiProjectSearch(QWidget):
331
334
  """Populate the result tree."""
332
335
  if results and nwItem:
333
336
  tHandle = nwItem.itemHandle
334
- docIcon = SHARED.theme.getItemIcon(
335
- nwItem.itemType, nwItem.itemClass,
336
- nwItem.itemLayout, nwItem.mainHeading
337
- )
338
337
  ext = "+" if capped else ""
339
338
 
340
339
  tItem = QTreeWidgetItem()
341
340
  tItem.setText(self.C_NAME, nwItem.itemName)
342
- tItem.setIcon(self.C_NAME, docIcon)
341
+ tItem.setIcon(self.C_NAME, nwItem.getMainIcon())
343
342
  tItem.setData(self.C_NAME, self.D_HANDLE, tHandle)
344
343
  tItem.setText(self.C_COUNT, f"({len(results):n}{ext})")
345
344
  tItem.setTextAlignment(self.C_COUNT, QtAlignRight)
novelwriter/gui/theme.py CHANGED
@@ -151,6 +151,9 @@ class GuiTheme:
151
151
  self.guiFont = QApplication.font()
152
152
  self.guiFontB = QApplication.font()
153
153
  self.guiFontB.setBold(True)
154
+ self.guiFontBU = QApplication.font()
155
+ self.guiFontBU.setBold(True)
156
+ self.guiFontBU.setUnderline(True)
154
157
  self.guiFontSmall = QApplication.font()
155
158
  self.guiFontSmall.setPointSizeF(0.9*self.guiFont.pointSizeF())
156
159
 
@@ -500,14 +503,15 @@ class GuiIcons:
500
503
 
501
504
  # Format Icons
502
505
  "fmt_bold", "fmt_bold-md", "fmt_italic", "fmt_italic-md", "fmt_mark", "fmt_strike",
503
- "fmt_strike-md", "fmt_subscript", "fmt_superscript", "fmt_underline",
506
+ "fmt_strike-md", "fmt_subscript", "fmt_superscript", "fmt_underline", "margin_bottom",
507
+ "margin_left", "margin_right", "margin_top", "size_height", "size_width",
504
508
 
505
509
  # General Button Icons
506
510
  "add", "add_document", "backward", "bookmark", "browse", "checked", "close", "copy",
507
511
  "cross", "document", "down", "edit", "export", "font", "forward", "import", "list",
508
512
  "maximise", "menu", "minimise", "more", "noncheckable", "open", "panel", "quote",
509
- "refresh", "remove", "revert", "search_replace", "search", "settings", "star", "unchecked",
510
- "up", "view",
513
+ "refresh", "remove", "revert", "search_replace", "search", "settings", "star", "toolbar",
514
+ "unchecked", "up", "view",
511
515
 
512
516
  # Switches
513
517
  "sticky-on", "sticky-off",
novelwriter/guimain.py CHANGED
@@ -44,7 +44,7 @@ from novelwriter.dialogs.about import GuiAbout
44
44
  from novelwriter.dialogs.preferences import GuiPreferences
45
45
  from novelwriter.dialogs.projectsettings import GuiProjectSettings
46
46
  from novelwriter.dialogs.wordlist import GuiWordList
47
- from novelwriter.enum import nwDocAction, nwDocInsert, nwDocMode, nwFocus, nwView
47
+ from novelwriter.enum import nwDocAction, nwDocInsert, nwDocMode, nwFocus, nwItemType, nwView
48
48
  from novelwriter.gui.doceditor import GuiDocEditor
49
49
  from novelwriter.gui.docviewer import GuiDocViewer
50
50
  from novelwriter.gui.docviewerpanel import GuiDocViewerPanel
@@ -62,6 +62,7 @@ from novelwriter.tools.manuscript import GuiManuscript
62
62
  from novelwriter.tools.noveldetails import GuiNovelDetails
63
63
  from novelwriter.tools.welcome import GuiWelcome
64
64
  from novelwriter.tools.writingstats import GuiWritingStats
65
+ from novelwriter.types import QtModShift
65
66
 
66
67
  logger = logging.getLogger(__name__)
67
68
 
@@ -212,15 +213,19 @@ class GuiMain(QMainWindow):
212
213
  SHARED.indexChangedTags.connect(self.docEditor.updateChangedTags)
213
214
  SHARED.indexChangedTags.connect(self.docViewerPanel.updateChangedTags)
214
215
  SHARED.indexCleared.connect(self.docViewerPanel.indexWasCleared)
215
- SHARED.indexScannedText.connect(self.docViewerPanel.projectItemChanged)
216
- SHARED.indexScannedText.connect(self.itemDetails.updateViewBox)
217
- SHARED.indexScannedText.connect(self.projView.updateItemValues)
218
216
  SHARED.mainClockTick.connect(self._timeTick)
217
+ SHARED.projectItemChanged.connect(self.docEditor.onProjectItemChanged)
218
+ SHARED.projectItemChanged.connect(self.docViewer.onProjectItemChanged)
219
+ SHARED.projectItemChanged.connect(self.docViewerPanel.onProjectItemChanged)
220
+ SHARED.projectItemChanged.connect(self.itemDetails.onProjectItemChanged)
221
+ SHARED.projectItemChanged.connect(self.projView.onProjectItemChanged)
219
222
  SHARED.projectStatusChanged.connect(self.mainStatus.updateProjectStatus)
220
223
  SHARED.projectStatusMessage.connect(self.mainStatus.setStatusMessage)
224
+ SHARED.rootFolderChanged.connect(self.novelView.updateRootItem)
225
+ SHARED.rootFolderChanged.connect(self.outlineView.updateRootItem)
226
+ SHARED.rootFolderChanged.connect(self.projView.updateRootItem)
221
227
  SHARED.spellLanguageChanged.connect(self.mainStatus.setLanguage)
222
228
  SHARED.statusLabelsChanged.connect(self.docViewerPanel.updateStatusLabels)
223
- SHARED.statusLabelsChanged.connect(self.projView.refreshUserLabels)
224
229
 
225
230
  self.mainMenu.requestDocAction.connect(self._passDocumentAction)
226
231
  self.mainMenu.requestDocInsert.connect(self._passDocumentInsert)
@@ -233,15 +238,7 @@ class GuiMain(QMainWindow):
233
238
 
234
239
  self.projView.openDocumentRequest.connect(self._openDocument)
235
240
  self.projView.projectSettingsRequest.connect(self.showProjectSettingsDialog)
236
- self.projView.rootFolderChanged.connect(self.novelView.updateRootItem)
237
- self.projView.rootFolderChanged.connect(self.outlineView.updateRootItem)
238
- self.projView.rootFolderChanged.connect(self.projView.updateRootItem)
239
241
  self.projView.selectedItemChanged.connect(self.itemDetails.updateViewBox)
240
- self.projView.treeItemChanged.connect(self.docEditor.updateDocInfo)
241
- self.projView.treeItemChanged.connect(self.docViewer.updateDocInfo)
242
- self.projView.treeItemChanged.connect(self.docViewerPanel.projectItemChanged)
243
- self.projView.treeItemChanged.connect(self.itemDetails.updateViewBox)
244
- self.projView.wordCountsChanged.connect(self._updateStatusWordCount)
245
242
 
246
243
  self.novelView.openDocumentRequest.connect(self._openDocument)
247
244
  self.novelView.selectedItemChanged.connect(self.itemDetails.updateViewBox)
@@ -250,8 +247,6 @@ class GuiMain(QMainWindow):
250
247
  self.projSearch.selectedItemChanged.connect(self.itemDetails.updateViewBox)
251
248
 
252
249
  self.docEditor.closeEditorRequest.connect(self.closeDocEditor)
253
- self.docEditor.docCountsChanged.connect(self.itemDetails.updateCounts)
254
- self.docEditor.docCountsChanged.connect(self.projView.updateCounts)
255
250
  self.docEditor.docTextChanged.connect(self.projSearch.textChanged)
256
251
  self.docEditor.editedStatusChanged.connect(self.mainStatus.updateDocumentStatus)
257
252
  self.docEditor.itemHandleChanged.connect(self.novelView.setActiveHandle)
@@ -259,7 +254,8 @@ class GuiMain(QMainWindow):
259
254
  self.docEditor.loadDocumentTagRequest.connect(self._followTag)
260
255
  self.docEditor.novelItemMetaChanged.connect(self.novelView.updateNovelItemMeta)
261
256
  self.docEditor.novelStructureChanged.connect(self.novelView.refreshTree)
262
- self.docEditor.requestNewNoteCreation.connect(self.projView.createNewNote)
257
+ self.docEditor.openDocumentRequest.connect(self._openDocument)
258
+ self.docEditor.requestNewNoteCreation.connect(SHARED.createNewNote)
263
259
  self.docEditor.requestNextDocument.connect(self.openNextDocument)
264
260
  self.docEditor.requestProjectItemRenamed.connect(self.projView.renameTreeItem)
265
261
  self.docEditor.requestProjectItemSelected.connect(self.projView.setSelectedHandle)
@@ -297,14 +293,25 @@ class GuiMain(QMainWindow):
297
293
  self.keyReturn.setKey("Return")
298
294
  self.keyReturn.activated.connect(self._keyPressReturn)
299
295
 
296
+ self.keyShiftReturn = QShortcut(self)
297
+ self.keyShiftReturn.setKey("Shift+Return")
298
+ self.keyShiftReturn.activated.connect(self._keyPressReturn)
299
+
300
300
  self.keyEnter = QShortcut(self)
301
301
  self.keyEnter.setKey("Enter")
302
302
  self.keyEnter.activated.connect(self._keyPressReturn)
303
303
 
304
+ self.keyShiftEnter = QShortcut(self)
305
+ self.keyShiftEnter.setKey("Shift+Enter")
306
+ self.keyShiftEnter.activated.connect(self._keyPressReturn)
307
+
304
308
  self.keyEscape = QShortcut(self)
305
309
  self.keyEscape.setKey("Esc")
306
310
  self.keyEscape.activated.connect(self._keyPressEscape)
307
311
 
312
+ # Internal Variables
313
+ self._lastTotalCount = 0
314
+
308
315
  # Initialise Main GUI
309
316
  self.initMain()
310
317
  self.asProjTimer.start()
@@ -502,11 +509,9 @@ class GuiMain(QMainWindow):
502
509
 
503
510
  def saveProject(self, autoSave: bool = False) -> bool:
504
511
  """Save the current project."""
505
- if not SHARED.hasProject:
506
- logger.error("No project open")
507
- return False
508
- self.projView.saveProjectTasks()
509
- return SHARED.saveProject(autoSave=autoSave)
512
+ if SHARED.hasProject:
513
+ return SHARED.saveProject(autoSave=autoSave)
514
+ return False
510
515
 
511
516
  ##
512
517
  # Document Actions
@@ -718,8 +723,11 @@ class GuiMain(QMainWindow):
718
723
  logger.warning("No item selected")
719
724
  return
720
725
 
721
- if tHandle:
722
- self.openDocument(tHandle, sTitle=sTitle, changeFocus=False, doScroll=False)
726
+ if tHandle and SHARED.project.tree.checkType(tHandle, nwItemType.FILE):
727
+ if QApplication.keyboardModifiers() == QtModShift:
728
+ self.viewDocument(tHandle)
729
+ else:
730
+ self.openDocument(tHandle, sTitle=sTitle, changeFocus=False, doScroll=False)
723
731
 
724
732
  return
725
733
 
@@ -730,9 +738,8 @@ class GuiMain(QMainWindow):
730
738
  QApplication.setOverrideCursor(QCursor(Qt.CursorShape.WaitCursor))
731
739
  tStart = time()
732
740
 
733
- self.projView.saveProjectTasks()
734
- SHARED.project.index.rebuildIndex()
735
- self.projView.populateTree()
741
+ SHARED.project.index.rebuild()
742
+ SHARED.project.tree.refreshAllItems()
736
743
  self.novelView.refreshTree()
737
744
 
738
745
  tEnd = time()
@@ -1044,13 +1051,8 @@ class GuiMain(QMainWindow):
1044
1051
  self.initMain()
1045
1052
  self.saveDocument()
1046
1053
 
1047
- if restart:
1048
- SHARED.info(self.tr(
1049
- "Some changes will not be applied until novelWriter has been restarted."
1050
- ))
1051
-
1052
1054
  if tree:
1053
- self.projView.populateTree()
1055
+ SHARED.project.tree.refreshAllItems()
1054
1056
 
1055
1057
  if theme:
1056
1058
  # We are doing this manually instead of connecting to
@@ -1076,8 +1078,16 @@ class GuiMain(QMainWindow):
1076
1078
  self.projView.initSettings()
1077
1079
  self.novelView.initSettings()
1078
1080
  self.outlineView.initSettings()
1081
+
1082
+ # Force update of word count
1083
+ self._lastTotalCount = 0
1079
1084
  self._updateStatusWordCount()
1080
1085
 
1086
+ if restart:
1087
+ SHARED.info(self.tr(
1088
+ "Some changes will not be applied until novelWriter has been restarted."
1089
+ ))
1090
+
1081
1091
  return
1082
1092
 
1083
1093
  @pyqtSlot()
@@ -1213,8 +1223,10 @@ class GuiMain(QMainWindow):
1213
1223
  self.mainStatus.setUserIdle(editIdle or userIdle)
1214
1224
  SHARED.updateIdleTime(currTime, editIdle or userIdle)
1215
1225
  self.mainStatus.updateTime(idleTime=SHARED.projectIdleTime)
1216
- if CONFIG.memInfo and int(currTime) % 5 == 0: # pragma: no cover
1217
- self.mainStatus.memInfo()
1226
+ if int(currTime) % 5 == 0:
1227
+ self._updateStatusWordCount()
1228
+ if CONFIG.memInfo: # pragma: no cover
1229
+ self.mainStatus.memInfo()
1218
1230
  return
1219
1231
 
1220
1232
  @pyqtSlot()
@@ -1242,15 +1254,19 @@ class GuiMain(QMainWindow):
1242
1254
  if not SHARED.hasProject:
1243
1255
  self.mainStatus.setProjectStats(0, 0)
1244
1256
 
1245
- SHARED.project.updateWordCounts()
1246
- if CONFIG.incNotesWCount:
1247
- iTotal = sum(SHARED.project.data.initCounts)
1248
- cTotal = sum(SHARED.project.data.currCounts)
1249
- self.mainStatus.setProjectStats(cTotal, cTotal - iTotal)
1250
- else:
1251
- iNovel, _ = SHARED.project.data.initCounts
1252
- cNovel, _ = SHARED.project.data.currCounts
1253
- self.mainStatus.setProjectStats(cNovel, cNovel - iNovel)
1257
+ currentTotalCount = SHARED.project.currentTotalCount
1258
+ if self._lastTotalCount != currentTotalCount:
1259
+ self._lastTotalCount = currentTotalCount
1260
+
1261
+ SHARED.project.updateWordCounts()
1262
+ if CONFIG.incNotesWCount:
1263
+ iTotal = sum(SHARED.project.data.initCounts)
1264
+ cTotal = sum(SHARED.project.data.currCounts)
1265
+ self.mainStatus.setProjectStats(cTotal, cTotal - iTotal)
1266
+ else:
1267
+ iNovel, _ = SHARED.project.data.initCounts
1268
+ cNovel, _ = SHARED.project.data.currCounts
1269
+ self.mainStatus.setProjectStats(cNovel, cNovel - iNovel)
1254
1270
 
1255
1271
  return
1256
1272
 
novelwriter/shared.py CHANGED
@@ -26,6 +26,7 @@ from __future__ import annotations
26
26
 
27
27
  import logging
28
28
 
29
+ from enum import Enum
29
30
  from pathlib import Path
30
31
  from time import time
31
32
  from typing import TYPE_CHECKING, TypeVar
@@ -37,9 +38,11 @@ from PyQt5.QtWidgets import QFileDialog, QFontDialog, QMessageBox, QWidget
37
38
  from novelwriter.common import formatFileFilter
38
39
  from novelwriter.constants import nwFiles
39
40
  from novelwriter.core.spellcheck import NWSpellEnchant
41
+ from novelwriter.enum import nwChange, nwItemClass
40
42
 
41
43
  if TYPE_CHECKING: # pragma: no cover
42
44
  from novelwriter.core.project import NWProject
45
+ from novelwriter.core.status import T_StatusKind
43
46
  from novelwriter.gui.theme import GuiTheme
44
47
  from novelwriter.guimain import GuiMain
45
48
 
@@ -55,15 +58,16 @@ class SharedData(QObject):
55
58
  "_idleTime", "_idleRefTime",
56
59
  )
57
60
 
58
- projectStatusChanged = pyqtSignal(bool)
59
- projectStatusMessage = pyqtSignal(str)
60
- spellLanguageChanged = pyqtSignal(str, str)
61
61
  focusModeChanged = pyqtSignal(bool)
62
- indexScannedText = pyqtSignal(str)
62
+ indexAvailable = pyqtSignal()
63
63
  indexChangedTags = pyqtSignal(list, list)
64
64
  indexCleared = pyqtSignal()
65
- indexAvailable = pyqtSignal()
66
65
  mainClockTick = pyqtSignal()
66
+ projectItemChanged = pyqtSignal(str, Enum)
67
+ rootFolderChanged = pyqtSignal(str, Enum)
68
+ projectStatusChanged = pyqtSignal(bool)
69
+ projectStatusMessage = pyqtSignal(str)
70
+ spellLanguageChanged = pyqtSignal(str, str)
67
71
  statusLabelsChanged = pyqtSignal(str)
68
72
 
69
73
  def __init__(self) -> None:
@@ -173,10 +177,12 @@ class SharedData(QObject):
173
177
  logger.debug("Thread Pool Max Count: %d", QThreadPool.globalInstance().maxThreadCount())
174
178
  return
175
179
 
176
- def closeEditor(self, tHandle: str | None = None) -> None:
180
+ def closeDocument(self, tHandle: str | None = None) -> None:
177
181
  """Close the document editor, optionally a specific document."""
178
182
  if tHandle is None or tHandle == self.mainGui.docEditor.docHandle:
179
183
  self.mainGui.closeDocument()
184
+ if tHandle is None or tHandle == self.mainGui.docViewer.docHandle:
185
+ self.mainGui.closeViewerPanel()
180
186
  return
181
187
 
182
188
  def saveEditor(self, tHandle: str | None = None) -> None:
@@ -302,30 +308,52 @@ class SharedData(QObject):
302
308
  QDesktopServices.openUrl(QUrl(url))
303
309
  return
304
310
 
311
+ @pyqtSlot(str, nwItemClass)
312
+ def createNewNote(self, tag: str, itemClass: nwItemClass) -> None:
313
+ """Process new note request."""
314
+ self.project.createNewNote(tag, itemClass)
315
+ return
316
+
305
317
  ##
306
- # Signal Proxy
318
+ # Signal Proxies
307
319
  ##
308
320
 
309
- def indexSignalProxy(self, data: dict) -> None:
310
- """Emit signals on behalf of the index."""
311
- event = data.get("event")
312
- logger.debug("Received '%s' event from the index", event)
313
- if event == "updateTags":
314
- self.indexChangedTags.emit(data.get("updated", []), data.get("deleted", []))
315
- elif event == "scanText":
316
- self.indexScannedText.emit(data.get("handle", ""))
317
- elif event == "clearIndex":
321
+ def emitIndexChangedTags(
322
+ self, project: NWProject, updated: list[str], deleted: list[str]
323
+ ) -> None:
324
+ """Emit the indexChangedTags signal."""
325
+ if self._project and self._project.data.uuid == project.data.uuid:
326
+ self.indexChangedTags.emit(updated, deleted)
327
+ return
328
+
329
+ def emitIndexCleared(self, project: NWProject) -> None:
330
+ """Emit the indexCleared signal."""
331
+ if self._project and self._project.data.uuid == project.data.uuid:
318
332
  self.indexCleared.emit()
319
- elif event == "buildIndex":
333
+ return
334
+
335
+ def emitIndexAvailable(self, project: NWProject) -> None:
336
+ """Emit the indexAvailable signal."""
337
+ if self._project and self._project.data.uuid == project.data.uuid:
320
338
  self.indexAvailable.emit()
321
339
  return
322
340
 
323
- def projectSingalProxy(self, data: dict) -> None:
324
- """Emit signals on project data change."""
325
- event = data.get("event")
326
- logger.debug("Received '%s' event from project data", event)
327
- if event == "statusLabels":
328
- self.statusLabelsChanged.emit(data.get("kind", ""))
341
+ def emitStatusLabelsChanged(self, project: NWProject, kind: T_StatusKind) -> None:
342
+ """Emit the statusLabelsChanged signal."""
343
+ if self._project and self._project.data.uuid == project.data.uuid:
344
+ self.statusLabelsChanged.emit(kind)
345
+ return
346
+
347
+ def emitProjectItemChanged(self, project: NWProject, handle: str, change: nwChange) -> None:
348
+ """Emit the projectItemChanged signal."""
349
+ if self._project and self._project.data.uuid == project.data.uuid:
350
+ self.projectItemChanged.emit(handle, change)
351
+ return
352
+
353
+ def emitRootFolderChanged(self, project: NWProject, handle: str, change: nwChange) -> None:
354
+ """Emit the rootFolderChanged signal."""
355
+ if self._project and self._project.data.uuid == project.data.uuid:
356
+ self.rootFolderChanged.emit(handle, change)
329
357
  return
330
358
 
331
359
  ##
@@ -386,6 +414,7 @@ class SharedData(QObject):
386
414
  """Create a new project and spell checking instance."""
387
415
  from novelwriter.core.project import NWProject
388
416
  if isinstance(self._project, NWProject):
417
+ self._project.clear()
389
418
  del self._project
390
419
  del self._spelling
391
420
  self._project = NWProject()
@@ -28,11 +28,13 @@ import re
28
28
 
29
29
  from novelwriter import CONFIG
30
30
  from novelwriter.common import compact, uniqueCompact
31
- from novelwriter.constants import nwRegEx
31
+ from novelwriter.constants import nwRegEx, nwUnicode
32
32
 
33
33
 
34
34
  class RegExPatterns:
35
35
 
36
+ AMBIGUOUS = (nwUnicode.U_APOS, nwUnicode.U_RSQUO)
37
+
36
38
  # Static RegExes
37
39
  _rxUrl = re.compile(nwRegEx.URL, re.ASCII)
38
40
  _rxWords = re.compile(nwRegEx.WORDS, re.UNICODE)
@@ -87,16 +89,25 @@ class RegExPatterns:
87
89
  def dialogStyle(self) -> re.Pattern | None:
88
90
  """Dialogue detection rule based on user settings."""
89
91
  if CONFIG.dialogStyle > 0:
90
- end = "|$" if CONFIG.allowOpenDial else ""
91
92
  rx = []
92
93
  if CONFIG.dialogStyle in (1, 3):
93
94
  qO = CONFIG.fmtSQuoteOpen.strip()[:1]
94
95
  qC = CONFIG.fmtSQuoteClose.strip()[:1]
95
- rx.append(f"(?:\\B{qO}.*?(?:{qC}\\B{end}))")
96
+ if qO == qC:
97
+ rx.append(f"(?:\\B{qO}.+?{qC}\\B)")
98
+ else:
99
+ rx.append(f"(?:{qO}[^{qO}]+{qC})")
100
+ if CONFIG.allowOpenDial:
101
+ rx.append(f"(?:{qO}.+?$)")
96
102
  if CONFIG.dialogStyle in (2, 3):
97
103
  qO = CONFIG.fmtDQuoteOpen.strip()[:1]
98
104
  qC = CONFIG.fmtDQuoteClose.strip()[:1]
99
- rx.append(f"(?:\\B{qO}.*?(?:{qC}\\B{end}))")
105
+ if qO == qC:
106
+ rx.append(f"(?:\\B{qO}.+?{qC}\\B)")
107
+ else:
108
+ rx.append(f"(?:{qO}[^{qO}]+{qC})")
109
+ if CONFIG.allowOpenDial:
110
+ rx.append(f"(?:{qO}.+?$)")
100
111
  return re.compile("|".join(rx), re.UNICODE)
101
112
  return None
102
113
 
@@ -106,7 +117,8 @@ class RegExPatterns:
106
117
  if CONFIG.altDialogOpen and CONFIG.altDialogClose:
107
118
  qO = re.escape(compact(CONFIG.altDialogOpen))
108
119
  qC = re.escape(compact(CONFIG.altDialogClose))
109
- return re.compile(f"\\B{qO}.*?{qC}\\B", re.UNICODE)
120
+ qB = r"\B" if (qO == qC or qC in self.AMBIGUOUS) else ""
121
+ return re.compile(f"{qO}.*?{qC}{qB}", re.UNICODE)
110
122
  return None
111
123
 
112
124
 
@@ -179,14 +179,20 @@ class GuiManuscriptBuild(NDialog):
179
179
  self.buildBox.setVerticalSpacing(sp4)
180
180
 
181
181
  # Dialog Buttons
182
+ self.buttonBox = QDialogButtonBox(self)
183
+
182
184
  self.btnOpen = QPushButton(SHARED.theme.getIcon("browse"), self.tr("Open Folder"), self)
183
185
  self.btnOpen.setIconSize(bSz)
186
+ self.btnOpen.setAutoDefault(False)
187
+ self.buttonBox.addButton(self.btnOpen, QtRoleAction)
188
+
184
189
  self.btnBuild = QPushButton(SHARED.theme.getIcon("export"), self.tr("&Build"), self)
185
190
  self.btnBuild.setIconSize(bSz)
191
+ self.btnBuild.setAutoDefault(True)
192
+ self.buttonBox.addButton(self.btnBuild, QtRoleAction)
186
193
 
187
- self.dlgButtons = QDialogButtonBox(QtDialogClose, self)
188
- self.dlgButtons.addButton(self.btnOpen, QtRoleAction)
189
- self.dlgButtons.addButton(self.btnBuild, QtRoleAction)
194
+ self.btnClose = self.buttonBox.addButton(QtDialogClose)
195
+ self.btnClose.setAutoDefault(False)
190
196
 
191
197
  # Assemble GUI
192
198
  # ============
@@ -213,7 +219,7 @@ class GuiManuscriptBuild(NDialog):
213
219
  self.outerBox.addSpacing(sp4)
214
220
  self.outerBox.addLayout(self.buildBox, 0)
215
221
  self.outerBox.addSpacing(sp16)
216
- self.outerBox.addWidget(self.dlgButtons, 0)
222
+ self.outerBox.addWidget(self.buttonBox, 0)
217
223
  self.outerBox.setSpacing(0)
218
224
 
219
225
  self.setLayout(self.outerBox)
@@ -229,7 +235,7 @@ class GuiManuscriptBuild(NDialog):
229
235
  # Signals
230
236
  self.btnReset.clicked.connect(self._doResetBuildName)
231
237
  self.btnBrowse.clicked.connect(self._doSelectPath)
232
- self.dlgButtons.clicked.connect(self._dialogButtonClicked)
238
+ self.buttonBox.clicked.connect(self._dialogButtonClicked)
233
239
  self.listFormats.itemSelectionChanged.connect(self._resetProgress)
234
240
 
235
241
  logger.debug("Ready: GuiManuscriptBuild")
@@ -260,7 +266,7 @@ class GuiManuscriptBuild(NDialog):
260
266
  @pyqtSlot("QAbstractButton*")
261
267
  def _dialogButtonClicked(self, button: QAbstractButton) -> None:
262
268
  """Handle button clicks from the dialog button box."""
263
- role = self.dlgButtons.buttonRole(button)
269
+ role = self.buttonBox.buttonRole(button)
264
270
  if role == QtRoleAction:
265
271
  if button == self.btnBuild:
266
272
  self._runBuild()
@@ -388,13 +394,9 @@ class GuiManuscriptBuild(NDialog):
388
394
  if isinstance(rItem, NWItem):
389
395
  rootMap[rHandle] = rItem.itemName
390
396
 
391
- itemIcon = SHARED.theme.getItemIcon(
392
- nwItem.itemType, nwItem.itemClass,
393
- nwItem.itemLayout, nwItem.mainHeading
394
- )
395
397
  rootName = rootMap.get(rHandle, "??????")
396
398
  item = QListWidgetItem(f"{rootName}: {nwItem.itemName}")
397
- item.setIcon(itemIcon)
399
+ item.setIcon(nwItem.getMainIcon())
398
400
  self.listContent.addItem(item)
399
401
 
400
402
  return