novelWriter 2.2rc1__py3-none-any.whl → 2.2.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. {novelWriter-2.2rc1.dist-info → novelWriter-2.2.1.dist-info}/METADATA +1 -1
  2. {novelWriter-2.2rc1.dist-info → novelWriter-2.2.1.dist-info}/RECORD +113 -111
  3. {novelWriter-2.2rc1.dist-info → novelWriter-2.2.1.dist-info}/WHEEL +1 -1
  4. novelwriter/__init__.py +10 -5
  5. novelwriter/assets/i18n/nw_de_DE.qm +0 -0
  6. novelwriter/assets/i18n/nw_en_US.qm +0 -0
  7. novelwriter/assets/i18n/nw_es_419.qm +0 -0
  8. novelwriter/assets/i18n/nw_fr_FR.qm +0 -0
  9. novelwriter/assets/i18n/nw_it_IT.qm +0 -0
  10. novelwriter/assets/i18n/nw_ja_JP.qm +0 -0
  11. novelwriter/assets/i18n/nw_nb_NO.qm +0 -0
  12. novelwriter/assets/i18n/nw_zh_CN.qm +0 -0
  13. novelwriter/assets/i18n/project_de_DE.json +1 -0
  14. novelwriter/assets/i18n/project_en_US.json +1 -0
  15. novelwriter/assets/i18n/project_es_419.json +11 -0
  16. novelwriter/assets/i18n/project_fr_FR.json +11 -0
  17. novelwriter/assets/i18n/project_it_IT.json +11 -0
  18. novelwriter/assets/i18n/project_ja_JP.json +2 -1
  19. novelwriter/assets/i18n/project_nb_NO.json +1 -0
  20. novelwriter/assets/i18n/project_zh_CN.json +11 -0
  21. novelwriter/assets/icons/typicons_dark/icons.conf +3 -2
  22. novelwriter/assets/icons/typicons_dark/nw_tb-bold-md.svg +4 -0
  23. novelwriter/assets/icons/typicons_dark/nw_tb-bold.svg +3 -1
  24. novelwriter/assets/icons/typicons_dark/nw_tb-italic-md.svg +4 -0
  25. novelwriter/assets/icons/typicons_dark/nw_tb-italic.svg +3 -1
  26. novelwriter/assets/icons/typicons_dark/nw_tb-strike-md.svg +4 -0
  27. novelwriter/assets/icons/typicons_dark/nw_tb-strike.svg +3 -1
  28. novelwriter/assets/icons/typicons_dark/nw_tb-subscript.svg +4 -2
  29. novelwriter/assets/icons/typicons_dark/nw_tb-superscript.svg +4 -2
  30. novelwriter/assets/icons/typicons_dark/nw_tb-underline.svg +4 -2
  31. novelwriter/assets/icons/typicons_light/icons.conf +3 -2
  32. novelwriter/assets/icons/typicons_light/nw_tb-bold-md.svg +4 -0
  33. novelwriter/assets/icons/typicons_light/nw_tb-bold.svg +3 -1
  34. novelwriter/assets/icons/typicons_light/nw_tb-italic-md.svg +4 -0
  35. novelwriter/assets/icons/typicons_light/nw_tb-italic.svg +3 -1
  36. novelwriter/assets/icons/typicons_light/nw_tb-strike-md.svg +4 -0
  37. novelwriter/assets/icons/typicons_light/nw_tb-strike.svg +3 -1
  38. novelwriter/assets/icons/typicons_light/nw_tb-subscript.svg +4 -2
  39. novelwriter/assets/icons/typicons_light/nw_tb-superscript.svg +4 -2
  40. novelwriter/assets/icons/typicons_light/nw_tb-underline.svg +4 -2
  41. novelwriter/assets/manual.pdf +0 -0
  42. novelwriter/assets/sample.zip +0 -0
  43. novelwriter/assets/text/release_notes.htm +50 -7
  44. novelwriter/common.py +16 -29
  45. novelwriter/config.py +3 -3
  46. novelwriter/constants.py +1 -1
  47. novelwriter/core/buildsettings.py +1 -1
  48. novelwriter/core/coretools.py +2 -1
  49. novelwriter/core/docbuild.py +1 -1
  50. novelwriter/core/document.py +1 -1
  51. novelwriter/core/index.py +2 -2
  52. novelwriter/core/item.py +2 -2
  53. novelwriter/core/options.py +3 -3
  54. novelwriter/core/project.py +3 -3
  55. novelwriter/core/projectdata.py +2 -2
  56. novelwriter/core/projectxml.py +1 -1
  57. novelwriter/core/sessions.py +2 -2
  58. novelwriter/core/spellcheck.py +4 -3
  59. novelwriter/core/status.py +3 -3
  60. novelwriter/core/storage.py +1 -1
  61. novelwriter/core/tohtml.py +2 -2
  62. novelwriter/core/tokenizer.py +1 -1
  63. novelwriter/core/tomd.py +2 -2
  64. novelwriter/core/toodt.py +1 -1
  65. novelwriter/core/tree.py +2 -2
  66. novelwriter/dialogs/about.py +30 -31
  67. novelwriter/dialogs/docmerge.py +24 -15
  68. novelwriter/dialogs/docsplit.py +27 -16
  69. novelwriter/dialogs/editlabel.py +19 -7
  70. novelwriter/dialogs/preferences.py +44 -57
  71. novelwriter/dialogs/projdetails.py +29 -36
  72. novelwriter/dialogs/projload.py +32 -36
  73. novelwriter/dialogs/projsettings.py +20 -15
  74. novelwriter/dialogs/quotes.py +32 -25
  75. novelwriter/dialogs/updates.py +4 -14
  76. novelwriter/dialogs/wordlist.py +34 -21
  77. novelwriter/enum.py +5 -4
  78. novelwriter/error.py +1 -1
  79. novelwriter/extensions/circularprogress.py +1 -1
  80. novelwriter/extensions/configlayout.py +3 -15
  81. novelwriter/extensions/{wheeleventfilter.py → eventfilters.py} +15 -5
  82. novelwriter/extensions/novelselector.py +1 -1
  83. novelwriter/extensions/pageddialog.py +1 -1
  84. novelwriter/extensions/pagedsidebar.py +2 -5
  85. novelwriter/extensions/simpleprogress.py +8 -9
  86. novelwriter/extensions/statusled.py +1 -1
  87. novelwriter/extensions/switch.py +4 -4
  88. novelwriter/extensions/switchbox.py +1 -6
  89. novelwriter/gui/doceditor.py +72 -64
  90. novelwriter/gui/dochighlight.py +3 -2
  91. novelwriter/gui/docviewer.py +22 -47
  92. novelwriter/gui/docviewerpanel.py +68 -23
  93. novelwriter/gui/editordocument.py +3 -3
  94. novelwriter/gui/itemdetails.py +2 -2
  95. novelwriter/gui/mainmenu.py +35 -30
  96. novelwriter/gui/noveltree.py +44 -53
  97. novelwriter/gui/outline.py +2 -1
  98. novelwriter/gui/projtree.py +5 -6
  99. novelwriter/gui/sidebar.py +6 -4
  100. novelwriter/gui/statusbar.py +47 -4
  101. novelwriter/gui/theme.py +5 -6
  102. novelwriter/guimain.py +139 -189
  103. novelwriter/shared.py +53 -29
  104. novelwriter/tools/dictionaries.py +2 -2
  105. novelwriter/tools/lipsum.py +34 -28
  106. novelwriter/tools/manusbuild.py +3 -4
  107. novelwriter/tools/manuscript.py +19 -26
  108. novelwriter/tools/manussettings.py +2 -4
  109. novelwriter/tools/projwizard.py +3 -3
  110. novelwriter/tools/writingstats.py +17 -4
  111. novelwriter/assets/icons/typicons_dark/nw_tb-markdown.svg +0 -8
  112. novelwriter/assets/icons/typicons_dark/nw_tb-shortcode.svg +0 -8
  113. novelwriter/assets/icons/typicons_light/nw_tb-markdown.svg +0 -8
  114. novelwriter/assets/icons/typicons_light/nw_tb-shortcode.svg +0 -8
  115. {novelWriter-2.2rc1.dist-info → novelWriter-2.2.1.dist-info}/LICENSE.md +0 -0
  116. {novelWriter-2.2rc1.dist-info → novelWriter-2.2.1.dist-info}/entry_points.txt +0 -0
  117. {novelWriter-2.2rc1.dist-info → novelWriter-2.2.1.dist-info}/top_level.txt +0 -0
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–2023, Veronica Berglyd Olsen
9
+ Copyright 2018–2024, Veronica Berglyd Olsen
10
10
 
11
11
  This program is free software: you can redistribute it and/or modify
12
12
  it under the terms of the GNU General Public License as published by
@@ -56,7 +56,6 @@ from novelwriter.dialogs.wordlist import GuiWordList
56
56
  from novelwriter.dialogs.preferences import GuiPreferences
57
57
  from novelwriter.dialogs.projdetails import GuiProjectDetails
58
58
  from novelwriter.dialogs.projsettings import GuiProjectSettings
59
- from novelwriter.tools.lipsum import GuiLipsum
60
59
  from novelwriter.tools.manuscript import GuiManuscript
61
60
  from novelwriter.tools.projwizard import GuiProjectWizard
62
61
  from novelwriter.tools.dictionaries import GuiDictionaries
@@ -66,7 +65,7 @@ from novelwriter.core.coretools import ProjectBuilder
66
65
  from novelwriter.enum import (
67
66
  nwDocAction, nwDocInsert, nwDocMode, nwItemType, nwItemClass, nwWidget, nwView
68
67
  )
69
- from novelwriter.common import getGuiItem, hexToInt
68
+ from novelwriter.common import hexToInt
70
69
  from novelwriter.constants import nwFiles
71
70
 
72
71
  logger = logging.getLogger(__name__)
@@ -338,19 +337,9 @@ class GuiMain(QMainWindow):
338
337
  self.asProjTimer.start()
339
338
  self.asDocTimer.start()
340
339
  self.mainStatus.clearStatus()
341
-
342
- # Handle Windows Mode
343
340
  self.showNormal()
344
341
 
345
342
  logger.debug("Ready: GUI")
346
-
347
- if __hexversion__[-2] == "a" and not CONFIG.isDebug:
348
- SHARED.warn(
349
- "You are running an untested development version of novelWriter. "
350
- "Please be careful when you are working on live projects "
351
- "and make sure you take regular backups."
352
- )
353
-
354
343
  logger.info("novelWriter is ready ...")
355
344
  self.mainStatus.setStatusMessage(self.tr("novelWriter is ready ..."))
356
345
 
@@ -617,7 +606,7 @@ class GuiMain(QMainWindow):
617
606
  if self.docEditor.loadText(tHandle, tLine):
618
607
  SHARED.project.data.setLastHandle(tHandle, "editor")
619
608
  self.projView.setSelectedHandle(tHandle, doScroll=doScroll)
620
- self.novelView.setActiveHandle(tHandle)
609
+ self.novelView.setActiveHandle(tHandle, doScroll=doScroll)
621
610
  if changeFocus:
622
611
  self.docEditor.setFocus()
623
612
  else:
@@ -876,213 +865,113 @@ class GuiMain(QMainWindow):
876
865
 
877
866
  return None
878
867
 
868
+ @pyqtSlot()
879
869
  def showPreferencesDialog(self) -> None:
880
870
  """Open the preferences dialog."""
881
- dlgConf = GuiPreferences(self)
882
- dlgConf.exec_()
883
-
884
- if dlgConf.result() == QDialog.Accepted:
885
- logger.debug("Applying new preferences")
886
- self.initMain()
887
- self.saveDocument()
888
-
889
- if dlgConf.needsRestart:
890
- SHARED.info(self.tr(
891
- "Some changes will not be applied until novelWriter has been restarted."
892
- ))
893
-
894
- if dlgConf.refreshTree:
895
- self.projView.populateTree()
896
-
897
- if dlgConf.updateTheme:
898
- # We are doing this manually instead of connecting to
899
- # qApp.paletteChanged since the processing order matters
900
- SHARED.theme.loadTheme()
901
- self.docEditor.updateTheme()
902
- self.docViewer.updateTheme()
903
- self.docViewerPanel.updateTheme()
904
- self.sideBar.updateTheme()
905
- self.projView.updateTheme()
906
- self.novelView.updateTheme()
907
- self.outlineView.updateTheme()
908
- self.itemDetails.updateTheme()
909
- self.mainStatus.updateTheme()
910
-
911
- if dlgConf.updateSyntax:
912
- SHARED.theme.loadSyntax()
913
- self.docEditor.updateSyntaxColours()
914
-
915
- self.docEditor.initEditor()
916
- self.docViewer.initViewer()
917
- self.projView.initSettings()
918
- self.novelView.initSettings()
919
- self.outlineView.initSettings()
920
-
921
- self._updateStatusWordCount()
922
-
871
+ dialog = GuiPreferences(self)
872
+ dialog.newPreferencesReady.connect(self._processConfigChanges)
873
+ dialog.exec_()
923
874
  return
924
875
 
876
+ @pyqtSlot()
925
877
  @pyqtSlot(int)
926
- def showProjectSettingsDialog(self, focusTab: int = GuiProjectSettings.TAB_MAIN) -> bool:
878
+ def showProjectSettingsDialog(self, focusTab: int = GuiProjectSettings.TAB_MAIN) -> None:
927
879
  """Open the project settings dialog."""
928
- if not SHARED.hasProject:
929
- logger.error("No project open")
930
- return False
931
-
932
- dlgProj = GuiProjectSettings(self, focusTab=focusTab)
933
- dlgProj.exec_()
934
-
935
- if dlgProj.result() == QDialog.Accepted:
936
- logger.debug("Applying new project settings")
937
- SHARED.updateSpellCheckLanguage()
938
- self.itemDetails.refreshDetails()
939
- self._updateWindowTitle(SHARED.project.data.name)
940
-
941
- return True
880
+ if SHARED.hasProject:
881
+ dialog = GuiProjectSettings(self, focusTab=focusTab)
882
+ dialog.newProjectSettingsReady.connect(self._processProjectSettingsChanges)
883
+ dialog.exec_()
884
+ return
942
885
 
943
- def showProjectDetailsDialog(self) -> bool:
886
+ @pyqtSlot()
887
+ def showProjectDetailsDialog(self) -> None:
944
888
  """Open the project details dialog."""
945
- if not SHARED.hasProject:
946
- logger.error("No project open")
947
- return False
948
-
949
- dlgDetails = getGuiItem("GuiProjectDetails")
950
- if dlgDetails is None:
951
- dlgDetails = GuiProjectDetails(self)
952
- assert isinstance(dlgDetails, GuiProjectDetails)
953
-
954
- dlgDetails.setModal(True)
955
- dlgDetails.show()
956
- dlgDetails.raise_()
957
- dlgDetails.updateValues()
958
-
959
- return True
889
+ if SHARED.hasProject:
890
+ dialog = GuiProjectDetails(self)
891
+ dialog.setModal(True)
892
+ dialog.show()
893
+ dialog.raise_()
894
+ qApp.processEvents()
895
+ dialog.updateValues()
896
+ return
960
897
 
961
898
  @pyqtSlot()
962
- def showBuildManuscriptDialog(self) -> bool:
899
+ def showBuildManuscriptDialog(self) -> None:
963
900
  """Open the build manuscript dialog."""
964
- if not SHARED.hasProject:
965
- logger.error("No project open")
966
- return False
967
-
968
- dlgBuild = getGuiItem("GuiManuscript")
969
- if dlgBuild is None:
970
- dlgBuild = GuiManuscript(self)
971
- assert isinstance(dlgBuild, GuiManuscript)
972
-
973
- dlgBuild.setModal(False)
974
- dlgBuild.show()
975
- dlgBuild.raise_()
976
- qApp.processEvents()
977
-
978
- dlgBuild.loadContent()
979
-
980
- return True
981
-
982
- def showLoremIpsumDialog(self) -> bool:
983
- """Open the insert lorem ipsum text dialog."""
984
- if not SHARED.hasProject:
985
- logger.error("No project open")
986
- return False
987
-
988
- dlgLipsum = getGuiItem("GuiLipsum")
989
- if dlgLipsum is None:
990
- dlgLipsum = GuiLipsum(self)
991
- assert isinstance(dlgLipsum, GuiLipsum)
992
-
993
- dlgLipsum.setModal(False)
994
- dlgLipsum.show()
995
- dlgLipsum.raise_()
996
- qApp.processEvents()
997
-
998
- return True
901
+ if SHARED.hasProject:
902
+ if (dialog := SHARED.findTopLevelWidget(GuiManuscript)) is None:
903
+ dialog = GuiManuscript(self)
904
+ dialog.setModal(False)
905
+ dialog.show()
906
+ dialog.raise_()
907
+ qApp.processEvents()
908
+ dialog.loadContent()
909
+ return
999
910
 
1000
- def showProjectWordListDialog(self) -> bool:
911
+ @pyqtSlot()
912
+ def showProjectWordListDialog(self) -> None:
1001
913
  """Open the project word list dialog."""
1002
- if not SHARED.hasProject:
1003
- logger.error("No project open")
1004
- return False
1005
-
1006
- dlgWords = GuiWordList(self)
1007
- dlgWords.exec_()
1008
-
1009
- if dlgWords.result() == QDialog.Accepted:
1010
- logger.debug("Reloading word list")
1011
- SHARED.updateSpellCheckLanguage(reload=True)
1012
- self.docEditor.spellCheckDocument()
1013
-
1014
- return True
914
+ if SHARED.hasProject:
915
+ dialog = GuiWordList(self)
916
+ dialog.newWordListReady.connect(self._processWordListChanges)
917
+ dialog.exec_()
918
+ return
1015
919
 
1016
- def showWritingStatsDialog(self) -> bool:
920
+ @pyqtSlot()
921
+ def showWritingStatsDialog(self) -> None:
1017
922
  """Open the session stats dialog."""
1018
- if not SHARED.hasProject:
1019
- logger.error("No project open")
1020
- return False
1021
-
1022
- dlgStats = getGuiItem("GuiWritingStats")
1023
- if dlgStats is None:
1024
- dlgStats = GuiWritingStats(self)
1025
- assert isinstance(dlgStats, GuiWritingStats)
1026
-
1027
- dlgStats.setModal(False)
1028
- dlgStats.show()
1029
- dlgStats.raise_()
1030
- qApp.processEvents()
1031
- dlgStats.populateGUI()
1032
-
1033
- return True
1034
-
1035
- def showAboutNWDialog(self, showNotes: bool = False) -> bool:
1036
- """Show the about dialog for novelWriter."""
1037
- dlgAbout = getGuiItem("GuiAbout")
1038
- if dlgAbout is None:
1039
- dlgAbout = GuiAbout(self)
1040
- assert isinstance(dlgAbout, GuiAbout)
923
+ if SHARED.hasProject:
924
+ if (dialog := SHARED.findTopLevelWidget(GuiWritingStats)) is None:
925
+ dialog = GuiWritingStats(self)
926
+ dialog.setModal(False)
927
+ dialog.show()
928
+ dialog.raise_()
929
+ qApp.processEvents()
930
+ dialog.populateGUI()
931
+ return
1041
932
 
1042
- dlgAbout.setModal(True)
1043
- dlgAbout.show()
1044
- dlgAbout.raise_()
933
+ @pyqtSlot()
934
+ def showAboutNWDialog(self, showNotes: bool = False) -> None:
935
+ """Show the novelWriter about dialog."""
936
+ dialog = GuiAbout(self)
937
+ dialog.setModal(True)
938
+ dialog.show()
939
+ dialog.raise_()
1045
940
  qApp.processEvents()
1046
- dlgAbout.populateGUI()
1047
-
941
+ dialog.populateGUI()
1048
942
  if showNotes:
1049
- dlgAbout.showReleaseNotes()
1050
-
1051
- return True
943
+ dialog.showReleaseNotes()
944
+ return
1052
945
 
946
+ @pyqtSlot()
1053
947
  def showAboutQtDialog(self) -> None:
1054
- """Show the about dialog for Qt."""
948
+ """Show the Qt about dialog."""
1055
949
  msgBox = QMessageBox(self)
1056
950
  msgBox.aboutQt(self, "About Qt")
1057
951
  return
1058
952
 
953
+ @pyqtSlot()
1059
954
  def showUpdatesDialog(self) -> None:
1060
955
  """Show the check for updates dialog."""
1061
- dlgUpdate = getGuiItem("GuiUpdates")
1062
- if dlgUpdate is None:
1063
- dlgUpdate = GuiUpdates(self)
1064
- assert isinstance(dlgUpdate, GuiUpdates)
1065
-
1066
- dlgUpdate.setModal(True)
1067
- dlgUpdate.show()
1068
- dlgUpdate.raise_()
956
+ dialog = GuiUpdates(self)
957
+ dialog.setModal(True)
958
+ dialog.show()
959
+ dialog.raise_()
1069
960
  qApp.processEvents()
1070
- dlgUpdate.checkLatest()
1071
-
961
+ dialog.checkLatest()
1072
962
  return
1073
963
 
1074
964
  @pyqtSlot()
1075
965
  def showDictionariesDialog(self) -> None:
1076
966
  """Show the download dictionaries dialog."""
1077
- dlgDicts = GuiDictionaries(self)
1078
- dlgDicts.setModal(True)
1079
- dlgDicts.show()
1080
- dlgDicts.raise_()
967
+ dialog = GuiDictionaries(self)
968
+ dialog.setModal(True)
969
+ dialog.show()
970
+ dialog.raise_()
1081
971
  qApp.processEvents()
1082
- if not dlgDicts.initDialog():
1083
- dlgDicts.close()
972
+ if not dialog.initDialog():
973
+ dialog.close()
1084
974
  SHARED.error(self.tr("Could not initialise the dialog."))
1085
-
1086
975
  return
1087
976
 
1088
977
  def reportConfErr(self) -> bool:
@@ -1248,6 +1137,65 @@ class GuiMain(QMainWindow):
1248
1137
  # Private Slots
1249
1138
  ##
1250
1139
 
1140
+ @pyqtSlot(bool, bool, bool, bool)
1141
+ def _processConfigChanges(self, restart: bool, tree: bool, theme: bool, syntax: bool) -> None:
1142
+ """Refresh GUI based on flags from the Preferences dialog."""
1143
+ logger.debug("Applying new preferences")
1144
+ self.initMain()
1145
+ self.saveDocument()
1146
+
1147
+ if restart:
1148
+ SHARED.info(self.tr(
1149
+ "Some changes will not be applied until novelWriter has been restarted."
1150
+ ))
1151
+
1152
+ if tree:
1153
+ self.projView.populateTree()
1154
+
1155
+ if theme:
1156
+ # We are doing this manually instead of connecting to
1157
+ # qApp.paletteChanged since the processing order matters
1158
+ SHARED.theme.loadTheme()
1159
+ self.docEditor.updateTheme()
1160
+ self.docViewer.updateTheme()
1161
+ self.docViewerPanel.updateTheme()
1162
+ self.sideBar.updateTheme()
1163
+ self.projView.updateTheme()
1164
+ self.novelView.updateTheme()
1165
+ self.outlineView.updateTheme()
1166
+ self.itemDetails.updateTheme()
1167
+ self.mainStatus.updateTheme()
1168
+
1169
+ if syntax:
1170
+ SHARED.theme.loadSyntax()
1171
+ self.docEditor.updateSyntaxColours()
1172
+
1173
+ self.docEditor.initEditor()
1174
+ self.docViewer.initViewer()
1175
+ self.projView.initSettings()
1176
+ self.novelView.initSettings()
1177
+ self.outlineView.initSettings()
1178
+ self._updateStatusWordCount()
1179
+
1180
+ return
1181
+
1182
+ @pyqtSlot()
1183
+ def _processProjectSettingsChanges(self) -> None:
1184
+ """Refresh data dependent on project settings."""
1185
+ logger.debug("Applying new project settings")
1186
+ SHARED.updateSpellCheckLanguage()
1187
+ self.itemDetails.refreshDetails()
1188
+ self._updateWindowTitle(SHARED.project.data.name)
1189
+ return
1190
+
1191
+ @pyqtSlot()
1192
+ def _processWordListChanges(self) -> None:
1193
+ """Reload project word list."""
1194
+ logger.debug("Reloading word list")
1195
+ SHARED.updateSpellCheckLanguage(reload=True)
1196
+ self.docEditor.spellCheckDocument()
1197
+ return
1198
+
1251
1199
  @pyqtSlot(str, nwDocMode)
1252
1200
  def _followTag(self, tag: str, mode: nwDocMode) -> None:
1253
1201
  """Follow a tag after user interaction with a link."""
@@ -1331,6 +1279,8 @@ class GuiMain(QMainWindow):
1331
1279
  self.mainStatus.setUserIdle(editIdle or userIdle)
1332
1280
  SHARED.updateIdleTime(currTime, editIdle or userIdle)
1333
1281
  self.mainStatus.updateTime(idleTime=SHARED.projectIdleTime)
1282
+ if CONFIG.memInfo and int(currTime) % 5 == 0: # pragma: no cover
1283
+ self.mainStatus.memInfo()
1334
1284
  return
1335
1285
 
1336
1286
  @pyqtSlot()
@@ -1474,8 +1424,8 @@ class GuiMain(QMainWindow):
1474
1424
  self.addAction(self.mainMenu.aReplaceNext)
1475
1425
 
1476
1426
  # Format
1477
- self.addAction(self.mainMenu.aFmtEmph)
1478
- self.addAction(self.mainMenu.aFmtStrong)
1427
+ self.addAction(self.mainMenu.aFmtItalic)
1428
+ self.addAction(self.mainMenu.aFmtBold)
1479
1429
  self.addAction(self.mainMenu.aFmtStrike)
1480
1430
  self.addAction(self.mainMenu.aFmtDQuote)
1481
1431
  self.addAction(self.mainMenu.aFmtSQuote)
novelwriter/shared.py CHANGED
@@ -3,10 +3,11 @@ novelWriter – Shared Data Class
3
3
  ===============================
4
4
 
5
5
  File History:
6
- Created: 2023-08-10 [2.1rc1]
6
+ Created: 2023-08-10 [2.1rc1] SharedData
7
+ Created: 2023-08-14 [2.1rc1] _GuiAlert
7
8
 
8
9
  This file is a part of novelWriter
9
- Copyright 2018–2023, Veronica Berglyd Olsen
10
+ Copyright 2018–2024, Veronica Berglyd Olsen
10
11
 
11
12
  This program is free software: you can redistribute it and/or modify
12
13
  it under the terms of the GNU General Public License as published by
@@ -26,7 +27,7 @@ from __future__ import annotations
26
27
  import logging
27
28
 
28
29
  from time import time
29
- from typing import TYPE_CHECKING
30
+ from typing import TYPE_CHECKING, TypeVar
30
31
  from pathlib import Path
31
32
 
32
33
  from PyQt5.QtCore import QObject, QRunnable, QThreadPool, pyqtSignal
@@ -41,11 +42,13 @@ if TYPE_CHECKING: # pragma: no cover
41
42
 
42
43
  logger = logging.getLogger(__name__)
43
44
 
45
+ NWWidget = TypeVar("NWWidget", bound=QWidget)
46
+
44
47
 
45
48
  class SharedData(QObject):
46
49
 
47
50
  __slots__ = (
48
- "_gui", "_theme", "_project", "_spelling", "_lockedBy", "_alert",
51
+ "_gui", "_theme", "_project", "_spelling", "_lockedBy", "_lastAlert",
49
52
  "_idleTime", "_idleRefTime",
50
53
  )
51
54
 
@@ -68,7 +71,7 @@ class SharedData(QObject):
68
71
 
69
72
  # Settings
70
73
  self._lockedBy = None
71
- self._alert = None
74
+ self._lastAlert = ""
72
75
  self._idleTime = 0.0
73
76
  self._idleRefTime = time()
74
77
 
@@ -122,9 +125,9 @@ class SharedData(QObject):
122
125
  return self._idleTime
123
126
 
124
127
  @property
125
- def alert(self) -> _GuiAlert | None:
126
- """Return a pointer to the last alert box."""
127
- return self._alert
128
+ def lastAlert(self) -> str:
129
+ """Return the last alert message."""
130
+ return self._lastAlert
128
131
 
129
132
  ##
130
133
  # Methods
@@ -214,6 +217,13 @@ class SharedData(QObject):
214
217
  QThreadPool.globalInstance().start(runnable, priority=priority)
215
218
  return
216
219
 
220
+ def findTopLevelWidget(self, kind: type[NWWidget]) -> NWWidget | None:
221
+ """Find a top level widget."""
222
+ for widget in self.mainGui.children():
223
+ if isinstance(widget, kind):
224
+ return widget
225
+ return None
226
+
217
227
  ##
218
228
  # Signal Proxy
219
229
  ##
@@ -238,44 +248,53 @@ class SharedData(QObject):
238
248
 
239
249
  def info(self, text: str, info: str = "", details: str = "", log: bool = True) -> None:
240
250
  """Open an information alert box."""
241
- self._alert = _GuiAlert(self.mainGui, self.theme)
242
- self._alert.setMessage(text, info, details)
243
- self._alert.setAlertType(_GuiAlert.INFO, False)
251
+ alert = _GuiAlert(self.mainGui, self.theme)
252
+ alert.setMessage(text, info, details)
253
+ alert.setAlertType(_GuiAlert.INFO, False)
254
+ self._lastAlert = alert.logMessage
244
255
  if log:
245
- logger.info(self._alert.logMessage, stacklevel=2)
246
- self._alert.exec_()
256
+ logger.info(self._lastAlert, stacklevel=2)
257
+ alert.exec_()
258
+ alert.deleteLater()
247
259
  return
248
260
 
249
261
  def warn(self, text: str, info: str = "", details: str = "", log: bool = True) -> None:
250
262
  """Open a warning alert box."""
251
- self._alert = _GuiAlert(self.mainGui, self.theme)
252
- self._alert.setMessage(text, info, details)
253
- self._alert.setAlertType(_GuiAlert.WARN, False)
263
+ alert = _GuiAlert(self.mainGui, self.theme)
264
+ alert.setMessage(text, info, details)
265
+ alert.setAlertType(_GuiAlert.WARN, False)
266
+ self._lastAlert = alert.logMessage
254
267
  if log:
255
- logger.warning(self._alert.logMessage, stacklevel=2)
256
- self._alert.exec_()
268
+ logger.warning(self._lastAlert, stacklevel=2)
269
+ alert.exec_()
270
+ alert.deleteLater()
257
271
  return
258
272
 
259
273
  def error(self, text: str, info: str = "", details: str = "", log: bool = True,
260
274
  exc: Exception | None = None) -> None:
261
275
  """Open an error alert box."""
262
- self._alert = _GuiAlert(self.mainGui, self.theme)
263
- self._alert.setMessage(text, info, details)
264
- self._alert.setAlertType(_GuiAlert.ERROR, False)
276
+ alert = _GuiAlert(self.mainGui, self.theme)
277
+ alert.setMessage(text, info, details)
278
+ alert.setAlertType(_GuiAlert.ERROR, False)
265
279
  if exc:
266
- self._alert.setException(exc)
280
+ alert.setException(exc)
281
+ self._lastAlert = alert.logMessage
267
282
  if log:
268
- logger.error(self._alert.logMessage, stacklevel=2)
269
- self._alert.exec_()
283
+ logger.error(self._lastAlert, stacklevel=2)
284
+ alert.exec_()
285
+ alert.deleteLater()
270
286
  return
271
287
 
272
288
  def question(self, text: str, info: str = "", details: str = "", warn: bool = False) -> bool:
273
289
  """Open a question box."""
274
- self._alert = _GuiAlert(self.mainGui, self.theme)
275
- self._alert.setMessage(text, info, details)
276
- self._alert.setAlertType(_GuiAlert.WARN if warn else _GuiAlert.ASK, True)
277
- self._alert.exec_()
278
- return self._alert.result() == QMessageBox.Yes
290
+ alert = _GuiAlert(self.mainGui, self.theme)
291
+ alert.setMessage(text, info, details)
292
+ alert.setAlertType(_GuiAlert.WARN if warn else _GuiAlert.ASK, True)
293
+ self._lastAlert = alert.logMessage
294
+ alert.exec_()
295
+ isYes = alert.result() == QMessageBox.StandardButton.Yes
296
+ alert.deleteLater()
297
+ return isYes
279
298
 
280
299
  ##
281
300
  # Internal Functions
@@ -312,6 +331,11 @@ class _GuiAlert(QMessageBox):
312
331
  super().__init__(parent=parent)
313
332
  self._theme = theme
314
333
  self._message = ""
334
+ logger.debug("Ready: _GuiAlert")
335
+ return
336
+
337
+ def __del__(self) -> None: # pragma: no cover
338
+ logger.debug("Delete: _GuiAlert")
315
339
  return
316
340
 
317
341
  @property
@@ -3,10 +3,10 @@ novelWriter – GUI Dictionary Downloader
3
3
  =======================================
4
4
 
5
5
  File History:
6
- Created: 2023-11-19 [2.2rc1]
6
+ Created: 2023-11-19 [2.2rc1] GuiDictionaries
7
7
 
8
8
  This file is a part of novelWriter
9
- Copyright 2018–2023, Veronica Berglyd Olsen
9
+ Copyright 2018–2024, Veronica Berglyd Olsen
10
10
 
11
11
  This program is free software: you can redistribute it and/or modify
12
12
  it under the terms of the GNU General Public License as published by