novelWriter 2.2.1__py3-none-any.whl → 2.3__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 (125) hide show
  1. {novelWriter-2.2.1.dist-info → novelWriter-2.3.dist-info}/METADATA +1 -1
  2. {novelWriter-2.2.1.dist-info → novelWriter-2.3.dist-info}/RECORD +116 -101
  3. novelWriter-2.3.dist-info/entry_points.txt +2 -0
  4. novelwriter/__init__.py +4 -4
  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_it_IT.qm +0 -0
  9. novelwriter/assets/i18n/nw_ja_JP.qm +0 -0
  10. novelwriter/assets/i18n/nw_nb_NO.qm +0 -0
  11. novelwriter/assets/i18n/nw_nl_NL.qm +0 -0
  12. novelwriter/assets/i18n/project_nl_NL.json +11 -0
  13. novelwriter/assets/i18n/project_pt_BR.json +11 -0
  14. novelwriter/assets/icons/typicons_dark/icons.conf +8 -0
  15. novelwriter/assets/icons/typicons_dark/mixed_document-new.svg +6 -0
  16. novelwriter/assets/icons/typicons_dark/mixed_import.svg +5 -0
  17. novelwriter/assets/icons/typicons_dark/typ_document-add.svg +4 -0
  18. novelwriter/assets/icons/typicons_dark/typ_document.svg +4 -0
  19. novelwriter/assets/icons/typicons_dark/typ_th-dot-more.svg +4 -0
  20. novelwriter/assets/icons/typicons_dark/typ_th-list.svg +9 -0
  21. novelwriter/assets/icons/typicons_light/icons.conf +8 -0
  22. novelwriter/assets/icons/typicons_light/mixed_document-new.svg +6 -0
  23. novelwriter/assets/icons/typicons_light/mixed_import.svg +5 -0
  24. novelwriter/assets/icons/typicons_light/typ_document-add.svg +4 -0
  25. novelwriter/assets/icons/typicons_light/typ_document.svg +4 -0
  26. novelwriter/assets/icons/typicons_light/typ_th-dot-more.svg +4 -0
  27. novelwriter/assets/icons/typicons_light/typ_th-list.svg +9 -0
  28. novelwriter/assets/images/novelwriter-text-dark.svg +4 -0
  29. novelwriter/assets/images/novelwriter-text-light.svg +4 -0
  30. novelwriter/assets/images/welcome-dark.jpg +0 -0
  31. novelwriter/assets/images/welcome-light.jpg +0 -0
  32. novelwriter/assets/manual.pdf +0 -0
  33. novelwriter/assets/sample.zip +0 -0
  34. novelwriter/assets/syntax/cyberpunk_night.conf +26 -0
  35. novelwriter/assets/syntax/default_dark.conf +1 -0
  36. novelwriter/assets/syntax/default_light.conf +1 -0
  37. novelwriter/assets/syntax/grey_dark.conf +1 -0
  38. novelwriter/assets/syntax/grey_light.conf +1 -0
  39. novelwriter/assets/syntax/light_owl.conf +1 -0
  40. novelwriter/assets/syntax/night_owl.conf +1 -0
  41. novelwriter/assets/syntax/solarized_dark.conf +1 -0
  42. novelwriter/assets/syntax/solarized_light.conf +1 -0
  43. novelwriter/assets/syntax/tango.conf +23 -0
  44. novelwriter/assets/syntax/tomorrow.conf +1 -0
  45. novelwriter/assets/syntax/tomorrow_night.conf +1 -0
  46. novelwriter/assets/syntax/tomorrow_night_blue.conf +1 -0
  47. novelwriter/assets/syntax/tomorrow_night_bright.conf +1 -0
  48. novelwriter/assets/syntax/tomorrow_night_eighties.conf +1 -0
  49. novelwriter/assets/text/credits_en.htm +4 -2
  50. novelwriter/assets/themes/cyberpunk_night.conf +29 -0
  51. novelwriter/assets/themes/default_dark.conf +2 -2
  52. novelwriter/assets/themes/default_light.conf +2 -2
  53. novelwriter/common.py +48 -37
  54. novelwriter/config.py +36 -41
  55. novelwriter/constants.py +38 -16
  56. novelwriter/core/buildsettings.py +7 -7
  57. novelwriter/core/coretools.py +196 -156
  58. novelwriter/core/docbuild.py +6 -3
  59. novelwriter/core/document.py +6 -6
  60. novelwriter/core/index.py +89 -56
  61. novelwriter/core/item.py +21 -3
  62. novelwriter/core/options.py +8 -7
  63. novelwriter/core/project.py +70 -44
  64. novelwriter/core/projectdata.py +1 -14
  65. novelwriter/core/projectxml.py +13 -41
  66. novelwriter/core/sessions.py +2 -1
  67. novelwriter/core/spellcheck.py +2 -1
  68. novelwriter/core/status.py +2 -1
  69. novelwriter/core/storage.py +182 -140
  70. novelwriter/core/tohtml.py +4 -2
  71. novelwriter/core/tokenizer.py +109 -82
  72. novelwriter/core/toodt.py +40 -30
  73. novelwriter/core/tree.py +3 -2
  74. novelwriter/dialogs/about.py +70 -160
  75. novelwriter/dialogs/docmerge.py +6 -5
  76. novelwriter/dialogs/docsplit.py +6 -6
  77. novelwriter/dialogs/editlabel.py +1 -1
  78. novelwriter/dialogs/preferences.py +553 -703
  79. novelwriter/dialogs/{projsettings.py → projectsettings.py} +288 -262
  80. novelwriter/dialogs/quotes.py +27 -23
  81. novelwriter/dialogs/wordlist.py +96 -40
  82. novelwriter/enum.py +20 -18
  83. novelwriter/error.py +1 -1
  84. novelwriter/extensions/circularprogress.py +11 -11
  85. novelwriter/extensions/configlayout.py +185 -134
  86. novelwriter/extensions/modified.py +81 -0
  87. novelwriter/extensions/novelselector.py +26 -12
  88. novelwriter/extensions/pagedsidebar.py +14 -16
  89. novelwriter/extensions/simpleprogress.py +5 -5
  90. novelwriter/extensions/statusled.py +8 -8
  91. novelwriter/extensions/switch.py +31 -63
  92. novelwriter/extensions/switchbox.py +1 -1
  93. novelwriter/extensions/versioninfo.py +153 -0
  94. novelwriter/gui/doceditor.py +178 -150
  95. novelwriter/gui/dochighlight.py +63 -92
  96. novelwriter/gui/docviewer.py +49 -51
  97. novelwriter/gui/docviewerpanel.py +72 -24
  98. novelwriter/gui/itemdetails.py +7 -7
  99. novelwriter/gui/mainmenu.py +14 -19
  100. novelwriter/gui/noveltree.py +9 -8
  101. novelwriter/gui/outline.py +98 -75
  102. novelwriter/gui/projtree.py +241 -106
  103. novelwriter/gui/sidebar.py +3 -4
  104. novelwriter/gui/statusbar.py +3 -4
  105. novelwriter/gui/theme.py +69 -70
  106. novelwriter/guimain.py +51 -156
  107. novelwriter/shared.py +15 -1
  108. novelwriter/tools/dictionaries.py +5 -6
  109. novelwriter/tools/manuscript.py +6 -6
  110. novelwriter/tools/manussettings.py +192 -221
  111. novelwriter/tools/noveldetails.py +525 -0
  112. novelwriter/tools/welcome.py +819 -0
  113. novelwriter/tools/writingstats.py +9 -9
  114. novelWriter-2.2.1.dist-info/entry_points.txt +0 -5
  115. novelwriter/assets/images/wizard-back.jpg +0 -0
  116. novelwriter/assets/text/gplv3_en.htm +0 -641
  117. novelwriter/assets/text/release_notes.htm +0 -60
  118. novelwriter/dialogs/projdetails.py +0 -518
  119. novelwriter/dialogs/projload.py +0 -294
  120. novelwriter/dialogs/updates.py +0 -172
  121. novelwriter/extensions/pageddialog.py +0 -130
  122. novelwriter/tools/projwizard.py +0 -478
  123. {novelWriter-2.2.1.dist-info → novelWriter-2.3.dist-info}/LICENSE.md +0 -0
  124. {novelWriter-2.2.1.dist-info → novelWriter-2.3.dist-info}/WHEEL +0 -0
  125. {novelWriter-2.2.1.dist-info → novelWriter-2.3.dist-info}/top_level.txt +0 -0
novelwriter/guimain.py CHANGED
@@ -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,23 +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
56
+ from novelwriter.dialogs.projectsettings import GuiProjectSettings
57
+ from novelwriter.tools.welcome import GuiWelcome
59
58
  from novelwriter.tools.manuscript import GuiManuscript
60
- from novelwriter.tools.projwizard import GuiProjectWizard
61
59
  from novelwriter.tools.dictionaries import GuiDictionaries
60
+ from novelwriter.tools.noveldetails import GuiNovelDetails
62
61
  from novelwriter.tools.writingstats import GuiWritingStats
63
- from novelwriter.core.coretools import ProjectBuilder
64
62
 
65
63
  from novelwriter.enum import (
66
- nwDocAction, nwDocInsert, nwDocMode, nwItemType, nwItemClass, nwWidget, nwView
64
+ nwDocAction, nwDocInsert, nwDocMode, nwItemType, nwWidget, nwView
67
65
  )
68
- from novelwriter.common import hexToInt
69
- from novelwriter.constants import nwFiles
66
+ from novelwriter.common import formatFileFilter, formatVersion, hexToInt
70
67
 
71
68
  logger = logging.getLogger(__name__)
72
69
 
@@ -82,13 +79,6 @@ class GuiMain(QMainWindow):
82
79
  function. Also, the project instance and theme instance are created
83
80
  here. These should be passed around to all other objects who need
84
81
  them and new instances of them should generally not be created.
85
-
86
- * All other GUI classes that depend on any components from the
87
- main GUI should be passed a reference to the instance of this
88
- class.
89
- * All non-GUI classes can be passed a reference to the NWProject
90
- instance if the Main GUI is not needed (which it generally
91
- shouldn't need).
92
82
  """
93
83
 
94
84
  def __init__(self) -> None:
@@ -359,12 +349,18 @@ class GuiMain(QMainWindow):
359
349
  self.openProject(cmdOpen)
360
350
 
361
351
  if not SHARED.hasProject:
362
- self.showProjectLoadDialog()
352
+ self.showWelcomeDialog()
363
353
 
364
- # Determine whether release notes need to be shown or not
354
+ # If this is a new release, let the user know
365
355
  if hexToInt(CONFIG.lastNotes) < hexToInt(__hexversion__):
366
356
  CONFIG.lastNotes = __hexversion__
367
- 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}")
368
364
 
369
365
  return
370
366
 
@@ -372,42 +368,6 @@ class GuiMain(QMainWindow):
372
368
  # Project Actions
373
369
  ##
374
370
 
375
- def newProject(self, projData: dict | None = None) -> bool:
376
- """Create a new project via the new project wizard."""
377
- if SHARED.hasProject:
378
- if not self.closeProject():
379
- SHARED.error(self.tr(
380
- "Cannot create a new project when another project is open."
381
- ))
382
- return False
383
-
384
- if projData is None:
385
- projData = self.showNewProjectDialog()
386
-
387
- if projData is None:
388
- return False
389
-
390
- projPath = projData.get("projPath", None)
391
- if projPath is None or projData is None:
392
- logger.error("No projData or projPath set")
393
- return False
394
-
395
- if (Path(projPath) / nwFiles.PROJ_FILE).is_file():
396
- SHARED.error(self.tr(
397
- "A project already exists in that location. "
398
- "Please choose another folder."
399
- ))
400
- return False
401
-
402
- logger.info("Creating new project")
403
- nwProject = ProjectBuilder()
404
- if nwProject.buildProject(projData):
405
- self.openProject(projPath)
406
- else:
407
- return False
408
-
409
- return True
410
-
411
371
  def closeProject(self, isYes: bool = False) -> bool:
412
372
  """Close the project if one is open. isYes is passed on from the
413
373
  close application event so the user doesn't get prompted twice
@@ -446,7 +406,7 @@ class GuiMain(QMainWindow):
446
406
  self.docViewerPanel.closeProjectTasks()
447
407
  self.outlineView.closeProjectTasks()
448
408
  self.novelView.closeProjectTasks()
449
- self.projView.clearProjectView()
409
+ self.projView.closeProjectTasks()
450
410
  self.itemDetails.clearDetails()
451
411
  self.mainStatus.clearStatus()
452
412
 
@@ -462,8 +422,7 @@ class GuiMain(QMainWindow):
462
422
  if projFile is None:
463
423
  return False
464
424
 
465
- # Make sure any open project is cleared out first before we load
466
- # another one
425
+ # Make sure any open project is cleared out first
467
426
  if not self.closeProject():
468
427
  return False
469
428
 
@@ -711,14 +670,9 @@ class GuiMain(QMainWindow):
711
670
  return False
712
671
 
713
672
  lastPath = CONFIG.lastPath()
714
- extFilter = [
715
- self.tr("Text files ({0})").format("*.txt"),
716
- self.tr("Markdown files ({0})").format("*.md"),
717
- self.tr("novelWriter files ({0})").format("*.nwd"),
718
- self.tr("All files ({0})").format("*"),
719
- ]
673
+ ffilter = formatFileFilter(["*.txt", "*.md", "*.nwd", "*"])
720
674
  loadFile, _ = QFileDialog.getOpenFileName(
721
- self, self.tr("Import File"), str(lastPath), filter=";;".join(extFilter)
675
+ self, self.tr("Import File"), str(lastPath), filter=ffilter
722
676
  )
723
677
  if not loadFile:
724
678
  return False
@@ -726,10 +680,10 @@ class GuiMain(QMainWindow):
726
680
  if loadFile.strip() == "":
727
681
  return False
728
682
 
729
- theText = None
683
+ text = None
730
684
  try:
731
685
  with open(loadFile, mode="rt", encoding="utf-8") as inFile:
732
- theText = inFile.read()
686
+ text = inFile.read()
733
687
  CONFIG.setLastPath(loadFile)
734
688
  except Exception as exc:
735
689
  SHARED.error(self.tr(
@@ -751,7 +705,7 @@ class GuiMain(QMainWindow):
751
705
  if not msgYes:
752
706
  return False
753
707
 
754
- self.docEditor.replaceText(theText)
708
+ self.docEditor.replaceText(text)
755
709
 
756
710
  return True
757
711
 
@@ -838,33 +792,14 @@ class GuiMain(QMainWindow):
838
792
  # Main Dialogs
839
793
  ##
840
794
 
841
- def showProjectLoadDialog(self) -> None:
842
- """Open the projects dialog for selecting either existing
843
- projects from a cache of recently opened projects, or provide a
844
- browse button for projects not yet cached. Selecting to create a
845
- new project is forwarded to the new project wizard.
846
- """
847
- dlgProj = GuiProjectLoad(self)
848
- dlgProj.exec_()
849
-
850
- if dlgProj.result() == QDialog.Accepted:
851
- if dlgProj.openState == GuiProjectLoad.OPEN_STATE:
852
- self.openProject(dlgProj.openPath)
853
- elif dlgProj.openState == GuiProjectLoad.NEW_STATE:
854
- self.newProject()
855
-
795
+ @pyqtSlot()
796
+ def showWelcomeDialog(self) -> None:
797
+ """Open the welcome dialog."""
798
+ dialog = GuiWelcome(self)
799
+ dialog.openProjectRequest.connect(self._openProjectFromWelcome)
800
+ dialog.exec_()
856
801
  return
857
802
 
858
- def showNewProjectDialog(self) -> dict | None:
859
- """Open the wizard and assemble a project options dict."""
860
- newProj = GuiProjectWizard(self)
861
- newProj.exec_()
862
-
863
- if newProj.result() == QDialog.Accepted:
864
- return self._assembleProjectWizardData(newProj)
865
-
866
- return None
867
-
868
803
  @pyqtSlot()
869
804
  def showPreferencesDialog(self) -> None:
870
805
  """Open the preferences dialog."""
@@ -875,19 +810,19 @@ class GuiMain(QMainWindow):
875
810
 
876
811
  @pyqtSlot()
877
812
  @pyqtSlot(int)
878
- def showProjectSettingsDialog(self, focusTab: int = GuiProjectSettings.TAB_MAIN) -> None:
813
+ def showProjectSettingsDialog(self, focusTab: int = GuiProjectSettings.PAGE_SETTINGS) -> None:
879
814
  """Open the project settings dialog."""
880
815
  if SHARED.hasProject:
881
- dialog = GuiProjectSettings(self, focusTab=focusTab)
816
+ dialog = GuiProjectSettings(self, gotoPage=focusTab)
882
817
  dialog.newProjectSettingsReady.connect(self._processProjectSettingsChanges)
883
818
  dialog.exec_()
884
819
  return
885
820
 
886
821
  @pyqtSlot()
887
- def showProjectDetailsDialog(self) -> None:
888
- """Open the project details dialog."""
822
+ def showNovelDetailsDialog(self) -> None:
823
+ """Open the novel details dialog."""
889
824
  if SHARED.hasProject:
890
- dialog = GuiProjectDetails(self)
825
+ dialog = GuiNovelDetails(self)
891
826
  dialog.setModal(True)
892
827
  dialog.show()
893
828
  dialog.raise_()
@@ -931,7 +866,7 @@ class GuiMain(QMainWindow):
931
866
  return
932
867
 
933
868
  @pyqtSlot()
934
- def showAboutNWDialog(self, showNotes: bool = False) -> None:
869
+ def showAboutNWDialog(self) -> None:
935
870
  """Show the novelWriter about dialog."""
936
871
  dialog = GuiAbout(self)
937
872
  dialog.setModal(True)
@@ -939,8 +874,6 @@ class GuiMain(QMainWindow):
939
874
  dialog.raise_()
940
875
  qApp.processEvents()
941
876
  dialog.populateGUI()
942
- if showNotes:
943
- dialog.showReleaseNotes()
944
877
  return
945
878
 
946
879
  @pyqtSlot()
@@ -950,17 +883,6 @@ class GuiMain(QMainWindow):
950
883
  msgBox.aboutQt(self, "About Qt")
951
884
  return
952
885
 
953
- @pyqtSlot()
954
- def showUpdatesDialog(self) -> None:
955
- """Show the check for updates dialog."""
956
- dialog = GuiUpdates(self)
957
- dialog.setModal(True)
958
- dialog.show()
959
- dialog.raise_()
960
- qApp.processEvents()
961
- dialog.checkLatest()
962
- return
963
-
964
886
  @pyqtSlot()
965
887
  def showDictionariesDialog(self) -> None:
966
888
  """Show the download dictionaries dialog."""
@@ -1179,13 +1101,15 @@ class GuiMain(QMainWindow):
1179
1101
 
1180
1102
  return
1181
1103
 
1182
- @pyqtSlot()
1183
- def _processProjectSettingsChanges(self) -> None:
1104
+ @pyqtSlot(bool)
1105
+ def _processProjectSettingsChanges(self, rebuildTrees: bool) -> None:
1184
1106
  """Refresh data dependent on project settings."""
1185
1107
  logger.debug("Applying new project settings")
1186
1108
  SHARED.updateSpellCheckLanguage()
1187
1109
  self.itemDetails.refreshDetails()
1188
1110
  self._updateWindowTitle(SHARED.project.data.name)
1111
+ if rebuildTrees:
1112
+ self.rebuildTrees()
1189
1113
  return
1190
1114
 
1191
1115
  @pyqtSlot()
@@ -1207,6 +1131,15 @@ class GuiMain(QMainWindow):
1207
1131
  self.viewDocument(tHandle=tHandle, sTitle=sTitle)
1208
1132
  return
1209
1133
 
1134
+ @pyqtSlot(Path)
1135
+ def _openProjectFromWelcome(self, path: Path) -> None:
1136
+ """Handle an open project request from the welcome dialog."""
1137
+ qApp.processEvents()
1138
+ self.openProject(path)
1139
+ if not SHARED.hasProject:
1140
+ self.showWelcomeDialog()
1141
+ return
1142
+
1210
1143
  @pyqtSlot(str, nwDocMode, str, bool)
1211
1144
  def _openDocument(self, tHandle: str, mode: nwDocMode, sTitle: str, setFocus: bool) -> None:
1212
1145
  """Handle an open document request."""
@@ -1461,44 +1394,6 @@ class GuiMain(QMainWindow):
1461
1394
  self.setWindowTitle(winTitle)
1462
1395
  return
1463
1396
 
1464
- def _assembleProjectWizardData(self, newProj: GuiProjectWizard) -> dict:
1465
- """Extract the user choices from the New Project Wizard and
1466
- store them in a dictionary.
1467
- """
1468
- projData = {
1469
- "projName": newProj.field("projName"),
1470
- "projTitle": newProj.field("projTitle"),
1471
- "projAuthor": newProj.field("projAuthor"),
1472
- "projPath": newProj.field("projPath"),
1473
- "popSample": newProj.field("popSample"),
1474
- "popMinimal": newProj.field("popMinimal"),
1475
- "popCustom": newProj.field("popCustom"),
1476
- "addRoots": [],
1477
- "addNotes": False,
1478
- "numChapters": 0,
1479
- "numScenes": 0,
1480
- }
1481
- if newProj.field("popCustom"):
1482
- addRoots = []
1483
- if newProj.field("addPlot"):
1484
- addRoots.append(nwItemClass.PLOT)
1485
- if newProj.field("addChar"):
1486
- addRoots.append(nwItemClass.CHARACTER)
1487
- if newProj.field("addWorld"):
1488
- addRoots.append(nwItemClass.WORLD)
1489
- projData["addRoots"] = addRoots
1490
- projData["addNotes"] = newProj.field("addNotes")
1491
- projData["numChapters"] = newProj.field("numChapters")
1492
- projData["numScenes"] = newProj.field("numScenes")
1493
-
1494
- try:
1495
- langIdx = newProj.field("projLang")
1496
- projData["projLang"] = CONFIG.listLanguages(CONFIG.LANG_PROJ)[langIdx][0]
1497
- except Exception:
1498
- projData["projLang"] = "en_GB"
1499
-
1500
- return projData
1501
-
1502
1397
  def _getTagSource(self, tag: str) -> tuple[str | None, str | None]:
1503
1398
  """Handle the index lookup of a tag and display an alert if the
1504
1399
  tag cannot be found.
novelwriter/shared.py CHANGED
@@ -31,8 +31,10 @@ from typing import TYPE_CHECKING, TypeVar
31
31
  from pathlib import Path
32
32
 
33
33
  from PyQt5.QtCore import QObject, QRunnable, QThreadPool, pyqtSignal
34
- from PyQt5.QtWidgets import QMessageBox, QWidget
34
+ from PyQt5.QtWidgets import QFileDialog, QMessageBox, QWidget
35
+ from novelwriter.common import formatFileFilter
35
36
 
37
+ from novelwriter.constants import nwFiles
36
38
  from novelwriter.core.spellcheck import NWSpellEnchant
37
39
 
38
40
  if TYPE_CHECKING: # pragma: no cover
@@ -217,6 +219,18 @@ class SharedData(QObject):
217
219
  QThreadPool.globalInstance().start(runnable, priority=priority)
218
220
  return
219
221
 
222
+ def getProjectPath(self, parent: QWidget, path: str | Path | None = None,
223
+ allowZip: bool = False) -> Path | None:
224
+ """Open the file dialog and select a novelWriter project file."""
225
+ label = (self.tr("novelWriter Project File or Zip File")
226
+ if allowZip else self.tr("novelWriter Project File"))
227
+ ext = f"{nwFiles.PROJ_FILE} *.zip" if allowZip else nwFiles.PROJ_FILE
228
+ ffilter = formatFileFilter([(label, ext), "*"])
229
+ selected, _ = QFileDialog.getOpenFileName(
230
+ parent, self.tr("Open Project"), str(path or ""), filter=ffilter
231
+ )
232
+ return Path(selected) if selected else None
233
+
220
234
  def findTopLevelWidget(self, kind: type[NWWidget]) -> NWWidget | None:
221
235
  """Find a top level widget."""
222
236
  for widget in self.mainGui.children():
@@ -37,7 +37,7 @@ from PyQt5.QtWidgets import (
37
37
 
38
38
  from novelwriter import CONFIG, SHARED
39
39
  from novelwriter.error import formatException
40
- from novelwriter.common import openExternalPath, formatInt, getFileSize
40
+ from novelwriter.common import formatFileFilter, openExternalPath, formatInt, getFileSize
41
41
 
42
42
  logger = logging.getLogger(__name__)
43
43
 
@@ -180,12 +180,11 @@ class GuiDictionaries(QDialog):
180
180
  @pyqtSlot()
181
181
  def _doBrowseHunspell(self):
182
182
  """Browse for a Free/Libre Office dictionary."""
183
- extFilter = [
184
- self.tr("Free or Libre Office extension ({0})").format("*.sox *.oxt"),
185
- self.tr("All files ({0})").format("*"),
186
- ]
183
+ ffilter = formatFileFilter([
184
+ (self.tr("Free or Libre Office extension"), "*.sox *.oxt"), "*"
185
+ ])
187
186
  soxFile, _ = QFileDialog.getOpenFileName(
188
- self, self.tr("Browse Files"), "", filter=";;".join(extFilter)
187
+ self, self.tr("Browse Files"), "", filter=ffilter
189
188
  )
190
189
  if soxFile:
191
190
  path = Path(soxFile).absolute()
@@ -375,9 +375,9 @@ class GuiManuscript(QDialog):
375
375
  @pyqtSlot()
376
376
  def _printDocument(self) -> None:
377
377
  """Open the print preview dialog."""
378
- thePreview = QPrintPreviewDialog(self)
379
- thePreview.paintRequested.connect(self.docPreview.printPreview)
380
- thePreview.exec_()
378
+ preview = QPrintPreviewDialog(self)
379
+ preview.paintRequested.connect(self.docPreview.printPreview)
380
+ preview.exec_()
381
381
  return
382
382
 
383
383
  ##
@@ -604,7 +604,7 @@ class _DetailsWidget(QWidget):
604
604
  for key in entries:
605
605
  sub = QTreeWidgetItem()
606
606
  sub.setText(0, build.getLabel(key))
607
- sub.setText(1, hFmt.apply(build.getStr(key), title))
607
+ sub.setText(1, hFmt.apply(build.getStr(key), title, 0))
608
608
  item.addChild(sub)
609
609
  for key in ["headings.hideScene", "headings.hideSection"]:
610
610
  sub = QTreeWidgetItem()
@@ -771,8 +771,8 @@ class _PreviewWidget(QTextBrowser):
771
771
  self.setHtml(html)
772
772
  qApp.processEvents()
773
773
  while self.find("!!tab!!"):
774
- theCursor = self.textCursor()
775
- theCursor.insertText("\t")
774
+ cursor = self.textCursor()
775
+ cursor.insertText("\t")
776
776
 
777
777
  self.verticalScrollBar().setValue(sPos)
778
778
  self._docTime = checkInt(data.get("time"), 0)