novelWriter 2.2rc1__py3-none-any.whl → 2.3b1__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 (153) hide show
  1. {novelWriter-2.2rc1.dist-info → novelWriter-2.3b1.dist-info}/METADATA +1 -1
  2. {novelWriter-2.2rc1.dist-info → novelWriter-2.3b1.dist-info}/RECORD +141 -129
  3. {novelWriter-2.2rc1.dist-info → novelWriter-2.3b1.dist-info}/WHEEL +1 -1
  4. novelwriter/__init__.py +11 -6
  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 +9 -2
  22. novelwriter/assets/icons/typicons_dark/mixed_import.svg +5 -0
  23. novelwriter/assets/icons/typicons_dark/nw_tb-bold-md.svg +4 -0
  24. novelwriter/assets/icons/typicons_dark/nw_tb-bold.svg +3 -1
  25. novelwriter/assets/icons/typicons_dark/nw_tb-italic-md.svg +4 -0
  26. novelwriter/assets/icons/typicons_dark/nw_tb-italic.svg +3 -1
  27. novelwriter/assets/icons/typicons_dark/nw_tb-strike-md.svg +4 -0
  28. novelwriter/assets/icons/typicons_dark/nw_tb-strike.svg +3 -1
  29. novelwriter/assets/icons/typicons_dark/nw_tb-subscript.svg +4 -2
  30. novelwriter/assets/icons/typicons_dark/nw_tb-superscript.svg +4 -2
  31. novelwriter/assets/icons/typicons_dark/nw_tb-underline.svg +4 -2
  32. novelwriter/assets/icons/typicons_dark/typ_document-add-col.svg +8 -0
  33. novelwriter/assets/icons/typicons_dark/typ_document-add.svg +4 -0
  34. novelwriter/assets/icons/typicons_dark/typ_document.svg +4 -0
  35. novelwriter/assets/icons/typicons_dark/typ_th-dot-more.svg +4 -0
  36. novelwriter/assets/icons/typicons_light/icons.conf +9 -2
  37. novelwriter/assets/icons/typicons_light/mixed_import.svg +5 -0
  38. novelwriter/assets/icons/typicons_light/nw_tb-bold-md.svg +4 -0
  39. novelwriter/assets/icons/typicons_light/nw_tb-bold.svg +3 -1
  40. novelwriter/assets/icons/typicons_light/nw_tb-italic-md.svg +4 -0
  41. novelwriter/assets/icons/typicons_light/nw_tb-italic.svg +3 -1
  42. novelwriter/assets/icons/typicons_light/nw_tb-strike-md.svg +4 -0
  43. novelwriter/assets/icons/typicons_light/nw_tb-strike.svg +3 -1
  44. novelwriter/assets/icons/typicons_light/nw_tb-subscript.svg +4 -2
  45. novelwriter/assets/icons/typicons_light/nw_tb-superscript.svg +4 -2
  46. novelwriter/assets/icons/typicons_light/nw_tb-underline.svg +4 -2
  47. novelwriter/assets/icons/typicons_light/typ_document-add-col.svg +8 -0
  48. novelwriter/assets/icons/typicons_light/typ_document-add.svg +4 -0
  49. novelwriter/assets/icons/typicons_light/typ_document.svg +4 -0
  50. novelwriter/assets/icons/typicons_light/typ_th-dot-more.svg +4 -0
  51. novelwriter/assets/images/novelwriter-text-dark.svg +4 -0
  52. novelwriter/assets/images/novelwriter-text-light.svg +4 -0
  53. novelwriter/assets/images/welcome-dark.jpg +0 -0
  54. novelwriter/assets/images/welcome-light.jpg +0 -0
  55. novelwriter/assets/manual.pdf +0 -0
  56. novelwriter/assets/sample.zip +0 -0
  57. novelwriter/assets/syntax/default_dark.conf +1 -0
  58. novelwriter/assets/syntax/default_light.conf +1 -0
  59. novelwriter/assets/syntax/grey_dark.conf +1 -0
  60. novelwriter/assets/syntax/grey_light.conf +1 -0
  61. novelwriter/assets/syntax/light_owl.conf +1 -0
  62. novelwriter/assets/syntax/night_owl.conf +1 -0
  63. novelwriter/assets/syntax/solarized_dark.conf +1 -0
  64. novelwriter/assets/syntax/solarized_light.conf +1 -0
  65. novelwriter/assets/syntax/tomorrow.conf +1 -0
  66. novelwriter/assets/syntax/tomorrow_night.conf +1 -0
  67. novelwriter/assets/syntax/tomorrow_night_blue.conf +1 -0
  68. novelwriter/assets/syntax/tomorrow_night_bright.conf +1 -0
  69. novelwriter/assets/syntax/tomorrow_night_eighties.conf +1 -0
  70. novelwriter/assets/text/credits_en.htm +4 -2
  71. novelwriter/assets/themes/default_dark.conf +2 -2
  72. novelwriter/assets/themes/default_light.conf +2 -2
  73. novelwriter/common.py +64 -66
  74. novelwriter/config.py +39 -44
  75. novelwriter/constants.py +39 -17
  76. novelwriter/core/buildsettings.py +8 -8
  77. novelwriter/core/coretools.py +194 -155
  78. novelwriter/core/docbuild.py +7 -4
  79. novelwriter/core/document.py +7 -7
  80. novelwriter/core/index.py +90 -57
  81. novelwriter/core/item.py +23 -5
  82. novelwriter/core/options.py +11 -10
  83. novelwriter/core/project.py +72 -47
  84. novelwriter/core/projectdata.py +3 -16
  85. novelwriter/core/projectxml.py +14 -42
  86. novelwriter/core/sessions.py +4 -3
  87. novelwriter/core/spellcheck.py +6 -4
  88. novelwriter/core/status.py +5 -4
  89. novelwriter/core/storage.py +179 -141
  90. novelwriter/core/tohtml.py +6 -4
  91. novelwriter/core/tokenizer.py +74 -46
  92. novelwriter/core/tomd.py +2 -2
  93. novelwriter/core/toodt.py +41 -31
  94. novelwriter/core/tree.py +5 -4
  95. novelwriter/dialogs/about.py +88 -179
  96. novelwriter/dialogs/docmerge.py +30 -20
  97. novelwriter/dialogs/docsplit.py +33 -22
  98. novelwriter/dialogs/editlabel.py +20 -8
  99. novelwriter/dialogs/preferences.py +562 -725
  100. novelwriter/dialogs/{projsettings.py → projectsettings.py} +301 -270
  101. novelwriter/dialogs/quotes.py +47 -36
  102. novelwriter/dialogs/wordlist.py +128 -59
  103. novelwriter/enum.py +25 -22
  104. novelwriter/error.py +2 -2
  105. novelwriter/extensions/circularprogress.py +12 -12
  106. novelwriter/extensions/configlayout.py +185 -146
  107. novelwriter/extensions/{wheeleventfilter.py → eventfilters.py} +15 -5
  108. novelwriter/extensions/modified.py +81 -0
  109. novelwriter/extensions/novelselector.py +27 -13
  110. novelwriter/extensions/pagedsidebar.py +15 -20
  111. novelwriter/extensions/simpleprogress.py +8 -9
  112. novelwriter/extensions/statusled.py +9 -9
  113. novelwriter/extensions/switch.py +32 -64
  114. novelwriter/extensions/switchbox.py +2 -7
  115. novelwriter/extensions/versioninfo.py +153 -0
  116. novelwriter/gui/doceditor.py +250 -214
  117. novelwriter/gui/dochighlight.py +66 -94
  118. novelwriter/gui/docviewer.py +71 -98
  119. novelwriter/gui/docviewerpanel.py +140 -47
  120. novelwriter/gui/editordocument.py +3 -3
  121. novelwriter/gui/itemdetails.py +9 -9
  122. novelwriter/gui/mainmenu.py +47 -46
  123. novelwriter/gui/noveltree.py +53 -61
  124. novelwriter/gui/outline.py +100 -76
  125. novelwriter/gui/projtree.py +193 -67
  126. novelwriter/gui/sidebar.py +9 -8
  127. novelwriter/gui/statusbar.py +49 -7
  128. novelwriter/gui/theme.py +65 -74
  129. novelwriter/guimain.py +173 -330
  130. novelwriter/shared.py +68 -30
  131. novelwriter/tools/dictionaries.py +7 -8
  132. novelwriter/tools/lipsum.py +34 -28
  133. novelwriter/tools/manusbuild.py +3 -4
  134. novelwriter/tools/manuscript.py +25 -32
  135. novelwriter/tools/manussettings.py +194 -225
  136. novelwriter/tools/noveldetails.py +525 -0
  137. novelwriter/tools/welcome.py +802 -0
  138. novelwriter/tools/writingstats.py +26 -13
  139. novelwriter/assets/icons/typicons_dark/nw_tb-markdown.svg +0 -8
  140. novelwriter/assets/icons/typicons_dark/nw_tb-shortcode.svg +0 -8
  141. novelwriter/assets/icons/typicons_light/nw_tb-markdown.svg +0 -8
  142. novelwriter/assets/icons/typicons_light/nw_tb-shortcode.svg +0 -8
  143. novelwriter/assets/images/wizard-back.jpg +0 -0
  144. novelwriter/assets/text/gplv3_en.htm +0 -641
  145. novelwriter/assets/text/release_notes.htm +0 -17
  146. novelwriter/dialogs/projdetails.py +0 -525
  147. novelwriter/dialogs/projload.py +0 -298
  148. novelwriter/dialogs/updates.py +0 -182
  149. novelwriter/extensions/pageddialog.py +0 -130
  150. novelwriter/tools/projwizard.py +0 -478
  151. {novelWriter-2.2rc1.dist-info → novelWriter-2.3b1.dist-info}/LICENSE.md +0 -0
  152. {novelWriter-2.2rc1.dist-info → novelWriter-2.3b1.dist-info}/entry_points.txt +0 -0
  153. {novelWriter-2.2rc1.dist-info → novelWriter-2.3b1.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
@@ -30,14 +30,15 @@ from time import time
30
30
  from pathlib import Path
31
31
  from datetime import datetime
32
32
 
33
- from PyQt5.QtCore import Qt, QTimer, pyqtSlot
34
33
  from PyQt5.QtGui import QCloseEvent, QCursor, QIcon
34
+ from PyQt5.QtCore import Qt, QTimer, pyqtSlot
35
35
  from PyQt5.QtWidgets import (
36
- QDialog, QFileDialog, QHBoxLayout, QMainWindow, QMessageBox, QShortcut,
37
- QSplitter, QStackedWidget, QVBoxLayout, QWidget, qApp
36
+ QFileDialog, QHBoxLayout, QMainWindow, QMessageBox, QShortcut, QSplitter,
37
+ QStackedWidget, QVBoxLayout, QWidget, qApp
38
38
  )
39
39
 
40
- from novelwriter import CONFIG, SHARED, __hexversion__
40
+ from novelwriter import CONFIG, SHARED, __hexversion__, __version__
41
+ from novelwriter.constants import nwConst
41
42
  from novelwriter.gui.theme import GuiTheme
42
43
  from novelwriter.gui.sidebar import GuiSideBar
43
44
  from novelwriter.gui.outline import GuiOutlineView
@@ -50,24 +51,19 @@ from novelwriter.gui.statusbar import GuiMainStatus
50
51
  from novelwriter.gui.itemdetails import GuiItemDetails
51
52
  from novelwriter.gui.docviewerpanel import GuiDocViewerPanel
52
53
  from novelwriter.dialogs.about import GuiAbout
53
- from novelwriter.dialogs.updates import GuiUpdates
54
- from novelwriter.dialogs.projload import GuiProjectLoad
55
54
  from novelwriter.dialogs.wordlist import GuiWordList
56
55
  from novelwriter.dialogs.preferences import GuiPreferences
57
- from novelwriter.dialogs.projdetails import GuiProjectDetails
58
- from novelwriter.dialogs.projsettings import GuiProjectSettings
59
- from novelwriter.tools.lipsum import GuiLipsum
56
+ from novelwriter.dialogs.projectsettings import GuiProjectSettings
57
+ from novelwriter.tools.welcome import GuiWelcome
60
58
  from novelwriter.tools.manuscript import GuiManuscript
61
- from novelwriter.tools.projwizard import GuiProjectWizard
62
59
  from novelwriter.tools.dictionaries import GuiDictionaries
60
+ from novelwriter.tools.noveldetails import GuiNovelDetails
63
61
  from novelwriter.tools.writingstats import GuiWritingStats
64
- from novelwriter.core.coretools import ProjectBuilder
65
62
 
66
63
  from novelwriter.enum import (
67
- nwDocAction, nwDocInsert, nwDocMode, nwItemType, nwItemClass, nwWidget, nwView
64
+ nwDocAction, nwDocInsert, nwDocMode, nwItemType, nwWidget, nwView
68
65
  )
69
- from novelwriter.common import getGuiItem, hexToInt
70
- from novelwriter.constants import nwFiles
66
+ from novelwriter.common import formatFileFilter, formatVersion, hexToInt
71
67
 
72
68
  logger = logging.getLogger(__name__)
73
69
 
@@ -83,13 +79,6 @@ class GuiMain(QMainWindow):
83
79
  function. Also, the project instance and theme instance are created
84
80
  here. These should be passed around to all other objects who need
85
81
  them and new instances of them should generally not be created.
86
-
87
- * All other GUI classes that depend on any components from the
88
- main GUI should be passed a reference to the instance of this
89
- class.
90
- * All non-GUI classes can be passed a reference to the NWProject
91
- instance if the Main GUI is not needed (which it generally
92
- shouldn't need).
93
82
  """
94
83
 
95
84
  def __init__(self) -> None:
@@ -338,19 +327,9 @@ class GuiMain(QMainWindow):
338
327
  self.asProjTimer.start()
339
328
  self.asDocTimer.start()
340
329
  self.mainStatus.clearStatus()
341
-
342
- # Handle Windows Mode
343
330
  self.showNormal()
344
331
 
345
332
  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
333
  logger.info("novelWriter is ready ...")
355
334
  self.mainStatus.setStatusMessage(self.tr("novelWriter is ready ..."))
356
335
 
@@ -370,12 +349,18 @@ class GuiMain(QMainWindow):
370
349
  self.openProject(cmdOpen)
371
350
 
372
351
  if not SHARED.hasProject:
373
- self.showProjectLoadDialog()
352
+ self.showWelcomeDialog()
374
353
 
375
- # Determine whether release notes need to be shown or not
354
+ # If this is a new release, let the user know
376
355
  if hexToInt(CONFIG.lastNotes) < hexToInt(__hexversion__):
377
356
  CONFIG.lastNotes = __hexversion__
378
- self.showAboutNWDialog(showNotes=True)
357
+ trVersion = self.tr(
358
+ "You are now running novelWriter version {0}.".format(formatVersion(__version__))
359
+ )
360
+ trRelease = self.tr(
361
+ "Please check the {0}release notes{1} for further details."
362
+ ).format(f"<a href='{nwConst.URL_RELEASES}'>", "</a>")
363
+ SHARED.info(f"{trVersion}<br>{trRelease}")
379
364
 
380
365
  return
381
366
 
@@ -383,42 +368,6 @@ class GuiMain(QMainWindow):
383
368
  # Project Actions
384
369
  ##
385
370
 
386
- def newProject(self, projData: dict | None = None) -> bool:
387
- """Create a new project via the new project wizard."""
388
- if SHARED.hasProject:
389
- if not self.closeProject():
390
- SHARED.error(self.tr(
391
- "Cannot create a new project when another project is open."
392
- ))
393
- return False
394
-
395
- if projData is None:
396
- projData = self.showNewProjectDialog()
397
-
398
- if projData is None:
399
- return False
400
-
401
- projPath = projData.get("projPath", None)
402
- if projPath is None or projData is None:
403
- logger.error("No projData or projPath set")
404
- return False
405
-
406
- if (Path(projPath) / nwFiles.PROJ_FILE).is_file():
407
- SHARED.error(self.tr(
408
- "A project already exists in that location. "
409
- "Please choose another folder."
410
- ))
411
- return False
412
-
413
- logger.info("Creating new project")
414
- nwProject = ProjectBuilder()
415
- if nwProject.buildProject(projData):
416
- self.openProject(projPath)
417
- else:
418
- return False
419
-
420
- return True
421
-
422
371
  def closeProject(self, isYes: bool = False) -> bool:
423
372
  """Close the project if one is open. isYes is passed on from the
424
373
  close application event so the user doesn't get prompted twice
@@ -457,7 +406,7 @@ class GuiMain(QMainWindow):
457
406
  self.docViewerPanel.closeProjectTasks()
458
407
  self.outlineView.closeProjectTasks()
459
408
  self.novelView.closeProjectTasks()
460
- self.projView.clearProjectView()
409
+ self.projView.closeProjectTasks()
461
410
  self.itemDetails.clearDetails()
462
411
  self.mainStatus.clearStatus()
463
412
 
@@ -473,8 +422,7 @@ class GuiMain(QMainWindow):
473
422
  if projFile is None:
474
423
  return False
475
424
 
476
- # Make sure any open project is cleared out first before we load
477
- # another one
425
+ # Make sure any open project is cleared out first
478
426
  if not self.closeProject():
479
427
  return False
480
428
 
@@ -617,7 +565,7 @@ class GuiMain(QMainWindow):
617
565
  if self.docEditor.loadText(tHandle, tLine):
618
566
  SHARED.project.data.setLastHandle(tHandle, "editor")
619
567
  self.projView.setSelectedHandle(tHandle, doScroll=doScroll)
620
- self.novelView.setActiveHandle(tHandle)
568
+ self.novelView.setActiveHandle(tHandle, doScroll=doScroll)
621
569
  if changeFocus:
622
570
  self.docEditor.setFocus()
623
571
  else:
@@ -722,14 +670,9 @@ class GuiMain(QMainWindow):
722
670
  return False
723
671
 
724
672
  lastPath = CONFIG.lastPath()
725
- extFilter = [
726
- self.tr("Text files ({0})").format("*.txt"),
727
- self.tr("Markdown files ({0})").format("*.md"),
728
- self.tr("novelWriter files ({0})").format("*.nwd"),
729
- self.tr("All files ({0})").format("*"),
730
- ]
673
+ ffilter = formatFileFilter(["*.txt", "*.md", "*.nwd", "*"])
731
674
  loadFile, _ = QFileDialog.getOpenFileName(
732
- self, self.tr("Import File"), str(lastPath), filter=";;".join(extFilter)
675
+ self, self.tr("Import File"), str(lastPath), filter=ffilter
733
676
  )
734
677
  if not loadFile:
735
678
  return False
@@ -737,10 +680,10 @@ class GuiMain(QMainWindow):
737
680
  if loadFile.strip() == "":
738
681
  return False
739
682
 
740
- theText = None
683
+ text = None
741
684
  try:
742
685
  with open(loadFile, mode="rt", encoding="utf-8") as inFile:
743
- theText = inFile.read()
686
+ text = inFile.read()
744
687
  CONFIG.setLastPath(loadFile)
745
688
  except Exception as exc:
746
689
  SHARED.error(self.tr(
@@ -762,7 +705,7 @@ class GuiMain(QMainWindow):
762
705
  if not msgYes:
763
706
  return False
764
707
 
765
- self.docEditor.replaceText(theText)
708
+ self.docEditor.replaceText(text)
766
709
 
767
710
  return True
768
711
 
@@ -849,240 +792,108 @@ class GuiMain(QMainWindow):
849
792
  # Main Dialogs
850
793
  ##
851
794
 
852
- def showProjectLoadDialog(self) -> None:
853
- """Open the projects dialog for selecting either existing
854
- projects from a cache of recently opened projects, or provide a
855
- browse button for projects not yet cached. Selecting to create a
856
- new project is forwarded to the new project wizard.
857
- """
858
- dlgProj = GuiProjectLoad(self)
859
- dlgProj.exec_()
860
-
861
- if dlgProj.result() == QDialog.Accepted:
862
- if dlgProj.openState == GuiProjectLoad.OPEN_STATE:
863
- self.openProject(dlgProj.openPath)
864
- elif dlgProj.openState == GuiProjectLoad.NEW_STATE:
865
- self.newProject()
866
-
795
+ @pyqtSlot()
796
+ def showWelcomeDialog(self) -> None:
797
+ """Open the welcome dialog."""
798
+ dialog = GuiWelcome(self)
799
+ dialog.openProjectRequest.connect(self._openProject)
800
+ dialog.exec_()
867
801
  return
868
802
 
869
- def showNewProjectDialog(self) -> dict | None:
870
- """Open the wizard and assemble a project options dict."""
871
- newProj = GuiProjectWizard(self)
872
- newProj.exec_()
873
-
874
- if newProj.result() == QDialog.Accepted:
875
- return self._assembleProjectWizardData(newProj)
876
-
877
- return None
878
-
803
+ @pyqtSlot()
879
804
  def showPreferencesDialog(self) -> None:
880
805
  """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
-
806
+ dialog = GuiPreferences(self)
807
+ dialog.newPreferencesReady.connect(self._processConfigChanges)
808
+ dialog.exec_()
923
809
  return
924
810
 
811
+ @pyqtSlot()
925
812
  @pyqtSlot(int)
926
- def showProjectSettingsDialog(self, focusTab: int = GuiProjectSettings.TAB_MAIN) -> bool:
813
+ def showProjectSettingsDialog(self, focusTab: int = GuiProjectSettings.PAGE_SETTINGS) -> None:
927
814
  """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
942
-
943
- def showProjectDetailsDialog(self) -> bool:
944
- """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()
815
+ if SHARED.hasProject:
816
+ dialog = GuiProjectSettings(self, gotoPage=focusTab)
817
+ dialog.newProjectSettingsReady.connect(self._processProjectSettingsChanges)
818
+ dialog.exec_()
819
+ return
958
820
 
959
- return True
821
+ @pyqtSlot()
822
+ def showNovelDetailsDialog(self) -> None:
823
+ """Open the novel details dialog."""
824
+ if SHARED.hasProject:
825
+ dialog = GuiNovelDetails(self)
826
+ dialog.setModal(True)
827
+ dialog.show()
828
+ dialog.raise_()
829
+ qApp.processEvents()
830
+ dialog.updateValues()
831
+ return
960
832
 
961
833
  @pyqtSlot()
962
- def showBuildManuscriptDialog(self) -> bool:
834
+ def showBuildManuscriptDialog(self) -> None:
963
835
  """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
836
+ if SHARED.hasProject:
837
+ if (dialog := SHARED.findTopLevelWidget(GuiManuscript)) is None:
838
+ dialog = GuiManuscript(self)
839
+ dialog.setModal(False)
840
+ dialog.show()
841
+ dialog.raise_()
842
+ qApp.processEvents()
843
+ dialog.loadContent()
844
+ return
999
845
 
1000
- def showProjectWordListDialog(self) -> bool:
846
+ @pyqtSlot()
847
+ def showProjectWordListDialog(self) -> None:
1001
848
  """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
849
+ if SHARED.hasProject:
850
+ dialog = GuiWordList(self)
851
+ dialog.newWordListReady.connect(self._processWordListChanges)
852
+ dialog.exec_()
853
+ return
1015
854
 
1016
- def showWritingStatsDialog(self) -> bool:
855
+ @pyqtSlot()
856
+ def showWritingStatsDialog(self) -> None:
1017
857
  """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)
858
+ if SHARED.hasProject:
859
+ if (dialog := SHARED.findTopLevelWidget(GuiWritingStats)) is None:
860
+ dialog = GuiWritingStats(self)
861
+ dialog.setModal(False)
862
+ dialog.show()
863
+ dialog.raise_()
864
+ qApp.processEvents()
865
+ dialog.populateGUI()
866
+ return
1041
867
 
1042
- dlgAbout.setModal(True)
1043
- dlgAbout.show()
1044
- dlgAbout.raise_()
868
+ @pyqtSlot()
869
+ def showAboutNWDialog(self) -> None:
870
+ """Show the novelWriter about dialog."""
871
+ dialog = GuiAbout(self)
872
+ dialog.setModal(True)
873
+ dialog.show()
874
+ dialog.raise_()
1045
875
  qApp.processEvents()
1046
- dlgAbout.populateGUI()
1047
-
1048
- if showNotes:
1049
- dlgAbout.showReleaseNotes()
1050
-
1051
- return True
876
+ dialog.populateGUI()
877
+ return
1052
878
 
879
+ @pyqtSlot()
1053
880
  def showAboutQtDialog(self) -> None:
1054
- """Show the about dialog for Qt."""
881
+ """Show the Qt about dialog."""
1055
882
  msgBox = QMessageBox(self)
1056
883
  msgBox.aboutQt(self, "About Qt")
1057
884
  return
1058
885
 
1059
- def showUpdatesDialog(self) -> None:
1060
- """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_()
1069
- qApp.processEvents()
1070
- dlgUpdate.checkLatest()
1071
-
1072
- return
1073
-
1074
886
  @pyqtSlot()
1075
887
  def showDictionariesDialog(self) -> None:
1076
888
  """Show the download dictionaries dialog."""
1077
- dlgDicts = GuiDictionaries(self)
1078
- dlgDicts.setModal(True)
1079
- dlgDicts.show()
1080
- dlgDicts.raise_()
889
+ dialog = GuiDictionaries(self)
890
+ dialog.setModal(True)
891
+ dialog.show()
892
+ dialog.raise_()
1081
893
  qApp.processEvents()
1082
- if not dlgDicts.initDialog():
1083
- dlgDicts.close()
894
+ if not dialog.initDialog():
895
+ dialog.close()
1084
896
  SHARED.error(self.tr("Could not initialise the dialog."))
1085
-
1086
897
  return
1087
898
 
1088
899
  def reportConfErr(self) -> bool:
@@ -1248,6 +1059,67 @@ class GuiMain(QMainWindow):
1248
1059
  # Private Slots
1249
1060
  ##
1250
1061
 
1062
+ @pyqtSlot(bool, bool, bool, bool)
1063
+ def _processConfigChanges(self, restart: bool, tree: bool, theme: bool, syntax: bool) -> None:
1064
+ """Refresh GUI based on flags from the Preferences dialog."""
1065
+ logger.debug("Applying new preferences")
1066
+ self.initMain()
1067
+ self.saveDocument()
1068
+
1069
+ if restart:
1070
+ SHARED.info(self.tr(
1071
+ "Some changes will not be applied until novelWriter has been restarted."
1072
+ ))
1073
+
1074
+ if tree:
1075
+ self.projView.populateTree()
1076
+
1077
+ if theme:
1078
+ # We are doing this manually instead of connecting to
1079
+ # qApp.paletteChanged since the processing order matters
1080
+ SHARED.theme.loadTheme()
1081
+ self.docEditor.updateTheme()
1082
+ self.docViewer.updateTheme()
1083
+ self.docViewerPanel.updateTheme()
1084
+ self.sideBar.updateTheme()
1085
+ self.projView.updateTheme()
1086
+ self.novelView.updateTheme()
1087
+ self.outlineView.updateTheme()
1088
+ self.itemDetails.updateTheme()
1089
+ self.mainStatus.updateTheme()
1090
+
1091
+ if syntax:
1092
+ SHARED.theme.loadSyntax()
1093
+ self.docEditor.updateSyntaxColours()
1094
+
1095
+ self.docEditor.initEditor()
1096
+ self.docViewer.initViewer()
1097
+ self.projView.initSettings()
1098
+ self.novelView.initSettings()
1099
+ self.outlineView.initSettings()
1100
+ self._updateStatusWordCount()
1101
+
1102
+ return
1103
+
1104
+ @pyqtSlot(bool)
1105
+ def _processProjectSettingsChanges(self, rebuildTrees: bool) -> None:
1106
+ """Refresh data dependent on project settings."""
1107
+ logger.debug("Applying new project settings")
1108
+ SHARED.updateSpellCheckLanguage()
1109
+ self.itemDetails.refreshDetails()
1110
+ self._updateWindowTitle(SHARED.project.data.name)
1111
+ if rebuildTrees:
1112
+ self.rebuildTrees()
1113
+ return
1114
+
1115
+ @pyqtSlot()
1116
+ def _processWordListChanges(self) -> None:
1117
+ """Reload project word list."""
1118
+ logger.debug("Reloading word list")
1119
+ SHARED.updateSpellCheckLanguage(reload=True)
1120
+ self.docEditor.spellCheckDocument()
1121
+ return
1122
+
1251
1123
  @pyqtSlot(str, nwDocMode)
1252
1124
  def _followTag(self, tag: str, mode: nwDocMode) -> None:
1253
1125
  """Follow a tag after user interaction with a link."""
@@ -1259,6 +1131,13 @@ class GuiMain(QMainWindow):
1259
1131
  self.viewDocument(tHandle=tHandle, sTitle=sTitle)
1260
1132
  return
1261
1133
 
1134
+ @pyqtSlot(Path)
1135
+ def _openProject(self, path: Path) -> None:
1136
+ """Handle an open project request."""
1137
+ qApp.processEvents()
1138
+ self.openProject(path)
1139
+ return
1140
+
1262
1141
  @pyqtSlot(str, nwDocMode, str, bool)
1263
1142
  def _openDocument(self, tHandle: str, mode: nwDocMode, sTitle: str, setFocus: bool) -> None:
1264
1143
  """Handle an open document request."""
@@ -1331,6 +1210,8 @@ class GuiMain(QMainWindow):
1331
1210
  self.mainStatus.setUserIdle(editIdle or userIdle)
1332
1211
  SHARED.updateIdleTime(currTime, editIdle or userIdle)
1333
1212
  self.mainStatus.updateTime(idleTime=SHARED.projectIdleTime)
1213
+ if CONFIG.memInfo and int(currTime) % 5 == 0: # pragma: no cover
1214
+ self.mainStatus.memInfo()
1334
1215
  return
1335
1216
 
1336
1217
  @pyqtSlot()
@@ -1474,8 +1355,8 @@ class GuiMain(QMainWindow):
1474
1355
  self.addAction(self.mainMenu.aReplaceNext)
1475
1356
 
1476
1357
  # Format
1477
- self.addAction(self.mainMenu.aFmtEmph)
1478
- self.addAction(self.mainMenu.aFmtStrong)
1358
+ self.addAction(self.mainMenu.aFmtItalic)
1359
+ self.addAction(self.mainMenu.aFmtBold)
1479
1360
  self.addAction(self.mainMenu.aFmtStrike)
1480
1361
  self.addAction(self.mainMenu.aFmtDQuote)
1481
1362
  self.addAction(self.mainMenu.aFmtSQuote)
@@ -1511,44 +1392,6 @@ class GuiMain(QMainWindow):
1511
1392
  self.setWindowTitle(winTitle)
1512
1393
  return
1513
1394
 
1514
- def _assembleProjectWizardData(self, newProj: GuiProjectWizard) -> dict:
1515
- """Extract the user choices from the New Project Wizard and
1516
- store them in a dictionary.
1517
- """
1518
- projData = {
1519
- "projName": newProj.field("projName"),
1520
- "projTitle": newProj.field("projTitle"),
1521
- "projAuthor": newProj.field("projAuthor"),
1522
- "projPath": newProj.field("projPath"),
1523
- "popSample": newProj.field("popSample"),
1524
- "popMinimal": newProj.field("popMinimal"),
1525
- "popCustom": newProj.field("popCustom"),
1526
- "addRoots": [],
1527
- "addNotes": False,
1528
- "numChapters": 0,
1529
- "numScenes": 0,
1530
- }
1531
- if newProj.field("popCustom"):
1532
- addRoots = []
1533
- if newProj.field("addPlot"):
1534
- addRoots.append(nwItemClass.PLOT)
1535
- if newProj.field("addChar"):
1536
- addRoots.append(nwItemClass.CHARACTER)
1537
- if newProj.field("addWorld"):
1538
- addRoots.append(nwItemClass.WORLD)
1539
- projData["addRoots"] = addRoots
1540
- projData["addNotes"] = newProj.field("addNotes")
1541
- projData["numChapters"] = newProj.field("numChapters")
1542
- projData["numScenes"] = newProj.field("numScenes")
1543
-
1544
- try:
1545
- langIdx = newProj.field("projLang")
1546
- projData["projLang"] = CONFIG.listLanguages(CONFIG.LANG_PROJ)[langIdx][0]
1547
- except Exception:
1548
- projData["projLang"] = "en_GB"
1549
-
1550
- return projData
1551
-
1552
1395
  def _getTagSource(self, tag: str) -> tuple[str | None, str | None]:
1553
1396
  """Handle the index lookup of a tag and display an alert if the
1554
1397
  tag cannot be found.