novelWriter 2.5b1__py3-none-any.whl → 2.5rc1__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 (62) hide show
  1. {novelWriter-2.5b1.dist-info → novelWriter-2.5rc1.dist-info}/METADATA +1 -1
  2. {novelWriter-2.5b1.dist-info → novelWriter-2.5rc1.dist-info}/RECORD +61 -61
  3. {novelWriter-2.5b1.dist-info → novelWriter-2.5rc1.dist-info}/WHEEL +1 -1
  4. novelwriter/__init__.py +3 -3
  5. novelwriter/assets/i18n/nw_pt_BR.qm +0 -0
  6. novelwriter/assets/i18n/project_pt_BR.json +74 -74
  7. novelwriter/assets/manual.pdf +0 -0
  8. novelwriter/assets/sample.zip +0 -0
  9. novelwriter/assets/themes/cyberpunk_night.conf +1 -0
  10. novelwriter/assets/themes/default_dark.conf +1 -0
  11. novelwriter/assets/themes/default_light.conf +1 -0
  12. novelwriter/assets/themes/dracula.conf +1 -0
  13. novelwriter/assets/themes/solarized_dark.conf +1 -0
  14. novelwriter/assets/themes/solarized_light.conf +1 -0
  15. novelwriter/common.py +2 -3
  16. novelwriter/config.py +67 -15
  17. novelwriter/constants.py +8 -10
  18. novelwriter/core/buildsettings.py +5 -3
  19. novelwriter/core/coretools.py +3 -1
  20. novelwriter/core/docbuild.py +1 -0
  21. novelwriter/core/tohtml.py +69 -29
  22. novelwriter/core/tokenizer.py +83 -14
  23. novelwriter/core/toodt.py +48 -21
  24. novelwriter/core/toqdoc.py +25 -9
  25. novelwriter/dialogs/about.py +10 -15
  26. novelwriter/dialogs/docmerge.py +16 -16
  27. novelwriter/dialogs/docsplit.py +16 -16
  28. novelwriter/dialogs/editlabel.py +6 -8
  29. novelwriter/dialogs/preferences.py +94 -68
  30. novelwriter/dialogs/projectsettings.py +10 -10
  31. novelwriter/dialogs/quotes.py +9 -5
  32. novelwriter/dialogs/wordlist.py +6 -6
  33. novelwriter/enum.py +4 -5
  34. novelwriter/extensions/configlayout.py +23 -4
  35. novelwriter/extensions/modified.py +22 -3
  36. novelwriter/extensions/{circularprogress.py → progressbars.py} +26 -3
  37. novelwriter/extensions/statusled.py +28 -22
  38. novelwriter/gui/doceditor.py +20 -11
  39. novelwriter/gui/dochighlight.py +30 -39
  40. novelwriter/gui/docviewer.py +21 -14
  41. novelwriter/gui/mainmenu.py +11 -11
  42. novelwriter/gui/outline.py +3 -3
  43. novelwriter/gui/projtree.py +19 -28
  44. novelwriter/gui/search.py +10 -1
  45. novelwriter/gui/statusbar.py +25 -29
  46. novelwriter/gui/theme.py +3 -0
  47. novelwriter/guimain.py +91 -84
  48. novelwriter/shared.py +10 -8
  49. novelwriter/text/patterns.py +113 -0
  50. novelwriter/tools/dictionaries.py +2 -8
  51. novelwriter/tools/lipsum.py +8 -12
  52. novelwriter/tools/manusbuild.py +9 -9
  53. novelwriter/tools/manuscript.py +10 -5
  54. novelwriter/tools/manussettings.py +7 -3
  55. novelwriter/tools/noveldetails.py +10 -10
  56. novelwriter/tools/welcome.py +10 -10
  57. novelwriter/tools/writingstats.py +3 -3
  58. novelwriter/types.py +5 -2
  59. novelwriter/extensions/simpleprogress.py +0 -53
  60. {novelWriter-2.5b1.dist-info → novelWriter-2.5rc1.dist-info}/LICENSE.md +0 -0
  61. {novelWriter-2.5b1.dist-info → novelWriter-2.5rc1.dist-info}/entry_points.txt +0 -0
  62. {novelWriter-2.5b1.dist-info → novelWriter-2.5rc1.dist-info}/top_level.txt +0 -0
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, nwItemType, nwView, nwWidget
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
@@ -210,67 +210,68 @@ class GuiMain(QMainWindow):
210
210
  # Connect Signals
211
211
  # ===============
212
212
 
213
- SHARED.projectStatusChanged.connect(self.mainStatus.updateProjectStatus)
214
- SHARED.projectStatusMessage.connect(self.mainStatus.setStatusMessage)
215
- SHARED.spellLanguageChanged.connect(self.mainStatus.setLanguage)
216
213
  SHARED.focusModeChanged.connect(self._focusModeChanged)
214
+ SHARED.indexAvailable.connect(self.docViewerPanel.indexHasAppeared)
215
+ SHARED.indexChangedTags.connect(self.docEditor.updateChangedTags)
217
216
  SHARED.indexChangedTags.connect(self.docViewerPanel.updateChangedTags)
217
+ SHARED.indexCleared.connect(self.docViewerPanel.indexWasCleared)
218
218
  SHARED.indexScannedText.connect(self.docViewerPanel.projectItemChanged)
219
- SHARED.indexScannedText.connect(self.projView.updateItemValues)
220
219
  SHARED.indexScannedText.connect(self.itemDetails.updateViewBox)
221
- SHARED.indexCleared.connect(self.docViewerPanel.indexWasCleared)
222
- SHARED.indexAvailable.connect(self.docViewerPanel.indexHasAppeared)
220
+ SHARED.indexScannedText.connect(self.projView.updateItemValues)
223
221
  SHARED.mainClockTick.connect(self._timeTick)
222
+ SHARED.projectStatusChanged.connect(self.mainStatus.updateProjectStatus)
223
+ SHARED.projectStatusMessage.connect(self.mainStatus.setStatusMessage)
224
+ SHARED.spellLanguageChanged.connect(self.mainStatus.setLanguage)
224
225
 
225
226
  self.mainMenu.requestDocAction.connect(self._passDocumentAction)
226
227
  self.mainMenu.requestDocInsert.connect(self._passDocumentInsert)
227
228
  self.mainMenu.requestDocInsertText.connect(self._passDocumentInsert)
228
229
  self.mainMenu.requestDocKeyWordInsert.connect(self.docEditor.insertKeyWord)
229
- self.mainMenu.requestFocusChange.connect(self.switchFocus)
230
+ self.mainMenu.requestFocusChange.connect(self._switchFocus)
230
231
  self.mainMenu.requestViewChange.connect(self._changeView)
231
232
 
232
233
  self.sideBar.requestViewChange.connect(self._changeView)
233
234
 
234
- self.projView.selectedItemChanged.connect(self.itemDetails.updateViewBox)
235
235
  self.projView.openDocumentRequest.connect(self._openDocument)
236
- self.projView.wordCountsChanged.connect(self._updateStatusWordCount)
236
+ self.projView.projectSettingsRequest.connect(self.showProjectSettingsDialog)
237
+ self.projView.rootFolderChanged.connect(self.novelView.updateRootItem)
238
+ self.projView.rootFolderChanged.connect(self.outlineView.updateRootItem)
239
+ self.projView.rootFolderChanged.connect(self.projView.updateRootItem)
240
+ self.projView.selectedItemChanged.connect(self.itemDetails.updateViewBox)
237
241
  self.projView.treeItemChanged.connect(self.docEditor.updateDocInfo)
238
242
  self.projView.treeItemChanged.connect(self.docViewer.updateDocInfo)
239
- self.projView.treeItemChanged.connect(self.itemDetails.updateViewBox)
240
243
  self.projView.treeItemChanged.connect(self.docViewerPanel.projectItemChanged)
241
- self.projView.rootFolderChanged.connect(self.outlineView.updateRootItem)
242
- self.projView.rootFolderChanged.connect(self.novelView.updateRootItem)
243
- self.projView.rootFolderChanged.connect(self.projView.updateRootItem)
244
- self.projView.projectSettingsRequest.connect(self.showProjectSettingsDialog)
244
+ self.projView.treeItemChanged.connect(self.itemDetails.updateViewBox)
245
+ self.projView.wordCountsChanged.connect(self._updateStatusWordCount)
245
246
 
246
- self.novelView.selectedItemChanged.connect(self.itemDetails.updateViewBox)
247
247
  self.novelView.openDocumentRequest.connect(self._openDocument)
248
+ self.novelView.selectedItemChanged.connect(self.itemDetails.updateViewBox)
248
249
 
249
250
  self.projSearch.openDocumentSelectRequest.connect(self._openDocumentSelection)
250
251
  self.projSearch.selectedItemChanged.connect(self.itemDetails.updateViewBox)
251
252
 
252
- self.docEditor.editedStatusChanged.connect(self.mainStatus.updateDocumentStatus)
253
+ self.docEditor.closeDocumentRequest.connect(self.closeDocEditor)
253
254
  self.docEditor.docCountsChanged.connect(self.itemDetails.updateCounts)
254
255
  self.docEditor.docCountsChanged.connect(self.projView.updateCounts)
256
+ self.docEditor.docTextChanged.connect(self.projSearch.textChanged)
257
+ self.docEditor.editedStatusChanged.connect(self.mainStatus.updateDocumentStatus)
255
258
  self.docEditor.loadDocumentTagRequest.connect(self._followTag)
256
- self.docEditor.novelStructureChanged.connect(self.novelView.refreshTree)
257
259
  self.docEditor.novelItemMetaChanged.connect(self.novelView.updateNovelItemMeta)
258
- self.docEditor.statusMessage.connect(self.mainStatus.setStatusMessage)
259
- self.docEditor.spellCheckStateChanged.connect(self.mainMenu.setSpellCheckState)
260
- self.docEditor.closeDocumentRequest.connect(self.closeDocEditor)
261
- self.docEditor.toggleFocusModeRequest.connect(self.toggleFocusMode)
262
- self.docEditor.requestProjectItemSelected.connect(self.projView.setSelectedHandle)
263
- self.docEditor.requestProjectItemRenamed.connect(self.projView.renameTreeItem)
260
+ self.docEditor.novelStructureChanged.connect(self.novelView.refreshTree)
264
261
  self.docEditor.requestNewNoteCreation.connect(self.projView.createNewNote)
265
- self.docEditor.docTextChanged.connect(self.projSearch.textChanged)
266
262
  self.docEditor.requestNextDocument.connect(self.openNextDocument)
263
+ self.docEditor.requestProjectItemRenamed.connect(self.projView.renameTreeItem)
264
+ self.docEditor.requestProjectItemSelected.connect(self.projView.setSelectedHandle)
265
+ self.docEditor.spellCheckStateChanged.connect(self.mainMenu.setSpellCheckState)
266
+ self.docEditor.statusMessage.connect(self.mainStatus.setStatusMessage)
267
+ self.docEditor.toggleFocusModeRequest.connect(self.toggleFocusMode)
267
268
 
269
+ self.docViewer.closeDocumentRequest.connect(self.closeDocViewer)
268
270
  self.docViewer.documentLoaded.connect(self.docViewerPanel.updateHandle)
269
271
  self.docViewer.loadDocumentTagRequest.connect(self._followTag)
270
- self.docViewer.closeDocumentRequest.connect(self.closeDocViewer)
271
272
  self.docViewer.reloadDocumentRequest.connect(self._reloadViewer)
272
- self.docViewer.togglePanelVisibility.connect(self._toggleViewerPanelVisibility)
273
273
  self.docViewer.requestProjectItemSelected.connect(self.projView.setSelectedHandle)
274
+ self.docViewer.togglePanelVisibility.connect(self._toggleViewerPanelVisibility)
274
275
 
275
276
  self.docViewerPanel.loadDocumentTagRequest.connect(self._followTag)
276
277
  self.docViewerPanel.openDocumentRequest.connect(self._openDocument)
@@ -304,9 +305,6 @@ class GuiMain(QMainWindow):
304
305
  self.keyEscape.setKey(Qt.Key.Key_Escape)
305
306
  self.keyEscape.activated.connect(self._keyPressEscape)
306
307
 
307
- # Check that config loaded fine
308
- self.reportConfErr()
309
-
310
308
  # Initialise Main GUI
311
309
  self.initMain()
312
310
  self.asProjTimer.start()
@@ -328,6 +326,15 @@ class GuiMain(QMainWindow):
328
326
 
329
327
  def postLaunchTasks(self, cmdOpen: str | None) -> None:
330
328
  """Process tasks after the main window has been created."""
329
+ QApplication.processEvents()
330
+ app = QApplication.instance()
331
+ if isinstance(app, QApplication):
332
+ app.focusChanged.connect(self._appFocusChanged)
333
+
334
+ # Check that config loaded fine
335
+ if CONFIG.hasError:
336
+ SHARED.error(CONFIG.errorText())
337
+
331
338
  if cmdOpen:
332
339
  QApplication.processEvents()
333
340
  logger.info("Command line path: %s", cmdOpen)
@@ -616,6 +623,10 @@ class GuiMain(QMainWindow):
616
623
  # Make sure main tab is in Editor view
617
624
  self._changeView(nwView.EDITOR)
618
625
 
626
+ # If we're loading the document in the editor, it may need to be saved
627
+ if tHandle == self.docEditor.docHandle and self.docEditor.docChanged:
628
+ self.saveDocument()
629
+
619
630
  logger.debug("Viewing document with handle '%s'", tHandle)
620
631
  updateHistory = tHandle != self.docViewer.docHandle
621
632
  if self.docViewer.loadText(tHandle, updateHistory=updateHistory):
@@ -647,7 +658,7 @@ class GuiMain(QMainWindow):
647
658
  logger.error("No project open")
648
659
  return False
649
660
 
650
- lastPath = CONFIG.lastPath()
661
+ lastPath = CONFIG.lastPath("import")
651
662
  ffilter = formatFileFilter(["*.txt", "*.md", "*.nwd", "*"])
652
663
  loadFile, _ = QFileDialog.getOpenFileName(
653
664
  self, self.tr("Import File"), str(lastPath), filter=ffilter
@@ -662,7 +673,7 @@ class GuiMain(QMainWindow):
662
673
  try:
663
674
  with open(loadFile, mode="rt", encoding="utf-8") as inFile:
664
675
  text = inFile.read()
665
- CONFIG.setLastPath(loadFile)
676
+ CONFIG.setLastPath("import", loadFile)
666
677
  except Exception as exc:
667
678
  SHARED.error(self.tr(
668
679
  "Could not read file. The file must be an existing text file."
@@ -719,14 +730,6 @@ class GuiMain(QMainWindow):
719
730
 
720
731
  return
721
732
 
722
- def editItemLabel(self, tHandle: str | None = None) -> None:
723
- """Open the edit item dialog."""
724
- if SHARED.hasProject:
725
- if tHandle is None and (self.docEditor.anyFocus() or SHARED.focusMode):
726
- tHandle = self.docEditor.docHandle
727
- self.projView.renameTreeItem(tHandle)
728
- return
729
-
730
733
  def rebuildTrees(self) -> None:
731
734
  """Rebuild the project tree."""
732
735
  self.projView.populateTree()
@@ -748,7 +751,6 @@ class GuiMain(QMainWindow):
748
751
  self.mainStatus.setStatusMessage(
749
752
  self.tr("Indexing completed in {0} ms").format(f"{(tEnd - tStart)*1000.0:.1f}")
750
753
  )
751
- self.docEditor.updateTagHighLighting()
752
754
  self._updateStatusWordCount()
753
755
  QApplication.restoreOverrideCursor()
754
756
 
@@ -800,7 +802,8 @@ class GuiMain(QMainWindow):
800
802
  def showBuildManuscriptDialog(self) -> None:
801
803
  """Open the build manuscript dialog."""
802
804
  if SHARED.hasProject:
803
- dialog = GuiManuscript(self)
805
+ if not (dialog := SHARED.findTopLevelWidget(GuiManuscript)):
806
+ dialog = GuiManuscript(self)
804
807
  dialog.activateDialog()
805
808
  dialog.loadContent()
806
809
  return
@@ -818,7 +821,8 @@ class GuiMain(QMainWindow):
818
821
  def showWritingStatsDialog(self) -> None:
819
822
  """Open the session stats dialog."""
820
823
  if SHARED.hasProject:
821
- dialog = GuiWritingStats(self)
824
+ if not (dialog := SHARED.findTopLevelWidget(GuiWritingStats)):
825
+ dialog = GuiWritingStats(self)
822
826
  dialog.activateDialog()
823
827
  dialog.populateGUI()
824
828
  return
@@ -827,8 +831,7 @@ class GuiMain(QMainWindow):
827
831
  def showAboutNWDialog(self) -> None:
828
832
  """Show the novelWriter about dialog."""
829
833
  dialog = GuiAbout(self)
830
- dialog.activateDialog()
831
- dialog.populateGUI()
834
+ dialog.exec()
832
835
  return
833
836
 
834
837
  @pyqtSlot()
@@ -848,15 +851,6 @@ class GuiMain(QMainWindow):
848
851
  SHARED.error(self.tr("Could not initialise the dialog."))
849
852
  return
850
853
 
851
- def reportConfErr(self) -> None:
852
- """Checks if the Config module has any errors to report, and let
853
- the user know if this is the case. The Config module caches
854
- errors since it is initialised before the GUI itself.
855
- """
856
- if CONFIG.hasError:
857
- SHARED.error(CONFIG.errorText())
858
- return
859
-
860
854
  ##
861
855
  # Main Window Actions
862
856
  ##
@@ -887,9 +881,7 @@ class GuiMain(QMainWindow):
887
881
 
888
882
  if SHARED.hasProject:
889
883
  self.closeProject(True)
890
-
891
884
  CONFIG.saveConfig()
892
- self.reportConfErr()
893
885
 
894
886
  QApplication.quit()
895
887
 
@@ -957,14 +949,37 @@ class GuiMain(QMainWindow):
957
949
  SHARED.setFocusMode(not SHARED.focusMode)
958
950
  return
959
951
 
952
+ ##
953
+ # Private Slots
954
+ ##
955
+
956
+ @pyqtSlot("QWidget*", "QWidget*")
957
+ def _appFocusChanged(self, old: QWidget, new: QWidget) -> None:
958
+ """Alert main widgets that they have received or lost focus."""
959
+ if isinstance(new, QWidget):
960
+ docEditor = False
961
+ docViewer = False
962
+ if self.docEditor.isAncestorOf(new):
963
+ docEditor = True
964
+ elif self.docViewer.isAncestorOf(new):
965
+ docViewer = True
966
+
967
+ self.docEditor.changeFocusState(docEditor)
968
+ self.docViewer.changeFocusState(docViewer)
969
+
970
+ logger.debug("Main focus switched to: %s", type(new).__name__)
971
+
972
+ return
973
+
960
974
  @pyqtSlot(bool)
961
975
  def _focusModeChanged(self, focusMode: bool) -> None:
962
- """Handle change of focus mode. The Main GUI Focus Mode hides tree,
963
- view, statusbar and menu.
976
+ """Handle change of focus mode. The Main GUI Focus Mode hides
977
+ tree, view, statusbar and menu.
964
978
  """
965
979
  if focusMode:
966
980
  logger.debug("Activating Focus Mode")
967
- self.switchFocus(nwWidget.EDITOR)
981
+ self._changeView(nwView.EDITOR)
982
+ self.docEditor.setFocus()
968
983
  else:
969
984
  logger.debug("Deactivating Focus Mode")
970
985
 
@@ -987,10 +1002,10 @@ class GuiMain(QMainWindow):
987
1002
  self.docEditor.ensureCursorVisibleNoCentre()
988
1003
  return
989
1004
 
990
- @pyqtSlot(nwWidget)
991
- def switchFocus(self, paneNo: nwWidget) -> None:
1005
+ @pyqtSlot(nwFocus)
1006
+ def _switchFocus(self, paneNo: nwFocus) -> None:
992
1007
  """Switch focus between main GUI views."""
993
- if paneNo == nwWidget.TREE:
1008
+ if paneNo == nwFocus.TREE:
994
1009
  if self.projStack.currentWidget() is self.projView:
995
1010
  if self.projView.treeHasFocus():
996
1011
  self._changeView(nwView.NOVEL)
@@ -1006,21 +1021,19 @@ class GuiMain(QMainWindow):
1006
1021
  else:
1007
1022
  self._changeView(nwView.PROJECT)
1008
1023
  self.projView.setTreeFocus()
1009
- elif paneNo == nwWidget.EDITOR:
1010
- self._changeView(nwView.EDITOR)
1011
- self.docEditor.setFocus()
1012
- elif paneNo == nwWidget.VIEWER:
1024
+ elif paneNo == nwFocus.DOCUMENT:
1013
1025
  self._changeView(nwView.EDITOR)
1014
- self.docViewer.setFocus()
1015
- elif paneNo == nwWidget.OUTLINE:
1026
+ if self.docEditor.anyFocus():
1027
+ self.docViewer.setFocus()
1028
+ elif self.docViewer.anyFocus():
1029
+ self.docEditor.setFocus()
1030
+ else:
1031
+ self.docEditor.setFocus()
1032
+ elif paneNo == nwFocus.OUTLINE:
1016
1033
  self._changeView(nwView.OUTLINE)
1017
1034
  self.outlineView.setTreeFocus()
1018
1035
  return
1019
1036
 
1020
- ##
1021
- # Private Slots
1022
- ##
1023
-
1024
1037
  @pyqtSlot(bool, bool, bool, bool)
1025
1038
  def _processConfigChanges(self, restart: bool, tree: bool, theme: bool, syntax: bool) -> None:
1026
1039
  """Refresh GUI based on flags from the Preferences dialog."""
@@ -1159,16 +1172,13 @@ class GuiMain(QMainWindow):
1159
1172
 
1160
1173
  @pyqtSlot(nwDocAction)
1161
1174
  def _passDocumentAction(self, action: nwDocAction) -> None:
1162
- """Pass on a document action to the document viewer if it has
1163
- focus, or pass it to the document editor if it or any of its
1164
- child widgets have focus. If neither has focus, ignore it.
1175
+ """Pass on a document action to the editor or viewer based on
1176
+ which one has focus, or if neither has focus, ignore it.
1165
1177
  """
1166
- if self.docViewer.hasFocus():
1167
- self.docViewer.docAction(action)
1168
- elif self.docEditor.hasFocus():
1178
+ if self.docEditor.hasFocus():
1169
1179
  self.docEditor.docAction(action)
1170
- else:
1171
- logger.debug("Action cancelled as neither editor nor viewer has focus")
1180
+ elif self.docViewer.hasFocus():
1181
+ self.docViewer.docAction(action)
1172
1182
  return
1173
1183
 
1174
1184
  @pyqtSlot(str)
@@ -1376,10 +1386,7 @@ class GuiMain(QMainWindow):
1376
1386
 
1377
1387
  def _updateWindowTitle(self, projName: str | None = None) -> None:
1378
1388
  """Set the window title and add the project's name."""
1379
- winTitle = CONFIG.appName
1380
- if projName is not None:
1381
- winTitle += " - %s" % projName
1382
- self.setWindowTitle(winTitle)
1389
+ self.setWindowTitle(" - ".join(filter(None, [projName, CONFIG.appName])))
1383
1390
  return
1384
1391
 
1385
1392
  def _getTagSource(self, tag: str) -> tuple[str | None, str | None]:
novelwriter/shared.py CHANGED
@@ -172,15 +172,21 @@ class SharedData(QObject):
172
172
  logger.debug("Thread Pool Max Count: %d", QThreadPool.globalInstance().maxThreadCount())
173
173
  return
174
174
 
175
- def closeDocument(self, tHandle: str | None = None) -> None:
175
+ def closeEditor(self, tHandle: str | None = None) -> None:
176
176
  """Close the document editor, optionally a specific document."""
177
177
  if tHandle is None or tHandle == self.mainGui.docEditor.docHandle:
178
178
  self.mainGui.closeDocument()
179
179
  return
180
180
 
181
- def saveDocument(self) -> None:
182
- """Forward save document call to main GUI."""
183
- self.mainGui.saveDocument()
181
+ def saveEditor(self, tHandle: str | None = None) -> None:
182
+ """Save the editor content, optionally a specific document."""
183
+ docEditor = self.mainGui.docEditor
184
+ if (
185
+ self.hasProject and docEditor.docHandle
186
+ and (tHandle is None or tHandle == docEditor.docHandle)
187
+ ):
188
+ logger.debug("Saving editor document before action")
189
+ docEditor.saveText()
184
190
  return
185
191
 
186
192
  def openProject(self, path: str | Path, clearLock: bool = False) -> bool:
@@ -316,7 +322,6 @@ class SharedData(QObject):
316
322
  if log:
317
323
  logger.info(self._lastAlert, stacklevel=2)
318
324
  alert.exec()
319
- alert.deleteLater()
320
325
  return
321
326
 
322
327
  def warn(self, text: str, info: str = "", details: str = "", log: bool = True) -> None:
@@ -328,7 +333,6 @@ class SharedData(QObject):
328
333
  if log:
329
334
  logger.warning(self._lastAlert, stacklevel=2)
330
335
  alert.exec()
331
- alert.deleteLater()
332
336
  return
333
337
 
334
338
  def error(self, text: str, info: str = "", details: str = "", log: bool = True,
@@ -343,7 +347,6 @@ class SharedData(QObject):
343
347
  if log:
344
348
  logger.error(self._lastAlert, stacklevel=2)
345
349
  alert.exec()
346
- alert.deleteLater()
347
350
  return
348
351
 
349
352
  def question(self, text: str, info: str = "", details: str = "", warn: bool = False) -> bool:
@@ -354,7 +357,6 @@ class SharedData(QObject):
354
357
  self._lastAlert = alert.logMessage
355
358
  alert.exec()
356
359
  isYes = alert.result() == QMessageBox.StandardButton.Yes
357
- alert.deleteLater()
358
360
  return isYes
359
361
 
360
362
  ##
@@ -0,0 +1,113 @@
1
+ """
2
+ novelWriter – Text Pattern Functions
3
+ ====================================
4
+
5
+ File History:
6
+ Created: 2024-06-01 [2.5ec1]
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
+ from PyQt5.QtCore import QRegularExpression
27
+
28
+ from novelwriter import CONFIG
29
+ from novelwriter.constants import nwRegEx
30
+ from novelwriter.types import QRegExUnicode
31
+
32
+
33
+ class RegExPatterns:
34
+
35
+ @property
36
+ def markdownItalic(self) -> QRegularExpression:
37
+ """Markdown italic style."""
38
+ rxRule = QRegularExpression(nwRegEx.FMT_EI)
39
+ rxRule.setPatternOptions(QRegExUnicode)
40
+ return rxRule
41
+
42
+ @property
43
+ def markdownBold(self) -> QRegularExpression:
44
+ """Markdown bold style."""
45
+ rxRule = QRegularExpression(nwRegEx.FMT_EB)
46
+ rxRule.setPatternOptions(QRegExUnicode)
47
+ return rxRule
48
+
49
+ @property
50
+ def markdownStrike(self) -> QRegularExpression:
51
+ """Markdown strikethrough style."""
52
+ rxRule = QRegularExpression(nwRegEx.FMT_ST)
53
+ rxRule.setPatternOptions(QRegExUnicode)
54
+ return rxRule
55
+
56
+ @property
57
+ def shortcodePlain(self) -> QRegularExpression:
58
+ """Plain shortcode style."""
59
+ rxRule = QRegularExpression(nwRegEx.FMT_SC)
60
+ rxRule.setPatternOptions(QRegExUnicode)
61
+ return rxRule
62
+
63
+ @property
64
+ def shortcodeValue(self) -> QRegularExpression:
65
+ """Plain shortcode style."""
66
+ rxRule = QRegularExpression(nwRegEx.FMT_SV)
67
+ rxRule.setPatternOptions(QRegExUnicode)
68
+ return rxRule
69
+
70
+ @property
71
+ def dialogStyle(self) -> QRegularExpression:
72
+ """Dialogue detection rule based on user settings."""
73
+ symO = ""
74
+ symC = ""
75
+ if CONFIG.dialogStyle in (1, 3):
76
+ symO += CONFIG.fmtSQuoteOpen
77
+ symC += CONFIG.fmtSQuoteClose
78
+ if CONFIG.dialogStyle in (2, 3):
79
+ symO += CONFIG.fmtDQuoteOpen
80
+ symC += CONFIG.fmtDQuoteClose
81
+
82
+ rxEnd = "|$" if CONFIG.allowOpenDial else ""
83
+ rxRule = QRegularExpression(f"\\B[{symO}].*?(?:[{symC}]\\B{rxEnd})")
84
+ rxRule.setPatternOptions(QRegExUnicode)
85
+ return rxRule
86
+
87
+ @property
88
+ def dialogLine(self) -> QRegularExpression:
89
+ """Dialogue line rule based on user settings."""
90
+ sym = QRegularExpression.escape(CONFIG.dialogLine)
91
+ rxRule = QRegularExpression(f"^{sym}.*?$")
92
+ rxRule.setPatternOptions(QRegExUnicode)
93
+ return rxRule
94
+
95
+ @property
96
+ def narratorBreak(self) -> QRegularExpression:
97
+ """Dialogue narrator break rule based on user settings."""
98
+ sym = QRegularExpression.escape(CONFIG.narratorBreak)
99
+ rxRule = QRegularExpression(f"\\B{sym}\\S.*?\\S{sym}\\B")
100
+ rxRule.setPatternOptions(QRegExUnicode)
101
+ return rxRule
102
+
103
+ @property
104
+ def altDialogStyle(self) -> QRegularExpression:
105
+ """Dialogue alternative rule based on user settings."""
106
+ symO = QRegularExpression.escape(CONFIG.altDialogOpen)
107
+ symC = QRegularExpression.escape(CONFIG.altDialogClose)
108
+ rxRule = QRegularExpression(f"\\B{symO}.*?{symC}\\B")
109
+ rxRule.setPatternOptions(QRegExUnicode)
110
+ return rxRule
111
+
112
+
113
+ REGEX_PATTERNS = RegExPatterns()
@@ -109,7 +109,7 @@ class GuiDictionaries(NNonBlockingDialog):
109
109
 
110
110
  # Buttons
111
111
  self.buttonBox = QDialogButtonBox(QtDialogClose, self)
112
- self.buttonBox.rejected.connect(self._doClose)
112
+ self.buttonBox.rejected.connect(self.reject)
113
113
 
114
114
  # Assemble
115
115
  self.innerBox = QVBoxLayout()
@@ -172,7 +172,7 @@ class GuiDictionaries(NNonBlockingDialog):
172
172
  def closeEvent(self, event: QCloseEvent) -> None:
173
173
  """Capture the user closing the window."""
174
174
  event.accept()
175
- self.deleteLater()
175
+ self.softDelete()
176
176
  return
177
177
 
178
178
  ##
@@ -220,12 +220,6 @@ class GuiDictionaries(NNonBlockingDialog):
220
220
  SHARED.error("Path not found.")
221
221
  return
222
222
 
223
- @pyqtSlot()
224
- def _doClose(self) -> None:
225
- """Close the dialog."""
226
- self.close()
227
- return
228
-
229
223
  ##
230
224
  # Internal Functions
231
225
  ##
@@ -26,34 +26,32 @@ from __future__ import annotations
26
26
  import logging
27
27
  import random
28
28
 
29
- from PyQt5.QtCore import Qt, pyqtSlot
29
+ from PyQt5.QtCore import pyqtSlot
30
30
  from PyQt5.QtWidgets import (
31
- QDialog, QDialogButtonBox, QGridLayout, QHBoxLayout, QLabel, QSpinBox,
32
- QVBoxLayout, QWidget
31
+ QDialogButtonBox, QGridLayout, QHBoxLayout, QLabel, QSpinBox, QVBoxLayout,
32
+ QWidget
33
33
  )
34
34
 
35
35
  from novelwriter import CONFIG, SHARED
36
36
  from novelwriter.common import readTextFile
37
+ from novelwriter.extensions.modified import NDialog
37
38
  from novelwriter.extensions.switch import NSwitch
38
39
  from novelwriter.types import QtAlignLeft, QtAlignRight, QtDialogClose, QtRoleAction
39
40
 
40
41
  logger = logging.getLogger(__name__)
41
42
 
42
43
 
43
- class GuiLipsum(QDialog):
44
+ class GuiLipsum(NDialog):
44
45
 
45
46
  def __init__(self, parent: QWidget) -> None:
46
47
  super().__init__(parent=parent)
47
48
 
48
49
  logger.debug("Create: GuiLipsum")
49
50
  self.setObjectName("GuiLipsum")
50
- if CONFIG.osDarwin:
51
- self.setWindowFlag(Qt.WindowType.Tool)
51
+ self.setWindowTitle(self.tr("Insert Placeholder Text"))
52
52
 
53
53
  self._lipsumText = ""
54
54
 
55
- self.setWindowTitle(self.tr("Insert Placeholder Text"))
56
-
57
55
  vSp = CONFIG.pxInt(4)
58
56
  nPx = CONFIG.pxInt(64)
59
57
 
@@ -95,7 +93,7 @@ class GuiLipsum(QDialog):
95
93
 
96
94
  # Buttons
97
95
  self.buttonBox = QDialogButtonBox(self)
98
- self.buttonBox.rejected.connect(self.close)
96
+ self.buttonBox.rejected.connect(self.reject)
99
97
 
100
98
  self.btnClose = self.buttonBox.addButton(QtDialogClose)
101
99
  self.btnClose.setAutoDefault(False)
@@ -104,8 +102,6 @@ class GuiLipsum(QDialog):
104
102
  self.btnInsert.clicked.connect(self._doInsert)
105
103
  self.btnInsert.setAutoDefault(False)
106
104
 
107
- self.rejected.connect(self.close)
108
-
109
105
  # Assemble
110
106
  self.outerBox = QVBoxLayout()
111
107
  self.outerBox.addLayout(self.innerBox)
@@ -132,7 +128,7 @@ class GuiLipsum(QDialog):
132
128
  cls = GuiLipsum(parent)
133
129
  cls.exec()
134
130
  text = cls.lipsumText
135
- cls.deleteLater()
131
+ cls.softDelete()
136
132
  return text
137
133
 
138
134
  ##