novelWriter 2.6b1__py3-none-any.whl → 2.6rc1__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 (114) hide show
  1. {novelWriter-2.6b1.dist-info → novelWriter-2.6rc1.dist-info}/METADATA +4 -4
  2. {novelWriter-2.6b1.dist-info → novelWriter-2.6rc1.dist-info}/RECORD +114 -98
  3. {novelWriter-2.6b1.dist-info → novelWriter-2.6rc1.dist-info}/WHEEL +1 -1
  4. novelwriter/__init__.py +50 -11
  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_nl_NL.qm +0 -0
  13. novelwriter/assets/i18n/nw_pl_PL.qm +0 -0
  14. novelwriter/assets/i18n/nw_pt_BR.qm +0 -0
  15. novelwriter/assets/i18n/nw_ru_RU.qm +0 -0
  16. novelwriter/assets/i18n/nw_zh_CN.qm +0 -0
  17. novelwriter/assets/i18n/project_de_DE.json +2 -2
  18. novelwriter/assets/i18n/project_ru_RU.json +11 -0
  19. novelwriter/assets/icons/typicons_dark/icons.conf +7 -0
  20. novelwriter/assets/icons/typicons_dark/mixed_margin-bottom.svg +6 -0
  21. novelwriter/assets/icons/typicons_dark/mixed_margin-left.svg +6 -0
  22. novelwriter/assets/icons/typicons_dark/mixed_margin-right.svg +6 -0
  23. novelwriter/assets/icons/typicons_dark/mixed_margin-top.svg +6 -0
  24. novelwriter/assets/icons/typicons_dark/mixed_size-height.svg +6 -0
  25. novelwriter/assets/icons/typicons_dark/mixed_size-width.svg +6 -0
  26. novelwriter/assets/icons/typicons_dark/nw_toolbar.svg +5 -0
  27. novelwriter/assets/icons/typicons_light/icons.conf +7 -0
  28. novelwriter/assets/icons/typicons_light/mixed_margin-bottom.svg +6 -0
  29. novelwriter/assets/icons/typicons_light/mixed_margin-left.svg +6 -0
  30. novelwriter/assets/icons/typicons_light/mixed_margin-right.svg +6 -0
  31. novelwriter/assets/icons/typicons_light/mixed_margin-top.svg +6 -0
  32. novelwriter/assets/icons/typicons_light/mixed_size-height.svg +6 -0
  33. novelwriter/assets/icons/typicons_light/mixed_size-width.svg +6 -0
  34. novelwriter/assets/icons/typicons_light/nw_toolbar.svg +5 -0
  35. novelwriter/assets/manual.pdf +0 -0
  36. novelwriter/assets/sample.zip +0 -0
  37. novelwriter/assets/text/credits_en.htm +1 -0
  38. novelwriter/common.py +38 -3
  39. novelwriter/config.py +19 -13
  40. novelwriter/constants.py +60 -45
  41. novelwriter/core/buildsettings.py +1 -1
  42. novelwriter/core/coretools.py +112 -126
  43. novelwriter/core/docbuild.py +4 -3
  44. novelwriter/core/document.py +1 -1
  45. novelwriter/core/index.py +10 -20
  46. novelwriter/core/item.py +40 -7
  47. novelwriter/core/itemmodel.py +518 -0
  48. novelwriter/core/options.py +1 -1
  49. novelwriter/core/project.py +68 -90
  50. novelwriter/core/projectdata.py +8 -2
  51. novelwriter/core/projectxml.py +1 -1
  52. novelwriter/core/sessions.py +1 -1
  53. novelwriter/core/spellcheck.py +1 -1
  54. novelwriter/core/status.py +24 -8
  55. novelwriter/core/storage.py +1 -1
  56. novelwriter/core/tree.py +269 -288
  57. novelwriter/dialogs/about.py +1 -1
  58. novelwriter/dialogs/docmerge.py +8 -18
  59. novelwriter/dialogs/docsplit.py +1 -1
  60. novelwriter/dialogs/editlabel.py +1 -1
  61. novelwriter/dialogs/preferences.py +4 -4
  62. novelwriter/dialogs/projectsettings.py +148 -98
  63. novelwriter/dialogs/quotes.py +1 -1
  64. novelwriter/dialogs/wordlist.py +11 -10
  65. novelwriter/enum.py +8 -1
  66. novelwriter/error.py +2 -2
  67. novelwriter/extensions/configlayout.py +7 -5
  68. novelwriter/extensions/eventfilters.py +1 -1
  69. novelwriter/extensions/modified.py +17 -5
  70. novelwriter/extensions/novelselector.py +1 -1
  71. novelwriter/extensions/pagedsidebar.py +4 -4
  72. novelwriter/extensions/progressbars.py +4 -4
  73. novelwriter/extensions/statusled.py +3 -3
  74. novelwriter/extensions/switch.py +3 -3
  75. novelwriter/extensions/switchbox.py +1 -1
  76. novelwriter/extensions/versioninfo.py +1 -1
  77. novelwriter/formats/shared.py +1 -1
  78. novelwriter/formats/todocx.py +35 -39
  79. novelwriter/formats/tohtml.py +15 -16
  80. novelwriter/formats/tokenizer.py +26 -22
  81. novelwriter/formats/tomarkdown.py +1 -1
  82. novelwriter/formats/toodt.py +54 -125
  83. novelwriter/formats/toqdoc.py +93 -45
  84. novelwriter/formats/toraw.py +1 -1
  85. novelwriter/gui/doceditor.py +233 -220
  86. novelwriter/gui/dochighlight.py +1 -1
  87. novelwriter/gui/docviewer.py +39 -10
  88. novelwriter/gui/docviewerpanel.py +15 -23
  89. novelwriter/gui/editordocument.py +1 -1
  90. novelwriter/gui/itemdetails.py +20 -27
  91. novelwriter/gui/mainmenu.py +14 -9
  92. novelwriter/gui/noveltree.py +13 -13
  93. novelwriter/gui/outline.py +18 -20
  94. novelwriter/gui/projtree.py +545 -1201
  95. novelwriter/gui/search.py +11 -19
  96. novelwriter/gui/sidebar.py +1 -1
  97. novelwriter/gui/statusbar.py +20 -3
  98. novelwriter/gui/theme.py +8 -4
  99. novelwriter/guimain.py +60 -48
  100. novelwriter/shared.py +53 -24
  101. novelwriter/text/counting.py +1 -1
  102. novelwriter/text/patterns.py +18 -6
  103. novelwriter/tools/dictionaries.py +1 -1
  104. novelwriter/tools/lipsum.py +1 -1
  105. novelwriter/tools/manusbuild.py +14 -12
  106. novelwriter/tools/manuscript.py +7 -7
  107. novelwriter/tools/manussettings.py +43 -53
  108. novelwriter/tools/noveldetails.py +1 -1
  109. novelwriter/tools/welcome.py +1 -1
  110. novelwriter/tools/writingstats.py +1 -1
  111. novelwriter/types.py +9 -3
  112. {novelWriter-2.6b1.dist-info → novelWriter-2.6rc1.dist-info}/LICENSE.md +0 -0
  113. {novelWriter-2.6b1.dist-info → novelWriter-2.6rc1.dist-info}/entry_points.txt +0 -0
  114. {novelWriter-2.6b1.dist-info → novelWriter-2.6rc1.dist-info}/top_level.txt +0 -0
novelwriter/gui/search.py CHANGED
@@ -6,7 +6,7 @@ File History:
6
6
  Created: 2024-03-21 [2.4b1] GuiProjectSearch
7
7
 
8
8
  This file is a part of novelWriter
9
- Copyright 2018–2024, Veronica Berglyd Olsen
9
+ Copyright (C) 2024 Veronica Berglyd Olsen and novelWriter contributors
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
@@ -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()
@@ -130,12 +133,8 @@ class GuiProjectSearch(QWidget):
130
133
  self.headerBox.setContentsMargins(0, 0, 0, 0)
131
134
  self.headerBox.setSpacing(0)
132
135
 
133
- self.headerWidget = QWidget(self)
134
- self.headerWidget.setLayout(self.headerBox)
135
- self.headerWidget.setContentsMargins(0, 0, 0, 0)
136
-
137
136
  self.outerBox = QVBoxLayout()
138
- self.outerBox.addWidget(self.headerWidget, 0)
137
+ self.outerBox.addLayout(self.headerBox, 0)
139
138
  self.outerBox.addWidget(self.searchText, 0)
140
139
  self.outerBox.addWidget(self.searchResult, 1)
141
140
  self.outerBox.setContentsMargins(0, 0, 0, 0)
@@ -161,9 +160,6 @@ class GuiProjectSearch(QWidget):
161
160
  colBase = cssCol(qPalette.base().color())
162
161
  colFocus = cssCol(qPalette.highlight().color())
163
162
 
164
- self.headerWidget.setStyleSheet(f"QWidget {{background: {colBase};}}")
165
- self.headerWidget.setAutoFillBackground(True)
166
-
167
163
  self.setStyleSheet(
168
164
  "QToolBar {padding: 0; background: none;} "
169
165
  f"QLineEdit {{border: {bPx}px solid {colBase}; padding: {mPx}px;}} "
@@ -331,15 +327,11 @@ class GuiProjectSearch(QWidget):
331
327
  """Populate the result tree."""
332
328
  if results and nwItem:
333
329
  tHandle = nwItem.itemHandle
334
- docIcon = SHARED.theme.getItemIcon(
335
- nwItem.itemType, nwItem.itemClass,
336
- nwItem.itemLayout, nwItem.mainHeading
337
- )
338
330
  ext = "+" if capped else ""
339
331
 
340
332
  tItem = QTreeWidgetItem()
341
333
  tItem.setText(self.C_NAME, nwItem.itemName)
342
- tItem.setIcon(self.C_NAME, docIcon)
334
+ tItem.setIcon(self.C_NAME, nwItem.getMainIcon())
343
335
  tItem.setData(self.C_NAME, self.D_HANDLE, tHandle)
344
336
  tItem.setText(self.C_COUNT, f"({len(results):n}{ext})")
345
337
  tItem.setTextAlignment(self.C_COUNT, QtAlignRight)
@@ -6,7 +6,7 @@ File History:
6
6
  Created: 2022-05-10 [2.0rc1] GuiSideBar
7
7
 
8
8
  This file is a part of novelWriter
9
- Copyright 2018–2024, Veronica Berglyd Olsen
9
+ Copyright (C) 2022 Veronica Berglyd Olsen and novelWriter contributors
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
@@ -6,7 +6,7 @@ File History:
6
6
  Created: 2019-04-20 [0.0.1] GuiMainStatus
7
7
 
8
8
  This file is a part of novelWriter
9
- Copyright 2018–2024, Veronica Berglyd Olsen
9
+ Copyright (C) 2019 Veronica Berglyd Olsen and novelWriter contributors
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
@@ -35,6 +35,7 @@ from novelwriter import CONFIG, SHARED
35
35
  from novelwriter.common import formatTime
36
36
  from novelwriter.constants import nwConst
37
37
  from novelwriter.enum import nwTrinary
38
+ from novelwriter.extensions.modified import NClickableLabel
38
39
  from novelwriter.extensions.statusled import StatusLED
39
40
 
40
41
  logger = logging.getLogger(__name__)
@@ -92,12 +93,16 @@ class GuiMainStatus(QStatusBar):
92
93
 
93
94
  # The Session Clock
94
95
  # Set the minimum width so the label doesn't rescale every second
95
- self.timeIcon = QLabel(self)
96
- self.timeText = QLabel("", self)
96
+ self.timeIcon = NClickableLabel(self)
97
+ self.timeIcon.mouseClicked.connect(self._onClickTimerLabel)
98
+
99
+ self.timeText = NClickableLabel("", self)
97
100
  self.timeText.setToolTip(self.tr("Session Time"))
98
101
  self.timeText.setMinimumWidth(SHARED.theme.getTextWidth("00:00:00:"))
99
102
  self.timeIcon.setContentsMargins(0, 0, 0, 0)
100
103
  self.timeText.setContentsMargins(0, 0, 0, 0)
104
+ self.timeText.setVisible(CONFIG.showSessionTime)
105
+ self.timeText.mouseClicked.connect(self._onClickTimerLabel)
101
106
  self.addPermanentWidget(self.timeIcon)
102
107
  self.addPermanentWidget(self.timeText)
103
108
 
@@ -224,6 +229,18 @@ class GuiMainStatus(QStatusBar):
224
229
  self.setDocumentStatus(nwTrinary.NEGATIVE if status else nwTrinary.POSITIVE)
225
230
  return
226
231
 
232
+ ##
233
+ # Private Slots
234
+ ##
235
+
236
+ @pyqtSlot()
237
+ def _onClickTimerLabel(self) -> None:
238
+ """Process mouse click on timer label."""
239
+ state = not CONFIG.showSessionTime
240
+ self.timeText.setVisible(state)
241
+ CONFIG.showSessionTime = state
242
+ return
243
+
227
244
  ##
228
245
  # Debug
229
246
  ##
novelwriter/gui/theme.py CHANGED
@@ -7,7 +7,7 @@ Created: 2019-05-18 [0.1.3] GuiTheme
7
7
  Created: 2019-11-08 [0.4] GuiIcons
8
8
 
9
9
  This file is a part of novelWriter
10
- Copyright 2018–2024, Veronica Berglyd Olsen
10
+ Copyright (C) 2019 Veronica Berglyd Olsen and novelWriter contributors
11
11
 
12
12
  This program is free software: you can redistribute it and/or modify
13
13
  it under the terms of the GNU General Public License as published by
@@ -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
@@ -6,7 +6,7 @@ File History:
6
6
  Created: 2018-09-22 [0.0.1] GuiMain
7
7
 
8
8
  This file is a part of novelWriter
9
- Copyright 2018–2024, Veronica Berglyd Olsen
9
+ Copyright (C) 2018 Veronica Berglyd Olsen and novelWriter contributors
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
@@ -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()
@@ -950,12 +957,8 @@ class GuiMain(QMainWindow):
950
957
  docEditor = True
951
958
  elif self.docViewer.isAncestorOf(new):
952
959
  docViewer = True
953
-
954
960
  self.docEditor.changeFocusState(docEditor)
955
961
  self.docViewer.changeFocusState(docViewer)
956
-
957
- logger.debug("Main focus switched to: %s", type(new).__name__)
958
-
959
962
  return
960
963
 
961
964
  @pyqtSlot(bool)
@@ -1044,13 +1047,8 @@ class GuiMain(QMainWindow):
1044
1047
  self.initMain()
1045
1048
  self.saveDocument()
1046
1049
 
1047
- if restart:
1048
- SHARED.info(self.tr(
1049
- "Some changes will not be applied until novelWriter has been restarted."
1050
- ))
1051
-
1052
1050
  if tree:
1053
- self.projView.populateTree()
1051
+ SHARED.project.tree.refreshAllItems()
1054
1052
 
1055
1053
  if theme:
1056
1054
  # We are doing this manually instead of connecting to
@@ -1076,8 +1074,16 @@ class GuiMain(QMainWindow):
1076
1074
  self.projView.initSettings()
1077
1075
  self.novelView.initSettings()
1078
1076
  self.outlineView.initSettings()
1077
+
1078
+ # Force update of word count
1079
+ self._lastTotalCount = 0
1079
1080
  self._updateStatusWordCount()
1080
1081
 
1082
+ if restart:
1083
+ SHARED.info(self.tr(
1084
+ "Some changes will not be applied until novelWriter has been restarted."
1085
+ ))
1086
+
1081
1087
  return
1082
1088
 
1083
1089
  @pyqtSlot()
@@ -1213,8 +1219,10 @@ class GuiMain(QMainWindow):
1213
1219
  self.mainStatus.setUserIdle(editIdle or userIdle)
1214
1220
  SHARED.updateIdleTime(currTime, editIdle or userIdle)
1215
1221
  self.mainStatus.updateTime(idleTime=SHARED.projectIdleTime)
1216
- if CONFIG.memInfo and int(currTime) % 5 == 0: # pragma: no cover
1217
- self.mainStatus.memInfo()
1222
+ if int(currTime) % 5 == 0:
1223
+ self._updateStatusWordCount()
1224
+ if CONFIG.memInfo: # pragma: no cover
1225
+ self.mainStatus.memInfo()
1218
1226
  return
1219
1227
 
1220
1228
  @pyqtSlot()
@@ -1242,15 +1250,19 @@ class GuiMain(QMainWindow):
1242
1250
  if not SHARED.hasProject:
1243
1251
  self.mainStatus.setProjectStats(0, 0)
1244
1252
 
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)
1253
+ currentTotalCount = SHARED.project.currentTotalCount
1254
+ if self._lastTotalCount != currentTotalCount:
1255
+ self._lastTotalCount = currentTotalCount
1256
+
1257
+ SHARED.project.updateWordCounts()
1258
+ if CONFIG.incNotesWCount:
1259
+ iTotal = sum(SHARED.project.data.initCounts)
1260
+ cTotal = sum(SHARED.project.data.currCounts)
1261
+ self.mainStatus.setProjectStats(cTotal, cTotal - iTotal)
1262
+ else:
1263
+ iNovel, _ = SHARED.project.data.initCounts
1264
+ cNovel, _ = SHARED.project.data.currCounts
1265
+ self.mainStatus.setProjectStats(cNovel, cNovel - iNovel)
1254
1266
 
1255
1267
  return
1256
1268
 
novelwriter/shared.py CHANGED
@@ -7,7 +7,7 @@ Created: 2023-08-10 [2.1rc1] SharedData
7
7
  Created: 2023-08-14 [2.1rc1] _GuiAlert
8
8
 
9
9
  This file is a part of novelWriter
10
- Copyright 2018–2024, Veronica Berglyd Olsen
10
+ Copyright (C) 2023 Veronica Berglyd Olsen and novelWriter contributors
11
11
 
12
12
  This program is free software: you can redistribute it and/or modify
13
13
  it under the terms of the GNU General Public License as published by
@@ -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()
@@ -8,7 +8,7 @@ Rewritten: 2024-02-27 [2.4b1] preProcessText, standardCounter
8
8
  Created: 2024-02-27 [2.4b1] bodyTextCounter
9
9
 
10
10
  This file is a part of novelWriter
11
- Copyright 2018–2024, Veronica Berglyd Olsen
11
+ Copyright (C) 2024 Veronica Berglyd Olsen and novelWriter contributors
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
@@ -7,7 +7,7 @@ Created: 2024-06-01 [2.5rc1] RegExPatterns
7
7
  Created: 2024-11-04 [2.6b1] DialogParser
8
8
 
9
9
  This file is a part of novelWriter
10
- Copyright 2018–2024, Veronica Berglyd Olsen
10
+ Copyright (C) 2024 Veronica Berglyd Olsen and novelWriter contributors
11
11
 
12
12
  This program is free software: you can redistribute it and/or modify
13
13
  it under the terms of the GNU General Public License as published by
@@ -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
 
@@ -6,7 +6,7 @@ File History:
6
6
  Created: 2023-11-19 [2.2rc1] GuiDictionaries
7
7
 
8
8
  This file is a part of novelWriter
9
- Copyright 2018–2024, Veronica Berglyd Olsen
9
+ Copyright (C) 2023 Veronica Berglyd Olsen and novelWriter contributors
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
@@ -6,7 +6,7 @@ File History:
6
6
  Created: 2022-04-02 [2.0rc1] GuiLipsum
7
7
 
8
8
  This file is a part of novelWriter
9
- Copyright 2018–2024, Veronica Berglyd Olsen
9
+ Copyright (C) 2022 Veronica Berglyd Olsen and novelWriter contributors
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